AIRLOCK

[ SUPPLY-CHAIN DEFENSE ]STELLAR / SOROBAN0.0.1 / UNRELEASED

AIRLOCK

A scanner for the shapes that have actually compromised Stellar and Soroban repositories. It reads the tracked tree, judges a change against the policy that existed before the change, and fails closed on every file it did not read.

[ Measured 2026-10-27 ]

Seven ways past the old gate. All seven exited 0.

Read the cases →
[ 01 ]

The gate was already there. It did not close.

Why / Appendix B

Five public Soroban repositories shipped the same copy-pasted scan step. A review of that step found seven ways to commit anything you like past it.

On 2026-10-27 all seven were run against the real script. Every one exited 0, which the workflow reads as a pass. The shapes include an artifact dropped before any rule sees it, a payload line that exempts itself with a comment, and a decode-then-evaluate line in a build config that scores one warning where only HIGH affects the exit code.

The measurements, and what Airlock does with each shape →

Airlock is the rewrite those campaigns should have had. It fails closed on anything it did not read, judges a change by the policy that existed before it, and keeps detection rules as data with a fixture behind every one.

It is a scanner for a specific attack class, not a general-purpose antivirus. A clean report means nothing matched the rules that ship, which is a narrower claim than "no compromise found".

7 / 7Bypasses confirmed against the old gate
6 / 7Shapes with a fail-closed regression
1 / 7Open: Cargo vectors, task 6.4
[ 02 ]

What it checks

Detectors / Rule data
[ 01 ] config_role

Build tooling and editor autorun

Capability, not signature: evaluate a string, open a socket, spawn a process, name a chain RPC, or append a tail past the config. Reads raw text, with no comment or string masking.

cfg-dynamic-code cfg-network-exec cfg-chain-endpoint cfg-appended-tail
Read the rules →
[ 02 ] wasm_manifest

Artifact integrity

A committed Wasm file is trusted only when a hash-keyed manifest in the trusted base names its bytes. Unlisted, swapped, moved and stale artifacts are reported, and an archive is reported as unread.

wasm-unlisted wasm-hash-mismatch wasm-path-moved
Read the rules →
[ 03 ] pat-*

Text-side patterns

Invisible and direction-flipping characters, a decoder feeding an evaluator, paste and tunnel endpoints, obfuscator identifier grammar. The roles each rule reads are stated in the rule data, not in detector code.

pat-decode-eval pat-invisible-unicode pat-exfil-endpoint
Read the rules →
[ 04 ] ci_surface

The CI layer

Workflows, package manifests, install hooks and editor settings get roles of their own. Pairing rules catch a secret interpolated into a display command, and a privileged trigger checking out a pull request head.

pat-secret-dump pat-ptr-head-checkout pat-lifecycle-exec
Read the rules →
[ 03 ]

Everything it did not read is a finding

Fail closed / Exit codes

Every skip carries a rule id, a severity and a path. There is no default directory skip, and no inline marker that hides a line from every rule. A scan that could not run never reports success.

0Nothing at or above the threshold
1A finding at or above the threshold
2Usage or configuration error, including an unresolvable base
3Internal error
skipped-oversize skipped-unreadable skipped-symlink skipped-unmerged submodule-present nul-in-text non-utf8-text cfg-long-line detector-error history-rewritten no-trusted-base policy-changed policy-unknown-key untrusted-policy-source suppression-invalid suppression-expiring

How each state finding is decided →

[ 04 ]

The trusted base

Trust model / Modes

A change cannot relax the rules it is judged by.

In pr and push modes, policy, suppressions and the Wasm manifest are read from the trusted base: the pull request base commit, or the commit a push started at. Neither is read from HEAD. A base that cannot be resolved is exit 2, not a scan against HEAD.

Policy source by mode
ModePolicy comes fromUse for
prThe pull request base commitGating a contribution
pushgithub.event.before, where the push startedGating a merge
fullThe working tree, announced with an INFOA local look at your own repository
[ 05 ]

Start from a checkout

Install / Quick start
Install and scan
$ git clone https://github.com/posture-sec/airlock.git
$ cd airlock
$ python3 -m venv .venv && . .venv/bin/activate
$ pip install .
$ airlock scan --mode full
# exit 0 when nothing reached fail_on, 1 when something did

Python 3.11 or newer. There is no package index release yet, so install from a checkout; the PyPI name is not Airlock's to promise.

The composite Action is task 4.1 and does not exist. Until it ships, make the CLI the step in your workflow and keep the checkout at fetch-depth: 0 so a pull request base is reachable.

Full usage, policy and manifest workflow →