Secure Coding Guidelines
View SourceThis 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:
- 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.
- The errors that we expect to handle gracefully, such as a web server responding with a 404 error when it cannot find a file.
- 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
endSTL-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
endDSG-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 ->
...
endLNG-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:
Using the
privateoption for ETS tables containing sensitive data, preventing the table contents from being read by other processes.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.
Implementing the
format_status/2callback forgen*behaviors (gen_serveret 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:
- The secret becomes at least as long-lived as the
fun(), increasing the risk of it showing up in a crash dump. - 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)
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.
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-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-007may also help.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, followingDSG-007may also help.Otherwise, please refer to the CWE description for general advice on how to deal with this.
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-007may also help.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.
CWE-787 - Out-of-bounds Write
This issue cannot occur because Erlang is memory safe.
CWE-22 - Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
See
MSC-002. As with other injection attacks, followingDSG-007may also help.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.
CWE-125 - Out-of-bounds read
This issue cannot occur because Erlang is memory safe.
CWE-78 - Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
See
MSC-003. As with other injection attacks, followingDSG-007may also help.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-007may also help.CWE-120 - Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')
This issue cannot occur because Erlang is memory safe.
CWE-434 - Unrestricted Upload of File with Dangerous Type
When using the
ssh_sftpdmodule, a customfile_handlermodule 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.
CWE-476 - NULL Pointer Dereference
This issue cannot occur because Erlang is memory safe.
CWE-121 - Stack-based Buffer Overflow
This issue cannot occur because Erlang is memory safe.
CWE-502 - Deserialization of Untrusted Data
See
DSG-011.CWE-122 - Heap-based Buffer Overflow
This issue cannot occur because Erlang is memory safe.
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.
CWE-20 - Improper Input Validation
Special care should be taken around atoms as described in the Atom Exhaustion section. Following
DSG-003,DSG-011andDSG-007may also help.Otherwise, please refer to the CWE description for general advice on how to deal with this.
CWE-284 - Improper Access Control
This is not Erlang-specific, please refer to CWE description for general advice on how to deal with this.
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.
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.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-007may also help.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-007may also help.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.
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.CWE-266 - Incorrect Privilege Assignment
This is not Erlang-specific, please refer to CWE description for general advice on how to deal with this.
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.
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.
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.
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.
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.
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
sslApplication andsshApplication for more information.Otherwise, please refer to the CWE description for general advice on how to deal with this.
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.
CWE-427 - Uncontrolled Search Path Element
See
DEP-004.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.
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-008may also help.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.
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.CWE-119 - Improper Restriction of Operations within the Bounds of a Memory Buffer
This issue cannot occur because Erlang is memory safe.
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.
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.
A02:2025 - Security Misconfiguration
See
A01:2025and the A02:2025 description for advice on how to deal with this.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.
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
cryptoApplication section.Otherwise, please refer to the A04:2025 description for advice on how to deal with this.
A05:2025 - Injection
See
MSC-004and the A05:2025 description for advice on how to deal with this.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.
A07:2025 - Authentication Failures
See
A01:2025and the A07:2025 description for advice on how to deal with this.A08:2025 - Software or Data Integrity Failures
Erlang/OTP provides various tools to help ensure data integrity, notably
public_key:sign/4andpublic_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.
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
loggerframework 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.
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-001and 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.
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.
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.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.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.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.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.API7:2023 - Server Side Request Forgery
This is a general issue, please refer to
CWE-918and the API7:2023 description for advice on how to deal with this.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.
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.
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.