Updates (October 2025 to December 2025)
October 2025 — Updated the topic to add the logger names for authentication hook testing and scan run logs.
Traceable provides the capability to write pre-hooks. The pre-hooks can be used to carry out multiple tasks, for example, authentication before starting the tests, test setup, and so on. These pre-hooks can be configured to run at multiple levels. For example, you can configure a pre-hook to run before each individual test plugin or test plugin category for the entire scan. For example, in the sample config.yaml file shown below, you can see that a pre-hook runs before the start of the plugin and one specific to a test. The bfla pre-hook shown in the sample config.yaml is for an individual Authorization plugin, while crap_login_prehook is for logging into an application. These pre-hooks are written in Python and used in Traceable CLI.
Note
Pre-hooks are application-specific. Use the examples as guidance when defining your custom pre-hooks.
You can capture authentication hook testing logs using
logger = logging.getLogger("traceable.ast.testsuite.hookutils.simulate_hooks"), and scan run logs usinglogger = logging.getLogger("traceable.cli.scan").
What will you learn in this topic?
By the end of this topic, you will be able to:
Understand the use of hooks in authentication.
Understand the concept of the crAPI credential pre-hook.
Understand the concept of the CSRF token pre-hook.
Understand how to configure pre-hooks.
Sample user config
# This is a sample user config.
# Copy it to traceable config folder ~/.traceable/config.yaml to apply.
# This config overrides data in default.yaml.
# Do not make edits to default.yaml as changes will be lost.
scan:
plugins:
# Enable prehooks for all plugins
pre_hooks:
- crapi_login_prehook
post_hooks:
- crapi_login_posthook
authentication:
unauthenticated_access:
pre_hooks:
# Disable crapi_login_prehook for unauthenticated_access
- crapi_login_prehook:
disabled: true
# Disable crapi_login_posthook for unauthenticated_access
- crapi_login_posthook:
disabled: true
authorization:
bfla:
pre_hooks:
- crapi_creds_prehook
custom:
sample_plugin: {}Make a note of the following that are used in the examples below:
The authentication hook method is invoked before sending every request.
You may or may not want to access the authentication endpoint before each request. You can control that behavior using
scanctxandpluginctx.scanctxis a dictionary scoped per scan. To reuse any value throughout the scan, add that value with the key of your choice and use it whenever it is present. Following is an example:if scanctx.get("token_key") == None: token = (make request to obtain token).getToken scanctx["token_key] = token token = scanctx.get("token_key")Similar to
scanctx,pluginctxis scoped per plugin. So, a unique dictionary is used per plugin. You can store values at the key level.Hook can add key-value pairs in headers, query parameters, cookies, or the request body. You need to add these to the attributes map. For example,
You need to first fetch the attributes from the
testcase(which essentially translates to the parameter of the next request)attributes = testcase.get_attributes()To Add Cookie:
attributes.set("mutated.http.request.cookie.%s" % (cookie.name), cookie.value)To Add Header:
attributes.set("mutated.http.request.header.authorization", "Bearer fnasfjasjfoijfoiefjkewwrw")To add Query parameter:
attributes.set("mutated.http.request.query.authorization", "Bearer fnasfjasjfoijfoiefjkewwrw")To add body parameter:
attributes.set("mutated.http.request.body.authorization", "Bearer fnasfjasjfoijfoiefjkewwrw")
The following two examples are two sample authentication pre-hooks for the OWASP crAPI application. For more information, see crAPI.
crAPI credential pre-hook
The following is a sample pre-hook function in Python:
def sample_traceable_login_prehook(scanctx: ScanContext, pluginctx: PluginContext, testcase: TestCase, **kwargs) -> list[Assertion]:
attributes = testcase.get_attributes()
url = urllib.parse.urlparse(
attributes.get_one("mutated.http.request.url", ""))
logger.info("Running crapi_login_prehook for plugin %s" % pluginctx.get_plugin())
if scanctx.get("crapi.role.user", None) is None:
# Login:
logger.debug("crapi_login_prehook: Token not found. Creating token")
u = urllib.parse.urlunparse(
url._replace(path="/identity/api/auth/login"))
res = requests.post(u, json={
"email": "test@example.com",
"password": "Test!123"
}, headers={
"content-type": "application/json",
"x-traceable-ast": "0,0,0"
})
if res.status_code != 200:
logger.error("Failed to login to crapi, got status %d" %
res.status_code)
return []
res = res.json()
scanctx.set("crapi.role.user", res["token"])
else:
logger.debug("Already logged in to CRAPI")
attributes.set("mutated.auth.attribute", "mutated.http.request.header.authorization")
attributes.set("mutated.role.user", "Bearer %s" % scanctx.get("crapi.role.user"))
attributes.set("mutated.role.bolauser", "Bearer %s" % scanctx.get("crapi.role.bolauser"))
logger.debug("Mutated role user: %s" % scanctx.get("crapi.role.user"))
return []Explanation
This function is a pre-hook that is used to manage authentication for the crAPI in a security scan context.
First, the function parses the URL from the mutated.http.request.url attribute in the attributes parameter, and then checks if a token for the user's role is already present in the scanctx object. If not, the function logs into crAPI by sending a POST request to the login endpoint with a test email and password, and retrieves a token. The retrieved token is then stored in the scanctx object.
If a token is already present, the function logs a message indicating that the user is already logged in. The function then sets two attributes in the attributes object: mutated.auth.attribute is set to mutated.http.request.header.authorization, and mutated.role.user is set to "Bearer" plus the token retrieved from scanctx.
scanctx is managed by Traceable CLI internally at run time.
CSRF token pre-hook
The following is a sample code to set the header csrf_token to a random string in an attack request. A random string can be replaced with an actual token at runtime:
def set_csrf_token_prehook(scanctx: ScanContext, pluginctx: PluginContext, attributes: AttributeList) -> list[Assertion]:
if not scanctx.get("csrf_token"):
scanctx.set("csrf_token", "".join(random.sample(string.ascii_lowercase, k=10)))
attributes.set("mutated.http.request.header.csrf_token", scanctx.get("csrf_token"))
return []Explanation
The function checks if a CSRF token is already present in the scanctx object by attempting to retrieve it using the get() method with the key "csrf_token". If a CSRF token is not present, the function generates a new CSRF token by randomly selecting 10 characters from the lowercase English alphabet using the random.sample() method and stores it in the scanctx object with the same key "csrf_token".
The function then sets the mutated.http.request.header.crf_token attribute in the attributes object to the CSRF token retrieved from scanctx using the get() method with the same key "csrf_token".
Configuring the pre-hook
The pre-hooks that you create need to be configured in Traceable CLI. Make sure that Traceable CLI is installed before proceeding. The important files and directories in Traceable CLI that are used for pre-hooks are:
hooks directory – Your pre-hooks and post-hooks are stored in this directory.
config.yamldefault.yaml
The default.yaml file is provided by Traceable. Whatever is added to default.yaml is overwritten by config.yaml. It is a practical approach to have your working pre-hooks configured in config.yaml. The config.yaml file is given preference at run time. Refer to the config.yaml listed at the start of the topic.