Skip to main content
Cordon

Cordon

CI PyPI Python Runtime dependencies License Coverage matrix Ecosystems

A language-agnostic software supply-chain security scanner. It reads source, manifests, lockfiles, build scripts, CI config, Dockerfiles and IaC — and reports malicious packages, install-time behaviour, leaked credentials and dependency risk.

It also reads what a repository hands an AI coding agent: instruction files, skills, MCP server configs and their source, hooks and approvals — for every agent in common use — against Cordon's own rules and 815 of the Agent Threat Rules. An optional judge (--judge) has a language model read that text for wordings no rule anticipated (tutorial 18).

Seventeen package ecosystems, from npm and PyPI to Conan, Hex, CRAN and Bazel — the file-by-file list, and which checks each one gets, is in docs/07-ECOSYSTEMS.md.

   ┌─────────────────────────────────────────────────────────────────────────┐
   │  Cordon READS.  It never executes the code it scans, and never sends    │
   │  anything about your code or your dependencies anywhere unless you      │
   │  ask.  By default it makes no network request at all; --offline         │
   │  guarantees it.  Safe on hostile packages, safe in an air-gap,          │
   │  and its results are reproducible.                                      │
   └─────────────────────────────────────────────────────────────────────────┘
pipx install cordon-scanner
cordon-scanner scan .
   scan .
     │  walk        identify            run every            sort, dedupe,
     │  the tree    the target          applicable check     score
     ▼     │            │                    │                   │
   repo ───┴──▶ languages/manifests ──▶ detectors ──▶ findings ──▶ report
   dir/file/archive     lockfiles/CI                              text·json·sarif·…

New here? The tutorials are short, diagram-first walkthroughs — one per use case.


What it looks like

A package whose postinstall posts the environment to a webhook and pipes a download into a shell, and a workflow that sends a publish token to a remote host:

Terminal output: seven critical findings in a compromised npm package

Real output, rendered from a captured run. It is an SVG, not a GIF — text, so it can be read in a diff before it is trusted. A tool that flags committed binaries should not ship one to advertise itself. Regenerate with python scripts/render_demo.py.

  Map of this page
  ├─ Install ................ pipx · pip · Action · container
  ├─ Run it ................. what to scan, what to emit, what appears
  ├─ Configure it ........... the file, the four layers, limits
  ├─ Environments ........... local · pre-commit · CI · monorepo · air-gap · enterprise
  ├─ Tuning ................. the noise ladder, least-blunt first
  ├─ Adopting ............... baseline the debt, gate on the new
  ├─ Accuracy ............... measured, reproducible
  ├─ What fails a build ..... compromise fails; posture is reported
  ├─ Exit codes ............. 0 clean · 1 findings · 2 broke · 3 config · 4 incomplete
  └─ Why this exists ........ the attack, and the three invariants

Install

Method Command Use when
pipx pipx install cordon-scanner Local development. Isolated, on PATH.
pip pip install cordon-scanner Inside a virtualenv you already manage.
GitHub Action uses: Threx-code/cordon/action@<sha> GitHub Actions.
Container docker run --rm -v "$PWD:/scan" ghcr.io/threx-code/cordon:<version> A container-native pipeline.

Python 3.11 / 3.12 / 3.13 on Linux, macOS and Windows.

   OPTIONAL EXTRAS — opt-in, and each degrades to a STATED limit, never a crash
   ┌────────────────────────────────────────────────────────────────────────┐
   │  base           zero third-party runtime deps                          │
   │  [ast-js]       tree-sitter → JS/TS semantic rules   (tutorial 13)     │
   │  [attest]       sigstore    → provenance verification (tutorial 05)    │
   └────────────────────────────────────────────────────────────────────────┘
   PIN BY DIGEST, NOT BY TAG
   Action  → @<commit sha>     a tag is mutable; pinning to something the author
   Image   → @sha256:<digest>  can move defeats the point of running a scanner.

The Action and image are built from the distroless, non-root Dockerfile here, signed keylessly with cosign, and carry SLSA build provenance — the same controls the wheel and sdist get.

cordon --version
cordon-scanner rules list      # what will run

Run it

cordon-scanner scan .                                  # a directory
cordon-scanner scan ./package.tgz                      # an archive, read in memory
cordon-scanner scan . --severity high --fail-on high   # gate a pipeline

What gets scanned

   --exclude 'vendor/**'      skip paths (repeatable)
   --include 'src/**'         restrict to paths
   --tracked                  only files git tracks
   --git-diff origin/main     only what changed vs a ref
   --staged                   the git INDEX, not the working tree

   WHY --staged for a pre-commit hook
   ┌──────────────────────────────────────────────────────────────────┐
   │ a hook reading the WORKING TREE is defeated by: stage a poisoned │
   │ file → restore the clean one. The poisoned blob commits; the     │
   │ clean one is scanned. Reading the index closes that.             │
   └──────────────────────────────────────────────────────────────────┘

   Narrowing applies to FILE analysis only. Dependency & manifest checks always
   run against the whole tree — a malicious transitive dep appears in no diff.

What it emits

   -f text                 humans at a terminal (default)
   -f json                 machine-readable
   -f sarif:cordon.sarif   code-scanning platforms
   -f junit:results.xml    CI test reporters
   -f markdown             PR comments, job summaries
   -f github               inline annotations on a diff

   -f is repeatable; FMT:PATH writes to a file, bare FMT goes to stdout:
   cordon-scanner scan . -f github -f sarif:cordon.sarif -f json:result.json

What appears in the report

   --evidence masked      (default) matched values are masked
   --evidence hash_only   no snippets — for a widely-readable report (PR, upload)
   Secret findings are hash-only regardless.  --reachability annotates CVE noise
   (tutorial 04).  --online adds live registry + provenance checks (tutorial 05).

   --notify slack,teams,webhook   post a failed gate (URLs from CORDON_NOTIFY_*)
   --clamav /run/clamd.sock       also hand each file to a local ClamAV daemon
   --judge cordon-cloud           also have a language model read agent-facing text
   --upload / --cloud-policy      Cordon Cloud: signed results, the org's policy

Other commands

   inventory .            what is this repo, and the evidence
   rules list|show|test   what can fire · one rule · run every rule's samples
   config explain         effective settings + which layer supplied each
   guard install|verify   fail-closed git hooks (tutorial 10); --global for every
                          repository cloned or created from now on
   clone URL              clone, scanned from git's objects before checkout
   pull                   fetch, scan what would be merged, merge only if it passes
   review --base REF      what a dependency update adds (tutorial 21)
   baseline create|compare adopt incrementally (tutorial 14)
   advisories sync        refresh the intel (tutorial 09)
   bundle create|install  air-gapped install (tutorial 11)
   sbom generate          CycloneDX / SPDX from the resolved graph
                          --vulnerabilities  advisory matches, KEV/EUVD marked
                          --ai               the AI bill of materials (CycloneDX 1.6)
   intel status|update    how current the signed threat-intel feed is
   login|logout|whoami    Cordon Cloud sign-in (device flow, SSO)
   runner                 run cloud scan jobs inside your network, outbound only
   agent inventory|report this machine's AI agents and MCP servers, for MDM

Accuracy, measured

Every number is produced by a script in this repository, against code nobody here wrote.

corpus size result
Every known-malicious package record in the intel 249,646 records, 287,899 checks, 10 ecosystems 100% caught
Packages in real lockfiles, read against Trivy 190,274 packages, 1,687 lockfiles, 14 registries 99.6% agree (98.9% per lockfile); the rest sorted by cause
Real malicious packages 39,328 (every DataDog npm, PyPI, AI-skill and IDE-extension sample, and malregistry) 94.2% detected; 79.7% by reading the code alone, the rest by matching a known malicious release
The same malware, against GuardDog 498 (a fixed-seed draw) 95.2% vs GuardDog's 85.5%
Popular packages wrongly blocked top 1,000 PyPI + 1,000 npm 1.6% vs GuardDog's 16.8%
CVEs agreed with Trivy and OSV-Scanner 100 lockfiles 98.4%, every disagreement explained
AI-agent attacks, Agent Threat Rules test cases 4,034 attacks, 289 evasions, 4,369 benign 97.7% detected, 80.6% of evasions, 92.4% of benign left clean
AI-agent configs in real repositories, never tuned on 372 repositories 10.5% warned, 1.1% blocked (each block read and correct)
Widely used open-source repositories (0.4.0 run) 1,427 85.4% pass the default gate
Reference infrastructure, as its vendors publish it (0.4.0 run) 13 repos, 20,310 files 2,560 findings, 795 blocking
Known-vulnerable releases, by advisory 940 pins, 11 ecosystems 100% reported

Per ecosystem and per attack technique, with the method for each and what the numbers are not: docs/08-ACCURACY.md.

What fails a build, and how to change it

By default a finding fails the build when it says this code is compromised or giving something away — malware, leaked credentials, obfuscation, exfiltration. A posture choice a project made about its own infrastructure is reported and does not fail: a security group open to the internet, privileged: true, a Dockerfile doing curl | sh.

That split, the configuration file, the noise ladder and every exit code: docs/10-CONFIGURATION.md.

Pre-commit, GitHub Actions, GitLab, Jenkins, containers and org-wide policy: docs/09-INTEGRATIONS.md.

Using it as a library

from cordon_scanner import Scanner

scanner = Scanner.for_target("./repository")
result = scanner.scan("./repository")

for finding in result.findings:
    print(finding.rule_id, finding.severity, finding.location)

Scanner.for_target assembles configuration through the same resolver the CLI uses, so the organisation ceiling applies. Everything returned is immutable and output is deterministic: identical inputs produce identical findings in a stable order.


Documentation

Document Contents
tutorials/ Diagram-first walkthroughs, one per use case: scan, detection, reachability, provenance, CI, air-gap
docs/01-ARCHITECTURE.md Components, detection engine, rule format, extension points
docs/02-THREAT-MODEL.md Attacker profiles, trust boundaries, the constraints they imply
docs/03-INTERFACES.md CLI, configuration and SDK reference; SARIF mapping
docs/04-OPERATIONS.md Deployment, rule authoring, performance, release process
docs/05-COVERAGE-MATRIX.md Every rule that ships, by threat domain and attack category
docs/06-SANDBOX.md The opt-in component that runs a package in isolation, and what it observes
docs/07-ECOSYSTEMS.md Every ecosystem read, the files read for each, and which checks it gets
docs/08-ACCURACY.md Detection rate by ecosystem and by attack technique, the method behind each number, and what they are not
docs/09-INTEGRATIONS.md Pre-commit, GitHub Actions, GitLab, Jenkins, containers, org-wide policy
docs/10-CONFIGURATION.md .cordon.yaml, what fails a build, the noise ladder, exit codes, adopting on an existing codebase
docs/11-RATIONALE.md What the other tools do, what they do not, and the gap this sits in
docs/assets/ The logo, as SVG: wordmark (light and dark), mark, and a filled square for an avatar

Project

Document Contents
CHANGELOG.md What changed in each release, and what it changes for you
CONTRIBUTING.md How to add a rule, and the evidence one needs before it ships
GOVERNANCE.md Who decides, and what a change has to prove
SECURITY.md Reporting a vulnerability in Cordon itself
SUPPORT.md Which door to knock on, and what a useful report contains
CODE_OF_CONDUCT.md What is not acceptable, and who to tell
CITATION.cff How to cite this in research

Status

Beta, and the classifier says so. The detection engine, rule packs, twenty-eight ecosystems, container images, the AI-agent chain, reporters, policy layer, baselines, git-aware scanning and the advisory layer are implemented and tested; docs/03-INTERFACES.md separates the commands that ship from those that are designed. Interfaces may still change before 1.0, the bundled advisory set is the malicious plus high/critical subset of OSV rather than all of it, eleven of the twenty-eight ecosystems have no advisory feed to match against at all, and reachability is modelled at its import tier, with function-level checks for Go (from the symbols its database lists) and for PyPI and npm (from the functions an advisory's text names, which can only raise a finding, never lower one) — none of it hidden: cordon-scanner rules list shows what runs, docs/07-ECOSYSTEMS.md shows which checks each ecosystem gets, and every reduction in coverage is reported as a finding rather than left for a reader to infer.

Licence

Apache-2.0. See LICENSE and NOTICE.

Metadata

Release files for cordon-scanner 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cordon-scanner 0.6.0
File Size Uploaded Provenance
cordon_scanner-0.6.0.tar.gz 13.1 MB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for cordon-scanner 0.6.0
File Interpreter ABI Platform
cordon_scanner-0.6.0-py3-none-any.whl Python 3 none any Details

Total release size: 25.1 MB

Release files / cordon_scanner-0.6.0.tar.gz

Download URL cordon_scanner-0.6.0.tar.gz
Size 13.1 MB
Tags Source
SHA-256 checksum
How to use checksums
f575caffaf1199e3385981b85b76c0fb235048f559e430f8cc664f50a1d4aaa6
BLAKE2b-256 checksum
How to use checksums
e3647e97b209a0826481eaacf9aae9aac8f973f014f56885bab18f60f439c3c0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

Release files / cordon_scanner-0.6.0-py3-none-any.whl

Download URL cordon_scanner-0.6.0-py3-none-any.whl
Size 12.0 MB
Tags Python 3
SHA-256 checksum
How to use checksums
03cac1cbc5adc0a2c149b3edce88665c405fe9139f99aa9f5c4602dfc624a475
BLAKE2b-256 checksum
How to use checksums
340db997496801fa5a28264b707f4b2fbfd5211a2e1097856ec529c509d1d3df
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

Security for cordon-scanner 0.6.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for cordon_scanner-0.6.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page