From 3c8f5feadbb83d4696499956f121fd18127f2766 Mon Sep 17 00:00:00 2001 From: Antonios Voulvoulis Date: Sat, 19 Sep 2026 09:35:12 +0300 Subject: [PATCH] v0.1.0-alpha1 claim boundary, and reproducibility stated per artifact MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ISEDRAF Technical Preview — Linux Host Assurance & Evidence Engine. The README now states what is claimed and, explicitly, what is not: no GA or production readiness, not all Linux distributions, no ARM64 certification, no organizational compliance, no CIS or ISO 27001 mapping, no NIS2/DORA, no PDF reports, no privileged production Mode A. A technical preview is a thing you can install, inspect and verify. It is not a thing to run a compliance programme on. Reproducibility is stated per artifact, because the three are not interchangeable: source tarball bit-for-bit reproducible across tested builders .deb bit-for-bit reproducible across tested builders .rpm package semantics and payload reproducible; NOT claimed byte-for-byte across rpm toolchain versions rpm 4 writes a gzip payload and rpm 6 writes zstd. That is toolchain variation, not a different ISEDRAF payload — BUILDTIME was identical on both builders, so SOURCE_DATE_EPOCH works across toolchains. No 'Reproducible Builds' badge will appear without a qualifier naming which formats actually have byte-identical proof. The repository topology is recorded in the decisions register: this public repository receives a sanitized export, private engineering history is never exported, and a release or tag is a separate owner-authorized act. Implements: D-86, D-88, D-90, D-110, GOV-001 Assisted-by: Claude (claim alignment) --- README.md | 46 +++++++++++++++++- docs/CURRENT_STATE.md | 5 +- docs/development/GOVERNANCE_GAPS.md | 73 ++++++++++++++++++++++++++++- scripts/ci/falsifiable_lib.sh | 25 ++++++++++ scripts/ci/project_status.json | 5 +- 5 files changed, 148 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0e7aa47..e91710c 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,32 @@ ISEDRAF is designed around: - declared / resolved / active state where applicable; - operator and auditor views derived from the same evidence. +## v0.1.0-alpha1 — what is claimed, and what is not + +**ISEDRAF Technical Preview — Linux Host Assurance & Evidence Engine.** + +Implemented and observed: + +| | | +|---|---| +| deterministic host identity | immutable identity evidence | +| hash-chained ledger | independent verification | +| host inventory | JSON + Markdown reports | +| DEB / RPM / source packages | Python 3.6+ production-code compatibility | +| validated Linux distribution families | CodeQL | +| Scorecard execution | SPDX SBOM | +| artifact attestations | tamper-verification | +| falsifiable internal gates | | + +**Not yet claimed** — each of these is absent on purpose, and none is coming in this release: + +`GA / production readiness` · `all Linux distributions` · `ARM64 certification` · +`organizational compliance` · `CIS mapping` · `ISO 27001 mapping` · `NIS2 / DORA compliance` · +`PDF reports` · `privileged production Mode A` + +A technical preview is a thing you can install, inspect and verify. It is not a thing to run a +compliance programme on. + ## Framework mappings **None exist, none are bundled, and none are licensed.** No third-party control text, identifier set @@ -160,7 +186,7 @@ What is true today, and verifiable from this repository: | OpenSSF Scorecard runs against this repository; results go to code scanning, and **no score is published or displayed** | [`scorecard.yml`](.github/workflows/scorecard.yml) | | Release artifacts carry build provenance and an SBOM attestation, and the attestation has been **observed to refuse a forgery** — each artifact verifies, a copy with one flipped byte does not | [`check_attestation_falsifiable.sh`](scripts/ci/check_attestation_falsifiable.sh) | | Packaging metadata is checked as text, on any machine, before a commit — a package that builds on the author's distribution is not a package | `make check-packaging` | -| The source tarball and the `.deb` rebuild **bit-identically on a different distribution**, and the locally rebuilt files verify against the attestation GitHub produced | `make check-reproducible`, `make check-deb-ordering`, [`KGG-016`](docs/development/GOVERNANCE_GAPS.md) | +| The source tarball and the `.deb` rebuild **bit-for-bit on a different distribution**, and the locally rebuilt files verify against the attestation GitHub produced. The `.rpm` is **not** claimed byte-identical across rpm toolchain versions — rpm 4 and rpm 6 choose different payload compression, which is toolchain variation and not a different ISEDRAF payload | `make check-reproducible`, `make check-deb-ordering`, [`KGG-016`](docs/development/GOVERNANCE_GAPS.md) | | A machine-readable SBOM describes each artifact, generated from the **final package** and checked against it — for the RPM, against `rpm`'s own recorded per-file digests | `scripts/ci/generate_sbom.py`, `make check-sbom` | | Every tracked file carries a licence statement, and third-party framework content is deny-by-default: unknown licensing state means not distributable | `make check-licensing`, [`FRAMEWORK_SOURCE_REGISTRY`](docs/licensing/FRAMEWORK_SOURCE_REGISTRY.md) | | Controls that are intended but **not** in force are written down, not glossed over | [`docs/development/GOVERNANCE_GAPS.md`](docs/development/GOVERNANCE_GAPS.md) | @@ -199,6 +225,24 @@ jobs is conditional on the repository being public, a skipped job reports **gree one of them is paired with a `guard` job that **fails** if the analysis was due and did not run. `docs/CURRENT_STATE.md` still understands `WRITTEN_NEVER_RUN` as a status, and will use it again. +### What may be said about reproducibility + +Three artifacts, three different strengths of claim, and they are not interchangeable: + +| Artifact | Claim | +|---|---| +| source tarball | **bit-for-bit reproducible across tested builders** — observed | +| `.deb` | **bit-for-bit reproducible across tested builders** — observed | +| `.rpm` | **package semantics and payload reproducible.** *Not* claimed byte-for-byte reproducible across rpm toolchain versions | + +Tested builders: Fedora 44 / btrfs / rpm 6.0.2 and `ubuntu-latest` / ext4 / rpm 4.18.2. `BUILDTIME` +was identical on both, so `SOURCE_DATE_EPOCH` taken from the commit works across toolchains; the +`.rpm` bytes differ because rpm 6 writes a **zstd** payload where rpm 4 writes **gzip**. + +**No `Reproducible Builds ✓` badge will be shown**, now or later, without a qualifier naming which +formats actually have byte-identical proof. A green tick beside three artifacts when two of them +qualify is the kind of claim this project exists not to make. + ### The rule Every green mark is clickable and leads to the evidence behind it — a workflow run, a release provenance diff --git a/docs/CURRENT_STATE.md b/docs/CURRENT_STATE.md index 13ed538..8135288 100644 --- a/docs/CURRENT_STATE.md +++ b/docs/CURRENT_STATE.md @@ -18,7 +18,7 @@ version drifted until it announced that no product code existed while three comm | | | |---|---| | Project stage | **TECHNICAL_PREVIEW_CANDIDATE** | -| Public release | **NOT_AUTHORIZED** | +| Public release | **AWAITING_OWNER_AUTHORIZATION** | | Production Python floor | 3.6 | | Tooling Python floor | 3.9 | | Execution model | unprivileged, ISEDRAF_STATE_ROOT required | @@ -114,4 +114,5 @@ Not asserted. Each number is counted at generation time. The public repository is **not** authorized while any of these is open. -- no GitHub Release or tag is published; publication is a separate owner decision +- GitGuardian has access to the PRIVATE itcmsgr/isedraf-dev - measured with a positive control, not assumed. Repository access must be restricted by the owner in the GitHub UI; the credentials available to CI cannot modify an App installation +- no GitHub Release or tag is published; publication is a separate owner-authorized act (D-110) diff --git a/docs/development/GOVERNANCE_GAPS.md b/docs/development/GOVERNANCE_GAPS.md index fdd8fc6..3fc9ac5 100644 --- a/docs/development/GOVERNANCE_GAPS.md +++ b/docs/development/GOVERNANCE_GAPS.md @@ -58,7 +58,53 @@ require the four checks, bypass limited to owner emergency use. ## KGG-002 — GitHub secret scanning and push protection unavailable -**State:** `NOT_AVAILABLE_CURRENT_PLAN` +**State:** `NOT_AVAILABLE_CURRENT_PLAN` · **an external scanner was found inside the trust surface, see below** + +### An unapproved external service was observing both repositories + +Found 2026-09-19. A **GitGuardian** GitHub App produced a check on every pull request. Nobody in this +project installed it for ISEDRAF; it arrived through an account-level installation. + +**It was measured, not assumed.** The credentials available here cannot enumerate App installations +(`user/installations` → 403). The first experiment pushed a branch to the private repository, saw no +check, and was **discarded as invalid**: the same push to the *public* repository also produced no +check, so the method could not detect the thing it was looking for. A negative result from a method +with no positive control is not evidence. + +The second experiment opened a pull request in each repository, with the public one as the positive +control: + +| Repository | Visibility | GitGuardian check | +|---|---|---| +| `itcmsgr/isedraf` | public | **yes** — control fires | +| `itcmsgr/isedraf-dev` | **private** | **yes** | + +Both probes were closed and their branches deleted immediately. + +**Declared permissions** (public App manifest, owner `GitGuardian`, app id `46505`): `contents: read`, +and **write** on `checks`, `issues` and `pull_requests`. Scanning happens on the provider's +infrastructure, so repository content leaves GitHub. + +**It cannot block a merge today** — the ruleset requires seven checks and GitGuardian is not one of +them. That is a property of our ruleset, not of the App. + +**Verdict: RESTRICT.** Not because the service behaved badly — every check it produced passed — but +because `isedraf-dev` is the source of truth this project deliberately does not publish, and its +contents were being sent to a third party nobody chose for that purpose. The rule is that an external +service does not appear silently inside the trust surface. + +**Owner action required, and it is a release blocker until done.** An App installation cannot be +modified with the credentials available to this project. GitHub → Settings → Applications → Installed +GitHub Apps → GitGuardian → Configure → *Only select repositories* → remove `itcmsgr/isedraf-dev`. +The account-level installation may stay if it is used elsewhere; only the repository selection needs +to change. + +Keeping it on the **public** repository afterwards is defensible and would be recorded here as +`APPROVED_PUBLIC_ONLY`: nothing leaves that was not already published, and a second independent +scanner beside `check-privacy` — which is our own code checking our own rules — is genuinely useful. + +### The local gate, which is not equivalent + **Mitigation:** a deterministic local secret-pattern gate is added to `make check` and CI at Prompt 04 W0. It is **defense in depth and is not equivalent to GitHub secret scanning** — it matches obvious private-key, @@ -459,3 +505,28 @@ argument *inside a heredoc*, silently disabling the mutation it was meant to gua **The pattern worth keeping:** a gate that cannot run must say so. A gate that silently passes on an absent subject teaches the reader that the subject was checked. + + +## KGG-018 — `CI_EXECUTED_AND_DETECTED` is required for release-critical gates + +**State:** `IMPLEMENTED (as an invariant)` · owner decision 2026-09-19 + +> `local mutation works` **≠** `CI mutation proven` + +For a release-critical gate, `MUTATION_EXECUTED_AND_DETECTED` on a workstation is **not sufficient +evidence**. The injection must also be observed firing in the environment that **builds the release**, +because the release environment is part of the proof. + +This came out of the `ar -D`/`-U` finding, and the finding matters more than the bug it exposed. A +reproducibility injection dropped `ar`'s deterministic flag and fired locally; on `ubuntu-latest` it +passed **silently**, because whether plain `ar rc` is deterministic depends on how the local binutils +was *compiled*. Forcing `U` also fired locally and also passed silently there. Two green injections +that proved nothing, on the exact machine that produces the released artifacts. + +**What is already true:** `make check-falsifiable` runs in CI on every push and a non-firing injection +fails that job, so the second observation does exist for every injection that runs there — and it is +what caught both failures. + +**What this records:** that it *must* exist, and that an environment-dependent mutation is not +evidence until it has been seen to fire where the release is built. New release/build injections +declare which observations they have. The harness is **not** redesigned for this now. diff --git a/scripts/ci/falsifiable_lib.sh b/scripts/ci/falsifiable_lib.sh index f9abb54..ede8194 100644 --- a/scripts/ci/falsifiable_lib.sh +++ b/scripts/ci/falsifiable_lib.sh @@ -29,6 +29,31 @@ # MUTATION_EXECUTED_AND_DETECTED mutation applied, gate failed, gate named the reason -> PASS # MUTATION_EXECUTED_BUT_NOT_DETECTED mutation applied, gate passed -> FAIL # MUTATION_TOOL_CRASHED gate failed but produced no rejection evidence -> FAIL +# +# INVARIANT, owner decision 2026-09-19 (from the `ar -D`/`-U` finding): +# +# local mutation works != CI mutation proven +# +# For a RELEASE-CRITICAL gate, MUTATION_EXECUTED_AND_DETECTED on a workstation is not +# sufficient evidence. The injection must also be observed firing in the environment that +# BUILDS THE RELEASE, because the release environment is part of the proof. +# +# This was learned the hard way and twice in one afternoon. A reproducibility injection +# dropped `ar`'s deterministic flag and fired locally; on ubuntu-latest it passed silently, +# because whether plain `ar rc` is deterministic depends on how the local binutils was +# COMPILED. Forcing `U` also fired locally and also passed silently there. A green +# injection that proves nothing, on the exact machine that produces the artifacts. +# +# New release/build injections SHALL declare which of these they have: +# +# MUTATION_EXECUTED_AND_DETECTED observed firing locally +# CI_EXECUTED_AND_DETECTED observed firing in CI, on the release builder +# +# The harness is not redesigned here; `make check-falsifiable` runs in CI on every push and +# a non-firing injection fails that job, so the second observation exists for every +# injection that runs there. What this records is that it MUST exist, and that an +# environment-dependent mutation is not evidence until it has been seen to fire where the +# release is built. # HARNESS_ERROR the mutation never applied, or the copy failed -> FAIL PASS=0 SKIPPED=0; FAIL=0 diff --git a/scripts/ci/project_status.json b/scripts/ci/project_status.json index 968b2a9..7dc68fe 100644 --- a/scripts/ci/project_status.json +++ b/scripts/ci/project_status.json @@ -1,7 +1,7 @@ { "$comment": "THE single authoritative status registry. docs/CURRENT_STATE.md is GENERATED from this file, and check_docs_truth.py validates documentation claims against it. One source, so a generated page and a gate cannot disagree \u2014 which is exactly how CURRENT_STATE.md came to announce that no product code existed while three commands worked.", "project_stage": "TECHNICAL_PREVIEW_CANDIDATE", - "public_release": "NOT_AUTHORIZED", + "public_release": "AWAITING_OWNER_AUTHORIZATION", "capabilities": { "w1a_evidence_contract": { "status": "CERTIFIED", @@ -206,6 +206,7 @@ "none_certified_because": "reboot stability and version-upgrade stability were not exercised" }, "release_blockers": [ - "no GitHub Release or tag is published; publication is a separate owner decision" + "GitGuardian has access to the PRIVATE itcmsgr/isedraf-dev - measured with a positive control, not assumed. Repository access must be restricted by the owner in the GitHub UI; the credentials available to CI cannot modify an App installation", + "no GitHub Release or tag is published; publication is a separate owner-authorized act (D-110)" ] }