To access the latest features keep your code editor plug-in up to date.
Determines when authorization logic is applied to a user identity that has not been properly verified. Because the the user’s identity has not been verified yet, the outcome of the authorization check cannot be trusted. A malicious user might be able to get themselves authorized as a different user than they really are - or they may not be logged in at all.
Iterates over all descendants of the HTTP server request. If an event labeled
security.authentication
is encountered, the rule is satisfied. If an event labeled
security.authorization
is encountered, and the event returns a truthy value, the descendants of
the authentication event are searched for an event labeled security.authentication
. If such an
event is found, the rule is satisfied. Otherwise a finding is emitted.
A security.authorization
event which returns a falsey value (false
, null
, etc) will not
trigger a finding.
The security.authentication
event must return a truthy value (true
, any object) in order to
satisfy the rule.
Ensure that the user’s identity is established before performing authorization.
None
- rule: authzBeforeAuthn
Finds cycles in the package dependency graph. Cyclic dependencies make code hard to maintain, because all the code in the cycle is inter-dependent. While it might look like the code in the different packages has separate functions, in essence all the code in the cycle acts like one big package.
Builds the package dependency graph, in which each package is a graph node, and each function call from one package to another is an edge. The edges are directional, pointing from the caller package to the callee package.
A graph traversal finds cycles - a cycle being a path which starts with a package (a node), traverses through other packages (via edges), and returns to the original package.
For each package cycle that’s detected in the call graph, the rule then searches for a sequence of function calls that matches the cycle. This sequence of events is returned in the finding.
There may be multiple paths through the event trace that produce a given package cycle. Only one of these paths is reported in the finding.
If a package in the circular dependency is designed to call back into the code that call it, it can
be added to the ignoredPackages
list. This is common with helper and middleware code.
Otherwise, you can fix the cyclic dependency by refactoring the code. One way to do this is to refactor some of the code into a new package, class, or library.
depth
. Minimum number of packages in a path that will be reported. Default: 4.ignoredPackages:
MatchPatternConfig[]
. Packages
which match this pattern are not included in the dependency graph. Default: empty - including all
packages.- rule: circularDependency
properties:
depth: 4 # default
ignoredPackages:
- equal: activesupport
- equal: app/helpers
Ensure that cryptographic operations do not use deprecated algorithms.
Finds occurrances of deserialization in which the mechanism employed is known to be unsafe, and the data comes from an untrusted source and hasn’t passed through a sanitization mechanism.
Finds all events labeled deserialize.unsafe
that receive tainted data (as determined by object
identity or string value) as an input.
For each of these events; checks if all the inputs have been sanitized.
Data that has been passed to a function labeled deserialize.sanitize
is assumed to be sanitized
from this point onwards. Such a function could either check the value is sanitized (note no
verification is currently done to ensure this result is checked) or return the transformed value
after any necessary sanitization.
Data passed to a function labeled deserialized.safe
is considered in all functions called by it
(down the callstack). Functions that first sanitize data and then use an unsafe deserialization
function should carry this label.
The set of tracked tainted data initially includes the HTTP message parameters and is expanded to include any non-primitive (ie. longer than 5 characters) observed outputs of functions that consume tainted data.
The reliability of this rule now depends on completeness of the AppMap. If there is a data transformation that is not captured it’s invisible to the rule and will result in failure to associate it with the tracked untrusted data.
With insecure deserialization, it is usually possible for an attacker to craft a malicious payload that executes code shortly after deserialization.
Consider if the library you’re using offers a safe deserialization function variant that you can use instead. Using unsafe functions is only rarely needed and typically requires a good reason.
If you need to use the unsafe function, make sure you’re able to handle unexpected input safely.
Sanitize the data thoroughly first; label the sanitization function with deserialize.sanitize
label or wrap the whole sanitization and deserialization logic in a function labeled
deserialize.safe
.
If you need to deserialize untrusted data, JSON is often a good choice as it is only capable of returning ‘primitive’ types such as strings, arrays, hashes, numbers and nil. If you need to deserialize other classes, you should handle this manually. Never deserialize to a user specified class¹.
Ensure that the JSON library provided by your language and framework does not perform unsafe deserialization.
None
- rule: deserializationOfUntrustedData
Find occurrances of system command execution in which the command string is not guaranteed to be safe.
Find all events labeled system.exec
that are not a descendant of an event labeled
system.exec.safe
. For each of these events, all event parameters are checked.
Each parameter whose type is string
or object
is verified to ensure that it’s trusted. For data
to be trusted, it must be the return value of a function labeled system.exec.sanitize
.
If you can guarantee that you are using system command execution in a safe way, but it’s not
possible to obtain the raw data from a function labeled system.exec.sanitize
, you can wrap the
system command in a function labeled system.exec.safe
.
None
- rule: execOfUntrustedCommand
Identifies when an HTTP server request has returned a 500 status code. HTTP 500 status code generally indicate an unanticipated problem in the backend that is not handled in a predictable way. 500 status codes are also hard for client code to handle, because they don’t indicate any particular problem or suggest a solution.
An HTTP 500 status code that’s returned by an HTTP server request will be emitted as a finding by this rule.
The execution trace of request may show an unhandled exception. Or it may show exception or error handling that failed in some way, and was caught and handled generically by the framework. Use the trace to figure out the root cause of the problem, and update the code to report the problem using a more informative HTTP status code.
None
- rule: http500
Ensures that all calls to a specified callee
package come from an approved caller
package. This
is a way of defining and enforcing an architectural constraint that may not be provided by the
programming language itself.
All packages which call the calleePackage
are inspected. Each one must match one of the
callerPackage
options.
Many programming languages do not provide a way to make a package “private” or “protected” in the same way as a class or function. Yet, it’s often desirable to limit the places from which code in a certain package can be called. This rule provides a way to define and enforce this type of architectural constraint.
The code which is illegally calling into the calleePackage
should be refactored to call one of the
allowed callerPackages
instead. If the needed functionality is not available on a callerPackage
,
then one of the callerPackages
should be modified to provide it.
callerPackages:
MatchPatternConfig[]
. Packages
which are allowed to call the calleePackage
. Required.calleePackage:
MatchPatternConfig. Package whose
callers should be checked. Required.- rule: illegalPackageDependency
properties:
callerPackages:
- equal: actionpack
calleePackage:
equal: app/controllers
Detects HTTP client requests which are incompatible with a published OpenAPI schema.
Each HTTP client request is converted to an OpenAPI schema document. This is done by examining the request method, request URI, parameters, body, headers, etc. and representing them as OpenAPI. Then, the client OpenAPI schema is compared to the published server OpenAPI schema. If any breaking changes are detected between the client request and the published server schema, these are reported as findings.
Modify the HTTP client request to conform to the published schema.
schemata: Record<string, string>
A map from server hostnames to schema URLs. A schema must be
provided for each server hostname that’s invoked in the AppMap.- rule: incompatibleHttpClientRequest
parameters:
schemata:
'myserver:8080': https://github.com/mycorp/myserver/tree/main/openapi.yaml
Identifies cases in which secrets are being compared directly using string.equals
.
Ordinary string comparison of user-provided data with a piece of secret data can leak information about the secret through timing attacks, because of short-circuting (returning false on first mismatch). This can allow the attacker to significantly speed up brute forcing, turning an intractable exponential problem into a linear one.
The rule looks for events labeled secret
and string.equals
.
On encountering an event labeled secret
it remembers the return value.
On encountering string.equals
it looks at the arguments and the receiver. If any of the arguments
is a previously seen secret, or matches a list of known secret-like regular expressions, the
comparison is flagged as insecure, unless any of the arguments is a BCrypt digest.
When generating appmaps, ensure that string comparison functions (such as String#==
in Ruby and
String.equals
in Java) are traced and correctly labeled with string.equals
. Any functions you
know to return a secret (eg. a model accessor for an API key) should be labeled secret
.
Since using this rule generally requires labeling all string comparisons its use is currently limited due to performance and space overhead of tracing them: string comparisons are commonly used throughout the code base and libraries.
It’s advisable to only trace string equality on a limited subset of tests (perhaps the ones known to touch secret related functionality); alas, this is not something easily attainable with current AppMap recorders and requires swapping or modifying configuration files.
To fix this issue, either hash values before comparing or use a constant-time comparison in such
operations. Constant-time comparison functions always compare every character of the string,
removing the timing side channel. You can usually find functions like this in cryptographic and
utility libraries, eg. ActiveSupport::SecurityUtils.secure_compare
.
None
- rule: insecureCompare
Finds jobs which are created in a transaction, and not cancelled when the transaction is rolled back.
THe rule identifies SQL transaction boundaries by examining the SQL queries for START TRANSACTION
,
ROLLBACK
, COMMIT
, etc.
Within each transaction, the rule looks for events labeled job.create
. If the transaction is
rolled back, and there is not a corresponding event labeled job.cancel
, then a finding is
reported.
It’s recommended to program delayed jobs defensively to check for data and silently do nothing if that data is missing (eg. due to a rollback or a duplicate job); job details in this design are stored in the database and the queue entries only reference them.
This rule is designed for use when this practice is impossible or not followed, and with external job queues that do not automatically roll back with the transaction. If the job queue is the database and the same connection is used for the job queue and for the request, then this check is not needed.
Cancel any queued jobs when the transaction rolls back.
None
- rule: jobNotCanceled
Finds usage of unsecured JWTs which use the none
algorithm. When declaring this algorithm, there
is no signature contained within the token that may be cryptographically verified. As a result, the
data encoded within the token may be easily forged.
Any function which encodes a new JWT will have its return value checked for presence of the none
algorithm within the token header.
None
- rule: jwt-algorithm-none
Finds cases where a JWT is decoded but the signature is never verified. Without proper signature verification, the service will unknowingly accept arbitrary token payloads from any origin.
Following a function call to decode a JWT, a subsequent function call to verify the token signature is expected.
None
- rule: jwt-unverified-signature
Determines when a user has been logged out from the application, but the session hasn’t been cleared. When the session isn’t cleared after logout, the session is vulnerable to a session fixation attack.
Iterates over all descendants of the HTTP server request. If an event labeled security.logout
is
encountered, the event must have a descendant labeled http.session.clear
, otherwise a finding is
emitted.
Return values from security.logout
and http.session.clear
are not checked, as these methods are
assumed to always succeed.
None
- rule: logoutWithoutSessionReset
An HTTP server request is missing authentication. In this case, the request may be serving assets that should be protected by authentication - but no authentication is actually happening.
This rule checks all HTTP server requests that satisfy the following conditions:
< 300
For each matching request, any event that satisfies either of these conditions will satisfy the rule:
access.public
.security.authentication
, and returns a truthy value.If a request does not require an authenticated user (e.g. because it contains completely public
information), then this rule can be satisfied by calling any function labeled access.public
.
If the security.authentication
event returns a falsey value (false
, null
, etc), then
authentication is assumed to be denied, and the rule is not satisfied.
If the request is designed to be public, and the omission of authentication is intentionaly, modify
the code so that it calls a function labeled access.public
.
Otherwise, modify the code so that it calls a function labeled security.authentication
which
returns a truthy result (for example, a User object).
includeContentTypes:
MatchPatternConfig[]
.
Default: empty - including all content types.excludeContentTypes:
MatchPatternConfig[]
.
Default: empty - excluding no content types.- rule: missingAuthentication
properties:
includeContentTypes:
- match: ^application/json
Finds HTTP server requests that don’t provide a Content-Type
header in the response.
Every HTTP server request is checked. If the status code indicates redirect or “No content”, then
the rule passes. Otherwise, a finding is issued if the response is missing a Content-Type
header.
When a response is missing the Content-Type
header, it’s unclear to clients how to handle the
response data. Bugs are likely to result.
Provide a Content-Type
in the response.
None
- rule: missingContentType
Finds occurrences of a query being repeated within a loop.
Within each command, SQL queries are normalized, grouped, and counted.
If the number of duplicate instances of a normalized query exceeds the threshold, it’s reported as a finding.
N plus one queries typically occur when a data access object (DAO) has a one-to-many or many-to-many relationship, and the relationship is enumerated within a loop. The DAO will issue a separate query to fetch each related record. If the number of related objects is large, or if each one is fairly expensive to fetch, application performance suffers.
DAO libraries typically offer a feature called “eager loading”. For example:
Enable eager loading on the association in question to fetch the data efficiently, without repeated, identical queries.
warningLimit
Threshold for reporting a warning. Default: 5.errorLimit
Threshold for reporting an error. Default: 10.- rule: nPlusOneQuery
parameters:
warningLimit: 5
errorLimit: 10
Ensures that SQL queries are made only from an approved list of packages. This helps to make the code more maintainable by encapsulating access to the database.
When too many packages are performing direct SQL queries, it’s very hard to keep the code modular. The result is a lack of coherent design and degredation in maintainability.
All functions which make SQL queries are inspected. Each one must match one of the
allowedPackages
.
The code which is making the direct query should be refactored to use the database through one of the allowed packages.
allowedPackages:
MatchPatternConfig[]
. Packages
which are allowed to make queries. Required.allowedQueries:
MatchPatternConfig[]
. Queries
which are allowed from anywhere. Default:
[/\bBEGIN\b/i, /\bCOMMIT\b/i, /\bROLLBACK\b/i, /\bRELEASE\b/i, /\bSAVEPOINT\b/i]
.- rule: queryFromInvalidPackage
properties:
callerPackages:
- equal: actionpack
calleePackage:
equal: app/controllers
Ensures that SQL queries are not performed directly from the view layer. This helps to make the code more maintainable by encapsulating access to the database.
Performing SQL queries directly from the view layer introduces several maintainability concerns:
Each query is tested to see if it has an ancestor event with the view label.
Data objects which are passed to the view layer for rendering should not have access to the database. Disable database access in some way, or transfer data from DAO objects into plain old structs.
forbiddenLabel
. Label which identifies the view layer. Default: mvc.template
.- rule: queryFromView
properties:
forbiddenLabel: mvc.template
Identifies HTTP client requests which do not utilize a circuit breaker.
Each HTTP client request is expected to have a descendant labeled with the expected label.
Use the circuit breaker pattern in microservices architecture to make system behavior more predictable when a service becomes overloaded or unavailable.
Utilize a circuit breaker library - your organization may have a specific preference.
Some examples:
expectedLabel
. Label which identifies the circuit breaker function. Default:
rpc.circuit_breaker
.- rule: rpcWithoutCircuitBreaker
properties:
expectedLabel: rpc.circuit_breaker
Ensures that data saved by data access object is validated first.
Finds events whose method name is save
or save!
. Then verifies that each of these events has a
descendant whose method name is valid?
or validate!
.
In a future revision, this rule will be refactored to use labels rather than method names.
Ensure that data is validated before being saved; for example, using a before_save
hook.
None
- rule: saveWithoutValidation
Identifies when a known or assumed secret is written to a log. Logs are often transported into other systems that are treated with lesser security - such as backups. Therefore, secrets written into log files are more likely to be leaked or discovered by cyber-attackers.
Operation of this rule depends on two labels: secret
and log
. A function labeled secret
is
assumed to return a secret value - this rule keeps a running tally of all the secrets created in an
AppMap. When a function labeled log
is called, the rule looks at the log message, and checks
whether any of the known secrets are in it.
The log message is also tested against a list of known regular expressions that are likely to match secrets.
Be sure and apply the secret
label to any function in your code base that generates a secret (such
as encryption keys, user tokens, API keys, reset tokens, etc.).
If the log message is written by code that you control, the simplest resolution is to remove the log statement - or to remove the secret from the log message.
If the log message is not controlled by you (it’s generated by a framework), your choices are more limited. You could open a pull request with the framework, or you can change the log level to suppress the offending message.
None
- rule: secretInLog
Ensures that function elapsed time does not exceed a threshold.
Checks all configured functions to see if the elapsed time exceeds the configured threshold.
This rule is most useful when applied to code that is specifically designed to test application performance.
Optimize the function elapsed time using a profiler, SQL tuning, etc.
functions:
MatchPatternConfig[]
list of functions
to check. Required.timeAllowed
max time (in seconds) allowed for the function.- rule: slowFunctionCall
properties:
timeAllowed: 0.25
functions:
- match: ^app/models
- match: ^app/jobs
Ensures that HTTP server request elapsed time does not exceed a threshold.
Checks HTTP server requests to see if the elapsed time exceeds the configured threshold.
This rule is most useful when applied to code that is specifically designed to test application performance.
Optimize the request elapsed time using a profiler, SQL tuning, etc.
timeAllowed
max time (in seconds) allowed for the request.- rule: slowHttpServerRequest
properties:
timeAllowed: 0.25
Ensures that SQL query elapsed time does not exceed a threshold.
Checks all configured queries to see if the elapsed time exceeds the configured threshold.
This rule is most useful when applied to code that is specifically designed to test application performance.
Optimize the elapsed time using SQL tuning.
timeAllowed
max time (in seconds) allowed for the query.- rule: slowQuery
properties:
timeAllowed: 0.25
Verifies that the number of joins in SQL queries does not exceed a threshold.
Counts the number of joins in each SQL query. If the count exceeds the configured threshold, a finding is reported.
Queries with too many joins often have poor or unpredictable performance.
Having multiple joins may be legitimate - but it is a situation that merits a closer look, especially when the query is generated by an ORM.
Take a look at a query plan from a database that has a realistically high volume of data, and verify that the query can be executed efficiently.
warningLimit
the maximum number of joins allowed.- rule: tooManyJoins
properties:
warningLimit: 5
Verifies that the number of SQL and RPC updates performed by a command does not exceed a threshold.
Counts the number of SQL and RPC updates in each command. A SQL update is any INSERT
or UPDATE
query. An RPC update is an HTTP client request that uses PUT
, POST
, or PATCH
.
If the number of updates exceeds the threshold, a finding is reported.
As a codebase evolves, sometimes a request can start to make more and more SQL and RPC updates.There are several negative repercussions of this:
Consider refactoring the command into multiple commands.
Schedule a job to perform some of the work offline.
warningLimit
the maximum number of joins allowed.- rule: tooManyUpdates
properties:
warningLimit: 20
Ensures that encryption operations use authenticated encryption.
Finds all events labeled crypto.encrypt
. For each of these events, there should be another event
in the same AppMap that has the same receiver.object_id
and also has the label
crypto.set_auth_data
.
OWASP recommends against the use of unauthenticated encryption.
Change the encryption code to use a current authenticated encryption algorithm. At the time of this
writing, an example is aes-256-gcm
.
Examples:
None
- rule: unauthenticated-encryption
Finds large data sets that are queried from the database and loaded into memory.
Examines all SQL SELECT queries (as opposed to insert/update). If the query satisfies any of the following conditions:
sqlite_master
table)then the query is skipped.
Otherwise, the rule checks to see if the query has an ancestor labeled dao.materialize
. If so,
it’s emitted as a finding.
Materializing large amounts of data code objects is a frequent cause of poor performance and memory exhaustion.
A COUNT
or LIMIT
clause is a good indication that the code is taking steps to limit the amount
of data that’s loaded into memory.
If data is being loaded into memory from an un-LIMITed query:
None
- rule: unbatchedMaterializedQuery
Finds promises which have been created during the recording but have remained unfulfilled at the end.
The rule simply looks for return events with value of Promise { <pending> }
without a subsequent
update.
Promise { <pending> }
is a special value used by the appmap-node agent to represent promises that
haven’t been fulfilled yet. Normally, once the promise is fulfilled, an event update is added to the
AppMap with the resolved value and total elapsed run time.
However, if a promise remains unfulfilled when the recording ends (for example when a HTTP response is sent or a test case finishes) this pending state is never updated.
Most of the time it means some asynchronous computation has been started and its results not awaited for and therefore not collected; such computations should be removed or the results awaited.
In some cases this is an intended behaviour; for example, a background book-keeping or cleanup process might be started which doesn’t directly impact the results of the operation that triggered it. Note that during testing such processes should still run to completion before a test case finishes (or else be suppressed altogether); otherwise there might be unintended interference between test cases leading to brittle tests. Each test case should start from a known, clean and quiescent environment.
Identify where the promise is created and make sure to await
on it. Even when the result is void,
some other part of the program might assume the asynchronous computation has completed. Without
awaiting it at that point a race condition can result, leading to intermittent stability issues.
None
- rule: unfulfilledPromise
Finds SQL updates that are performed in an HTTP server GET
request.
Checks each HTTP server GET
and HEAD
request. Within each of these requests, checks for SQL
queries that match the queryInclude
option and don’t match queryExclude
. If any such queries
exist, they are emitted as findings.
Performing data updates in a GET
request is anti-pattern, and counter to the intent of HTTP. For
data modifications, use PUT
, POST
, or PATCH
.
Data updates which are used for the purposes of tracking user activity are fine in any type of
request. Use the queryExclude
option to allow these queries.
Perform data updates in PUT
, POST
, or PATCH
requests.
queryInclude: RegExp[]
. Default: [/\binsert\b/i, /\bupdate\b/i]
.queryExclude: RegExp[]
. Default: empty.- rule: updateInGetRequest
properties:
queryExclude:
- /\bINSERT\b/i
- /\bUPDATE\b/i