Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 45 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) |
Expand Down Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions docs/CURRENT_STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down Expand Up @@ -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)
73 changes: 72 additions & 1 deletion docs/development/GOVERNANCE_GAPS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
25 changes: 25 additions & 0 deletions scripts/ci/falsifiable_lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions scripts/ci/project_status.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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)"
]
}
Loading