This document explains every claim the kaos-compliance dashboard makes and how a third party can independently reproduce the verification. If a claim doesn’t have a verification path here, it’s a bug — please file an issue.
Principles
- Public sources only. Every signal is extracted from a publicly accessible source: GitHub REST API, PyPI JSON + simple-index, the sigstore Rekor transparency log, or files committed to the public
273v/kaos-*repos. No private endpoints, no privileged credentials, no internal-only artifacts. - JSON is the source of truth. The dashboard’s HTML is one render of
data/snapshots/latest.json. The same JSON is published for compliance ingest athttps://273v.github.io/kaos-compliance/api/v1/snapshot.json. - Every claim links to evidence. Each card on the dashboard carries a Verify link that opens the underlying source (a GitHub workflow run, a PyPI metadata page, a Rekor log entry, or a file in this repo).
- No invented scores. We surface the OpenSSF Scorecard aggregate (a pre-composited industry-standard number) and the underlying signals. We do not compute a competing composite. Buyers compose their own bar from the per-signal evidence.
- Stale data is loudly marked stale. Every snapshot carries a generation timestamp; the dashboard shows a freshness indicator on every card. If the cron stops running, the indicator goes amber within 2 hours and red within 24.
Cadence
| Cron | Cadence | What runs | Why |
|---|---|---|---|
| Light | every 1h | CI conclusions, open PR counts, queue depth | These move fast; staleness is misleading. |
| Security | every 4h | bandit / vulture / cargo-* / pip-audit / gitleaks conclusions; OSV cross-check; PyPI attestation refresh | Slower-moving but high-signal; refresh often enough that a published advisory shows up within a working day. |
| Full | every 24h (UTC midnight) | Full sweep + SBOM rebuild + LLM diary + history rotation | The expensive path. Rebuilds the full transitive dep tree and runs the daily narrative. |
Cron is driven primarily by GitHub Actions on this repo. A local cron job on the developer machine acts as a backup and runs the LLM diary when the GHA secrets don’t have the model-provider API key wired.
Public PR and CI/CD hardening policy
The KAOS package repos are public, so the dashboard treats outside PRs as an explicit trust boundary rather than a convenience feature. The policy baseline for public 273v/kaos-* repos and this compliance repo is:
- untrusted PR code runs only from
pull_request, neverpull_request_targetwith contributor code checked out; - the default
GITHUB_TOKENis read-only; - workflows declare explicit
permissions:; - PR build/test jobs have no secrets, no
id-token: write, no package-publish authority, and nocontents: write; - checkout uses
persist-credentials: false; - external Actions references are pinned to full commit SHAs;
- fork PR workflow runs require approval for all external contributors;
mainrequires status checks, CODEOWNER review, stale-review dismissal, approval of the most recent push, linear history, and no force-pushes;v*release tags are protected against deletion and force-update;- secret scanning and push protection are enabled.
For kaos-compliance specifically, external fork PR code is not an accepted contribution path and is not executed in CI. GitHub does not allow disabling forks for an org-owned public repository, so the repo uses policy plus workflow guards: public issues and private security reports remain open, while CI jobs run only for main and PR branches whose head repository is 273v/kaos-compliance.
This policy is documented in full in kaos-modules/docs/oss/40-ci-cd/public-pr-security.md. The compliance dashboard does not currently render these repository settings as a green public claim, because several are visible only through GitHub admin APIs. The dashboard may surface workflow-file checks that are publicly reproducible, such as pinned uses: references and explicit workflow permissions. Admin-only settings are audited with the runbook commands in docs/RUNBOOK.md until GitHub exposes a public evidence path.
The kaos-compliance sweep workflow is a trusted main/schedule workflow, not an untrusted PR workflow. It still has a tracked trust-lane follow-up: split collection/rendering, keyless signing, final render, and Pages deploy into separate jobs so dependency installation never shares a job token with contents: write or id-token: write. Until that lands, the snapshot signature proves which workflow produced the JSON, but it does not prove that the workflow followed the least-privilege job split described here.
Frameworks anchored
This dashboard maps signals to:
- OpenSSF Scorecard v5 — all 19 checks.
- SLSA 1.0 — Build L1–L3, Source L1–L3 (Source track still draft as of 2026-05).
- NIST SSDF (SP 800-218) — the 12 practice IDs with publicly evidenceable signals.
- CISA SBOM Minimum Elements.
- PEP 740 + sigstore + PyPI Trusted Publishers.
- Cyber Resilience Act (Annex I Part II conformity signals; full enforcement guidance not expected until 2027).
See docs/research/01-compliance-signal-inventory.md for the per-framework signal-to-source mapping table.
NIST SSDF practice-ID matrix
The dashboard surfaces signals against the publicly-evidenceable subset of NIST SP 800-218. Practices whose evidence is internal-only (e.g. PO.5.1 “Implement and maintain secure development environments”) are deliberately omitted — the public-source-only contract can’t reach them.
| SSDF | Asks for | Dashboard signal | Snapshot path |
|---|---|---|---|
PO.1.1 | Define security requirements | Methodology + threat model | docs/METHODOLOGY.md |
PO.3.2 | Verify software releases | PEP 740 attestation + Rekor | modules[].supply_chain.attestations.* |
PO.5.2 | Secure environments | Public PR hardening policy; GitHub-hosted runner policy | Policy documented above; workflow-file checks are public, admin settings are runbook-audited |
PS.1.1 | Least-privilege source storage | Branch protection on main | modules[].governance.branch_protection_enabled |
PS.2.1 | Verify release integrity | Sigstore signature on snapshot + per-package attestations | api/v1/snapshot.sig, modules[].supply_chain.attestations.* |
PS.3.1 | Archive and protect each release | PyPI immutability + Rekor transparency log | modules[].supply_chain.attestations.rekor_log_index |
PS.3.2 | Provide SBOM per release | CycloneDX 1.5 per package | modules[].supply_chain.sbom.sbom_artifact_path |
PW.4.1 | Acquire well-secured software | OSV cross-check; CVE feed | modules[].security.workflow_conclusion |
PW.7.1 | Code review | Per-package CI matrix | modules[].ci.workflow_conclusion, .matrix |
RV.1.3 | Vulnerability-disclosure policy | SECURITY.md present | modules[].governance.security_md_present |
RV.2.1 | Analyze vulnerabilities to root cause | Suppressions ledger | security.html#sup-h |
CRA Annex II / Article 13 traceability
The EU Cyber Resilience Act, as drafted, requires manufacturers to maintain technical documentation (Annex II) covering specific conformity signals (Article 13). The mapping below names which signals satisfy which clause; clauses without a verifiable public signal are surfaced as honest gaps rather than silently claimed. CRA full enforcement guidance is not expected until 2027 — this matrix will be revised against the final implementing acts when published.
| CRA reference | Requirement (compressed) | Dashboard signal |
|---|---|---|
| Annex II §1 | Product description with cybersecurity properties | Per-package detail pages |
| Annex II §2 | Risk assessment | Methodology page; SBOM + advisory feed |
| Annex II §3 | SBOM | CycloneDX 1.5 published per package |
| Annex II §4 | Vulnerability-handling process | SECURITY.md + disclosure window |
| Annex II §5 | Information on compliance processes | This document + docs/RUNBOOK.md |
| Annex II §6 | Technical specifications used for development | docs/research/01-compliance-signal-inventory.md |
| Article 13(2) | Risk assessment must be documented | Methodology + diary |
| Article 13(15) | Updates available for the lifetime of the product | Release cadence per package (governance.html) |
| Annex I Part II §1 | Vulnerability handling — publicly available info | OSV cross-check; advisory rollup on security.html |
| Annex I Part II §3 | Apply effective regular tests / reviews | CI matrix + Security workflow conclusions |
| Annex I Part II §5 | Establish disclosure policy | SECURITY.md present + disclosure-window field |
| Annex I Part II §7 | Security updates without delay | Tag → PyPI publish latency |
CVE / advisory feed sources
The Security page’s “0 open advisories” claim is only meaningful when the feed sources are named. The dashboard cross-references:
- OSV.dev —
https://api.osv.dev/v1/query, keyed by PURL (pkg:pypi/<name>@<version>,pkg:cargo/<crate>@<version>). Cursor: live query at sweep time; no snapshotting. - GitHub Security Advisories —
gh api /advisoriesfiltered by affected ecosystem and package name. Cursor: live; deduplicated against OSV using the GHSA ID.
Both feeds are queried during the 4-hour Security cron. A package clears the “0 advisories” bar only when both feeds return empty for its declared version. The OSV PURL form is canonical; if a package publishes under a non-PyPI distribution name the lookup may miss — a known gap, tracked in docs/research/08-followup.md.
Anti-patterns we explicitly avoid
The dashboard does not surface:
- Raw test coverage percentage as a top-line metric. A 90%-covered abandoned project is worse than a 60%-covered actively-maintained one. Coverage trend is shown one click down; coverage absolute is not.
- “Signed commits” as a binary green check. Without policy enforcement, signed-commit ratio is decorative. We show the ratio paired with the branch-protection-required-signature state, or omit.
- SBOM presence without dependency edges. A component list with no
dependencies[]graph is a manifest, not an SBOM. We visibly downgrade these. - A composite “compliance score out of 100”. Buyers see through it; it incentivizes gaming cheap signals (badges, file presence) at the cost of expensive ones (review discipline, attestation hygiene).
- Maintainer-identity signals (country, employer, real name). Encourages discrimination, generates false positives on pseudonymous-but-trusted maintainers, adds zero supply-chain integrity.
- GitHub stars. A popularity metric, not a trust metric.
Where the maintainer-identity line is drawn
The dashboard collects per-commit aggregates — DCO sign-off rate, verified-commit ratio, conventional-commits rate, unique-committer count — without collecting per-commit identities. The distinction is deliberate:
- Ratios over 90 days measure repository discipline. They answer “was the policy followed” without naming who followed or didn’t. These are surfaced on governance.html.
- Per-commit identity would attach a name (or pseudonym) to an approve/sign-off action. The dashboard collects only the count of unique committers, not the committer identities themselves.
- Cardinality is the operational signal we care about for bus factor: a
unique_committers_90d == 1is a procurement-relevant fact whether the one committer is anonymous or named.
In particular, verified_commit_ratio_90d is a ratio of GitHub’s verification.verified boolean over commits-in-window — not an identity claim. It is rendered always paired with the branch-protection state, because without required-signed-commits enforcement (a branch-protection API field, not an identity check) the ratio is decorative.
Pill semantics
Every pill on the dashboard is one of four states. The state is decided from the JSON snapshot using the rules below — there is no fall-through to a default colour. Gray means “no signal yet,” never “assumed green.” Each row anchors the per-pill link on the index and per-package pages.
| Signal | Snapshot path | Green | Amber | Red | Gray |
|---|---|---|---|---|---|
| Build | modules[].ci.workflow_conclusion |
success |
cancelled |
failure, timed_out, action_required |
absent / unknown |
| Tests | modules[].ci.workflow_conclusion |
success |
cancelled |
failure, timed_out, action_required |
absent / unknown |
| Security | modules[].security.workflow_conclusion |
success |
cancelled |
failure, timed_out, action_required |
absent / unknown |
| Signing | modules[].supply_chain.attestations.{verified_count,total_count} |
verified_count == total_count > 0 |
0 < verified_count < total_count |
total_count > 0 and verified_count == 0 |
total_count absent (no PyPI release yet) |
| Releases tile (index) | modules[].supply_chain.attestations.pep740_present, .publisher_kind |
Tile shows two ratios, not a pill. Top row: count of modules with pep740_present == true AND verified_count == total_count > 0. Bottom row: count of modules with non-null publisher_kind (Trusted Publisher proxy until the snapshot exposes a dedicated publisher_state field). |
|||
| License | modules[].supply_chain.sbom.{strong_copyleft,weak_copyleft,unknown_license,components_count} + policy/license-allowlist.yml |
components_count > 0, no strong copyleft, every weak / unknown component covered by an allowlist entry or a documented parser gap |
at least one weak-copyleft or unknown-license component is NOT covered by policy | any strong-copyleft component is present, OR an allowlist entry overlaps a hard-blocked license class | components_count absent (no SBOM yet) |
| Deps | modules[].supply_chain.sbom.{strong_copyleft,unknown_license} + policy |
SBOM present, no strong copyleft, no unknown licenses uncovered by a parser-gap entry | uncovered unknown licenses remain | any strong-copyleft component | no SBOM yet |
| Branch prot. (index column + rollup tile) | modules[].governance.branch_protection_enabled |
true (required reviews + status checks active on main) |
false — acceptable on a pre-alpha repo, surfaced as “Off (alpha)” rather than hidden |
(not used today; promote to red once a repo is post-GA and still off) | null (call returned without the protection block, typically permissions) |
| Repositories (headline) | len(modules) |
Cardinality, not a ratio. Always rendered as a count; never coloured. | |||
| Commits (headline) | sum(modules[].governance.commits_90d) over the 90-day window |
Cardinality. Rendered as — when no module has the signal yet; never zero-filled. |
|||
| Tests (headline) | sum(modules[].code_metrics.{python,rust}.tests_count) |
Cardinality of test functions across the org — Python def test_* / async def test_* in files pytest collects (a tests/ dir or test_*.py / *_test.py name), and Rust #[test] / #[tokio::test] / #[rstest] / #[test_case] attributes (counted inline across .rs). Counted statically from the source — the dashboard never executes the suites — so a parametrized row (@pytest.mark.parametrize, #[rstest] cases) counts once; this is a deliberate, honest lower bound on test cases. Rendered — when no clone is inspectable; never zero-filled. |
|||
| CI test legs (headline) | sum over modules of count(ci.matrix[] where name ~ "Test (<os> / Python <ver>)") |
Cardinality: number of (os, python) CI test legs across the org — testing breadth, not test count. Bounded above by Supported platforms × repos. |
|||
| Supported platforms (headline) | card(union of (os, python) cells across every modules[].ci.matrix) |
Cardinality of the union; deduplicated across packages. | |||
| Lines of code (headline) | sum(modules[].code_metrics.{python,rust}.{src_loc,tests_loc}) |
Cardinality. Counts non-blank, non-comment lines (Python + Rust). Excludes .venv, target, dist, build, __pycache__, _site, lockfiles. Reported separately for Python source, Python tests, Rust source, Rust tests. |
|||
A pill state is a property of the snapshot — it is not an opinion of the renderer. To dispute a pill state, dispute the underlying field in api/v1/snapshot.json.
Verifying a claim independently
Every dashboard card has a Verify link. Following it should let you reproduce the underlying lookup without any privileged access:
| Card | Verify link points at |
|---|---|
| Latest PyPI version | https://pypi.org/project/<pkg>/<version>/ |
| Wheel platform matrix | https://pypi.org/pypi/<pkg>/<version>/json (urls[]) |
| PEP 740 attestation | https://pypi.org/simple/<pkg>/ (Accept: application/vnd.pypi.simple.v1+json) → files[i].provenance URL |
| Sigstore Rekor entry | https://rekor.sigstore.dev/api/v1/log/entries?logIndex=<N> |
| CI run | https://github.com/273v/<pkg>/actions/runs/<run-id> |
| Security scan | Same as above for the Security workflow |
| Open advisories | https://api.osv.dev/v1/query with the package PURL |
| SBOM | data/sbom/<pkg>-<version>.cdx.json in this repo |
| Branch protection | gh api repos/273v/<pkg>/branches/main/protection |
| Disclosure policy | https://github.com/273v/<pkg>/blob/main/SECURITY.md |
If a Verify link doesn’t reproduce, the dashboard claim is wrong and should be reported.
Repository-level Actions settings such as fork-approval policy, default workflow-token permissions, SHA-pinning enforcement, and secret scanning are intentionally absent from this table today. They are important controls, but their GitHub API evidence requires maintainer or admin authority. We audit them operationally and avoid rendering a public green check until the evidence can be reproduced by a third party.
Limits and honest gaps
Things this dashboard does not prove:
- Reproducible builds. We surface SLSA build-level claims but do not independently rebuild artifacts. A separate effort is required to attest reproducibility.
- Per-commit author identity beyond GitHub’s
verification.verifiedstate. GitHub’s GPG-key claim is what we report; deeper identity proofing (e.g., DCO sign-off matching a known steward) is not done. - Source-of-truth for transitive licenses. We rely on the published license metadata of each dep. If a published license is wrong, our aggregation is wrong. We do not legally audit license claims.
- Operational security of the maintainer machines. A dashboard can prove the artifact came from a specific workflow; it cannot prove the workflow’s secrets weren’t exfiltrated upstream. That’s outside the scope of any public dashboard.
- OpenSSF Scorecard per-check results. The Scorecard workflows are installed, but the dashboard does not yet ingest the per-check SARIF/JSON output. Tracked as
R8in docs/research/08-followup.md. A buyer can runscorecard --repo=273v/kaos-compliance --format=jsonlocally to fill the gap today. - SLSA Build Level not formally attested. Each package’s PEP 740 attestation + PyPI Trusted Publisher state puts it effectively at SLSA Build L2. The supply-chain page surfaces an implied-level pill per package; we do not emit a formal
slsa.build.levelclaim until the SBOM dep-graph (F9) and the Scorecard ingest (F18) land. - CISA SBOM Minimum Elements: relationships gap. The seven required elements (author, supplier, name, version, unique ID, relationships, timestamp) are visible per package on supply-chain.html; relationships is flagged yellow across the org because the SBOM emitter doesn’t yet build the dependency edge graph.
These gaps are intentionally surfaced so a buyer knows what they still need to ask for in a vendor questionnaire.
Snapshot integrity + schema
The HTML pages above are one rendering of a JSON document
published at api/v1/snapshot.json. Two artifacts
accompany that JSON so a third party can verify both its
shape and its provenance:
-
JSON Schema (Draft 2020-12) at
api/v1/snapshot.schema.json. Programmatic consumers should validate the snapshot against the schema before trusting any field; the schema is derived deterministically from the dataclasses incollector/snapshot.pyso any drift is caught at render time. -
Keyless sigstore signature at
api/v1/snapshot.sig— a DSSE bundle minted by thesweep.ymlworkflow via GitHub OIDC. Verification requires cosign and asserts the bundle was issued by THIS workflow on THIS repository before the JSON is trusted. The full verification recipe lives in docs/EVIDENCE.md.
The data model itself — field-by-field meaning and units —
is documented in
docs/DATA-MODEL.md.
Methodology versioning
This document follows semver. The bump rules are tight on purpose: the property that makes the dashboard credible — that an external reviewer can pin a snapshot to a methodology version and reproduce the assessment — fails the first time the green threshold for a signal is silently “clarified” without bumping the version.
| Change | Bump |
|---|---|
| Add a new signal that previously didn’t exist (new column, new pill) | minor |
| Add a new framework anchor (e.g. ingest Scorecard results) | minor |
| Remove a signal | major |
| Change the green / amber / red threshold for an existing signal | major |
| Change the snapshot-path source for an existing signal | major |
| Change a default-fallback (e.g. “gray means” wording) | major |
| Rewrite an existing section without changing meaning | patch |
| Add an honest-gap entry under “Limits and honest gaps” | patch |
| Add or expand the per-framework mapping tables | patch |
| Typos, link fixes, formatting | patch |
A bump requires an entry in CHANGELOG.md with the version, the change, the affected signals, and the rationale. The published snapshot at api/v1/snapshot.json carries schema_version separately — that field bumps independently when the JSON shape changes; the methodology version below governs the meaning of the signals, not the wire format.
Trend lines + baselines
Each row of the package grid carries up to four status sparklines — one for build, tests, security, and PEP 740 attestation. Activity surfaces also use inline SVG for commits, releases, and code-surface counters. There is no JavaScript and no client-side state. What you’re seeing is the rolling 90-day history of the underlying signal, sampled once per UTC day:
- Resolution. Daily. Each x-axis position is one UTC day; multiple sweeps on the same day collapse to the last write.
- Encoding. Booleans (build pass / tests pass / security pass / attestation present) are projected to the top of the band when true and the bottom when false. The right-most marker is a solid dot so the eye lands on the current state. Days where the signal is missing render as a small open dot — visible gap, not silent.
- First-deploy behavior. The first time this dashboard ever ran, it had exactly one day of history. The sparkline collapses to a single dot and the cell carries an
accumulating historylabel so the absence of a line is not mistaken for a flat signal. We never extrapolate, smooth, or backfill historical data. - Baseline. The pill hover-title carries a delta arrow comparing the current state to the previous sweep (the most recent UTC day with data before today). ↑ for an improvement, ↓ for a regression, ≡ for no change. We do not implement industry or peer-package baselines; those rolls are a separate compliance question and have their own follow-up entry.
- Source. The whole rolling history is published at
api/v1/history.jsonand the per-day raw files live underapi/v1/history/YYYY-MM-DD.json. Activity counters includecommits_90d,releases_90d,loc_total,src_loc,tests_loc, andfiles_total. The format is documented atapi/v1/.
Per-package detail pages render a separate “Changes since last sweep” section above the evidence list. That section uses the same previous-sweep baseline and lists every signal that flipped, with a timestamp pair. Packages with no prior sweep show no prior sweep yet.
Methodology version 1.2.0 — last updated 2026-06-01. Source: docs/METHODOLOGY.md.