Skip to content

Releases

v0.7.0

Choose a tag to compare

@Threx-code Threx-code released this 06 Nov 13:39
fe0aae8
Cordon

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

Choose a tag to compare

@github-actions github-actions released this 08 Oct 08:05
Cordon

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

Choose a tag to compare

@github-actions github-actions released this 05 Oct 22:53
Cordon

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

Choose a tag to compare

@github-actions github-actions released this 05 Oct 15:32
Cordon

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

Choose a tag to compare

@github-actions github-actions released this 25 Sep 14:49

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

Choose a tag to compare

@Threx-code Threx-code released this 23 Sep 07:02

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 show printed a title and a remediation and nothing that
    explained the claim, and references did 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.py holds 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 -security placeholder. See Changed.

  • Negation in a composite rule's terms. unless speaks about paths, and
    SUSPECT.DECODE_EXEC.001 needed to say "a process start, but not the shell's".

  • Two output formats for somebody else's tooling. codeclimate is GitLab
    Code Quality, the only report GitLab renders inline on every plan -- its
    security dashboards are a licensed feature and this is not. vex is
    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 as not_affected with the
    justification code_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_encrypted absent
    from an aws_db_instance is 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.py reads terraform providers schema -json and
    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 have storage_encrypted is
    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_run checking 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 clears complete rather 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_target returned 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...

Read more