Internal rules¶
Every scan rule and every checklist is stored in the database: every instance of the server sharing the database applies the same rules. Your security teams can add scan rules and checklist items, edit the default rules and enable or disable any rule, without touching the code.
Rules are managed with the security-review-controller rules command (import and export of
TOML packs, enabling, disabling, deletion) or from the Rules screen of the
web interface. Checklist items are managed from the Checklists screen of
this interface, or with the TOML packs of the rules command. Both screens also
import and export the TOML packs.
Default rules¶
The server ships with a set of default rules and checklists, described in the
default_rules.toml file of the package (same format as a pack).
- At first start (creation of the database by the server or by a CLI command), this content is copied into the database.
- At every start, the default rules added by a new version of the server are added, and the ones it fixed are updated, unless an administrator edited them: an edited rule is never overwritten.
- A default rule can be edited (pattern, severity, advice...) or disabled, but not
deleted.
rules listthen shows it with themodifiedorigin (modified: truein JSON). - If the database cannot be reached or was never initialized, the server applies the rules of the file directly: the scan never runs without rules.
- To go back to the default rules, use
rules import --replace(see below).
Python scans also include PY-026, a bounded Tree-sitter analysis for direct local flows from
input() or common request fields (request.args, request.form, and similar) into dynamically
constructed SQL passed to execute, executemany, or text. It is intentionally narrow and
does not replace a full SAST analysis. It follows arguments and returned values through direct
calls to local functions in the same file. Bound query parameters are not reported.
TypeScript, JavaScript, and Angular scans also include NG-021, which tracks simple local flows
from URL query/route data into innerHTML assignments or Angular bypassSecurityTrust* calls.
It recognizes DomSanitizer.sanitize(SecurityContext.HTML, value) as a sanitizer barrier and does
not report text-only DOM updates. This focused check is not a full browser taint analysis; it does
not infer sanitizer wrappers or indirect calls. It follows tainted arguments and returned values
through directly called local function declarations and arrow functions in the same file.
The same scans include NG-022 for message event listeners that read event.data without
checking event.origin. The check is limited to inline callbacks passed to addEventListener;
origin validation delegated to another function is not inferred.
Java scans also include JV-019, a bounded Tree-sitter check that follows request data through
Servlet request getters and Spring @RequestParam, @PathVariable, @RequestBody, and
@RequestHeader parameters are treated as untrusted sources. The analysis tracks local
assignments, string concatenation, String.format, concat, StringBuilder.append and
toString, plus arguments and returned values across direct local method calls. SQL execution
methods on JDBC/JPA APIs are sinks. It does not report values bound separately with
PreparedStatement or JPA setParameter. The analysis does not resolve overloads, fields,
framework wrappers, or calls across files, and does not infer arbitrary validation or escaping
as a sanitizer.
Other focused additions include PY-027 for explicitly disabled JWT signature verification,
JV-018 for a direct request value passed to JNDI lookup, and NG-023 for assignments that
mutate __proto__ or constructor.prototype. These are line-level patterns and do not infer
indirect data flows; review the surrounding code before deciding whether a finding is exploitable.
security-review-controller rules import examples/rules/company.toml # import a pack
security-review-controller rules list --custom # check
rules command¶
| Command | Role |
|---|---|
rules list [--language L] [--custom] [--format text\|json] |
Lists every rule with its origin (builtin, modified, custom) and its state (enabled / disabled) |
rules import PATH… [--replace] |
Imports TOML packs (files, or folders whose *.toml are read in alphabetical order) |
rules export [-o FILE] |
Exports as a re-importable TOML pack what differs from the defaults (custom or edited rules, disabled rules) |
rules disable ID… / rules enable ID… |
Disables or enables default or custom rules |
rules delete ID… |
Deletes custom rules or custom checklist items |
- The import is all or nothing: if an entry is invalid, nothing is imported and the command
lists the errors (exit code
2). - Without
--replace, the import adds or replaces the entries by identifier and adds the identifiers ofdisabled_rulesto the rules already disabled. With--replace, the stored content is first reset to the defaults (custom rules and checklists deleted, edits undone, every rule enabled again): the database then matches the default rules plus the imported packs (handy to version the rules in a reviewed repository and deploy them from the CI). enable,disableanddeleterefuse an unknown identifier without changing anything. A default rule or checklist item cannot be deleted: disable or edit it.- The command does not follow symbolic links; a pack is limited to 256 KiB.
security-review-controller rules disable PY-008 # too noisy default rule
security-review-controller rules export -o rules.toml # backup or review
security-review-controller rules import --replace rules.toml
Import and export in the web interface¶
The Rules and Checklists screens have Export and Import buttons (permission
rules.manage):
- Export downloads a TOML pack of what differs from the defaults, like
rules export:security-review-rules.toml(custom or edited rules, disabled rules) from the Rules screen,security-review-checklists.toml(custom or edited checklist items) from the Checklists screen. - Import reads a TOML pack chosen on your computer (256 KiB at most), on either screen: the
rules, checklist items and
disabled_rulesit holds are imported, all or nothing, likerules importwithout--replace(entries added or replaced by identifier, the other entries kept). The message gives the number of imported entries, or the errors when nothing was imported. To reset the content to the defaults, userules import --replacein the CLI.
| Method | Route | Role |
|---|---|---|
GET |
/api/v1/packs/rules[?sections=rules\|checklist] |
rules.manage: TOML pack (both sections by default) |
POST |
/api/v1/packs/rules |
rules.manage: body {"name": "acme.toml", "content": "<TOML>"}; 422 with the errors if the pack is invalid |
curl -H "Authorization: Bearer $ADMIN_TOKEN" -o rules.toml \
"https://secreview.example.com/api/v1/packs/rules?sections=rules"
Pack format¶
A TOML pack is the exchange format of rules import and rules export:
disabled_rules = ["PY-008"] # disabled rules (default or custom)
[[rules]]
id = "ACME-001" # unique identifier, e.g. ACME-001 (50 characters max.)
title = "Direct JDBC connection"
severity = "medium" # critical | high | medium | low | info
cwe = "CWE-400" # optional
languages = ["java"] # or ["*"]
pattern = '''DriverManager\.getConnection\s*\('''
advice = "Use the managed DataSource."
ignore_case = true # optional (true by default)
[[checklist]]
domain = "company" # "company" is added to EVERY review prompt
id = "ACME-CL-01"
title = "Tenant isolation"
what_to_look_for = "Every query is filtered by the tenant of the token."
cwe = "CWE-639"
A full example is provided in examples/rules/company.toml.
Behavior¶
- An
idequal to a default rule edits it (it then shows asmodifiedand is no longer updated by new versions of the server). - A new
domaincan be used at once withget_review_checklist, and the review prompts name it for the agent to fetch. - A change (CLI, web interface) is picked up by every running server within a few seconds,
without restart; the
reload_rulestool and the Reload button of the Rules screen apply it immediately. Changes made in the Checklists screen reload the registry immediately. - A stored rule that no longer passes the validation is ignored and reported (
list_rulestool,load_errorsfield); if the database cannot be reached, the default rules of thedefault_rules.tomlfile apply. - Patterns with nested quantifiers (
(a+)+), too long (500 characters) or matching the empty string are refused. Lines longer than 2000 characters are truncated before analysis. security-review-ci scan-codeapplies the rules of the database of the controller.
Trusted content
Internal rules are trusted content: only the operators who have access to the database
(CLI) and the admin role of the web interface can change them. Version the packs in a
reviewed repository and deploy them with rules import --replace.
Default rules by platform¶
| Family | Main coverage |
|---|---|
GEN-* (every language) |
Hard-coded credentials, private keys, cloud keys, tokens with a known format (GitHub, GitLab, Slack, Google, Stripe, npm), credentials in a URL, hard-coded JWT, obsolete SSL/TLS, secrets written to logs, plain HTTP, clear-text LDAP, FTP, AMQP, Redis, MQTT or Telnet URLs, TLS disabled in a connection setting (sslmode=disable, useSSL=false, Kafka PLAINTEXT), unverified certificate outside HTTP (TLS_REQCERT never, trustServerCertificate=true) |
PY-* (Python) |
eval/exec, deserialization (pickle, torch.load, joblib...), YAML, shell commands, formatted SQL, disabled TLS, weak crypto or ECB mode, non-cryptographic randomness, XXE (lxml), SSTI, hard-coded SECRET_KEY, ALLOWED_HOSTS = ['*'], disabled CSRF, mark_safe, cookies without Secure/HttpOnly, SSRF, non-constant-time secret comparison, temporary files, disabled JWT signature verification |
JV-* (Java) |
Deserialization (including XStream, SnakeYAML), OS commands, concatenated SQL and LDAP, request-to-dynamic-SQL flows through local calls (JV-019), XXE, CSRF, CORS, weak crypto, "trust all" TLS, paths and redirects built from the request, SSRF from direct request URLs, JNDI lookup from request data, SpEL/EL, cookies, Random, printStackTrace |
NG-* (Angular, JavaScript, TypeScript) |
Sanitizer bypass, innerHTML and other HTML sinks, URL-to-DOM flows, unchecked message origins, prototype chain mutation, eval, setTimeout with a string, tokens in web storage, postMessage('*'), disabled XSRF, open redirects; on the Node.js side: shell commands, interpolated SQL, $where, file paths, open CORS, disabled TLS, jwt.decode, weak crypto |
CFG-* (Docker, Compose, Kubernetes, servers) |
Privileged or root container, Docker socket, host namespaces, dangerous capabilities, privilege escalation, sensitive mounts, exposed database ports, secrets in ENV/ARG, remote ADD, default passwords, services without authentication, * or cluster-admin RBAC, public buckets, * CORS, directory listing, weak SSH, curl \| sh, debug, WAF disabled, in detection-only mode or with broad exclusions (ModSecurity, Coraza, Ingress NGINX) |
GL-* (GitLab CI) |
Remote include, printed secrets, DinD without TLS, allow_failure, CI token on the command line, untrusted variables evaluated by a shell, CI_DEBUG_TRACE, public artifacts, GIT_SSL_NO_VERIFY |
JK-* (Jenkinsfile) |
Injection in sh, unpinned shared library, agent any, disabled sandbox, credential interpolated in a Groovy string, internal Jenkins.instance API |
CI-* (common CI/CD) |
Disabled TLS or signature verification, shell tracing, install without lockfile |
SP-* (Spring) |
Exposed or unprotected Actuator, H2 console, disabled TLS, SQL logs, permitAll, entities bound to the request body, returned error details, password in the configuration, web.ignoring(), disabled security headers |
FA-* (FastAPI) |
* CORS or .* regex, unverified JWT signature, reload mode |
The default checklists cover the python, java, angular, api, config, cicd and
dependencies domains (for example: validation of OAuth/OIDC tokens, webhooks, sensitive
business flows, HTTP desync, CRLF injection, validation before normalization, file upload,
Kubernetes hardening, RBAC, proxy headers, WAF, encrypted connections to back-end services such
as LDAPS, lockfile integrity).
get_review_checklist and the Checklists screen of the web interface show them in full.
Languages accepted by scan_code: python, java, typescript, javascript, angular,
config, yaml, docker, gitlab-ci, jenkins (aliases: jenkinsfile, groovy, gitlab,
yml...).
Checklists in review prompts¶
| Prompt | Embedded checklists |
|---|---|
security-review |
The checklists of language (python, java, angular for typescript and javascript, config and cicd for pipelines), then api |
codebase-review |
The checklists of languages, then api, config and cicd |
api-security-review |
api |
configuration-review |
config, plus cicd when the files look like a pipeline |
dependency-review |
dependencies |
The company checklist is added to every prompt. The other domains, internal ones included, are
named at the end of the checklists: the agent fetches with get_review_checklist those that
apply to the code it reviews. It then declares the outcome of each item it walked through in
save_security_report (review coverage).
Suppressing a false positive¶
Add a comment on the line concerned:
subprocess.run(cmd, shell=True) # secreview:ignore PY-004 fixed command, no user input
Without an identifier, every rule of the line is ignored.
Suppressions are not hidden: scan_code returns them in suppressed, and the prompts ask
the agent to report any unjustified suppression (a merge request author could otherwise hide a
vulnerability).