Repository navigation
Releases
v0.7.0
Cordon 0.7.0
Cordon offered to the AI agents that install things for you, an install guard in front of
pnpm, npm and pip, and npm malware measured at full scale.
┌──────────────────────────────────────────────────────────────────────────┐
│ npm malware, blocked by reading the code 6,187 of 6,793 91.08% │
│ benign npm packages, blocked anyway 330 of 34,437 0.96% │
│ │
│ Every release of both sets, on the release commit; the method and the │
│ counts behind them are in docs/08-ACCURACY.md. │
└──────────────────────────────────────────────────────────────────────────┘
Map of this release
├─ Install ................ pipx · the Action · the containers
├─ For AI agents .......... the MCP server, in every agent and both editor families
├─ Put it to work ......... eleven copy-paste implementations, laptop to organisation
├─ Added .................. rule files, provenance, images, notifications, Python 3.15
├─ Stricter ............... intel stale after a week, scan targets cannot blind the scan
└─ Verify it .............. hashes, signatures, provenance
Install
pipx install cordon-scanner==0.7.0 Python 3.11 to 3.15, no runtime dependencies
GitHub Action uses: Threx-code/cordon/action@v0.7.0 (pin the commit SHA the tag names)
Container ghcr.io/threx-code/cordon:0.7.0 amd64 and arm64; pin it by digest
Runner ghcr.io/threx-code/cordon-runner:0.7.0
Bitbucket pipe ghcr.io/threx-code/cordon-bitbucket-pipe:0.7.0
For AI agents
cordon-scanner mcp a Model Context Protocol server; every tool only reads
cordon-scanner mcp config --client claude-code | cursor | codex | vscode | … (eleven agents)
before an agent … it asks
runs an install command check_install_command
adds a package check_package, check_packages
loads a skill / adds an MCP server check_skill, check_mcp_server, check_mcp_tools
follows an instruction file check_agent_file
The VS Code extension registers the server with the editor's own agents; the JetBrains plugin
brings findings to IntelliJ-based IDEs. Tutorials 30 and 41.
Put it to work
LEVEL 1 one machine 33 on your laptop · 34 guard pnpm, npm and pip installs
LEVEL 2 every pipeline 35 GitHub Actions · 36 GitLab CI · 37 Jenkins, Azure,
Bitbucket, CircleCI, Buildkite, any shell · 38 images
LEVEL 3 the organisation 39 rollout · 40 air-gapped · 41 AI agents · 42 Python API
Every command in them was run as shown, and a test runs every command the documentation shows
through the real CLI.
Added
rule files your own signatures (--rule-file), on cordon-rule, Cordon's own engine
provenance Bazel modules, Helm charts and Ansible collections; Sigstore with no
optional dependency
images scan pkg:docker/<image>: pulled, every blob checked against its digest
release identity a downloaded wheel or tarball is matched against the vulnerability
advisories for itself, not only the malware records
air gap advisories sync --bundle DIR: the signed bundle from a carried folder
notifications Slack, Teams and a signed webhook; --notify-on; notify test
Python 3.15
Stricter
intel stale after 7 days without a feed (was 30): the scan says so and is
incomplete; an operator's own --config may choose longer
scan targets a repository's own cordon.yaml can no longer switch the staleness check
off by a long limit, as it already could not by 0
incomplete an incomplete scan never passes under --fail-on-incomplete, the
Action's default
The signed intel feed is not on in this release: no feed root is pinned, as in 0.6.0. Every
default scan still asks OSV what changed before it matches anything, and an offline site
installs the release's signed advisory bundle (advisories sync --bundle). The feed, with
Cordon's own advisories, comes in a release that pins its root once its host is live.
The full list, each fix with its test: CHANGELOG.
Verify it
cordon-scanner verify --online the installed files against the wheel's PyPI
provenance, signed by Cordon's release workflow
action/requirements.txt the hashes the Action and every CI template install by
docs/12-SIGNING-KEYS.md the keys that sign the feed and the advisory bundle
No signed offline advisory bundle ships with this release. The advisory database's signing key is not configured yet, so offline installs sync only from what the wheel carries.
v0.6.0
Cordon 0.6.0
Measured at scale, then shipped: every known-malicious record caught, real lockfiles read the
way Trivy reads them, and code from a teammate or a clone checked before it lands.
┌──────────────────────────────────────────────────────────────────────────┐
│ 249,646 known-malicious records 100% caught, 287,899 checks │
│ 190,274 packages in 1,687 lockfiles 99.6% agree with Trivy │
│ 8,692 Agent Threat Rules cases 97.7% of attacks, 80.6% of │
│ evasions, 92.4% of benign clean │
└──────────────────────────────────────────────────────────────────────────┘
Map of this release
├─ Install ................ pipx · the Action · the containers
├─ Incoming code .......... clone and pull, scanned before they land
├─ Added .................. review, hosts, live MCP, workloads, intent, packs, policy
├─ Measured ............... the at-scale runs, and what they fixed
└─ Verify it .............. hashes, signatures, provenance
Install
pipx install cordon-scanner==0.6.0 or, already installed:
pipx runpip cordon-scanner install --require-hashes -r <your pinned requirements>
GitHub Action uses: Threx-code/cordon/action@v0.6.0 (pin the commit SHA the tag names)
Container ghcr.io/threx-code/cordon:0.6.0 amd64 and arm64; pin it by digest
Runner ghcr.io/threx-code/cordon-runner:0.6.0
Bitbucket pipe ghcr.io/threx-code/cordon-bitbucket-pipe:0.6.0
Incoming code
cordon-scanner clone URL clone --no-checkout, scan the commit from git's objects,
check out only if it passes; a blocked clone is removed
cordon-scanner pull fetch, scan what would be merged, merge only if it passes;
a blocked pull leaves the checkout exactly as it was
cordon-scanner guard install --global
every clone and git init from now on carries post-checkout
and post-merge hooks: a blocked pull, branch switch or clone
is undone before anything has run it
blocks anything malicious; anything critical by default (--fail-on high to tighten)
never a cordon.yaml, policy or baseline inside the incoming code: it cannot configure
its own check
Tutorial: 22 · Code arriving from someone else.
Added
current advisories unless offline, each scan first fetches what OSV published or
changed since the bundled intel was built, every 15 minutes at most;
the requests name no package; over a day old marks it incomplete
review --base REF what a dependency update adds, upgrades, downgrades and removes;
--online fetches, verifies and compares each release (tutorial 21)
scan --host / --home an installed system: dpkg, apk, rpm, pacman, portage, and the
language packages and runtimes outside any project (tutorial 23)
live MCP remote servers asked what they serve now, against what was approved
with agent mcp-approve (tutorial 18)
Kubernetes workloads the images pod templates and Kustomizations run: unpinned, unsigned
intent, offline what agent-facing text asks for, in six languages, followed across
files: evasions caught 72.3% -> 80.6%
language packs Swift, Objective-C, Haskell, Julia, Zig, Nim, OCaml, Clojure
release comparison crates.io, RubyGems, NuGet, Go, Hex, pub and Maven, beside npm and PyPI
the sandbox RubyGems; a second install with the clock 400 days ahead
policy pinned by digest; fetch-policy; policy-drift; pin bumps everywhere
(tutorial 24)
images Wolfi, Chainguard, Arch, Gentoo; runtimes; default gems; jars
Measured
real malware, 39,328 samples 94.2% detected; 79.7% by the code alone (0.5.x called
the overall rate "content alone": corrected here)
the same malware, GuardDog 95.2% against 85.5%, on one fixed-seed draw of 498
every known-malicious record 249,646 records, 10 ecosystems, none missed
(two were missed on the first run: a 213-character name
and a 74-character version, cut short; fixed)
container images against Syft 175 images: OS packages 99.9% the same; every package
95.1% mean after stated exclusions; known gaps named
real lockfiles against Trivy 1,687 lockfiles, 14 registries, 99.6% per package;
every remaining difference sorted by cause, and most are
Trivy listing what the build does not use
found and fixed on the way pnpm 10 lockfiles lost their whole tree · npm links named
after their version · Go indirect modules left to go.sum ·
Go never checked against the checksum database
The method, every table and what the numbers are not:
docs/08-ACCURACY.md.
Verify it
wheel and sdist ... signed, SLSA build provenance; PyPI serves exactly what was uploaded
images ............ signed keylessly with cosign, SLSA provenance, amd64 and arm64
SBOMs ............. sbom.cdx.json and sbom.spdx.json attached below
compared .......... the wheel scanned against 0.5.2's with --compare-with before publishing
install by hash ... pip install --require-hashes -r action/requirements.txt (at the v0.6.0 tag)
Every change, in full: CHANGELOG.md, 0.6.0.
v0.5.1
Cordon 0.5.1
A home screen that reports the state you are in, and a help command.
┌──────────────────────────────────────────────────────────────────────────┐
│ Detection is unchanged from 0.5.0: the same rulepack, the same │
│ advisories, the same findings. This release is the command line a │
│ person meets first, three commands that crashed or misled, and the │
│ data behind a future allowlist refresh. │
└──────────────────────────────────────────────────────────────────────────┘
Map of this release
├─ Install ................ pipx · the Action · the container
├─ The home screen ........ what it reports, and where it reads it
├─ help ................... a command, an action, a misspelling
├─ Fixed .................. crashes, wrong command names
└─ Verify it .............. hashes, signatures, provenance
Install
pipx install cordon-scanner==0.5.1 or, already installed:
pipx runpip cordon-scanner install --require-hashes -r <your pinned requirements>
GitHub Action uses: Threx-code/cordon/action@v0.5.1 (pin the commit SHA the tag names)
Container ghcr.io/threx-code/cordon:0.5.1 pin it by digest in a pipeline
Bitbucket pipe ghcr.io/threx-code/cordon-bitbucket-pipe:0.5.1
The home screen
$ cordon-scanner
██████ ██████ ██████ ██████ ██████ ███ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██
██ ██ ██ ██████ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██ ██ ██████ ██████ ██ ████
Software supply-chain security scanner v0.5.1
ENVIRONMENT ─────────────────────────────────────────────────────────────
Rulepack 0.2.0 Hooks pre-push runs cordon
Policy cordon.yaml · 1 suppression Cloud not signed in
Intel bundled · 1d old Mode offline by default · executes nothing
SCAN AND INSPECT ───────────────── POLICY ──────────────────────────
scan scan a directory, … rules list, test and show …
…
FACT READ FROM NEVER
Rulepack the installed package fetched
Policy cordon.yaml here, and its suppressions guessed: an invalid file says so
Intel the bundled feed's age, stale or not refreshed
Hooks .git/hooks or core.hooksPath, following a credited unless the hook or the
shim to the tracked hook it names tracked hook it names runs cordon
Cloud the stored sign-in sent anywhere
Mode CORDON_OFFLINE assumed
A fact that cannot be read says `unavailable`; the screen still draws.
ON A TERMINAL PIPED, CAPTURED, NO_COLOR
the CORDON wordmark plain text
the console's palette no escape sequences
(24-bit, or 256-colour stand-ins) no block letters
two columns from 88 wide the same facts and commands
help
cordon-scanner help this screen
cordon-scanner help sbom one command's help
cordon-scanner help sbom generate one action's help
cordon-scanner help sbm did you mean: cordon-scanner help sbom
Fixed
BEFORE NOW
cordon-scanner config | report | baseline each prints its own help, exit 3
→ "internal error: AttributeError … a bug in cordon"
cordon-scanner agent | intel | sbom | bundle | each prints its own help, exit 3
advisories → one terse line
whoami, sign-in errors, --judge and --upload help, they say cordon-scanner login;
several hints → "run `cordon login`" (no such a test now fails if a message
command; `cordon` on PyPI is another project) names a `cordon` command
cordon-scanner help → "invalid choice" the help command above
every subcommand's --help → plain argparse coloured headings, flags, values
MAINTENANCE (not in the package, no released scan changed)
the allowlist refresh read a rate-limited registry (HTTP 429) as the end of the list;
pub.dev and RubyGems are now paced and retried, and an unreadable page fails its
ecosystem instead of shortening it
Verify it
wheel and sdist ... signed, SLSA build provenance; PyPI serves exactly what was uploaded
images ............ signed keylessly with cosign, SLSA provenance
SBOMs ............. sbom.cdx.json and sbom.spdx.json attached below
install by hash ... pip install --require-hashes -r action/requirements.txt (at the v0.5.1 tag)
Every change, in full: CHANGELOG.md, 0.5.1.
v0.5.0
Cordon 0.5.0
From a repository scanner to the client side of a supply-chain platform.
┌──────────────────────────────────────────────────────────────────────────┐
│ Every addition is opt-in or offline-safe. A scan with no new flags │
│ finds what 0.4 found, plus the new detectors below, and sends nothing │
│ anywhere it did not before. No feed root key is pinned in this │
│ release, so a default scan makes no network request at all. │
└──────────────────────────────────────────────────────────────────────────┘
Map of this release
├─ Install ................ pipx · the Action · the container
├─ What a scan finds ...... the agent chain, artefacts, dependencies, install time
├─ Fewer false blocks ..... what stopped failing builds it should not
├─ New commands ........... SBOMs, notifications, intel, the cloud client
├─ Measured ............... detection and false-block rates
└─ Verify it .............. hashes, signatures, provenance
Install
pipx install cordon-scanner==0.5.0
GitHub Action uses: Threx-code/cordon/action@fd7e0d5b15c17de4cbe4eb1e298d56f0ff3e31b9
(that commit pins cordon-scanner 0.5.0 by hash in action/requirements.txt,
so pinning the Action by SHA pins what pip downloads)
Container ghcr.io/threx-code/cordon:0.5.0 pin it by digest in a pipeline
Bitbucket pipe ghcr.io/threx-code/cordon-bitbucket-pipe:0.5.0
VS Code cordon-0.5.0.vsix (attached below)
From scanner to platform client
0.4 0.5
┌──────────────────────────────────┐ ┌──────────────────────────────────────────────┐
│ scan a repository, locally, │ │ everything 0.4 did, plus, all opt-in: │
│ offline, nothing sent anywhere │──▶│ │
└──────────────────────────────────┘ │ login ······ device flow through your SSO │
│ logout ····· revokes the sign-in in the cloud│
│ --upload ··· signed results (DSSE / in-toto) │
│ --cloud-policy Ed25519-verified org policy │
│ runner ····· outbound-only job runner │
│ agent ······ MDM inventory of AI agents │
│ --judge ···· a model reads agent-facing text │
└──────────────────────────────────────────────┘
What a scan finds
THE AGENT CHAIN
├─ Agent Threat Rules ........ 815 of the 825 open ATR rules, matched the way ATR matches
│ (97.7% of 4,026 attacks found, 92.5% of benign left clean)
├─ what a config runs ........ hooks, MCP launches, autorun tasks classified by what they do:
│ fetch-and-execute, exfiltration, a reverse shell
├─ where traffic goes ........ an API base URL pointed at someone else (CVE-2026-21852)
├─ what an MCP server gets ... the Docker socket, host root, injected env, plain-http remotes,
│ lookalike packages, broad token scopes
├─ tool descriptions ......... poisoning read in an MCP server's own descriptions
├─ instruction files ......... CLAUDE.md, AGENTS.md, Cursor/Windsurf rules, skills, commands
└─ the judge (--judge) ....... cordon-cloud, anthropic, openai:<model>, ollama:<model>;
only agent-facing text is sent; a verdict must quote evidence
ARTEFACTS
├─ archives in a directory ... opened and scanned (--no-expand skips, marked incomplete)
├─ models ................... pickles importing os.system and friends, PyTorch zips; never loaded
├─ documents ................ macros that run on open, remote templates, DDE, PDF auto-actions
├─ images ................... payloads appended to images
└─ container images ......... dpkg, apk and RPM inventories from docker save / OCI tarballs
DEPENDENCIES
├─ exploited CVEs ........... on CISA KEV or ENISA EUVD: CRITICAL
├─ slopsquatting ............ hallucinated names offline; unregistered names with --online
├─ lookalikes ............... a package named like a popular one
├─ ranges ................... a range that admits a known-malicious release
└─ reachability ............. modelled at import tier; function level for Go, PyPI and npm
INSTALL TIME
├─ .pth startup files, payloads that hide their own code, code spelled as character codes
├─ persistence (systemd, written a path component at a time), miners under other names
├─ callbacks and beacons, commands held in variables and f-strings
└─ ClamAV, optional: --clamav hands each file to your own daemon
Fewer false blocks
NO LONGER FAILS A BUILD WHY
workspace members, link: entries, npm aliases ...... not registry packages
fetch-and-run a person has to ask for .............. not an install-time action
agent instructions that install a CLI .............. ordinary setup, not an attack
infrastructure in tests, examples, vendored code ... not what the project ships
a token sent to the service that issued it ......... that is how tokens work
a tool's own official installer .................... the vendor's documented path
OAuth client IDs ................................... identifiers, not secrets
withdrawn advisories ............................... no longer shipped
Go dependencies pinned to a commit ................. now match their advisories
a word in a package's directory name ............... no longer silences its install hook
New commands
sbom generate --vulnerabilities advisory matches in the CycloneDX SBOM, KEV/EUVD marked
sbom generate --ai the AI bill of materials (CycloneDX 1.6)
--notify slack,teams,webhook a failed gate posted; the webhook is a signed event
intel status | update the signed, TUF-style intel feed
login · logout · whoami Cordon Cloud sign-in; logout revokes it in the cloud
scan --upload · --cloud-policy signed results up, the organisation's policy down
runner [--git-credential] outbound-only scan jobs, private GitLab and Bitbucket clones
agent inventory | report this machine's agents and MCP servers, for MDM
schemas/ the contracts with the cloud, K1 to K9
CI templates GitLab, Bitbucket, Azure, CircleCI, Jenkins, hash-pinned
Measured
CORPUS RESULT
39,002 real malicious packages, by content alone 94.1% detected
995 of them, against GuardDog 95.4% (GuardDog 83.9%)
top 1,000 PyPI + 1,000 npm, wrongly blocked 1.6% (GuardDog 16.8%)
100 lockfiles, CVEs agreed with Trivy and OSV 98.4%, every disagreement explained
ATR attack cases / benign cases 97.7% found / 92.5% left clean
372 real agent-config repositories, never tuned on 1.1% blocked, each block read and correct
521 known-malicious pins / 940 known-vulnerable 100% reported / 100% reported
method and caveats for every number: docs/08-ACCURACY.md
Verify it
wheel and sdist ... signed, SLSA build provenance; PyPI serves exactly what was uploaded
sha256 2f484ac0109acbf258d745dd1e3eb4e9cdbf9f4b2c2598d1381440ffeda00821
sha256 736c2137dec418d10ba9572fb8d5630bb58b37dfadd3af043855c1b3bbbf5145
images ............ signed keylessly with cosign, SLSA provenance
SBOMs ............. sbom.cdx.json and sbom.spdx.json attached below
install by hash ... pip install --require-hashes -r action/requirements.txt
Every change, in full: CHANGELOG.md, 0.5.0.
v0.4.1
Hash-pinned Python dependencies were reported as carrying no hash. A project laying its
requirements out as requirements/base.txt, requirements/dev.txt - which is what
pip-compile produces for anything past a toy, and the layout most likely to belong to a team
that also pins by hash - had every one of those dependencies named by
POLICY.DEPENDENCY.INTEGRITY.001. One repository saw 112 of 112 entries accused, each of them
carrying two --hash=sha256: lines directly beneath it.
The parser was never wrong; it was never reached. manifest_globs matched both
requirements*.txt and requirements/*.txt from the start and lockfile_globs matched only
the first, so the directory layout was read as a manifest and never as a lockfile.
_parse_pinned_requirements reads --hash= continuations correctly and sat behind a dispatch
that could not see the file. The two dispatches answer the same question about the same file,
and they disagreed.
Both now match on a requirements path SEGMENT rather than a prefix, so
backend/requirements/base.txt in a monorepo resolves like requirements/base.txt at a root -
the same narrowness, one level up, which the first fix would have left in place.
Also: a recorded hash no longer keeps the line continuation that followed it. pip-compile
writes every hash but the last as --hash=sha256:abc... \\, and the trailing backslash rode
into LockEntry.integrity, so the stored hash did not equal the hash - the one thing an
integrity field must not get wrong. It was invisible while the dispatch bug meant nothing
reached this code.
The finding was the exact inverse of the truth, and it landed hardest on the projects that had
done the work.
v0.4.0
Infrastructure posture findings from Bicep, ARM and Dockerfiles were failing
builds. advisory_domains is what separates "this code is compromised" from
"a posture choice this project made about its own infrastructure", and it is
read from a rule's threat domain. The Azure and Dockerfile rule families had no
entry in that table, so they inherited a default of malware and scanner --
and sourceAddressPrefix: '*' in a Bicep file stopped a pipeline while the
identical finding in Terraform did not. A build that fails today on one of
those rules will pass after upgrading, which is what the README's own table
always said should happen.
Known-vulnerability detection worked for two ecosystems and not for the other
three. The OSV importer read only ECOSYSTEM-typed version ranges, and npm,
crates.io and Go publish almost entirely as SEMVER -- 215,140 of 229,191 npm
records, 2,827 of 2,844 crates.io, 9,260 of 9,305 Go. Everything else about
those advisories was correct, and none of them reached the database: npm shipped
397 vulnerability records beside 23,550 malicious ones, cargo shipped 37 and
gomod 112. A lockfile pinning lodash@4.17.15, axios@0.21.0,
minimist@1.2.0, smallvec@0.6.13 and github.com/gogo/protobuf@v1.3.1 --
44 real advisories between them -- reported nothing at all, while the Maven and
PyPI pins beside them reported 71 findings. The scan said complete: true and
gave no reason to doubt it.
Both range types are now read, and a range describing several disjoint intervals
is no longer folded into one that spans the gap between them. The database went
from 59,982 records to 268,578 across eleven ecosystems, and got smaller: it
ships gzipped, 36 MB to 7.2 MB. cargo 49 -> 964, gomod 112 -> 3,677,
npm 23,947 -> 227,937.
The [attest] extra reported every honest publisher as a forgery.
Verification called verify_artifact, which requires a bundle carrying a
messageSignature; npm --provenance and PyPI PEP 740 both publish DSSE
envelopes, so every genuine attestation was rejected with "Missing bundle
message signature" and reported at CRITICAL as a failed verification.
sigstore@4.1.0 on npm and sigstore==4.5.0 on PyPI -- published by the
Sigstore project itself -- were both accused. DSSE envelopes now go through
verify_dsse, and because that call proves who signed the envelope and nothing
about which artefact the statement describes, the in-toto subject digest is
compared against the pinned one: a genuine attestation for a different release
is still INVALID.
--online failed every Yarn Berry build. Berry's checksum: is a digest of
Yarn's own cache entry, prefixed with the cache key, and it was compared against
the tarball hash npm publishes -- which it can never equal. An untouched,
correct lockfile produced one CRITICAL "lockfile hash disagrees with the
registry" per dependency, up to the query ceiling, with the remediation "Do not
install". Only a value that resolves to a known algorithm and a digest of that
algorithm's length is compared now, and comparison is per algorithm, so a
lockfile recording npm's sha1 shasum no longer contradicts its sha512
integrity.
An advisory identifier is not unique, and the database was deduplicated as
though it were. One GHSA names several packages -- every tensorflow
advisory also names tensorflow-gpu and tensorflow-cpu, and 223 PyPI
identifiers name more than one package -- and one GHSA splits into several
records when the affected set is several disjoint windows, which is how Django's
212 records carry 80 identifiers. Collapsing on (ecosystem, identifier) threw
away 15,999 of 275,076 records: tensorflow-gpu 2.5.0 reported nothing while
tensorflow 2.5.0 reported 67, and 132 of Django's records never loaded. The
collision key now includes the package name, and only the hand-curated entries
suppress a generated one.
A pin that spells a release differently matched nothing. An enumerated
advisory lists versions as the upstream feed spells them -- OSV names Django's
release 3.2 -- and they were compared to the lockfile's string. pip install django==3.2.0 installs that same release, and django==3.2.0 in a
requirements.txt reported none of the twelve advisories django==3.2
reported, with the scan complete either way. Enumerated versions now go through
the same per-ecosystem comparator the ranges use.
The whole Swift feed was unreachable. OSV identifies a Swift package by its
clone URL (github.com/apple/swift-nio) and a Package.resolved by its
repository path (apple/swift-nio), so all 39 shipped Swift advisories were
loaded, indexed and matched by nothing.
One lockfile spoke for every ecosystem in its directory. Coverage was
recorded per project path, so a requirements.txt beside a conanfile.txt and
an environment.yml produced a graph of the Python pins alone -- the C++ and
conda dependencies were dropped entirely, and nothing said so.
Added
-
A reference on every rule the tool can emit, and a message on the 99 that had
none.cordon rules showprinted a title and a remediation and nothing that
explained the claim, andreferencesdid not exist on a detector rule at all
-- so the one place a reader goes to ask "says who?" answered for neither kind
of rule.core/references.pyholds the vocabulary: 45 URLs owned by a
standards body or by the vendor whose surface the rule is about. The 1,081
infrastructure policies take theirs from their control family, because the
family is the claim. Four rules carry none and say why: they report on the
scan itself, and there is no standards page for "the lookup failed". -
POLICY.DEPENDENCY.SECURITY_PLACEHOLDER.001, for a dependency pinned to
npm's-securityplaceholder. See Changed. -
Negation in a composite rule's terms.
unlessspeaks about paths, and
SUSPECT.DECODE_EXEC.001needed to say "a process start, but not the shell's". -
Two output formats for somebody else's tooling.
codeclimateis GitLab
Code Quality, the only report GitLab renders inline on every plan -- its
security dashboards are a licensed feature and this is not.vexis
CycloneDX VEX: an SBOM says what is in the artefact, and a VEX says which of
its vulnerabilities apply. With--reachability, a transitive dependency
first-party code does not import is recorded asnot_affectedwith the
justificationcode_not_reachable, so a consumer's tooling can drop what has
already been ruled out instead of raising it again. -
Infrastructure policy evaluated per resource. A new detector reads a
Terraform, Kubernetes, CloudFormation or Compose file into blocks and asks two
questions inside each one: does it say something insecure, and does it fail to
say something it must. The second is most of what infrastructure policy is
about and no file-level pattern can express it --storage_encryptedabsent
from anaws_db_instanceis an unencrypted database, written nowhere.
1,032 policies ship across Terraform, Kubernetes, CloudFormation, Compose
and Dockerfiles, each with the block it must report and the block it must not,
both run by the suite on every push. -
Most of those policies are generated from the providers' own schemas.
scripts/build_iac_policies.pyreadsterraform providers schema -jsonand
the AWS CloudFormation resource specification, and emits one policy per
resource that actually declares the attribute a control is about -- 3,845
resources between four providers. Which resources havestorage_encryptedis
a fact rather than a memory, and a policy naming an attribute a provider does
not have can never fire: it looks exactly like a clean scan. The set ships
gzipped beside the advisory data with a sidecar recording the provider
versions it was built from, and it is read one resource kind at a time, so a
repository with no infrastructure in it builds none of it. -
Nine more CI/CD rules, across four systems. A fork's pull request on a
self-hosted runner,workflow_runchecking out the commit that triggered it,
a publishing workflow restoring a cache a pull request can write,
permissions: write-all, and a reusable workflow called by a mutable ref --
plus the script-injection each of GitLab, Azure Pipelines, CircleCI and
Jenkins has, none of which had a rule of its own before. -
Six more ecosystems: Swift, Hex, CRAN, Conan, conda and Bazel, with the
three manifest shapes that were unreadable before them -- a Maven POM's own
coordinates and property substitution, an npm entry whose version is
inherited, and a hoisted entry that is not a direct dependency. -
Advisory feeds for Swift and Hex, taking the database to 268,578 records
from eleven ecosystems. CRAN was requested too and OSV's export produced
nothing this build keeps, so it is not shipped and not claimed: an empty file
is indistinguishable from a populated one at every layer above it. -
OPERATIONAL.ADVISORY.NO_FEED.001. Cordon reads seventeen ecosystems and
eleven have advisory records; a scan that includes a Conan, conda, Bazel or
CocoaPods dependency now says which of them could not be checked at all,
and clearscompleterather than ending in "0 findings, scan complete". -
A popular-package set and an allowlist for every ecosystem. Eight of the
seventeen had no popular set, so_typosquat_targetreturned before comparing
anything and the check silently did not run; six had no allowlist, which is
the state that makes a real package reportable as a squat of a name it
resembles. Both now ship for all seventeen. -
docs/07-ECOSYSTEMS.md: every ecosystem, the manifests and lockfiles read for
it, and which of advisories, typosquat, registry and provenance reach it.
Generated from the registry and asserted by the suite, like the coverage
matrix -- the README named no ecosystem at all before it. -
A live-registry gate on the release...