<!--
%CopyrightBegin%

SPDX-License-Identifier: Apache-2.0

Copyright Ericsson AB 2025-2026. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

%CopyrightEnd%
-->

# Secure Coding Guidelines
[](){: #secure-coding }

This section provides a guideline for writing secure Erlang code, describing
common pitfalls and weaknesses best avoided. From time to time, it will
reference the [Common Weakness Enumeration] (CWE) maintained by [MITRE], as
well as the application security risks maintained by [OWASP].

There are also sections addressing the [Top 40 CWEs], the [OWASP Top 10]
security risks, and the [OWASP API Security Top 10]. Finally, there is a
section with concrete [Secure Coding Rules].

This is a living document and is updated as new best practices are discovered.
It is by no means complete, and contributions to improve it are warmly
welcomed. Those following these guidelines will hopefully avoid the listed
issues, but undiscovered issues remain.

[Common Weakness Enumeration]: https://cwe.mitre.org
[MITRE]: https://mitre.org
[OWASP]: https://owasp.org

## Background

Before going into rules and guidelines, it is important to describe a few
things specific to Erlang that may not be obvious coming from other languages.
We will begin with the threat model used throughout Erlang/OTP itself. Your
applications may have a different model, but it is nevertheless necessary to
understand what Erlang/OTP can be expected to protect against, and what it
cannot be.

Throughout this document we will use the terms _trusted_ and _untrusted_ to
mean _fully trusted_ and _not fully trusted_, respectively. No distinction is
made between _partially trusted_ and _untrusted_ and it is important to
remember that anything that is not _fully and completely trusted_ must be
considered as _untrusted_ as something given to you by a malicious actor.

#### What _is not_ protected against

* All loaded code is assumed to be trusted. There is no built-in sand-boxing
  mechanism for running untrusted Erlang code. Any code loaded and executed
  within the environment has unrestricted access to the system.
  * This means that a malicious BEAM module can do anything, including breaking
    the memory safety protections of the runtime system and crashing the
    virtual machine. This is no different from other languages, where for
    example modifying a Rust executable on disk could also break memory safety.
    
    It is therefore important that the user secures the host system so that an
    attacker cannot modify any files relevant to the program.
  * Excessive resource usage is not prevented by default. Unless safeguards are
    are put in place (for example heap size limitations), a process is free to
    consume enough resources to crash the whole program.
* All nodes connected through Erlang distribution are assumed to be trusted,
  and have unrestricted access to all other connected systems.
  
  Crucially, there is no authentication built into the default distribution
  protocol, merely a ["cookie" mechanism] that prevents the unintentional
  mixing of Erlang clusters on the same network. For secure communication over
  an unsecured network, the distribution should be configured to use
  [TLS with client certificate verification].
* Inappropriate usage of functionality is not guaranteed to crash or produce
  sensible results. The arguments given to many Erlang functions are assumed to
  be trusted, so providing valid-appearing but nonsensical input to a function
  can have it produce valid-appearing garbage instead of crashing or returning
  an error.
  
  Furthermore, using undocumented functionality, whether it is a function,
  option, et cetera, can result in almost anything happening.

[TLS with client certificate verification]: `e:ssl:ssl_distribution.html`
["cookie" mechanism]: `e:system:distributed.html#security`

#### What _is_ protected against

* Erlang is a memory-safe language.
  
  CWE categories related to memory safety (both spatial and temporal), such as
  [`CWE-416`], [CWE-465] or [CWE-1218], cannot occur. This safety extends to
  concurrent operation and race conditions can only affect application logic,
  which is further limited as Erlang only provides message-passing (as opposed
  to memory-sharing) concurrency; a classical data race ([`CWE-362`]) cannot
  occur unless the programmer has explicitly built a shared resource for which
  it can happen. Furthermore, errors relating to the use of uninitialized
  variables cannot occur.
  
  Importing memory-unsafe code through the Foreign Function Interfaces (FFI),
  such as [drivers] and [Native Implemented Functions] (NIFs), may, of course,
  violate this property, but there are no unsafe constructs in the language
  itself.
* Classical memory leaks cannot occur. While data can be explicitly referenced
  for longer than necessary by mistake, memory that is no longer referenced
  will eventually be reclaimed by the garbage collector.
* Erlang processes are isolated and can only affect other entities through the
  use of signals: messages, links, monitors, and so on. A process that crashes
  will do so without any impact on other processes other than that defined by
  the programmer.
  
  Furthermore, the execution of a process cannot block that of another process,
  other than as defined by the programmer such as waiting for a response to a
  message that the other process never sends. The effects of, for example,
  [CWE-835] are thus limited to the offending process and can be recovered from
  through termination.
* The behavior of all Erlang programs is defined for all possible inputs, and
  while an operation may appear partial in that it only returns a result for
  certain arguments and otherwise throws an exception, the latter always leaves
  the program in a well-defined state.
  
  Like with memory safety, this property can be violated if the foreign
  function interfaces are misused.
* Erlang uses arbitrary precision integer arithmetic, and overflow will only
  occur once the numbers reach several megabits in size, and will in any event
  throw an exception instead of wrapping around or otherwise behaving in an
  unsafe manner ([`CWE-190`]).

[Native Implemented Functions]: `e:erts:erl_nif.html`
[drivers]: `e:erts:erl_driver.html`
[CWE-465]: https://cwe.mitre.org/data/definitions/465.html
[CWE-835]: https://cwe.mitre.org/data/definitions/835.html
[CWE-1218]: https://cwe.mitre.org/data/definitions/1218.html

[](){: #h-error-handling }
### Error Handling
[Error Handling]: #h-error-handling

Generally, errors can be split into three categories regardless of the language
used:

1. The errors that we do not expect to handle gracefully, such as our local
   disk storage suddenly becoming unavailable. A sensible way to deal with this
   would be, for example, to raise an alarm.
1. The errors that we expect to handle gracefully, such as a web server
   responding with a 404 error when it cannot find a file.
1. _Program bugs_, such as writing to a closed file.

What we wish to do with these three categories is entirely different, but
unfortunately classical error handling tends to mix the three, either using
return values or exceptions for all of them, which causes issues more often
than not. The third category is especially sinister because of the difficulty
in ensuring that the offending code has not left parts of the program in an
invalid state, but it is nevertheless necessary for a language to be able to
handle it since a broken invariant in an insignificant part of the program
would otherwise tear the entire program down.

The isolation properties given by Erlang processes works together with the idea
of supervision trees to allow generalized recovery from third-category errors
without the programmer having to define each particular case. Whatever the
crashing process worked on is lost, but the program as a whole is spared.

This has far-reaching implications. Because the effects of a crash are so
limited, a cornerstone of idiomatic error handling in Erlang is to abandon
execution once something unexpected occurs, leaving the consequences to the
program's supervision structure instead of ignoring or trying to handle the
condition and continuing execution. If a program encounters an error of the
first or second categories, it can either handle them explicitly, or leave it
to the supervision structure by either pattern-matching on the return value or
leaving exceptions uncaught.

This greatly improves security and reliability, as blindly continuing execution
may not only result in unexpected behavior, but can also become a security
issue as assumptions that are made may no longer be valid.

That is not to say that _expected_ errors should not be handled, but rather
that each component that cannot meaningfully handle all the consequences of an
error on its own should not attempt to do so, but should instead leave it to
the supervision structure. Another way of looking at this is to say that
program execution should be "deny by default" and that it may only continue as
explicitly defined.

Needless to say, a well-designed supervision structure is a prerequisite for
this. To be effective it must adequately model the program's domain such that
local failures are contained locally: for example, in a server a single failed
request should not tear down the entire program, but only the subtree started
through said request. Care should also be taken to handle the errors that are
expected and can be handled locally to avoid spamming the error logs, for
example by responding to the request with an error message (such as "404 File
not found" in a web context).

The importance of this cannot be overstated. Security issues are almost by
definition a result of unexpected program behavior, and restricting program
behavior to only that which is expected greatly reduces the surface area for
bugs and security issues.

[](){: #h-memory-management }
### Memory Management
[Memory Management]: #h-memory-management

Erlang is a memory-safe language, with automatic memory management handled by
its runtime system. Each process primarily allocates data on its own heap,
while large binaries can be shared efficiently between processes. Process heaps
undergo individual garbage collection, ensuring that unused memory within a
process is reclaimed promptly.

In addition to process heaps, Erlang provides Erlang Term Storage (ETS), an
in-memory storage system for terms. Memory for ETS entries is allocated
automatically upon insertion and freed when entries are removed. This automatic
memory management effectively eliminates many common memory-related
vulnerabilities typical in low-level languages, such as use-after-free errors.

Erlang's automatic memory management prevents memory leaks by releasing memory
once there are no remaining references. However, developers must still make
sure to drop references to unused data, just as in other programming languages.
Failing to do so can lead to memory retention issues similar to leaks, which
may cause performance degradation or potentially become exploitable
vulnerabilities. Therefore, careful management of data references remains
essential to maintain application reliability and security.

[](){: #h-atom-exhaustion }
### Atom Exhaustion
[Atom Exhaustion]: #h-atom-exhaustion

Data of the type `atom` is very space efficient and performant to use, but it
must be used with care as it is not intended to be used dynamically: the
intended use case is to provide named constants in code. The amount of atoms in
the system is limited (see [system limits] and [`CWE-770`]) and cannot be
reclaimed once created. That is, if you dynamically create a large amount of
atoms, the system might crash.

See [`DSG-003`] for ways to prevent this.

[system limits]: `e:system:system_limits.html`

[](){: #h-native-code }
### Native Code
[Native Code]: #h-native-code

Native code can be dynamically linked into the Erlang runtime system either as
a [driver] or [NIF library]. Drivers and NIF libraries are typically written in
C or C++ and have, if not full access, access to a large part of the runtime
system internals.

Drivers and NIF libraries are, of course, not necessarily unsafe and sometimes
their use is unavoidable, but it is much easier to avoid introducing
vulnerabilities and other problems by writing Erlang code whenever possible.
Thorough care needs to be taken both to follow secure programming guidelines
for the language that the native code is written in as well as the guidelines
for writing drivers and NIF libraries. Poorly written native code can both
introduce vulnerabilities as well as stability problems.

A NIF library is loaded using `erlang:load_nif/2` function while a driver is
loaded using one of the `m:erl_ddll` load functions.

[NIF library]: `e:erts:erl_nif.html`
[driver]: `e:erts:erl_driver.html`

## Application-Specific Guidelines

[](){: #h-crypto-application }
### [`crypto`] Application
[`crypto` Application]: #h-crypto-application
[`crypto`]: `e:crypto:index.html`

[](){: #initializing-crypto }
#### Initializing `crypto`
[Initializing `crypto`]: #initializing-crypto

The `libcrypto` library from OpenSSL will be loaded and configured when the
`m:crypto` module is loaded. During loading of the `m:crypto` module some crypto
application parameters will be read in order to configure the `libcrypto`
library. Such application parameters are only available after the application
has been started using the `application:start/1` functionality. That is, in
order to make sure that the `libcrypto` library is configured as expected, make
sure not to load the `m:crypto` module by any other means than calling
`application:start(crypto)`. Any calls to the `m:crypto` library will load the
module. Make sure not to call any functions in the `m:crypto` module, including
`crypto:start/0` prior to the call to `application:start(crypto)`.

In order to ensure that the FIPS mode is configured correctly in the
`libcrypto` library from OpenSSL, use the [`fips_mode`] application parameter
instead of calling `crypto:enable_fips_mode/1`.

[`fips_mode`]: `e:crypto:crypto_app.html#fips_mode`

[](){: #cryptographically-secure-random-numbers }
#### Cryptographically Secure Random Numbers
[Cryptographically Secure Random Numbers]: #cryptographically-secure-random-numbers

Do not use `crypto:rand_uniform/2` since it uses functionality from the
OpenSSL `libcrypto` library that in old versions of the library does not
produce cryptographically secure random numbers ([CWE-338]). An alternative is
using `rand:uniform/1` together with a cryptographically secure random number
generator like `crypto:rand_seed/0`. Note that `crypto:rand_seed/0` not
only produces a seed but also selects a generator that will be used by
`rand:uniform/1`.

The above mentioned functionality will store a state in the
[process dictionary] of the currently executing process. This state could be
modified to select another generator by other functionality called interleaved
with calls retrieving random numbers. A better solution is to use the
functional API, which will pass the state between calls instead of storing it in
the process dictionary. The functional API are functions in the `m:rand` and
`m:crypto` modules with function names having the suffix `_s`. That is, you
might want to consider using `crypto:rand_seed_s/0`/`rand:uniform_s/2`
instead of `crypto:rand_seed/0`/`rand:uniform/1`.

[process dictionary]: `e:system:ref_man_processes.html#process-dictionary`
[CWE-338]: https://cwe.mitre.org/data/definitions/338.html

[](){: #legacy-crypto-functions }
#### Legacy `crypto` Functions

RSA with PKCS-1 padding is weak and should be avoided: do not use the 
`rsa_pkcs1_padding` option with `crypto:private_encrypt/4`,
`crypto:private_decrypt/4`, `crypto:public_encrypt/4`, or
`crypto:public_decrypt/4`.

For signatures, use `crypto:sign/4`/`crypto:verify/5` instead.

[](){: #h-ssl-application }
### [`ssl`] Application
[`ssl` Application]: #h-ssl-application
[`ssl`]: `e:ssl:index.html`

The [`ssl`] application is secure by default and remains so if the
recommendations in its documentation is followed. One must be especially
careful when enabling legacy functionality, as it weakens security. It should
only be done when absolutely necessary.

However, disabling functionality can be beneficial. If you control both the
server and the client, consider disabling the (now old) TLS 1.2 in favor of
TLS 1.3. The [`cert_keys`](`t:ssl:common_option_cert/0`) option can also be
used to configure more than one possible certificate/key pair for a client or
server, giving good security while allowing interoperability with legacy
systems for a period of time.

[](){: #h-ssh-application }
### [`ssh`] Application
[`ssh` Application]: #h-ssh-application
[`ssh`]: `e:ssh:index.html`

See the [`ssh`] documentation's [hardening chapter](`e:ssh:hardening.html`).

[](){: #h-public_key-application }
### [`public_key`] Application
[`public_key` Application]: #h-public_key-application
[`public_key`]: `e:public_key:index.html`

RSA with PKCS-1 padding is weak and should be avoided: do not use the 
`rsa_pkcs1_padding` option with [`public_key:encrypt_private/2,3`],
[`public_key:decrypt_private/2,3`], [`public_key:encrypt_public/2,3`] or
[`public_key:decrypt_public/2,3`].

For signatures, use `public_key:sign/4`/`public_key:verify/5` instead.

[`public_key:encrypt_private/2,3`]: `public_key:encrypt_private/3`
[`public_key:decrypt_private/2,3`]: `public_key:decrypt_private/3`
[`public_key:encrypt_public/2,3`]: `public_key:encrypt_public/3`
[`public_key:decrypt_public/2,3`]: `public_key:decrypt_public/3`

[](){: #h-xmerl-application }
### [`xmerl`] Application
[`xmerl` Application]: #h-xmerl-application
[`xmerl`]: `e:xmerl:index.html`

The `m:xmerl_scan` module dynamically produces new atoms and is therefore not
suitable for decoding XML data originating untrusted sources.

When parsing untrusted XML, you want to prevent XML entity attacks by disabling
parsing of entities. In the `m:xmerl_sax_parser` you can do that by passing the
[`disallow_entities`](`t:xmerl_sax_parser:options/0`) option.

[](){: #secure-coding-standard }
[](){: #secure-coding-rules }
## Rules / Secure Coding Standard
[Secure Coding Rules]: #secure-coding-rules

[](){: #rule-priority }
[Rule priority:]: #rule-priority
This section contains some general rules that may be helpful in writing more
secure code. The rules link to related CWEs and OWASP risks, and also have a
rough priority for the order in which we think the rules should be applied on
an existing code base. The priorities are `Critical`, `High`, `Medium`, and
`Recommendation`. The priority of a rule largely reflects how likely it is for
related flaws to lead to a vulnerability, how severe said vulnerability is, and
how easy it is to fix.

### Code Style

[](){: #rule-stl-001 }
#### [`STL-001`] - Be Restrictive
[`STL-001`]: #rule-stl-001

As described in the [Error Handling] section, all unexpected errors should be
left to the supervision structure, and all unexpected _conditions_ should be
considered errors. In brief, this is because encountering something unexpected
means that we have left the _known and tested_ path, and continuing greatly
increases the risk for bugs and security issues.

Erlang code should be written as restrictively as possible, to provoke errors
whenever anything unexpected happens. The idea is to make the third error
category, program bugs, visible as a crash instead of silently continuing.

[Rule priority:] `High`

Related CWEs and OWASP risks: [CWE-252], [CWE-253], [CWE-391], [CWE-392],
[CWE-394], [CWE-396], [`A10:2025`]

[CWE-252]: https://cwe.mitre.org/data/definitions/252.html
[CWE-253]: https://cwe.mitre.org/data/definitions/253.html
[CWE-391]: https://cwe.mitre.org/data/definitions/391.html
[CWE-392]: https://cwe.mitre.org/data/definitions/392.html
[CWE-394]: https://cwe.mitre.org/data/definitions/394.html
[CWE-396]: https://cwe.mitre.org/data/definitions/396.html

```erlang
%% DO
case operation(A, B) of
    true -> C;
    false -> D
end.

%% DO NOT
case operation(A, B) of
    true -> C;
    %% What if operation/2 is extended to also return 'maybe', or someone
    %% misspells 'true' as 'tru'?
    _ -> D
end.

%% DO
ok = file:write(Fd, Data)

%% DO NOT
_ = file:write(Fd, Data)

%% DO
foo([First | Rest]) ->
    [bar(First) | foo(Rest)];
foo([]) ->
    [].

%% DO NOT
foo([First | Rest]) ->
    [bar(First) | foo(Rest)];
foo(_) ->
    [].

%% DO
input_to_atom(<<"foo">>) -> foo;
input_to_atom(<<"bar">>) -> bar;
input_to_atom(<<"quux">>) -> quux.

%% DO NOT, when set of possible atoms is known beforehand
input_to_atom(Text) -> binary_to_existing_atom(Text).

%% DO
try operation(A, B) of
    {ok, X} -> something(X)
catch
    error:specific_error -> error
end.

%% DO NOT
try operation(A, B) of
    {ok, X} -> something(X)
catch
    error:_ -> error
end.

%% PREFER
case my_filter(List0, unchanged) of
    unchanged -> List0;
    {changed, List} -> List
end

%% AVOID
case my_filter(List0, unchanged) of
    unchanged -> List0;
    %% What if a misspelled atom like 'uchanged' is returned?
    List -> List
end

%% PREFER
[op(L) || #my_record{}=L <:- ListOfMyRecord]

%% AVOID, this silently filters out entries that do not match #my_record{}
[op(L) || #my_record{}=L <- ListOfMyRecord]
```

[](){: #rule-stl-002 }
#### [`STL-002`] - Avoid Boolean Blindness
[`STL-002`]: #rule-stl-002

Whenever boolean values have a context, prefer using more descriptive atoms to
express the boolean value, for example `initialized`/`uninitialized` or
`changed`/`unchanged`. This makes it easier to distinguish between different
boolean variables when many of them are used together, especially when matching
in function heads and the like.

[Rule priority:] `Recommendation`

Related CWEs and OWASP risks: [CWE-628]

[CWE-628]: https://cwe.mitre.org/data/definitions/628.html

```erlang
%% DO
case my_filter(List0, unchanged) of
    unchanged -> List0;
    {changed, List} -> List
end

%% DO NOT
case my_filter(List0, false) of
    false -> List0;
    {true, List} -> List
end
```

[](){: #rule-stl-003 }
#### [`STL-003`] - Use Uppercase Names for Macros
[`STL-003`]: #rule-stl-003

Macros are distinguished by a `?` prefix, so an accidental omission of the
prefix leaves the name there instead of applying the macro. For example,
`function_call(?my_macro, SomeArg)` becomes `function_call(my_macro, SomeArg)`
which is syntactically valid, hiding the error.

Static analysis tools can often find these issues, but a quicker way to find
them is to adopt the convention that all macros should be upper-case. Missing a
`?` will in most cases then lead to an unbound variable error or similar.

[Rule priority:] `Recommendation`

```erlang
%% DO
-define(MY_MACRO, 65535).

%% DO NOT
-define(my_macro, 65535).
```

### Deployment

[](){: #rule-dep-001 }
#### [`DEP-001`] - Do Not Expose Default Erlang Distribution on Untrusted Networks
[`DEP-001`]: #rule-dep-001

The builtin Erlang distribution makes it possible to easily and transparently
communicate between Erlang nodes. By default, communication is performed over
an unencrypted TCP connection with a rudimentary [cookie based authentication]
only present in order to prevent mistakes. This configuration should *only* be
used in a trusted network. In order to communicate between nodes over an
untrusted network, the Erlang distribution protocol should be configured to
[communicate between nodes using TLS].

Note that the Erlang Port Mapper Daemon ([EPMD]) service will respond to
unauthenticated requests, and can by this leak information about what Erlang
nodes exist and what ports they are listening on ([CWE-668], [`CWE-200`]). You
are therefore advised to [disable the default EPMD] and implement your own
[EPMD module] using another port lookup scheme and [enable that EPMD module].
The simplest solution, assuming only one Erlang node per IP address, would be
to use a statically assigned port, skip registration of nodes, and just assume
that a node will be listening on that port.

Also note that all nodes admitted into an Erlang cluster must be trusted. Once
a node is connected to the cluster, it gains complete access to the resources
and operations of all other nodes, making node trustworthiness a critical
security consideration.

If the distribution protocol is not required per se, Erlang/OTP includes the
[`ssh`] and [`ssl`] applications, enabling secure communication over untrusted
networks. Depending on the nature of the communication and the remote parties
involved, you may need to explicitly validate and sanitize incoming data to
ensure safety and correctness.

[Rule priority:] `Critical`

Related CWEs and OWASP risks: [`CWE-200`], [CWE-668], [`A01:2025`],
[`A02:2025`], [`API2:2023`], [`API6:2023`], [`API8:2023`]

[cookie based authentication]: `e:system:distributed.html#security`
[communicate between nodes using TLS]: `e:ssl:ssl_distribution.html`
[EPMD]: `e:erts:erl_dist_protocol.html#epmd-protocol`
[disable the default EPMD]: `e:erts:erl_cmd.html#start_epmd`
[EPMD module]: `m:erl_epmd`
[enable that EPMD module]: `e:kernel:kernel_app.html#epmd_module`
[CWE-668]: https://cwe.mitre.org/data/definitions/668.html

[](){: #rule-dep-002 }
#### [`DEP-002`] - Build and Install Erlang/OTP Yourself
[`DEP-002`]: #rule-dep-002

Recommendations found elsewhere on the internet on how to build and install
Erlang/OTP are often problematic. While tools like `kerl` and `asdf` or
prebuilt docker images are convenient, they sometimes patch Erlang/OTP or set
`CFLAGS`/`LDFLAGS` themselves, which has caused problems in some cases. It is
better to [build Erlang/OTP] yourself instead of relying on external build
tools or already-built installations from other suppliers.

Setting `CFLAGS` and `LDFLAGS` may cause issues if you are not careful. When
building Erlang/OTP, a lot of different executables as well as shared libraries
will be built. Options set in `CFLAGS` and `LDFLAGS` will be used when
compiling both executables and shared libraries, so you only want to pass flags
that are useful for all executables as well as all shared libraries in that
manner.

Some guides recommend passing the `pie` options (position independent
executables), which *should not* be passed in `CFLAGS` and `LDFLAGS` since it
might conflict with the `pic` (position independent code) options used when
building shared libraries. In order to build position independent executables,
you instead want to pass the `--enable-pie` [`configure`] argument when
configuring the build, which will make sure to use `pie` options where
appropriate.

As of Erlang/OTP 28.0, the `configure` script that you run when building
Erlang/OTP will, on Unix like systems, try to enable most of the C/C++
hardening flags recommended by the Open Source Security Foundation. By setting
the environment variable `V=1` when building, you can see the full command
line for each invocation of the compiler as well as the linker. By inspecting
that output you can see what hardening flags is being used in your build.

Make sure to build Erlang/OTP using an up to date OpenSSL. By pointing out the
OpenSSL installation to use by passing the `--with-ssl=PATH` argument to
[`configure`] when building one can ensure that the build does not build
against another old OpenSSL installation found on the system. By passing the
`--disable-dynamic-ssl-lib` [`configure`] when building, the `libcrypto`
library from OpenSSL will be statically linked into the crypto NIF library
ensuring that the selected version from OpenSSL actually will be used, and not
another version being picked up from the system where Erlang/OTP is being run.
Note that only the `libcrypto` library from OpenSSL will be used and that
vulnerabilities in other parts of OpenSSL do not affect Erlang/OTP. The `ssl`
and `public_key` applications implement most things except for crypto
functionality, which is provided by the `crypto` application, which in turn
uses `libcrypto`.

[Rule priority:] `High`

Related CWEs and OWASP risks: [`A03:2025`]

[build Erlang/OTP]: `e:system:install.html`
[OTP Versions Tree]: https://erlang.org/download/otp_versions_tree.html
[`configure`]:  `e:system:install.html#advanced-configuration-and-build-of-erlang-otp`

[](){: #rule-dep-003 }
#### [`DEP-003`] - Use Actively Maintained Versions of Erlang/OTP
[`DEP-003`]: #rule-dep-003

Make sure to use an actively maintained version of Erlang/OTP. The
[OTP Versions Tree] page contains information about maintained Erlang/OTP
releases as well as CVEs affecting different OTP versions. [VEX documents],
which provide information about potential vulnerabilites, are regularly updated
for currently maintained OTP releases. Patches are announced on the
[`erlang-announce`] mailing list as well as on the [erlang forums] web site.

[Rule priority:] `Critical`

Related CWEs and OWASP risks: [`A03:2025`]

[VEX documents]: https://erlang.org/download/vex/
[`erlang-announce`]: https://erlang.org/pipermail/erlang-announce
[erlang forums]: https://erlangforums.com/

[](){: #rule-dep-004 }
#### [`DEP-004`] - Protect the Code Path
[`DEP-004`]: #rule-dep-004

If a malicious actor can write to a folder in the code path used by the Erlang
VM (either the Erlang code path or the host system's shared library load path),
and they have the capability to coerce the system into loading that code (made
worse by `interactive` mode, see [`DEP-005`]), then it is possible for an
attacker to load malicious code.

To mitigate this, ensure that the folders in the code paths are only writable
by a dedicated user, separate from the user running the VM.

[Rule priority:] `Medium`

Related CWEs and OWASP risks: [`CWE-427`], [`A08:2025`]

[](){: #rule-dep-005 }
#### [`DEP-005`] - Avoid `interactive` Mode in Production
[`DEP-005`]: #rule-dep-005

`interactive` mode loads code on demand, making it easy to trigger code
loading. There is generally little reason to use this mode outside of
development, so consider using `embedded` mode instead.

[Rule priority:] `Critical`

Related CWEs and OWASP risks: [`A08:2025`]

[](){: #rule-dep-006 }
#### [`DEP-006`] - Minimize VM Privileges
[`DEP-006`]: #rule-dep-006

The virtual machine should run with the fewest possible privileges your system
needs. It should not have the right to interact with (let alone affect)
anything it does not require for its operation.

The VM should at the very least run under its own dedicated user, and
preferably also be further restricted through `SELinux`/`AppArmor`,
containerization, and similar techniques.

[Rule priority:] `Medium`

### Design

[](){: #rule-dsg-001 }
#### [`DSG-001`] - Encode the Problem Domain in the Supervision Tree
[`DSG-001`]: #rule-dsg-001

The supervision tree is central to [Error Handling] in Erlang, and is what
allows graceful operation in the presence of errors regardless of their cause.

For this to be effective, the supervision structure must reflect the problem
that your program is trying to solve, and in particular the parts that are
isolated from each other in the problem domain should also be isolated as
separate Erlang processes.

At its face, this prevents unrelated flows from affecting others, limiting the
"blast radius" of problems and with it also the security implications (such as
in denial-of-service attacks).

However, one less well-understood benefit is that it moves a great deal of
complexity out of the program's _code_ and into its _overall structure_, where
it is much easier to handle. Error handling is notoriously difficult to get
right as errors are often uncommon by their very nature, and thereby difficult
to test.

When an unexpected condition merely crashes a single isolated process, _and the
program structure rolls back anything that process may have done_, the need for
pervasive error handling throughout the code at large is drastically reduced,
and with it a large source of bugs and security issues.

[Rule priority:] `Medium`

Related CWEs and OWASP risks: [CWE-389], [CWE-544], [CWE-653], [`A10:2025`]

[CWE-389]: https://cwe.mitre.org/data/definitions/389.html
[CWE-544]: https://cwe.mitre.org/data/definitions/544.html
[CWE-653]: https://cwe.mitre.org/data/definitions/653.html

[](){: #rule-dsg-002 }
#### [`DSG-002`] - Prefer Letting the User Decide What Warrants an Exception
[`DSG-002`]: #rule-dsg-002

Prefer to design your interfaces so that the user decides whether an error is
exceptional or not, by following the `{ok, Result} | {error, Reason}`
convention. Generally speaking the user of an interface has more context than
the one implementing it, and giving them the freedom to choose through pattern
matching tends to result in clearer code as the handling of raised exceptions
is more difficult to follow.

[Rule priority:] `Recommendation`

Related CWEs and OWASP risks: [CWE-389], [`A10:2025`]

```erlang
%% PREFER
{ok, C} = some_function(A, B)

%% PREFER
case some_function(A, B) of
    {ok, C} ->
        %% Happy path
        ...;
    {error, Error} ->
        %% Handle it
end

%% AVOID
try some_function(A, B) of
    C -> 
        %% Happy path
        ...
catch
    error:_ ->
        %% Handle it
end
```

[](){: #rule-dsg-003 }
#### [`DSG-003`] - Do Not Abuse Atoms
[`DSG-003`]: #rule-dsg-003

Atoms are designed to provide an easy way to create named constants in code.
Defining them programmatically is not something that we have optimized for, and
may also lead to [Atom Exhaustion].

Specifically, the [`binary_to_atom/1,2`] and [`list_to_atom/1`] functions will
create a new atom if it does not already exist. Doing so without care might
cause the system to crash. If you know that the atom already should exist in
the system, you can use the [`binary_to_existing_atom/1,2`] and
[`list_to_existing_atom/1`] instead. These functions will throw an exception
instead of creating a new atom if the atom does not already exist. Note that
there are valid use-cases for using [`binary_to_atom/1,2`] and
[`list_to_atom/1`], so you cannot blindly replace these calls.

However, before using the `*_to_existing_atom()` functions, consider whether an
explicit conversion is more appropriate. Quite often there are only a few atoms
that are valid in the context the conversion is done, and in those cases it is
better to translate them yourself and reject invalid inputs, as
`*_to_existing_atom()` can return *any* atom that exists in the system, not
just those expected in the context.

There are also a number APIs that create general Erlang terms from data of some
serialized format. You should not use such APIs if the data is not trusted (see
[`DSG-011`]) unless the API also provides some way of preventing creation of
atoms. For example, [`binary_to_term/2`] with the `safe` option will prevent
new atoms from being created. However, note that even if the `safe` option is
used and the data originates from an untrusted source, it still has to be
validated and sanitized, since it can still be harmful to the Erlang
application in other ways.

In general, it is best to avoid using such functions altogether on untrusted
data, even with the `safe` option.

[`binary_to_atom/1,2`]: `erlang:binary_to_atom/2`
[`binary_to_existing_atom/1,2`]: `erlang:binary_to_existing_atom/2`
[`binary_to_term/2`]: `erlang:binary_to_term/2`
[`list_to_atom/1`]: `erlang:list_to_atom/1`
[`list_to_existing_atom/1`]: `erlang:list_to_existing_atom/1`

[Rule priority:] `High`

Related CWEs and OWASP risks: [`CWE-770`], [`API10:2023`]

```erlang
%% DO, AND PREFER (see STL-001)
input_to_atom(<<"foo">>) -> foo;
input_to_atom(<<"bar">>) -> bar;
input_to_atom(<<"quux">>) -> quux.

%% DO
input_to_atom(Text) -> binary_to_existing_atom(Text).

%% DO NOT
input_to_atom(Text) -> binary_to_atom(Text).
```

[](){: #rule-dsg-004 }
#### [`DSG-004`] - Do Not Use Undocumented Functionality
[`DSG-004`]: #rule-dsg-004

Undocumented functions or functionality must **never** be used. This includes
undocumented arguments to documented functions and undocumented system
services. Using such functionality poses a serious security risk. These
functions and features are intended strictly for internal use within Erlang/OTP
and are not supported for external use.

Merely passing the wrong arguments to these functions can cause the system to
behave in unexpected ways from that point on, and their behavior may change or
they may be removed without prior notice.

[Rule priority:] `Critical`

Related CWEs and OWASP risks: [CWE-242], [CWE-477], [CWE-676]

[CWE-242]: https://cwe.mitre.org/data/definitions/242.html
[CWE-477]: https://cwe.mitre.org/data/definitions/477.html
[CWE-676]: https://cwe.mitre.org/data/definitions/676.html

[](){: #rule-dsg-005 }
#### [`DSG-005`] - Do Not Use Deprecated Functionality
[`DSG-005`]: #rule-dsg-005

When functionality is deprecated in Erlang/OTP, the documentation will
typically point to other or new functionality to use instead. The deprecation
may have been made for various reasons, which could include security issues,
but it is not the most frequent reason. However, if the compiler gives you a
deprecation warning it is worth taking a look at the functionality and how it
is used.

[Rule priority:] `Medium`

Related CWEs and OWASP risks: [CWE-242], [CWE-477], [CWE-676]

[](){: #rule-dsg-006 }
#### [`DSG-006`] - Do Not Use Unsafe Functionality
[`DSG-006`]: #rule-dsg-006

[Unsafe Functions] must not be used, and [Potentially Unsafe Functions] should
only be used in a safe manner, and are best avoided if possible.

[Rule priority:] `Critical`

Related CWEs and OWASP risks: [CWE-242]

[](){: #rule-dsg-007 }
#### [`DSG-007`] - Know Your Data
[`DSG-007`]: #rule-dsg-007

Always know what data your code is operating on, and make sure it is
documented. Preferably in a way such that it can be checked by automated tools
such as declaring [`-nominal`](`e:system:nominals.html`) types to distinguish
conceptually different but otherwise identical data (consider `20 meters` and
`20 feet`, when both are represented as the number `20`).

Something as simple as marking data as trusted or untrusted can be a great help
since all code implicitly trusts the data it is given to different degrees.
Documenting what data your code expects to be operating on helps protect
against, for example, injection attacks in match specifications (see
[`MSC-005`]), or the leakage of sensitive data (see [`MSC-004`]).

[Rule priority:] `Medium`

Related CWEs and OWASP risks: [`CWE-20`], [`CWE-22`], [CWE-74], [`CWE-78`],
[`CWE-89`], [`CWE-502`], [CWE-532], [CWE-843], [`CWE-918`], [CWE-1287]

[CWE-74]: https://cwe.mitre.org/data/definitions/74.html
[CWE-532]: https://cwe.mitre.org/data/definitions/532.html
[CWE-843]: https://cwe.mitre.org/data/definitions/843.html
[CWE-1287]: https://cwe.mitre.org/data/definitions/1287.html

[](){: #rule-dsg-008 }
#### [`DSG-008`] - Avoid Designing Unrealistic Interfaces
[`DSG-008`]: #rule-dsg-008

Much of what we do as programmers is to provide abstractions that make life
easier for other people. For the most part this works fine but it is important
not to design interfaces that run afoul of mathematical, physical, or otherwise
implacable constraints. As obvious as that might sound it is surprisingly easy
to make promises that are impossible to deliver without realizing it, and it is
difficult to back out once the interface is in wide use.

The clearest example of this would be interfaces that hide the fact that they
operate on a distributed system, whether through promising absolute consistency
and availability at all times (a violation of [PACELC]), through omitting a
recovery strategy, or simply just not documenting the trade-offs. This is
especially important to understand as Erlang acts as a distributed system
internally, with message passing between processes instead of over a physical
network. While a solution may very well work fine as long as nothing crashes,
it may [subtly fail] in the face of unknown errors.

It can also be as simple as an interface whose operations are not fully
independent, such as described in [`DSG-010`], or an interface that cannot
behave in a transactional manner (and thus persisting a broken state if
crashing in the middle of or between operations).

Before finishing an interface, consider what promises your implementation can
and cannot deliver, determine which of these could leak through to your users,
and ask yourself whether it is better to change the interface than to leave it
as it is (or whether to document said behavior).

[subtly fail]: https://en.wikipedia.org/wiki/Two_Generals%27_Problem
[PACELC]: https://en.wikipedia.org/wiki/PACELC_design_principle

[Rule priority:] `Recommendation`

Related CWEs and OWASP risks: [`CWE-362`]

[](){: #rule-dsg-009 }
#### [`DSG-009`] - Consider Providing Methods to Control Resource Consumption
[`DSG-009`]: #rule-dsg-009

When writing a library, consider providing ways for the user to limit resource
usage, such as a configurable upper bound on the number of concurrent sessions
or similar.

[Rule priority:] `Medium`

Related CWEs and OWASP risks: [`CWE-770`], [`API4:2023`]

[](){: #rule-dsg-010 }
#### [`DSG-010`] - Avoid Name Races
[`DSG-010`]: #rule-dsg-010

Erlang processes and ETS tables can have registered names, letting others find
them through their name instead of their process or table identifier. This
should be used with care, as a process or table may terminate at any time.
For example, if two messages are sent to a process through a registered name,
the second message may arrive to a newly restarted process that has not seen
the first, which may be significant ([CWE-386]).

To prevent these issues, either redesign your interface so that multiple
messages or lookups are not necessary, or look up the identifier from the
registered name and use the identifier instead.

[Rule priority:] `Recommendation`

Related CWEs and OWASP risks: [CWE-386]

[CWE-386]: https://cwe.mitre.org/data/definitions/386.html

```erlang
%% DO
Pid = whereis(registered_process),
Pid ! hello,
Pid ! world.

Tid = ets:whereis(registered_table),
A = ets:lookup(Tid, KeyA),
B = ets:lookup(Tid, KeyB).

%% DO NOT
registered_process ! hello,
registered_process ! world.

A = ets:lookup(registered_table, KeyA),
B = ets:lookup(registered_table, KeyB).
```

[](){: #rule-dsg-011 }
#### [`DSG-011`] - Only Deserialize Trusted Data
[`DSG-011`]: #rule-dsg-011

Erlang/OTP provides various functionality that serializes and deserializes
general Erlang terms. Such functionality is intended to be used in a trusted
environment and is not suitable for communication with untrusted entities. For
example, you do not want to load a [`mnesia`] backup from an untrusted entity.
One issue with this being the potential for atom exhaustion, but more
importantly you could potentially end up with a `mnesia` table containing
harmful data ([`CWE-502`]). Other examples are `m:dets` and `m:disk_log`.

JSON is an example of a better format to use when communicating with untrusted
entities. Erlang/OTP provides the `m:json` module for JSON encoding/decoding.
XML is another example of a format that can be used. The [`xmerl`] application
provides functionality for encoding/decoding; see the [`xmerl` Application]
section below for more security related information.

The decoded data, of course, needs to be validated and sanitized if it does not
originate from a trusted entity. Using the `m:xmerl_sax_parser` when decoding XML
you can do this validation during decoding, and, for example, prevent issues
like memory exhaustion. The `m:json` module also provides a SAX style decoding
with user defined callbacks in which you can do the same.

[Rule priority:] `High`

Related CWEs and OWASP risks: [CWE-74], [`A05:2025`]

[`mnesia`]: `e:mnesia:index.html`

### Language

[](){: #rule-lng-001 }
#### [`LNG-001`] - Prefer Tuples Over Exporting Variables
[`LNG-001`]: #rule-lng-001

For historical reasons, Erlang does not employ lexical scoping, and variables
defined in "inner" expressions are available in "outer" expressions that follow
them. Using this makes code harder to reason about, and it is preferable to
write your code as if Erlang has lexical scoping by returning a tuple instead.
There are no performance penalties for doing this.

[Rule priority:] `Recommendation`

```erlang
%% DO
some_function(State0)
    {C, State1} = case foo(State0) of
                      {ok, A} ->
                          {a, bar(A)};
                      b ->
                          {b, State0}
                  end,
    bar(C, State1).

%% DO NOT
some_function(State0)
    C = case foo(State0) of
            {ok, A} ->
                State1 = bar(A),
                a;
            b ->
                State1 = State0,
                b
        end,
    bar(C, State1).
```

[](){: #rule-lng-002 }
#### [`LNG-002`] - Do Not Use `catch`
[`LNG-002`]: #rule-lng-002

The legacy `catch` construct cannot distinguish between `throw/1` and a normal
return, which can have very unexpected results. For instance, the
`m:gen_server` behavior will unintentionally accept any documented return value
when thrown because of its use of `catch`.

Instead, the modern [`try ... catch ... end`](`e:system:expressions.html`)
construct should be used.

The compiler can raise warnings for this by enabling the
`warn_deprecated_catch` option.

[Rule priority:] `Recommendation`

Related CWEs and OWASP risks: [CWE-253], [CWE-480], [`A10:2025`]

[CWE-480]: https://cwe.mitre.org/data/definitions/480.html

```erlang
%% DO
try operation(A, B) of
    C -> ...
catch
    throw:Value ->
        ....;
    error:Reason ->
        ....
end

%% DO NOT
case (catch operation(A, B)) of
    {'EXIT', Reason} ->
        ...;
    C ->
        ...
end
```

[](){: #rule-lng-003 }
#### [`LNG-003`] - Do Not Use the Legacy `and` and `or` Operators
[`LNG-003`]: #rule-lng-003

These operators have been superseded by `andalso` and `orelse`, respectively.

The legacy operators have higher precedence than in most other languages. For
example `X and Y =:= 3` is parsed as `(X and Y) =:= 3`. In a function body,
this will crash, but when used in a guard it will silently fail. It can also
unexpectedly corrupt the intended logic without crashing when all operands are
booleans, for example `X and Y =:= Z` being parsed as `(X and Y) =:= Z`.

Instead, either use the modern `andalso` and `orelse` operators, or use `,`
and `;`, respectively.

[Rule priority:] `Recommendation`

Related CWEs and OWASP risks: [CWE-783]

[CWE-783]: https://cwe.mitre.org/data/definitions/783.html

### Miscellaneous

[](){: #rule-msc-001 }
#### [`MSC-001`] - Do Not Abuse `m:persistent_term`
[`MSC-001`]: #rule-msc-001

`m:persistent_term` is a very fast key-value storage intended for values that
rarely, if ever, change. Modifying one of these terms is allowed but comes at
an extreme performance cost as every single process in the system needs to be
scanned to potentially garbage-collect the old value. Use this feature with
great care.

[Rule priority:] `Medium`

[](){: #rule-msc-002 }
#### [`MSC-002`] - Consider Path Traversal Attacks
[`MSC-002`]: #rule-msc-002

The Erlang file routines do not protect against path traversal attacks by
default. When dealing with paths from an untrusted source (see [`DSG-007`]),
the `filelib:safe_relative_path/2` function should be used to get a safe path
relative to a given directory.

[Rule priority:] `Medium`

Related CWEs and OWASP risks: [`CWE-22`], [CWE-59]

[CWE-59]: https://cwe.mitre.org/data/definitions/59.html

[](){: #rule-msc-003 }
#### [`MSC-003`] - Use `open_port/2` With the `spawn_executable` Option To Start External Executables
[`MSC-003`]: #rule-msc-003

Calling [`open_port/2`] with the `{spawn, _}` argument will either start an
instance of a driver, or spawn an external program using the passed command
line. When spawning an external program, some platforms will start a shell,
which will search in the `PATH` in order to find the program. Environment
variable expansion may also be performed. If user input is to be part of the
command line for an external program, this makes it hard to verify and sanitize
this input. Even if user input is not part of the command, mistakes can easily
be made in non-trivial scenarios. Since loaded drivers and programs compete in
the same name-space, one can also unintentionally start an instance of a driver
instead of spawning an external program.

When spawning an external program, a safer approach is to use [`open_port/2`]
with the `{spawn_executable, _}` argument. No shell is used and the arguments
to the program needs to be explicitly listed using the `{args, Args}` option
where no environment variable expansion is made. You may also want to overwrite
certain environment variables using the `{env, Env}` option in order not to
expose those to the external program.

When starting an instance of a loaded driver, a safer approach is to use
[`open_port/2`] with the `{spawn_driver, _}` argument where no mix-up with
external programs may occur.

External OS commands can also be executed by calling `os:cmd/1` (or
`os:cmd/2`) with a command line as argument. `os:cmd/1,2` suffers from most of
the same issues as [`open_port/2`] with the `{spawn, _}` argument. A shell will
be started in which the passed command line is executed. The `PATH` will be
searched for the command and environment variables will be expanded. A safer
approach is to use [`open_port/2`] with the `{spawn_executable, _}` argument.
It requires gathering of the output from the command instead of getting it
returned as a string, but it is an easy task to handle.

[Rule priority:] `High`

Related CWEs and OWASP risks: [`CWE-78`]

[`open_port/2`]: `erlang:open_port/2`

[](){: #rule-msc-004 }
#### [`MSC-004`] - Protect Sensitive Data
[`MSC-004`]: #rule-msc-004

As Erlang logs many things by default, it is common for application data to
appear in log files and the like. This is not necessarily a problem but some
environments are under regulatory or compliance requirements not to leak
certain data to disk or similar in case an unauthorized person were to get
ahold of them (see [CWE-532]).

As the system cannot make any distinction between which data is sensitive and
which is not, it is up to the user to protect sensitive data. Ways of doing
this include, but are not limited to:

1. Using the `private` option for ETS tables containing sensitive data,
   preventing the table contents from being read by other processes.
1. Using `process_flag(sensitive, true)` for processes operating on sensitive
   data.
   
   This disables nearly all introspection for the process: other processes
   cannot inspect the message queue of this process, tracing is disabled, the
   process' data will not be included in a crash dump, and so on.
1. Implementing the `format_status/2` callback for `gen*` behaviors
   (`m:gen_server` et al) for processes operating on sensitive data.
   
   This lets you control how the process state is presented in introspection
   tools such as `observer`.

Furthermore, to prevent leaking data by stack traces or certain introspection
features, a common technique is to wrap secrets in a zero-arity `fun()` that is
then called in order to retrieve the secret. When passed around, the `fun()`
will appear instead of the data it retrieves when called. Whether the secret is
retrieved on demand or is part of the function environment is up to you, but
the latter has two drawbacks:

1. The secret becomes at least as long-lived as the `fun()`, increasing the
   risk of it showing up in a crash dump.
2. In a distributed system you may have differing versions of the code, and
   calling the `fun()` may crash because it's for a different version of its
   defining module. A workaround for that would be to extract the secret with
   `[Secret] = erlang:fun_info(Fun, env)` instead of calling the fun.

Another approach is to catch errors in sensitive sections of code and then
walking through each stack frame, discarding the arguments, before raising the
exception again. However, note that neither approach prevents the data from
leaking out through a crash or core dump.

[Rule priority:] `Medium`

Related CWEs and OWASP risks: [CWE-209], [CWE-532]

[CWE-209]: https://cwe.mitre.org/data/definitions/209.html

[](){: #rule-msc-005 }
#### [`MSC-005`] - Treat Match Specifications As Code
[`MSC-005`]: #rule-msc-005

Match specifications, such as those used with ETS, are vulnerable to injection
attacks if they are constructed based on untrusted input.

When untrusted data is matched verbatim (such as a key), it is important to
wrap it in `{const, UntrustedData}` expressions. Building general queries based
on untrusted data should be avoided, but if that cannot be done, the query
should be the result of _parsing_ the untrusted data into a match
specification (where the final shape is controlled by the programmer), rather
than attempting to _validate_ the data before passing it in unaltered.

[Rule priority:] `High`

Related CWEs and OWASP risks: [CWE-74]

```erlang
%% DO
find(Table, Needle) ->
    ets:match(Table, {'_', {const, Needle}, '$1'}).


%% DO NOT
find(Table, Needle) ->
    ets:match(Table, {'_', Needle, '$1'}).
```

[](){: #rule-msc-006 }
#### [`MSC-006`] - Consider "Link Following" Attacks
[`MSC-006`]: #rule-msc-006

When operating on untrusted file paths and trying to access files through them,
it is possible that the name does not actually identify a file, but a link
instead, which can in turn point at an unintended resource which is potentially
outside of the intended boundaries.

This can be mitigated by using `filelib:safe_relative_path/2` to ensure that
the path does not escape the given bounds regardless of links. Note that it is
impossible to guarantee atomicity across several filesystem operations, so care
must be taken to avoid time-of-check time-of-use (TOCTOU) race conditions where
a file or symbolic link is swapped out in the middle of these operations. When
operating on a shared folder structure, ensure that only one entity has access
to said structure.

[Rule priority:] `Medium`

Related CWEs and OWASP risks: [`CWE-22`], [CWE-59], [CWE-61]

[CWE-61]: https://cwe.mitre.org/data/definitions/61.html
[CWE-367]: https://cwe.mitre.org/data/definitions/367.html

```erlang
%% DO
open(UntrustedPath, Root, Opts) ->
    case filelib:safe_relative_path(UntrustedPath, Root) of
        unsafe -> {error, unsafe};
        Path -> file:open(filename:join(Root, Path), Opts)
    end.

%% DO NOT
file:open(UntrustedPath, Opts).
```

[](){: #rule-msc-007 }
#### [`MSC-007`] - Avoid Using Debug Functionality in Production
[`MSC-007`]: #rule-msc-007

Functionality that has been explicitly marked to be used only for debugging,
such as `erlang:list_to_pid/1` or the [`keep_secrets`](`t:ssl:common_option/0`)
[`ssl`] option should not be used in production environments, except during
interactive debugging. Unlike with normal functionality, there are no promises
of API stability for debug functionality, and they may change without notice.
They sometimes also have adverse effects on system properties while used (such
as greatly increasing scheduling latency), which are acceptable during
testing but not in production.

In production environments, debug functionality should be considered unsafe as
per [`DSG-006`].

[Rule priority:] `Critical`

Related CWEs and OWASP risks: [CWE-242], [CWE-489], [`A06:2025`]

[CWE-489]: https://cwe.mitre.org/data/definitions/489.html

[](){: #unsafe-functions }
### List of Unsafe Functionality that Should not be Used ([CWE-242])
[Unsafe Functions]: #unsafe-functions

| Unsafe functionality                       | Alternative functionality                                           | Note                                          |
|:-------------------------------------------|:--------------------------------------------------------------------|:----------------------------------------------|
| Undocumented functions/functionality       | *Only* use documented and supported functionality                   | See [`DSG-004`]                               |
| [`open_port/2`] with `{spawn, _}` argument | [`open_port/2`] with `{spawn_executable\|spawn_driver, _}` argument | See [`MSC-003`]                               |
| `m:http_uri` module                        | `m:uri_string` module                                               |                                               |
| `crypto:start/0`                           | `application:start(crypto)`                                         | See [Initializing `crypto`]                   |
| `crypto:enable_fips_mode/1`                | Use [`fips_mode`] application parameter instead                     | See [Initializing `crypto`]                   |
| `crypto:rand_uniform/2`                    | `rand:uniform_s/2` with a cryptographically strong generator        | See [Cryptographically Secure Random Numbers] |
| `crypto:private_encrypt/4` with `rsa_pkcs1_padding` option | For signatures use `crypto:sign/4` instead          | See [Legacy `crypto` Functions]               |
| `crypto:private_decrypt/4` with `rsa_pkcs1_padding` option |                                                     | See [Legacy `crypto` Functions]               |
| `crypto:public_encrypt/4` with `rsa_pkcs1_padding` option |                                                      | See [Legacy `crypto` Functions]               |
| `crypto:public_decrypt/4` with `rsa_pkcs1_padding` option | For signatures use `crypto:verify/5` instead         | See [Legacy `crypto` Functions]               |
| `public_key:encrypt_private/2`             |                                                                     | See [`public_key` Application]                |
| `public_key:encrypt_private/3` with `rsa_pkcs1_padding` option | For signatures use `public_key:sign/4` instead  | See [`public_key` Application]                |
| `public_key:decrypt_private/2`             |                                                                     | See [`public_key` Application]                |
| `public_key:decrypt_private/3` with `rsa_pkcs1_padding` option |                                                 | See [`public_key` Application]                |
| `public_key:encrypt_public/2`              |                                                                     | See [`public_key` Application]                |
| `public_key:encrypt_public/3` with `rsa_pkcs1_padding` option |                                                  | See [`public_key` Application]                |
| `public_key:decrypt_public/2`              |                                                                     | See [`public_key` Application]                |
| `public_key:decrypt_public/3` with `rsa_pkcs1_padding` option | For signatures use `public_key:verify/5` instead | See [`public_key` Application]                |

[Legacy `crypto` Functions]: #legacy-crypto-functions
[process dictionary]: `e:system:ref_man_processes.html#process-dictionary`

[](){: #potentially-unsafe-functions }
### List of Potentially Unsafe Functionality ([CWE-676])
[Potentially Unsafe Functions]: #potentially-unsafe-functions

*All of the below listed functions have valid use-cases*, but may become a concern if not used properly.

| Potentially unsafe functionality           | Potential alternative functionality                                 | Note                                   |
|:-------------------------------------------|:--------------------------------------------------------------------|:---------------------------------------|
| [`binary_to_atom/1`]                       | [`binary_to_existing_atom/1`]                                       | See [`DSG-003`]                        |
| [`binary_to_atom/2`]                       | [`binary_to_existing_atom/2`]                                       | See [`DSG-003`]                        |
| [`list_to_atom/1`]                         | [`list_to_existing_atom/1`]                                         | See [`DSG-003`]                        |
| `os:cmd/1`                                 | [`open_port/2`] with `{spawn_executable, _}` argument               | See [`MSC-003`]                        |
| `os:cmd/2`                                 | [`open_port/2`] with `{spawn_executable, _}` argument               | See [`MSC-003`]                        |
| `erlang:load_nif/2`                        |                                                                     | See [Native Code]                      |
| `erl_ddll:load/2`                          |                                                                     | See [Native Code]                      |
| `erl_ddll:load_driver/2`                   |                                                                     | See [Native Code]                      |
| `erl_ddll:try_load/3`                      |                                                                     | See [Native Code]                      |
| `erl_ddll:reload/2`                        |                                                                     | See [Native Code]                      |
| `erl_ddll:reload_driver/2`                 |                                                                     | See [Native Code]                      |
| `file:consult/1`                           |                                                                     | See [`DSG-003`] and [`DSG-011`]        |
| `file:path_consult/2`                      |                                                                     | See [`DSG-003`] and [`DSG-011`]        |
| `binary_to_term/1`                         | [`binary_to_term/2`] with `safe` option                             | See [`DSG-003`] and [`DSG-011`]        |
| `binary_to_term/2` without `safe` option   | [`binary_to_term/2`] with `safe` option                             | See [`xmerl` Application]              |
| `xmerl_scan:file/1`                        | `m:xmerl_sax_parser` module                                         | See [`xmerl` Application]              |
| `xmerl_scan:file/2`                        | `m:xmerl_sax_parser` module                                         | See [`xmerl` Application]              |
| `xmerl_scan:string/1`                      | `m:xmerl_sax_parser` module                                         | See [`xmerl` Application]              |
| `xmerl_scan:string/2`                      | `m:xmerl_sax_parser` module                                         | See [`xmerl` Application]              |
| `xmerl_scan:string/1`                      | `m:xmerl_sax_parser` module                                         | See [`xmerl` Application]              |
| `xmerl_scan:string/2`                      | `m:xmerl_sax_parser` module                                         | See [`xmerl` Application]              |
| `xmerl_sax_parser:file/2` without `disallow_entities` option | `xmerl_sax_parser:file/2` with `disallow_entities` option | See [`xmerl` Application]      |
| `xmerl_sax_parser:stream/2` without `disallow_entities` option | `xmerl_sax_parser:stream/2` with `disallow_entities` option | See [`xmerl` Application]  |
| `socket:open/1`                            |  `socket:open/2` with `dup` option, which must still be used with extreme care |                             |
| `socket:open/2` without `dup` option       |  `socket:open/2` with `dup` option, which must still be used with extreme care |                             |
| `ssl:prf/5`                                | `ssl:export_key_materials/4`                                        |                                        |
| `ssl:prf/5`                                | `ssl:export_key_materials/4`                                        |                                        |
| `ssl:prf/5`                                | `ssl:export_key_materials/4`                                        |                                        |
| Deprecated functionality                   |                                                                     | See [`DSG-005`]                        |

[`binary_to_atom/1`]: `erlang:binary_to_atom/1`
[`binary_to_atom/2`]: `erlang:binary_to_atom/2`
[`binary_to_existing_atom/1`]: `erlang:binary_to_existing_atom/1`
[`binary_to_existing_atom/2`]: `erlang:binary_to_existing_atom/2`
[`binary_to_term/1`]: `erlang:binary_to_term/1`

[](){: #h-top-40-cwes }
## Top 40 CWEs
[Top 40 CWEs]: #h-top-40-cwes

This section comments on the top 40 CWEs in 2025 and how they relate to
Erlang/OTP, covering both the [CWE Top 25] and the [On The Cusp] list.

[CWE Top 25]: https://cwe.mitre.org/top25/archive/2025/2025_cwe_top25.html
[On The Cusp]: https://cwe.mitre.org/top25/archive/2025/2025_onthecusp_list.html

   [](){: #cwe-79 }
1. [CWE-79] - Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
   [`CWE-79`]: #cwe-79
   
   Erlang/OTP does not include any web frameworks, please refer to the
   [CWE description](https://cwe.mitre.org/data/definitions/79.html) for
   general advice on how to deal with this.
   
   As with other injection attacks, following [`DSG-007`] may also help.
   
   [CWE-79]: https://cwe.mitre.org/data/definitions/79.html
   
   [](){: #cwe-89 }
1. [CWE-89] - Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
   [`CWE-89`]: #cwe-89
   
   Erlang/OTP does not provide any SQL database adapters. However, similar
   attacks could potentially be targeted at match specifications when used in
   ETS, see [`MSC-005`]. As with other injection attacks, following [`DSG-007`]
   may also help.
   
   Otherwise, please refer to the
   [CWE description](https://cwe.mitre.org/data/definitions/89.html) for
   general advice on how to deal with this.
   
   [CWE-89]: https://cwe.mitre.org/data/definitions/89.html
   
   [](){: #cwe-352 }
1. [CWE-352] - Cross-Site Request Forgery (CSRF)
   [`CWE-352`]: #cwe-352
   
   Erlang/OTP does not include any web frameworks, please refer to the
   [CWE description](https://cwe.mitre.org/data/definitions/352.html) for
   general advice on how to deal with this.
   
   As with other injection attacks, following [`DSG-007`] may also help.
   
   [CWE-352]: https://cwe.mitre.org/data/definitions/352.html
   
   [](){: #cwe-862 }
1. [CWE-862] - Missing Authorization
   [`CWE-862`]: #cwe-862
   
   Aside from the previously mentioned issues regarding the default Erlang
   distribution protocol, this is not an Erlang-specific issue.
   
   Please refer to the
   [CWE description](https://cwe.mitre.org/data/definitions/862.html) for
   general advice on how to deal with this.
   
   [CWE-862]: https://cwe.mitre.org/data/definitions/862.html

   [](){: #cwe-787 }
1. [CWE-787] - Out-of-bounds Write
   [`CWE-787`]: #cwe-787
   
   This issue cannot occur because Erlang is memory safe.
   
   [CWE-787]: https://cwe.mitre.org/data/definitions/787.html
   
   [](){: #cwe-22 }
1. [CWE-22] - Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
   [`CWE-22`]: #cwe-22
   
   See [`MSC-002`]. As with other injection attacks, following [`DSG-007`] may
   also help.
   
   [CWE-22]: https://cwe.mitre.org/data/definitions/22.html
   
   [](){: #cwe-416 }
1. [CWE-416] - Use After Free
   [`CWE-416`]: #cwe-416
   
   This issue cannot occur because Erlang is memory safe.
   
   However, as an example it is still possible to attempt to read from a file
   that the user has explicitly closed. Doing so results in an error being
   raised.
   
   [CWE-416]: https://cwe.mitre.org/data/definitions/416.html

   [](){: #cwe-125 }
1. [CWE-125] - Out-of-bounds read
   [`CWE-125`]: #cwe-125
   
   This issue cannot occur because Erlang is memory safe.
   
   [CWE-125]: https://cwe.mitre.org/data/definitions/125.html
   [](){: #cwe-78 }
1. [CWE-78] - Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
   [`CWE-78`]: #cwe-78
   
   See [`MSC-003`]. As with other injection attacks, following [`DSG-007`] may
   also help.
   
   [CWE-78]: https://cwe.mitre.org/data/definitions/78.html
   
   [](){: #cwe-94 }
1. [CWE-94] - Improper Control of Generation of Code ('Code Injection')
   [`CWE-94`]: #cwe-94
   
   This is not Erlang-specific, please refer to the
   [CWE description](https://cwe.mitre.org/data/definitions/94.html) for
   general advice on how to deal with this.
   
   As with other injection attacks, following [`DSG-007`] may also help.
   
   [CWE-94]: https://cwe.mitre.org/data/definitions/94.html
   
   [](){: #cwe-120 }
1. [CWE-120] - Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')
   [`CWE-120`]: #cwe-120
   
   This issue cannot occur because Erlang is memory safe.
   
   [CWE-120]: https://cwe.mitre.org/data/definitions/120.html

   [](){: #cwe-434 }
1. [CWE-434] - Unrestricted Upload of File with Dangerous Type
   [`CWE-434`]: #cwe-434
   
   When using the `ssh_sftpd` module, a custom `file_handler` module can be
   provided to filter out unwanted uploads, but note that this does not permit
   a deep inspection of file contents before upload, and is under no
   circumstances a replacement for proper authentication.

   Otherwise, please refer to the
   [CWE description](https://cwe.mitre.org/data/definitions/434.html) for
   general advice on how to deal with this.
   
   [CWE-434]: https://cwe.mitre.org/data/definitions/434.html
   
   [](){: #cwe-476 }
1. [CWE-476] - NULL Pointer Dereference
   [`CWE-476`]: #cwe-476
   
   This issue cannot occur because Erlang is memory safe.
   
   [CWE-476]: https://cwe.mitre.org/data/definitions/476.html

   [](){: #cwe-121 }
1. [CWE-121] - Stack-based Buffer Overflow
   [`CWE-121`]: #cwe-121
   
   This issue cannot occur because Erlang is memory safe.
   
   [CWE-121]: https://cwe.mitre.org/data/definitions/121.html
   
   [](){: #cwe-502 }
1. [CWE-502] - Deserialization of Untrusted Data
   [`CWE-502`]: #cwe-502
   
   See [`DSG-011`].
   
   [CWE-502]: https://cwe.mitre.org/data/definitions/502.html
   
   [](){: #cwe-122 }
1. [CWE-122] - Heap-based Buffer Overflow
   [`CWE-122`]: #cwe-122
   
   This issue cannot occur because Erlang is memory safe.
   
   [CWE-122]: https://cwe.mitre.org/data/definitions/122.html

   [](){: #cwe-863 }
1. [CWE-863] - Incorrect Authorization 
   [`CWE-863`]: #cwe-863
   
   Aside from the previously mentioned issues regarding the default Erlang
   distribution protocol (see [`DEP-001`]), this is
   a general issue.
   
   Please refer to the
   [CWE description](https://cwe.mitre.org/data/definitions/863.html) for
   general advice on how to deal with this.
   
   [CWE-863]: https://cwe.mitre.org/data/definitions/863.html

   [](){: #cwe-20 }
1. [CWE-20] - Improper Input Validation
   [`CWE-20`]: #cwe-20
   
   Special care should be taken around atoms as described in the
   [Atom Exhaustion] section. Following [`DSG-003`], [`DSG-011`] and
   [`DSG-007`] may also help.
   
   Otherwise, please refer to the
   [CWE description](https://cwe.mitre.org/data/definitions/20.html) for
   general advice on how to deal with this.
   
   [CWE-20]: https://cwe.mitre.org/data/definitions/20.html

   [](){: #cwe-284 }
1. [CWE-284] -  Improper Access Control
   [`CWE-284`]: #cwe-284
   
   This is not Erlang-specific, please refer to
   [CWE description](https://cwe.mitre.org/data/definitions/284.html) for
   general advice on how to deal with this.
   
   [CWE-284]: https://cwe.mitre.org/data/definitions/284.html

   [](){: #cwe-200 }
1. [CWE-200] - Exposure of Sensitive Information to an Unauthorized Actor
   [`CWE-200`]: #cwe-200
   
   Aside from the previously mentioned issues regarding the default Erlang
   distribution protocol (see [`DEP-001`]), this is a general issue.
   
   Please refer to the
   [CWE description](https://cwe.mitre.org/data/definitions/200.html) for
   general advice on how to deal with this.
   
   See also [`MSC-004`] and [`DSG-007`].
   
   [CWE-200]: https://cwe.mitre.org/data/definitions/200.html
   
   [](){: #cwe-306 }
1. [CWE-306] - Missing Authentication for Critical Function
   [`CWE-306`]: #cwe-306
   
   Aside from the previously mentioned issues regarding the default Erlang
   distribution protocol (see [`DEP-001`]), this is
   a general issue. Please refer to the
   [CWE description](https://cwe.mitre.org/data/definitions/306.html) for
   general advice on how to deal with this.
   
   [CWE-306]: https://cwe.mitre.org/data/definitions/306.html
   
   [](){: #cwe-918 }
1. [CWE-918] - Server-Side Request Forgery (SSRF) 
   [`CWE-918`]: #cwe-918
   
   Erlang/OTP does not include any web frameworks, please refer to the
   [CWE description](https://cwe.mitre.org/data/definitions/918.html) for
   general advice on how to deal with this.
   
   As with other injection attacks, following [`DSG-007`] may also help.
   
   [CWE-918]: https://cwe.mitre.org/data/definitions/918.html

   [](){: #cwe-77 }
1. [CWE-77] - Improper Neutralization of Special Elements used in a Command ('Command Injection')
   [`CWE-77`]: #cwe-77
   
   This is a general issue. Please refer to the
   [CWE description](https://cwe.mitre.org/data/definitions/77.html) for
   general advice on how to deal with this.
   
   As with other injection attacks, following [`DSG-007`] may also help.
   
   [CWE-77]: https://cwe.mitre.org/data/definitions/77.html

   [](){: #cwe-639 }
1. [CWE-639] - Authorization Bypass Through User-Controlled Key
   [`CWE-639`]: #cwe-639
   
   This is a general issue. Please refer to the
   [CWE description](https://cwe.mitre.org/data/definitions/639.html) for
   general advice on how to deal with this.
   
   [CWE-639]: https://cwe.mitre.org/data/definitions/639.html

   [](){: #cwe-770 }
1. [CWE-770] - Allocation of Resources Without Limits or Throttling
   [`CWE-770`]: #cwe-770
   
   See [`DSG-009`]. Otherwise, please refer to the
   [CWE description](https://cwe.mitre.org/data/definitions/770.html) for
   general advice on how to deal with this.
   
   [CWE-770]: https://cwe.mitre.org/data/definitions/770.html
   [](){: #cwe-266 }
1. [CWE-266] - Incorrect Privilege Assignment
   [`CWE-266`]: #cwe-266
   
   This is not Erlang-specific, please refer to
   [CWE description](https://cwe.mitre.org/data/definitions/266.html) for
   general advice on how to deal with this.
   
   [CWE-266]: https://cwe.mitre.org/data/definitions/266.html
   
   [](){: #cwe-276 }
1. [CWE-276] - Incorrect Default Permissions
   [`CWE-276`]: #cwe-276
   
   Aside from the previously mentioned issues regarding the default Erlang
   distribution protocol (which is effectively wide open by default, see
   [`DEP-001`]), this is a general issue.
   
   Please refer to the
   [CWE description](https://cwe.mitre.org/data/definitions/276.html) for
   general advice on how to deal with this.
   
   [CWE-276]: https://cwe.mitre.org/data/definitions/276.html
   
   [](){: #cwe-98 }
1. [CWE-98] - Improper Control of Filename for Include/Require Statement in PHP Program ('PHP Remote File Inclusion')
   [`CWE-98`]: #cwe-98
   
   This is specific to PHP, all other languages (including Erlang) are
   unaffected.
   
   [CWE-98]: https://cwe.mitre.org/data/definitions/98.html

   [](){: #cwe-269 }
1. [CWE-269] - Improper Privilege Management 
   [`CWE-269`]: #cwe-269
   
   This is not Erlang-specific, please refer to the
   [CWE description](https://cwe.mitre.org/data/definitions/269.html) for
   general advice on how to deal with this.
   
    [CWE-269]: https://cwe.mitre.org/data/definitions/269.html
   
   [](){: #cwe-190 }
1. [CWE-190] - Integer Overflow or Wraparound
   [`CWE-190`]: #cwe-190
   
   Erlang uses arbitrary-precision arithmetic rather than wrapping around or
   behaving in an ill-defined manner on overflow. Note that this weakness is
   distinct from throwing an error on overflow, which may still occur if the
   number grows to be several megabits in size. See [system limits].
   
   [CWE-190]: https://cwe.mitre.org/data/definitions/190.html

   [](){: #cwe-287 }
1. [CWE-287] - Improper Authentication
   [`CWE-287`]: #cwe-287
   
   Aside from the previously mentioned issues regarding the default Erlang
   distribution protocol (see [`DEP-001`]), this is
   a general issue.
   
   Please refer to the
   [CWE description](https://cwe.mitre.org/data/definitions/287.html) for
   general advice on how to deal with this.
   
   [CWE-287]: https://cwe.mitre.org/data/definitions/287.html
   
   [](){: #cwe-400 }
1. [CWE-400] - Uncontrolled Resource Consumption
   [`CWE-400`]: #cwe-400
   
   Aside from the previously mentioned issues regarding [Atom Exhaustion], this
   is a general issue.
   
   Most network-facing OTP applications provide options for controlling
   resource consumption. See [`ssl` Application] and [`ssh` Application] for
   more information.
   
   Otherwise, please refer to the
   [CWE description](https://cwe.mitre.org/data/definitions/400.html) for
   general advice on how to deal with this.
   
   [CWE-400]: https://cwe.mitre.org/data/definitions/400.html
   
   [](){: #cwe-288 }
1. [CWE-288] - Authentication Bypass Using an Alternate Path or Channel 
   [`CWE-288`]: #cwe-288
   
   This is a general issue. Please refer to the
   [CWE description](https://cwe.mitre.org/data/definitions/288.html) for
   general advice on how to deal with this.

   [CWE-288]: https://cwe.mitre.org/data/definitions/288.html
   
   [](){: #cwe-427 }
1. [CWE-427] - Uncontrolled Search Path Element
   [`CWE-427`]: #cwe-427
   
   See [`DEP-004`].
   
   [CWE-427]: https://cwe.mitre.org/data/definitions/427.html
   
   [](){: #cwe-798 }
1. [CWE-798] - Use of Hard-coded Credentials 
   [`CWE-798`]: #cwe-798
   
   This is not Erlang-specific, please refer to the
   [CWE description](https://cwe.mitre.org/data/definitions/798.html) for
   general advice on how to deal with this.
   
   [CWE-798]: https://cwe.mitre.org/data/definitions/798.html
   
   [](){: #cwe-362 }
1. [CWE-362] - Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')
   [`CWE-362`]: #cwe-362
   
   As Erlang provides message-passing concurrency, race conditions are very
   easily avoided. Wrapping a shared resource in an Erlang process and routing
   all access through it guarantees atomicity regardless of the nature of the
   resource.
   
   Following [`DSG-008`] may also help.
   
   [CWE-362]: https://cwe.mitre.org/data/definitions/362.html
   
   [](){: #cwe-401 }
1. [CWE-401] - Missing Release of Memory after Effective Lifetime
   [`CWE-401`]: #cwe-401

   Erlang has automatic memory management so this issue cannot occur. However,
   it is of course still possible to create something similar to a memory leak
   by explicitly keeping data around forever. For example, if your application
   has a cache that lacks an eviction strategy (i.e. never removes elements),
   it is possible for the cache to grow endlessly and cause the system to run
   out of memory.
   
   See the [Memory Management] section for more details.
   
   [CWE-401]: https://cwe.mitre.org/data/definitions/401.html

   [](){: #cwe-732 }
1. [CWE-732] - Incorrect Permission Assignment for Critical Resource
   [`CWE-732`]: #cwe-732
   
   Aside from the previously mentioned issues regarding the default Erlang
   distribution protocol (see [`DEP-001`]), this is a general issue. Please
   refer to the
   [CWE description](https://cwe.mitre.org/data/definitions/732.html) for
   general advice on how to deal with this.
   
   [CWE-732]: https://cwe.mitre.org/data/definitions/732.html

   [](){: #cwe-119 }
1. [CWE-119] - Improper Restriction of Operations within the Bounds of a Memory Buffer
   [`CWE-119`]: #cwe-119
   
   This issue cannot occur because Erlang is memory safe.

   [CWE-119]: https://cwe.mitre.org/data/definitions/119.html
   
   [](){: #cwe-601 }
1. [CWE-601] - URL Redirection to Untrusted Site ('Open Redirect')
   [`CWE-601`]: #cwe-601
   
   This is a general issue. When acting as a client using `m:httpc`, the
   `{autoredirect, false}` option can be passed to avoid blindly following
   redirects.
   
   Otherwise, please refer to the
   [CWE description](https://cwe.mitre.org/data/definitions/601.html) for
   general advice on how to deal with this.
   
   [CWE-601]: https://cwe.mitre.org/data/definitions/601.html

[](){: #h-owasp-top-10 }
## OWASP Top 10
[OWASP Top 10]: #h-owasp-top-10

This section comments on the [top ten security risks] as catalogued by [OWASP].
In general, following the Erlang/OTP conventions and documentation mitigates
these risks.

[top ten security risks]: https://owasp.org/Top10/2025/0x00_2025-Introduction/
[OWASP]: https://owasp.org/

   [](){: #owasp-a01-2025 }
1. [A01:2025] - Broken Access Control
   [`A01:2025`]: #owasp-a01-2025
   
   This risk does not relate to Erlang/OTP per se, other than the previously
   described deficiencies regarding the default distribution protocol, see
   [`DEP-001`].
   
   Otherwise, please refer to the [A01:2025] description for advice on how to
   deal with this.
   
   [A01:2025]: https://owasp.org/Top10/2025/A01_2025-Broken_Access_Control/
   [](){: #owasp-a02-2025 }
1. [A02:2025] - Security Misconfiguration
   [`A02:2025`]: #owasp-a02-2025
   
   See [`A01:2025`] and the [A02:2025] description for advice on how to deal
   with this.
   
   [A02:2025]: https://owasp.org/Top10/2025/A02_2025-Security_Misconfiguration/
   [](){: #owasp-a03-2025 }
1. [A03:2025] - Software Supply Chain Failures
   [`A03:2025`]: #owasp-a03-2025
   
   This risk does not relate to Erlang per se. For what it is worth, Erlang/OTP
   provides a built source Software Bill of Materials (SBoM) for every release.

   Otherwise, please refer to the [A03:2025] description for advice on how to
   deal with this.
   
   [A03:2025]: https://owasp.org/Top10/2025/A03_2025-Software_Supply_Chain_Failures/
   [](){: #owasp-a04-2025 }
1. [A04:2025] - Cryptographic Failures
   [`A04:2025`]: #owasp-a04-2025
   
   This risk does not relate to Erlang/OTP per se, but some risks can be
   mitigated by following the advice in the [`crypto` Application] section.

   Otherwise, please refer to the [A04:2025] description for advice on how to
   deal with this.
   
   [A04:2025]: https://owasp.org/Top10/2025/A04_2025-Cryptographic_Failures/
   [](){: #owasp-a05-2025 }
1. [A05:2025] - Injection
   [`A05:2025`]: #owasp-a05-2025
   
   See [`MSC-004`] and the [A05:2025] description for advice on how to deal
   with this.
   
   [A05:2025]: https://owasp.org/Top10/2025/A05_2025-Injection/
   [](){: #owasp-a06-2025 }
1. [A06:2025] - Insecure Design
   [`A06:2025`]: #owasp-a06-2025
   
   This risk does not relate to Erlang per se, but following this guide, our
   [Design principles], and the [A06:2025] description provides a good
   foundation to build upon.
   
   [Design principles]: https://www.erlang.org/doc/system/design_principles.html
   
   [A06:2025]: https://owasp.org/Top10/2025/A06_2025-Insecure_Design/
   [](){: #owasp-a07-2025 }
1. [A07:2025] - Authentication Failures
   [`A07:2025`]: #owasp-a07-2025
   
   See [`A01:2025`] and the [A07:2025] description for advice on how to deal
   with this.
   
   [A07:2025]: https://owasp.org/Top10/2025/A07_2025-Authentication_Failures/
   [](){: #owasp-a08-2025 }
1. [A08:2025] - Software or Data Integrity Failures
   [`A08:2025`]: #owasp-a08-2025
   
   Erlang/OTP provides various tools to help ensure data integrity, notably
   `public_key:sign/4` and `public_key:verify/5`.
   
   Note, however, that there is _no built-in support_ for software integrity
   checks. All components of the software itself are considered _trusted_ and
   are _only_ subject to checks that protect against some kinds of accidental
   corruption.

   Otherwise, please refer to the [A08:2025] description for advice on how to
   deal with this.
   
   [A08:2025]: https://owasp.org/Top10/2025/A08_2025-Software_or_Data_Integrity_Failures/
   [](){: #owasp-a09-2025 }
1. [A09:2025] - Logging & Alerting Failures
   [`A09:2025`]: #owasp-a09-2025
   
   Erlang/OTP logs many things by default, including processes that terminate
   abnormally ("crash"), which is especially effective when following the
   convention outlined in the [Error Handling] section. These logs are passed
   through the `m:logger` framework and can be configured at will.
   
   It is also important to consider the possibility of leaking sensitive
   information through logs, see [`MSC-004`].

   Otherwise, please refer to the [A09:2025] description for advice on how to
   deal with this.
   
   [A09:2025]: https://owasp.org/Top10/2025/A09_2025-Logging_and_Alerting_Failures/
   [](){: #owasp-a10-2025 }
1. [A10:2025] - Mishandling of Exceptional Conditions
   [`A10:2025`]: #owasp-a10-2025
   
   As mentioned in the [Error Handling] section, program execution should be
   restricted to that which is expected, and unexpected situations should be left
   to the supervision structure. See also [`DSG-001`] and the [A10:2025]
   description for advice on how to deal with this.
   
   [A10:2025]: https://owasp.org/Top10/2025/A10_2025-Mishandling_of_Exceptional_Conditions/

[](){: #owasp-api-top-10 }
## OWASP API Security Top 10
[OWASP API Security Top 10]: #owasp-api-top-10

This section comments on the [top ten API security risks] as catalogued by
[OWASP]. In general, following the Erlang/OTP conventions and documentation
mitigates these risks, but their nature means that there are few
Erlang-specific recommendations.

[top ten API security risks]: https://owasp.org/API-Security/editions/2023/en/0x11-t10/

   [](){: #owasp-api1-2023 }
1. [API1:2023] - Broken Object Level Authorization
   [`API1:2023`]: #owasp-api1-2023
   
   This is a general issue, please refer to the [API1:2023] description for
   advice on how to deal with this.
   
   [API1:2023]: https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/
   [](){: #owasp-api2-2023 }
1. [API2:2023] - Broken Authentication
   [`API2:2023`]: #owasp-api2-2023
   
   This is a general issue, similar to [`A01:2025`] - Broken Access Control.
   Please refer to the [API2:2023] description for advice on how to deal with
   this.
   
   [API2:2023]: https://owasp.org/API-Security/editions/2023/en/0xa2-broken-authentication/
   [](){: #owasp-api3-2023 }
1. [API3:2023] - Broken Object Property Level Authorization
   [`API3:2023`]: #owasp-api3-2023
   
   This is a general issue, similar to [`API1:2023`] - Broken Object Level
   Authorization. Please refer to the [API3:2023] description for advice on how
   to deal with this.
   
   [API3:2023]: https://owasp.org/API-Security/editions/2023/en/0xa3-broken-object-property-level-authorization/
   [](){: #owasp-api4-2023 }
1. [API4:2023] - Unrestricted Resource Consumption
   [`API4:2023`]: #owasp-api4-2023
   
   Aside from the previously mentioned issues regarding [Atom Exhaustion], this
   is a general issue. Please refer to [`DSG-009`], [`CWE-770`], and the
   [API4:2023] description for advice on how to deal with this.
   
   [API4:2023]: https://owasp.org/API-Security/editions/2023/en/0xa4-unrestricted-resource-consumption/
   [](){: #owasp-api5-2023 }
1. [API5:2023] - Broken Function Level Authorization
   [`API5:2023`]: #owasp-api5-2023
   
   This is a general issue, similar to [`API1:2023`] - Broken Object Level
   Authorization. Please refer to the [API5:2023] description for advice on how
   to deal with this.
   
   [API5:2023]: https://owasp.org/API-Security/editions/2023/en/0xa5-broken-function-level-authorization/
   [](){: #owasp-api6-2023 }
1. [API6:2023] - Unrestricted Access to Sensitive Business Flows
   [`API6:2023`]: #owasp-api6-2023
   
   This is a general issue, similar to [`API1:2023`] - Broken Object Level
   Authorization. Please refer to the [API6:2023] description for advice on how
   to deal with this.
   
   [API6:2023]: https://owasp.org/API-Security/editions/2023/en/0xa6-unrestricted-access-to-sensitive-business-flows/
   [](){: #owasp-api7-2023 }
1. [API7:2023] - Server Side Request Forgery
   [`API7:2023`]: #owasp-api7-2023
   
   This is a general issue, please refer to [`CWE-918`] and the [API7:2023]
   description for advice on how to deal with this.
   
   [API7:2023]: https://owasp.org/API-Security/editions/2023/en/0xa7-server-side-request-forgery/
   [](){: #owasp-api8-2023 }
1. [API8:2023] - Security Misconfiguration
   [`API8:2023`]: #owasp-api8-2023

   This risk does not relate to Erlang/OTP per se, other than the previously
   described deficiencies regarding the default distribution protocol (see
   [`DEP-001`]).
   
   Otherwise, please refer to the [API8:2023] description for advice on how to
   deal with this.
   
   [API8:2023]: https://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/
   [](){: #owasp-api9-2023 }
1. [API9:2023] - Improper Inventory Management
   [`API9:2023`]: #owasp-api9-2023
   
   This is a general issue, please refer to the [API9:2023] description for
   advice on how to deal with this.
   
   [API9:2023]: https://owasp.org/API-Security/editions/2023/en/0xa9-improper-inventory-management/
   [](){: #owasp-api10-2023 }
1. [API10:2023] - Unsafe Consumption of APIs
   [`API10:2023`]: #owasp-api10-2023
   
   This is a general issue, please refer to the [API10:2023] description for
   advice on how to deal with this.
   
   [API10:2023]: https://owasp.org/API-Security/editions/2023/en/0xaa-unsafe-consumption-of-apis/
