carabiner v0.1.13
Make any repo secure by default, keep it that way, and prove the controls fire.
- uses: MaXiMo000/carabiner@v0.1.13pip install carabiner-sec # installs the `carabiner` command
docker run --rm -v "$PWD:/repo:ro" ghcr.io/maximo000/carabiner:0.1.13 scan --allCalibrated against 60 public repositories
Tuned by scanning 60 real projects — tokio, grafana, vault, rails, next.js, home-assistant, airflow and more — across 13 languages, then re-checking every finding with an oracle written independently of the engines.
The default output is now a median of 9 findings per repository. Roughly 800 informational ones across the corpus are counted but not listed until you pass --info. Hidden never means disappeared: the count always shows.
A version tag on an action is informational. A moving branch in someone else's repository is not — that is what can change tonight without a diff on your side. Classifying refs by shape rather than by a list of branch names also caught stable, nightly and cargo-hack. And one unpinned reference is one finding, however many steps use it: tokio reaches dtolnay/rust-toolchain@stable 34 times in a single workflow, which is one decision, not 34 lines.
tokio went from 172 findings to 10. Corpus criticals went from 31 to 2 — and both survivors are real: a committed private key in grafana, and an unguarded pull_request_target + PR-head checkout in rollup.
New: a native Dockerfile engine
Trivy scans a built image — daemon, build, minutes. A Dockerfile is text, and the mistakes that matter most are visible before anything is built.
DOCK001 |
the final stage never drops root |
DOCK002 |
untagged or :latest base image |
DOCK003 |
a credential baked into ARG/ENV, readable with docker history |
DOCK004 |
a remote script piped straight into a shell at build time |
DOCK005 |
TLS verification disabled during the build |
54 real issues across 19 of the 60 repositories — but only after spot-checking killed 33 of the rule's first 36 findings. FROM builder is a stage reference, scratch is a keyword, ${BASE_IMAGE} is unknowable, and airflow embeds entire Python programs in BuildKit heredocs that were being parsed as instructions.
Fixed: findings that named an action or image were unreadable
The redactor treated /, . and - as token characters, so dtolnay/rust-toolchain@stable printed as dtol...hain@stable. A credential is a long unbroken run with no separators to split on — splitting on them keeps identifiers legible while still masking ghp_… and AKIA… tokens.
What it does
A ratchet. carabiner init accepts your existing findings into a baseline, so CI goes green today and only new problems fail it. --expires 90 puts a deadline on accepted debt.
A drill. carabiner drill doesn't read config — it plants a private key and checks your hooks actually block it, then asks GitHub whether push protection is really on. A check that could not run never reports as passing.
One normalized model → SARIF into the PR's Security tab, deduplicated across engines, most-severe-wins.
52 tests, 193 checks, on Linux and Windows across Python 3.10 and 3.13, plus a Docker build, a wheel install into a clean venv, and carabiner scanning itself — every commit.