Secure Coding Guidelines

View Source

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.

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.

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).

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.
  2. The errors that we expect to handle gracefully, such as a web server responding with a 404 error when it cannot find a file.
  3. 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.

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.

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.

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 erl_ddll load functions.

Application-Specific Guidelines

crypto Application

Initializing crypto

The libcrypto library from OpenSSL will be loaded and configured when the crypto module is loaded. During loading of the 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 crypto module by any other means than calling application:start(crypto). Any calls to the crypto library will load the module. Make sure not to call any functions in the 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.

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 rand and 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.

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.

ssl Application

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 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.

ssh Application

See the ssh documentation's hardening chapter.

public_key Application

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.

xmerl Application

The 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 xmerl_sax_parser you can do that by passing the disallow_entities option.

Rules / Secure Coding Standard

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

STL-001 - Be Restrictive

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

%% 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]

STL-002 - Avoid Boolean Blindness

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

%% 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

STL-003 - Use Uppercase Names for Macros

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

%% DO
-define(MY_MACRO, 65535).

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

Deployment

DEP-001 - Do Not Expose Default Erlang Distribution on Untrusted Networks

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

DEP-002 - Build and Install Erlang/OTP Yourself

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

DEP-003 - Use Actively Maintained Versions of Erlang/OTP

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

DEP-004 - Protect the Code Path

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

DEP-005 - Avoid interactive Mode in Production

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

DEP-006 - Minimize VM Privileges

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

DSG-001 - Encode the Problem Domain in the Supervision Tree

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

DSG-002 - Prefer Letting the User Decide What Warrants an Exception

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

%% 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

DSG-003 - Do Not Abuse Atoms

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.

Rule priority: High

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

%% 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).

DSG-004 - Do Not Use Undocumented Functionality

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

DSG-005 - Do Not Use Deprecated Functionality

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

DSG-006 - Do Not Use Unsafe Functionality

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

DSG-007 - Know Your Data

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 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

DSG-008 - Avoid Designing Unrealistic Interfaces

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).

Rule priority: Recommendation

Related CWEs and OWASP risks: CWE-362

DSG-009 - Consider Providing Methods to Control Resource Consumption

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

DSG-010 - Avoid Name Races

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

%% 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).

DSG-011 - Only Deserialize Trusted Data

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 dets and disk_log.

JSON is an example of a better format to use when communicating with untrusted entities. Erlang/OTP provides the 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 xmerl_sax_parser when decoding XML you can do this validation during decoding, and, for example, prevent issues like memory exhaustion. The 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

Language

LNG-001 - Prefer Tuples Over Exporting Variables

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

%% 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).

LNG-002 - Do Not Use catch

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

Instead, the modern try ... catch ... end 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

%% 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

LNG-003 - Do Not Use the Legacy and and or Operators

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

Miscellaneous

MSC-001 - Do Not Abuse persistent_term

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

MSC-002 - Consider Path Traversal Attacks

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

MSC-003 - Use open_port/2 With the spawn_executable Option To Start External Executables

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

MSC-004 - Protect Sensitive Data

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.

  2. 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.

  3. Implementing the format_status/2 callback for gen* behaviors (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

MSC-005 - Treat Match Specifications As Code

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

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


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

MSC-006 - Consider "Link Following" Attacks

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

%% 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).

MSC-007 - Avoid Using Debug Functionality in Production

Functionality that has been explicitly marked to be used only for debugging, such as erlang:list_to_pid/1 or the keep_secrets 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

List of Unsafe Functionality that Should not be Used (CWE-242)

Unsafe functionalityAlternative functionalityNote
Undocumented functions/functionalityOnly use documented and supported functionalitySee DSG-004
open_port/2 with {spawn, _} argumentopen_port/2 with {spawn_executable|spawn_driver, _} argumentSee MSC-003
http_uri moduleuri_string module
crypto:start/0application:start(crypto)See Initializing crypto
crypto:enable_fips_mode/1Use fips_mode application parameter insteadSee Initializing crypto
crypto:rand_uniform/2rand:uniform_s/2 with a cryptographically strong generatorSee Cryptographically Secure Random Numbers
crypto:private_encrypt/4 with rsa_pkcs1_padding optionFor signatures use crypto:sign/4 insteadSee Legacy crypto Functions
crypto:private_decrypt/4 with rsa_pkcs1_padding optionSee Legacy crypto Functions
crypto:public_encrypt/4 with rsa_pkcs1_padding optionSee Legacy crypto Functions
crypto:public_decrypt/4 with rsa_pkcs1_padding optionFor signatures use crypto:verify/5 insteadSee Legacy crypto Functions
public_key:encrypt_private/2See public_key Application
public_key:encrypt_private/3 with rsa_pkcs1_padding optionFor signatures use public_key:sign/4 insteadSee public_key Application
public_key:decrypt_private/2See public_key Application
public_key:decrypt_private/3 with rsa_pkcs1_padding optionSee public_key Application
public_key:encrypt_public/2See public_key Application
public_key:encrypt_public/3 with rsa_pkcs1_padding optionSee public_key Application
public_key:decrypt_public/2See public_key Application
public_key:decrypt_public/3 with rsa_pkcs1_padding optionFor signatures use public_key:verify/5 insteadSee public_key Application

List of Potentially Unsafe Functionality (CWE-676)

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

Potentially unsafe functionalityPotential alternative functionalityNote
binary_to_atom/1binary_to_existing_atom/1See DSG-003
binary_to_atom/2binary_to_existing_atom/2See DSG-003
list_to_atom/1list_to_existing_atom/1See DSG-003
os:cmd/1open_port/2 with {spawn_executable, _} argumentSee MSC-003
os:cmd/2open_port/2 with {spawn_executable, _} argumentSee MSC-003
erlang:load_nif/2See Native Code
erl_ddll:load/2See Native Code
erl_ddll:load_driver/2See Native Code
erl_ddll:try_load/3See Native Code
erl_ddll:reload/2See Native Code
erl_ddll:reload_driver/2See Native Code
file:consult/1See DSG-003 and DSG-011
file:path_consult/2See DSG-003 and DSG-011
binary_to_term/1binary_to_term/2 with safe optionSee DSG-003 and DSG-011
binary_to_term/2 without safe optionbinary_to_term/2 with safe optionSee xmerl Application
xmerl_scan:file/1xmerl_sax_parser moduleSee xmerl Application
xmerl_scan:file/2xmerl_sax_parser moduleSee xmerl Application
xmerl_scan:string/1xmerl_sax_parser moduleSee xmerl Application
xmerl_scan:string/2xmerl_sax_parser moduleSee xmerl Application
xmerl_scan:string/1xmerl_sax_parser moduleSee xmerl Application
xmerl_scan:string/2xmerl_sax_parser moduleSee xmerl Application
xmerl_sax_parser:file/2 without disallow_entities optionxmerl_sax_parser:file/2 with disallow_entities optionSee xmerl Application
xmerl_sax_parser:stream/2 without disallow_entities optionxmerl_sax_parser:stream/2 with disallow_entities optionSee xmerl Application
socket:open/1socket:open/2 with dup option, which must still be used with extreme care
socket:open/2 without dup optionsocket:open/2 with dup option, which must still be used with extreme care
ssl:prf/5ssl:export_key_materials/4
ssl:prf/5ssl:export_key_materials/4
ssl:prf/5ssl:export_key_materials/4
Deprecated functionalitySee DSG-005

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.

  1. CWE-79 - Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

    Erlang/OTP does not include any web frameworks, please refer to the CWE description for general advice on how to deal with this.

    As with other injection attacks, following DSG-007 may also help.

  2. CWE-89 - Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')

    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 for general advice on how to deal with this.

  3. CWE-352 - Cross-Site Request Forgery (CSRF)

    Erlang/OTP does not include any web frameworks, please refer to the CWE description for general advice on how to deal with this.

    As with other injection attacks, following DSG-007 may also help.

  4. CWE-862 - Missing Authorization

    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 for general advice on how to deal with this.

  5. CWE-787 - Out-of-bounds Write

    This issue cannot occur because Erlang is memory safe.

  6. CWE-22 - Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

    See MSC-002. As with other injection attacks, following DSG-007 may also help.

  7. CWE-416 - Use After Free

    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.

  8. CWE-125 - Out-of-bounds read

    This issue cannot occur because Erlang is memory safe.

  9. CWE-78 - Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

    See MSC-003. As with other injection attacks, following DSG-007 may also help.

  10. CWE-94 - Improper Control of Generation of Code ('Code Injection')

    This is not Erlang-specific, please refer to the CWE description for general advice on how to deal with this.

    As with other injection attacks, following DSG-007 may also help.

  11. CWE-120 - Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')

    This issue cannot occur because Erlang is memory safe.

  12. CWE-434 - Unrestricted Upload of File with Dangerous Type

    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 for general advice on how to deal with this.

  13. CWE-476 - NULL Pointer Dereference

    This issue cannot occur because Erlang is memory safe.

  14. CWE-121 - Stack-based Buffer Overflow

    This issue cannot occur because Erlang is memory safe.

  15. CWE-502 - Deserialization of Untrusted Data

    See DSG-011.

  16. CWE-122 - Heap-based Buffer Overflow

    This issue cannot occur because Erlang is memory safe.

  17. CWE-863 - Incorrect Authorization

    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 for general advice on how to deal with this.

  18. CWE-20 - Improper Input Validation

    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 for general advice on how to deal with this.

  19. CWE-284 - Improper Access Control

    This is not Erlang-specific, please refer to CWE description for general advice on how to deal with this.

  20. CWE-200 - Exposure of Sensitive Information to an Unauthorized Actor

    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 for general advice on how to deal with this.

    See also MSC-004 and DSG-007.

  21. CWE-306 - Missing Authentication for Critical Function

    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 for general advice on how to deal with this.

  22. CWE-918 - Server-Side Request Forgery (SSRF)

    Erlang/OTP does not include any web frameworks, please refer to the CWE description for general advice on how to deal with this.

    As with other injection attacks, following DSG-007 may also help.

  23. CWE-77 - Improper Neutralization of Special Elements used in a Command ('Command Injection')

    This is a general issue. Please refer to the CWE description for general advice on how to deal with this.

    As with other injection attacks, following DSG-007 may also help.

  24. CWE-639 - Authorization Bypass Through User-Controlled Key

    This is a general issue. Please refer to the CWE description for general advice on how to deal with this.

  25. CWE-770 - Allocation of Resources Without Limits or Throttling

    See DSG-009. Otherwise, please refer to the CWE description for general advice on how to deal with this.

  26. CWE-266 - Incorrect Privilege Assignment

    This is not Erlang-specific, please refer to CWE description for general advice on how to deal with this.

  27. CWE-276 - Incorrect Default Permissions

    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 for general advice on how to deal with this.

  28. CWE-98 - Improper Control of Filename for Include/Require Statement in PHP Program ('PHP Remote File Inclusion')

    This is specific to PHP, all other languages (including Erlang) are unaffected.

  29. CWE-269 - Improper Privilege Management

    This is not Erlang-specific, please refer to the CWE description for general advice on how to deal with this.

  30. CWE-190 - Integer Overflow or Wraparound

    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.

  31. CWE-287 - Improper Authentication

    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 for general advice on how to deal with this.

  32. CWE-400 - Uncontrolled Resource Consumption

    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 for general advice on how to deal with this.

  33. CWE-288 - Authentication Bypass Using an Alternate Path or Channel

    This is a general issue. Please refer to the CWE description for general advice on how to deal with this.

  34. CWE-427 - Uncontrolled Search Path Element

    See DEP-004.

  35. CWE-798 - Use of Hard-coded Credentials

    This is not Erlang-specific, please refer to the CWE description for general advice on how to deal with this.

  36. CWE-362 - Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')

    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.

  37. CWE-401 - Missing Release of Memory after Effective Lifetime

    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.

  38. CWE-732 - Incorrect Permission Assignment for Critical Resource

    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 for general advice on how to deal with this.

  39. CWE-119 - Improper Restriction of Operations within the Bounds of a Memory Buffer

    This issue cannot occur because Erlang is memory safe.

  40. CWE-601 - URL Redirection to Untrusted Site ('Open Redirect')

    This is a general issue. When acting as a client using httpc, the {autoredirect, false} option can be passed to avoid blindly following redirects.

    Otherwise, please refer to the CWE description for general advice on how to deal with this.

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.

  1. A01:2025 - Broken Access Control

    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.

  2. A02:2025 - Security Misconfiguration

    See A01:2025 and the A02:2025 description for advice on how to deal with this.

  3. A03:2025 - Software Supply Chain Failures

    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.

  4. A04:2025 - Cryptographic Failures

    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.

  5. A05:2025 - Injection

    See MSC-004 and the A05:2025 description for advice on how to deal with this.

  6. A06:2025 - Insecure Design

    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.

  7. A07:2025 - Authentication Failures

    See A01:2025 and the A07:2025 description for advice on how to deal with this.

  8. A08:2025 - Software or Data Integrity Failures

    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.

  9. A09:2025 - Logging & Alerting Failures

    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 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.

  10. A10:2025 - Mishandling of Exceptional Conditions

    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.

OWASP API Security 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.

  1. API1:2023 - Broken Object Level Authorization

    This is a general issue, please refer to the API1:2023 description for advice on how to deal with this.

  2. API2:2023 - Broken Authentication

    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.

  3. API3:2023 - Broken Object Property Level Authorization

    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.

  4. API4:2023 - Unrestricted Resource Consumption

    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.

  5. API5:2023 - Broken Function Level Authorization

    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.

  6. API6:2023 - Unrestricted Access to Sensitive Business Flows

    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.

  7. API7:2023 - Server Side Request Forgery

    This is a general issue, please refer to CWE-918 and the API7:2023 description for advice on how to deal with this.

  8. API8:2023 - Security Misconfiguration

    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.

  9. API9:2023 - Improper Inventory Management

    This is a general issue, please refer to the API9:2023 description for advice on how to deal with this.

  10. API10:2023 - Unsafe Consumption of APIs

    This is a general issue, please refer to the API10:2023 description for advice on how to deal with this.