Run it
git clone --depth 1 --filter=blob:none --sparse https://github.com/Duncan-Haywood/oss-monorepo.git devsec-src
cd devsec-src && git sparse-checkout set ventures/devsec-toolkit \
$(python3 ventures/devsec-toolkit/devsec.py list --paths) && cd ..
python3 devsec-src/ventures/devsec-toolkit/devsec.py scan . # text report
python3 devsec-src/ventures/devsec-toolkit/devsec.py scan . --format json -o devsec.json
python3 devsec-src/ventures/devsec-toolkit/devsec.py scan . --fail-on high # CI gate: exit 2
For GitHub Actions, copy github-workflow.yml to .github/workflows/devsec.yml: it fetches about 3 MB, writes a summary to the job page, uploads SARIF to code scanning and fails on new high findings. Every action in it is pinned to a commit SHA.
Already have hundreds of findings? devsec.py scan . --write-baseline .devsec-baseline.json once, then CI fails only on findings that are new. See the README for .devsec.json (skip checks, ignore paths, override severities).
Open a report
Open the devsec.json or devsec.sarif that a scan wrote, or paste it below, to sort and filter the findings. The file is read in this browser and never uploaded.
The example is a real scan of the toolkit's own deliberately vulnerable test repository.
| Severity | Location | Finding |
|---|
What it checks
Each check is its own open-source tool in the same repository, with its own tests and its own page. devsec-toolkit runs the ones that apply to your repository and says which did not apply, so a repository with no Dockerfile never reads as a clean Docker scan.
| Check | Area | Looks for |
|---|---|---|
| actions-injection | GitHub Actions workflows | Pwn-request checkouts and script injection |
| actions-artifact-poisoning | GitHub Actions workflows | Artifacts from untrusted runs executed in privileged jobs |
| actions-cache-poisoning | GitHub Actions workflows | Cache writes reachable from untrusted triggers |
| actions-secret-leak | GitHub Actions workflows | Secrets transformed past log masking |
| actions-secrets-inherit | GitHub Actions workflows | secrets: inherit to external or untrusted workflows |
| actions-self-hosted-exposure | GitHub Actions workflows | Self-hosted runners reachable from forks |
| actions-permissions | GitHub Actions workflows | GITHUB_TOKEN permissions left at write-all or unset |
| actions-checkout-credentials | GitHub Actions workflows | actions/checkout leaving the token in .git/config |
| pinned-actions | GitHub Actions workflows | Third-party actions referenced by a mutable tag |
| actions-runtime-deprecation | GitHub Actions workflows | Actions on deprecated or shut-down runtimes |
| actions-timeout | GitHub Actions workflows | Jobs with no timeout (6-hour default) |
| docker-build-secrets | Dockerfiles and Compose | Secrets baked into image layers |
| docker-compose-security | Dockerfiles and Compose | Privileged, socket-mounting and exposed Compose services |
| docker-base-image-eol | Dockerfiles and Compose | Base images past end of life |
| docker-base-image-pin | Dockerfiles and Compose | Base images referenced by a mutable tag |
| k8s-pod-security | Kubernetes manifests | Privileged pods, host namespaces, root containers |
| dependency-confusion | Dependencies, registries and runtimes | Dependency-confusion exposure (extra index URLs, unscoped private names) |
| node-version-drift | Dependencies, registries and runtimes | End-of-life or disagreeing Node.js versions |
| dependabot-config | Dependencies, registries and runtimes | Dependabot missing or not covering every ecosystem |
| jwt-verification-misuse | Application source | JWT signature or claim verification switched off |
| regex-redos | Application source | Regular expressions open to catastrophic backtracking (ReDoS) |
| agent-permission-config | AI coding-agent configuration | Over-broad AI agent permissions and MCP servers |
| hidden-unicode | AI coding-agent configuration | Invisible and bidirectional Unicode in instruction files and source (Trojan Source, ASCII smuggling) |
How it compares
Paid platforms
Snyk, GitGuardian, Socket, Aikido and Wiz bundle scanning with dashboards, triage and support, priced per developer or per repository. If you need those, buy them. devsec-toolkit is the free, local layer: no account, no upload, and output you own.
Free single-purpose scanners
zizmor (Actions), Trivy, Checkov, Semgrep, gitleaks and OSV-Scanner are excellent; keep running them. Each has its own install, output format and suppression scheme. devsec-toolkit covers ground they leave open, such as Actions cache and artifact poisoning, secrets: inherit, secrets re-encoded past log masking, AI-agent permissions, ReDoS and JWT misuse, behind one report and one baseline.
Linter aggregators
MegaLinter and Super-Linter run many tools from a large Docker image. devsec-toolkit is security-only, needs no Docker, fetches about 3 MB, and normalises every finding onto one schema rather than concatenating each tool's log.
Questions
Does a clean scan mean my repository is secure?
No. A clean report means these 23 checks found nothing. It is not a security audit, a penetration test or a compliance attestation, and it does not look for vulnerable dependency versions, leaked credentials in git history, or flaws in your application logic.
Does anything leave my machine?
No. Every check reads files already on disk and none makes a network call. The report viewer on this page reads the file you open in your browser and never uploads it.
Why not just run zizmor, Trivy, Checkov or Semgrep?
Run them: they are excellent and free. devsec-toolkit covers checks they do not (Actions cache and artifact poisoning, secrets: inherit, secret re-encoding past log masking, AI-agent permission configs, hidden Unicode in instruction files, ReDoS, JWT misuse) in one zero-dependency command with one report and one baseline. Adapters that run those tools and merge their findings into the same report are on the roadmap.
How do I adopt it on a repository that already has hundreds of findings?
Write a baseline once with --write-baseline, commit it, and point .devsec.json at it. CI then fails only on findings that are new, and known ones stay suppressed even when their line numbers move.
Do I need Docker or a package install?
No. It needs Python 3.9 or later and, for the six checks written in JavaScript, Node.js 18 or later. Without Node those six are reported as skipped, never as passing.
Where this is going: ROADMAP.md. Source: ventures/devsec-toolkit.