AIRLOCK

Overview / Rules

RULES

This page is generated from rules/builtin/*.toml. Every rule has a positive and a negative fixture, because a rule that cannot be shown to fire is not a rule.

[ 01 ]

Reference

Generated / Do not hand edit

Severity sets the exit-code threshold. Suppressible means a fingerprint-keyed entry in airlock.toml can hide that exact occurrence with a written reason; a scanner-state finding is never suppressible. The detector column says where the decision is made: the pattern engine runs the regex as data, and a code detector needs more than one line to decide.

The builtin_patterns family is rule data rather than a detector module. The module with that name carries a docstring and no code, because the engine is the thing that reads those rules.

All rules

[ 01 ] Pattern engine / builtin_patterns4 RULES / 2 HIGH / 2 WARN

pat-decode-eval

HIGH

Encoded payload fed to an evaluator

A string that has to be decoded before it runs was written to be read by the machine and not by the reviewer, which is the shape the reported campaigns used. The old script scored it WARN unless the payload spelled the call with no space between the verb and the decoder, and said nothing at all once the file sat under a vendored directory (PRD.md 2.3 items 5 and 7). What separates this from cfg-dynamic-code is what each asks for: that rule fires on an evaluator call in one of five config filenames, this one fires on a decoder feeding an evaluator in any file that is code.

Detector pattern engine Roles source, tooling-config, editor-autorun, other Suppressible yes

pat-invisible-unicode

HIGH

Invisible or direction-flipping character in code

A zero-width or bidi-override character inside an identifier is invisible in a diff, survives a reviewer reading the line out loud, and defeats a grep for the payload name, because the name on the screen is not the name in the file. This is why matching raw text with no masking is a hard rule rather than a style choice. U+200D is deliberately absent from the class: a zero-width joiner is how emoji sequences and several writing systems are spelled, and it is the one character in this neighbourhood that code carries legitimately.

Detector pattern engine Roles source, tooling-config, editor-autorun, other Suppressible yes

pat-exfil-endpoint

WARN

Paste, webhook or tunnel endpoint in code

A loader needs somewhere to put what it collected and somewhere to be told what to fetch, and the free services it uses for that are a short list that changes slowly. This is a separate rule from cfg-network rather than a wider role on that pattern: a named paste or tunnel host and a bare capability to open a socket have different false-positive profiles and different reasons to be suppressed, and only one of them is already a WARN inside a build config. The list is paste sites, tunnels and webhook or out-of-band callback hosts, kept as data so that adding a host never reads like a change to the matching logic.

Detector pattern engine Roles source, tooling-config, editor-autorun, other Suppressible yes

pat-obfuscator-id

WARN

Obfuscator identifier grammar

The `_0x` plus hex identifier is the naming scheme the JavaScript obfuscators emit, and it survives a rename of the tool because it is a property of the output rather than of a banner. ARCHITECTURE.md 9.6 is explicit that obfuscator fingerprints rotate weekly while the mechanism stays, so this keys on the identifier grammar and not on a list of tool names. It is a WARN because the shape has one ordinary owner: a minified or packed bundle committed to the repository, which is a release-integrity question for the artifact rules rather than a payload by itself.

Detector pattern engine Roles source, tooling-config, editor-autorun, other Suppressible yes

[ 02 ] Build tooling / config_role7 RULES / 5 HIGH / 2 WARN

cfg-dynamic-code

HIGH

Dynamic code execution in a build config

Build configs are evaluated at install and build time. Evaluating a string there is how this class of attack turns a dependency install into remote code execution.

Detector pattern engine Roles tooling-config Suppressible no

cfg-network

WARN

Network access from a build config

A build step that reaches the network can be handed its payload after install, which no lockfile review would have seen. Alone this is a WARN: some legitimate Next.js configs fetch at build time.

Detector pattern engine Roles tooling-config Suppressible yes

cfg-chain-endpoint

HIGH

Blockchain endpoint inside build tooling

The reported campaigns configured a chain RPC or JSON-RPC client inside tooling so the payload could sign and broadcast from the victim machine. A build config naming any chain endpoint is out of place, whichever chain it is. Bare SDK names stay out of this pattern: web tooling legitimately lists them in a webpack config, and the endpoint or its configuration variable is the signal. Specific C2 hosts belong in builtin_patterns once task 0.4 confirms the published list.

Detector pattern engine Roles tooling-config Suppressible yes

cfg-long-line

HIGH

Line longer than the configured cap

Regexes are not run on lines past line_cap, because a minified bundle is a backtracking target. The line still has to be accounted for, so its length is the finding.

Detector pattern engine Roles tooling-config, editor-autorun, source, doc, other Suppressible no

cfg-network-exec

HIGH

Network access combined with execution in one build config

Remote content plus a way to run it is the whole attack. Each half alone can be legitimate; together they are the fetch-decrypt-evaluate shape, which is why the combination is HIGH even when neither capability would be on its own.

Detector config_role (code) Roles tooling-config Suppressible no

cfg-process-spawn

WARN

Process execution from a build config

Spawning a shell from a build config is the install-hook dropper path. The detector escalates to HIGH when the command is not a build step stated up front but a downloader, a decoder or an inline script.

Detector config_role (code) Roles tooling-config Suppressible yes

cfg-appended-tail

HIGH

Payload appended to the end of a build config

Campaigns append a long high-entropy line after a valid config rather than editing one, so the diff looks like one more line. Thresholds are placeholders until tuned on the corpus (ARCHITECTURE.md 15).

Detector config_role (code) Roles tooling-config Suppressible yes

[ 03 ] CI surface / ci_surface6 RULES / 3 HIGH / 3 WARN

pat-privileged-trigger

WARN

Workflow trigger that runs base-context code with a write token

`pull_request_target` runs in the base repository with a writable token while the code it runs came from an outside fork. The trigger is not an attack on its own, so it is a WARN that tells a reviewer which files to read; what makes it fatal is the checkout of the head, which is the rule below. `workflow_run` and `issue_comment` are deliberately not in this pattern: both are real vectors and neither is the reported shape, so naming them here would cost coverage in false positives instead of buying it.

Detector pattern engine Roles workflow Suppressible yes

pat-secret-dump

HIGH

Secret interpolated into something that displays or writes it

`${{ secrets.X }}` in a `run:` line reaches the job log, where it stays readable to anyone who can see the artifact, and into `GITHUB_ENV`, where every later step inherits it. Passing a secret to a step through `env:` or `with:` is not this rule: that is how a workflow uses a secret, and it is the negative case. A line that masks the value first (`::add-mask::`) is the one echo that is deliberate hygiene.

Detector ci_surface (code) Roles workflow Suppressible yes

pat-ptr-head-checkout

HIGH

Privileged trigger combined with a checkout of the pull request head

This is the combination that turns a fork pull request into code execution with a write token: the trigger gives the base context and permissions, and the head ref gives the attacker's tree to run. A `pull_request` trigger with the same checkout is reviewed code, and a `pull_request_target` that leaves the default base ref is the safe form, so the rule needs the trigger, a head ref and a checkout step in one file. `ARCHITECTURE.md` 11 worries about exactly this class of weakness in the design document itself.

Detector ci_surface (code) Roles workflow Suppressible yes

pat-lifecycle-fetch

WARN

Install hook or lifecycle script reaches the network

A `postinstall` script and a commit hook both run without a developer choosing them, so a downloader named in one is a payload server the reviewer cannot see in a lockfile. The bare host name stays out of this: a `repository.url` in the same manifest is not a fetch, which is why the pattern asks for a client verb or an install-from-URL argument rather than any `https://` text.

Detector pattern engine Roles package-manifest, install-hook Suppressible yes

pat-lifecycle-exec

HIGH

Lifecycle script or hook runs what it fetched

The fetch rule becomes this one when the same line also hands the result to an interpreter or a shell, which is the PolinRider dropper shape: the server chooses the payload after install, so nothing in the repository or the lockfile describes what runs. An interpreter named without a fetch is a build step, and a fetch without an interpreter is the WARN above.

Detector ci_surface (code) Roles package-manifest, install-hook Suppressible yes

pat-editor-command-path

WARN

Editor setting that names an interpreter or executable

`.vscode/settings.json` is the second half of the editor auto-run surface: `tasks.json` says what can run, and these keys say what the editor runs by itself when a folder opens. A path to an interpreter or a compiler directory is loaded by the extension host, so a planted one needs no user action. Preferences with nothing executable behind them are not reported, which is what keeps this a WARN about a handful of key names rather than about the file.

Detector pattern engine Roles editor-autorun Suppressible yes

[ 04 ] Artifact integrity / wasm_manifest5 RULES / 2 HIGH / 3 WARN

wasm-unlisted

HIGH

Committed Wasm with no manifest entry

A binary artifact nobody declared is the payload delivery channel this class of attack ends in. A Wasm file the manifest does not carry, or that the scanner could not hash, has no stated origin, source or build command.

Detector wasm_manifest (code) Roles wasm Suppressible no

wasm-hash-mismatch

HIGH

Listed Wasm path holds a different artifact

The path was declared as one build and now holds another. Rebuilding a contract changes the hash, so this is a deliberate swap until proven otherwise.

Detector wasm_manifest (code) Roles wasm Suppressible no

wasm-path-moved

WARN

Known artifact moved to an unlisted path

The bytes are known, the location is not. A move is usually a refactor; it is also how an artifact gets in front of a release step that no longer checks for it.

Detector wasm_manifest (code) Roles wasm Suppressible yes

wasm-manifest-stale

WARN

Manifest entry matches no tracked file

A manifest that no longer describes the tree is a manifest nobody checks. It also hides the case where the artifact was deleted here and shipped from somewhere else.

Detector wasm_manifest (code) Roles wasm Suppressible yes

wasm-archive-skipped

WARN

Archive in the tree was not looked inside

A zip or tarball can carry a Wasm artifact past every check in this file. The scanner reads bytes, never unpacks them, so the archive is reported as an unexamined hole rather than passed over.

Detector wasm_manifest (code) Roles archive Suppressible yes

A rule that names a role no resolver can produce fails at load time, so a typo cannot silence a rule forever. Every rule's scope is in its own roles list: widening it is a data change a reviewer can see.

State findings such as skipped-unreadable and detector-error are not in this list because they are not matches inside a file. They are in the fail-closed table.