commitwork docs

commitwork Coverage Survey

Scanner inventory & coverage

Every security tool in the tree, which languages each one reaches, and which lanes were drawn but could not run.

Runnable now
49of 50 scheduled
Still blocked
1joern only
Installed today
14CLIs + 1 mix archive
Cannot report
5lanes, by design

Language coverage matrix

Rows are ordered by GitHub Octoverse 2025 rank.

Scanner
Language TruffleHog Gitleaks Semgrep CodeQL Joern Bearer gosec govulncheck OSV-Scanner dep-scan Trivy Retire.js Syft GuardDog Socket native lint
01TypeScript▲2 ●●●●○●●●●●●●
02Python▼1 ●●●●○●●●●
03JavaScript▼1 ●●●●○●●●●●●●
04Java— ●●●●○●●●●●
05C#— ●●●●●●●
06PHP▲1 ●●●○○●●●●
07Shell▲1 ●●●●
08C++▼2 ●●●●●●●●
09HCL▲5 ●●●●
10Go— ●●●○●●●●●●
—C▼ ●●●●●●●●
—Ruby— ●●●●●●●●
—Rust— ●●●●●●●
—Kotlin— ●●●○●●●●
—Swift— ●●●○●●●
—Dart— ●●●●●●
—Elixir— ●●○●●●●
—Haskell— ●●●●●●
● wired and running ○ unwired — lane exists, cannot run ○ requires --pro-languages flag · blank — no lane drawn

Reading the last column

native lint collects the per-language tools that exist for exactly one row: shellcheck for Shell, clippy for Rust, hlint for Haskell, sobelow for Elixir. All four now run. Semgrep shows hollow for Elixir for a specific reason set out below, and blank for Haskell because no Semgrep Haskell rules exist at all.

The Elixir lane reports a clean scan of a file it never parsed

False clean · measured

Semgrep 1.153.0 lists elixir among its 65 supported languages. It cannot actually parse it without a proprietary extension. Running an Elixir rule against an Elixir source file produces this:

$ semgrep scan --config rules/elixir.yaml elixir_sample.ex
exit 0
results []
notification level="note"  "Missing Semgrep extension needed for
             parsing Elixir target. Try adding `--pro-languages`."
invocation executionSuccessful=true

The repository already guards this class. monitor/sarif-read.mjs checks both channels precisely because "executionSuccessful can be true while the notifications carry the fatal error". But its filter at line 46 admits only error and fatal. Semgrep emits this one at note.

Passing the artifact through the repository's own reader settles it:

$ readSarif(out.sarif)
state   : ok
reason  : (none)
results : 0
norules : true

Not tool-failed. Not empty. ok — a clean scan of a file that was never parsed. The only surviving signal is norules, which the module header explicitly describes as "an advisory fact on 'ok', not a verdict". This is the same shape monitor/codeql-coverage.mjs was built to catch for CodeQL, arriving through a channel that module does not watch.

The narrow fix on our side is one line — admit note-level notifications whose descriptor id is Missing plugin. But the upstream behaviour is worse than the SARIF suggested, and worth reporting.

The text output asserts a scan that did not happen

Two runs of the same Semgrep build. The first genuinely analysed a Rust file; the second could not parse Elixir at all. Every line but the finding count is identical.

── rust, 2 rules actually executed ──────────────
✅ Scan completed successfully.
 • Findings: 2 (2 blocking)
 • Rules run: 2
 • Targets scanned: 1
 • Parsed lines: ~100.0%

── elixir, 0 rules executed, 0 lines parsed ─────
Warning: 2 rule(s) were skipped because they require Pro
✅ Scan completed successfully.
 • Findings: 0 (0 blocking)
 • Rules run: 2
 • Targets scanned: 1
 • Parsed lines: ~100.0%

There is a warning line, but the summary block contradicts it on three counts and then affirms success. --error does not change the exit code. For contrast, a ruleset that 404s exits 7 and emits notifications at level: error — so Semgrep already grades one "the analysis did not happen" case correctly, and this one differently.

Where it originates

Missing-plugin errors are handled in a branch of semgrep/output.py that deliberately bypasses _handle_semgrep_error, carrying an unresolved maintainer note:

# THINK: These perhaps should not be errors but reported as "skipped"

The summary counters sit further down the same file and count rules and targets that were selected, not ones that were analysed — so rules skipped for a missing plugin still register under "Rules run".

A hypothesis I tested and discarded

The counter is derived as executed_rule_count or len(filtered_rules), which in Python falls through on a zero count — an appealing one-line explanation. A mixed scan of one Python rule and two Elixir rules should then have reported "Rules run: 1". It reported 3. The fallback is not the mechanism, and the real cause is that the count never excludes skipped rules in the first place.

Extending Rust and Elixir ourselves

Yes for Rust, and the precedent is already in the tree. manifests/semgrep-taint/ is a locally authored rule pack wired into the sast check, written because the three registry packs produced byte-identical output with and without the Pro engine — the fleet was paying for a capability its own rule selection could not exercise.

Rust · proven working

A taint rule tracking std::env::var to std::process::Command::new across a function boundary fires correctly on the OSS engine, with no account and no plugin. A second pattern rule for unsafe blocks also fires. Two rules, two findings, zero errors.

This matters because it closes a real hole rather than a nominal one: clippy is explicitly not a security scanner, so Rust source currently has no lane that traces a source to a sink. Authored rules would be the first.

Elixir · blocked upstream

Elixir parsing sits behind --pro-languages, which needs a Semgrep account and semgrep install-semgrep-pro. Authoring rules is possible; running them on the OSS engine is not. Until that is resolved, Sobelow — now installed and running at 0.15.0 — is the only real Elixir security lane, and the Semgrep row must read hollow rather than green.

One constraint on where Rust rules go

They cannot join manifests/semgrep-taint/. That pack's entry condition, enforced by monitor/test/semgrep-taint.test.mjs, is that every rule must find something the OSS engine cannot — the test exists so the Semgrep account dependency stays load-bearing rather than decorative. Rust rules that work fine on OSS would violate it. They need a sibling pack with its own stated entry condition.

Wired this session

Both requested lanes were already written and already in the scheduled group. Neither needed a manifest edit — they were unwired only in the sense that the binary was absent.

ToolVersionUnblocksRoute
shellcheck0.11.0shell-lintbrew
actionlint1.7.12actions-actionlintbrew
zizmor1.29.0actions-zizmorbrew
go1.27.0toolchain for both Go lanesbrew
gosec2.28.0sast-go-gosecbrew
govulncheckv1.7.0deps-go-govulncheckgo install
elixir + erlang1.20.3 / 29.0.5runtime for Sobelowbrew
sobelow0.15.0sast-elixir-sobelowmix archive
hlint3.10lint-haskell-hlintbrew
prowler5.39.1cspm-githubuv tool
scorecard5.5.0posture-scorecardbrew
socket1.1.158supply-chain-socketnpm -g
bearer2.1.1sast-bearerpinned release + sha256
grype0.117.0preflight roster only — no lanebrew

Verified by running, not by resolving

shell-lint returns 8 findings across 9 shell scripts — 1 warning, 6 info, 1 style, of which six are SC2086, the unquoted-expansion class the manifest names as the injection surface. Under the manifest's own severity policy that is 0 high and 1 medium. actions-actionlint returns ran:true with 0 findings, which is a clean result carrying its own witness rather than an empty file.

Bearer needed a different route, not a fix

The brew path fails: no bottle exists for macOS 27, and the source build is refused because Xcode 26.6 is too old. That reads like a dead dependency and is not one — the project is actively maintained, with v2.1.1 released on 24 August 2026 and a push the same day. Only the packaging was blocked.

It ships a darwin_arm64 tarball with a checksums.txt alongside it, so the lane was unblocked by installing the pinned release and verifying its SHA-256 before extracting — not by piping an install script into a shell, which the repository's own unpinned-fetch gate would refuse.

Still blocked

joern — and the earlier reading here, that it was never attempted and needs a JVM and Scala toolchain, is now superseded by measurement. Probed 2026-08-30: joern-scan 4.0.610 is installed via Homebrew, builds a CPG, runs ScanPass and exits 0. It also returns zero results on a file containing strcpy(buf, argv[1]) and system(sprintf(...)), logging “Attempting to determine flows from empty list of sources” thirteen times. The full query bundle is present (sql-injection, shell-exec, xss-servlet and the rest) but resolves no sources or sinks for the input.

That is a worse state than unavailable, and the distinction matters here: a tool that cannot run reports tool-unavailable and the repository treats it as unscanned, whereas this one exits 0 with an empty result and would publish as a clean scan. The lane stays unwired deliberately until the query bundle can be shown to fire on a known-vulnerable input — an exit code is not a finding, and a green cell earned this way would be the exact defect this survey exists to expose.

One operational note worth acting on: govulncheck installed to ~/go/bin and bearer to ~/.local/bin, neither of which is on the default sweep path. Both resolve in an interactive shell and may not resolve under launchd.

Full inventory

Classification is the repository's own, read from LANE_KINDS in monitor/extractors.mjs rather than judged here. Of 43 published lanes, 38 can raise a security finding and 5 carry why: 'not-a-vulnerability' — they are executable scanners that will run, produce output, and never report a security issue, because that is not what they measure.

italic — can report a security issue in theory bold — structurally cannot ● running ○ unwired

Secrets

  • ●TruffleHoglanguage-agnosticScans git history for verified credentials.
  • ●Gitleakslanguage-agnosticScans working tree using repository rules.
  • ●secrets-sweep.mjsinternalFinds connection strings both scanners miss.
  • ●secrets-canary.mjsinternalPlants secrets and scores scanner recall.

Pattern static analysis

  • ●Semgrepmost languages · thin on Rust, blocked on ElixirMatches vulnerability patterns across many languages.
  • ●gosecGoAdds Go taint rules Semgrep lacks.
  • ●SobelowElixir, PhoenixFinds Phoenix injection and misconfiguration.
  • ○Opengrepmost languages · outside the all groupReplaces Semgrep if licensing changes.

Deep dataflow

  • ●CodeQLJS · TS · Java · Python · Ruby · C · C++ · C# · SwiftTraces tainted input to dangerous sinks.
  • ○JoernC · C++ · Java · JS · TS · Python · Go · Kotlin · binariesBuilds code property graphs without compiling.
  • ●BearerJS · TS · Ruby · Java · PHP · Python · GoFollows personal data through the code.

Correctness lint

  • ●ShellCheckShell · BashFlags quoting defects enabling shell injection.
  • ●actionlintGitHub Actions YAMLChecks workflow expressions and inline scripts.
  • ●ClippyRustReports Rust correctness and suspicious patterns.
  • ●HLintHaskellSuggests Haskell simplifications and likely mistakes.
  • ●deno lintDeno TS · JSLints Deno sources for suspicious patterns.
  • ●deno checkDeno TS · JSType-checks a Deno project.

Dependency vulnerabilities

  • ●OSV-Scanner16 lockfile ecosystemsResolves lockfiles against the OSV database.
  • ●OWASP dep-scansame ecosystemsAdjudicates reachability outside the Go lane.
  • ●TrivyJava · Kotlin · containersScans JVM manifests and fat jars.
  • ●govulncheckGoProves whether Go vulnerabilities are reachable.
  • ●Retire.jsJS · TSIdentifies vulnerable JavaScript by file content.
  • ●npm audit / yarn auditnpm · YarnQueries npm advisories for locked dependencies.
  • ●gradle-deps-scan.mjsGradle · internalResolves Gradle coordinates lacking a lockfile.
  • ●vendor-scan.mjsJS · internalIdentifies vendored bundles from version banners.

Supply chain & provenance

  • ●GuardDognpmScores package metadata for typosquats.
  • ●SocketnpmDetects malicious packages during install.
  • ●OpenSSF Scorecardrepository-levelScores repository supply-chain posture externally.
  • ●Renovatemulti-ecosystemReports pending updates without opening pull requests.
  • ●dep-provenance.mjsnpm · Yarn · internalRecords where each dependency resolved from.
  • ●gradle-wrapper checkGradle · internalVerifies wrapper distribution URL and jar.

Bill of materials

  • ●Syftall ecosystemsGenerates CycloneDX SBOMs across all ecosystems.
  • ●CycloneDX npmnpmGenerates SBOMs for npm lockfile repositories.
  • ●sbom-enrich.mjsnpm · internalRestores provenance the CycloneDX writer dropped.

Infrastructure & CI posture

  • ●Trivy configTerraform · HCL · Dockerfile · KubernetesFinds infrastructure and Dockerfile misconfiguration.
  • ●HadolintDockerfileLints Dockerfiles for security best practice.
  • ●zizmorGitHub Actions YAMLAudits Actions permissions and template injection.
  • ●ProwlerGitHub provider · AWSChecks organisation and repository security controls.

Runtime & dynamic

  • ●Nucleiprotocol-levelProbes live endpoints with exploit templates.
  • ●testssl.shHTTPS endpointsGrades TLS configuration and security headers.
  • ●SchemathesisOpenAPIFuzzes OpenAPI endpoints with generated cases.
  • ●authz-bola.mjsHTTP gateways · internalProbes cross-tenant object access through gateways.

Hygiene & integrity

  • ●minify-detect.mjsJS · TS · HTML · internalTreats unreadable code as its own signal.
  • ●a11y-scan.mjsHTML · internalAudits served HTML against WCAG criteria.
  • ●stub-detect.mjsall source · internalCounts unfinished work markers across sources.

Intelligence feeds

  • ●OSV · GHSA · NVD · deps.devadvisory sourcesSupply advisory records to the scanners.
  • ●EPSS (FIRST)monitor/epss.mjsFetches exploit probability scores per CVE.
  • ●CISA KEVexploitation catalogueMarks vulnerabilities with confirmed active exploitation.
  • ●advisory-reach.mjsinternalDemotes advisories that cannot reach code.
  • ●cwe-graph · capec-graphMITRE cataloguesLoad MITRE catalogues as typed graphs.

Compliance evidence

  • ●CSAF 2.0 + OpenVEXcra/vex-formats.mjsProject exploitability statements into published formats.
  • ●OSCALcra/oscal.mjsEmits NIST SP 800-53 component definitions.
  • ●SOC 2 Type IIcra/soc2.mjsAssembles evidence keyed to Trust Services Criteria.
  • ●EU CRA Article 14cra/watch.mjs · cra/report.mjsRuns statutory disclosure clocks and drafts.
  • ●ed25519 attestationcra/attest.mjsSigns the assembled evidence pack.

Registry entries with no execution path

Docker Bench and Falco are reachable only through bin/audit.mjs, gated on a Docker host and a production runtime. ScoutSuite is named only as an alternative inside Prowler's registry entry. Grype is version-tracked by the preflight and now installed, but drives no lane. Cosign is declared and never invoked — the actual signing in cra/attest.mjs uses Node's built-in ed25519, so any claim that this fleet signs with Sigstore is wrong today.

One arguable classification

The accessibility lane is marked as able to report, because the repository files it under policy with no not-a-vulnerability exemption and it reaches the severity sum. A WCAG failure is a conformance defect rather than a security issue, so this is the one row where the repository's own marker and the plain reading of "security issue" diverge.

On comparing languages

The row order above carries a caveat that should travel with it. No public dataset measures lines of code authored on GitHub — GitHub computes per-repository Linguist byte counts but has never published them in aggregate.

Worse for the delta column: Octoverse changed its metric between editions. The 2024 report ranked by total activity across commits, issues, pull requests and reviews; the 2025 report ranked by distinct monthly contributors. Rank movement across that boundary compares two different measurements, so the arrows are directional and not evidence.

MeasureCountsCadenceFitness here
Software HeritageArchived source files, cross-forge, from 1967research snapshotsBest proxy for code volume
GH ArchivePush and PR events per languagehourly, queryableBest for current authoring activity
RedMonkGitHub and Stack Overflow correlationbiannualTraction, not volume
OctoverseDistinct monthly contributorsannualPeople-weighted, metric unstable
TIOBE · PYPL · IEEESearch-engine and media mentionsmonthlyMeasures discourse, not code

The better denominator is already in the tree

Global popularity answers where the world's code is, not whether this fleet's coverage is adequate. An unscanned Elixir service you actually operate outranks PHP's global position every time.

monitor/codeql-coverage.mjs already derives per-language file counts from stored SARIF, using each lane's appliesIfSourceExt as a language-aware denominator — built because a naive ratio reports every polyglot repository as degraded. Its recorded fleet measurement is a better language axis than anything public, and it says something this matrix cannot:

python  200 runs   median 1.00   2 below 0.90
ruby     44 runs   median 1.00   0 below 0.90
csharp    3 runs   median 1.00   0 below 0.90
cpp       6 runs                 2 below 0.90 — worst 0.64
swift     2 runs                 1 below 0.90 — 0.67

The C++ and Swift lanes are wired, installed, and not reading the files they claim to cover. A support matrix can only show that a lane exists; this shows whether it looked.

Loose thread

Documentation · dangling reference

README.md line 235 indexes docs/TOP-100.md — the document grading all 110 vulnerability classes, and the only place the fleet's coverage is stated as a whole. That file is in neither the working tree nor HEAD. An evaluation document from 24 August cites its line numbers as verified.

This is the failure mode the repository conventions name directly: a commit is closed over its own change set, and committed content here references something that was never committed. It is invisible to its author and broken for everyone else.