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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,15 @@ repository still gets a decision, never by following the link; no release carrie
- :memo: docs(changelog): stamp the v0.3.0 section after tagging
- :memo: docs(spec): decompose the 2026-08-18 audit Next wave as epic P5-AUD2 (D-152)
- :memo: docs(spec): AUD2-S02 names resolveRunFacts, not the nonexistent loadProviderHosts
- :memo: docs(openspec): amend AUD2-S03 to the shipped pin and log D-153 (SEC-03)

### Fixes
- :bug: docs(decisions): reference the AUD2 spec as a path, not an mkdocs link

### Security
- :lock: fix(cmd): discriminate absent provider declaration from forge failure (REL-03)
- :lock: fix(release): pin cosign signer identity and issuer in install.sh (SEC-03)
- :lock: fix(release): widen the cosign identity pin to the real signer casing (SEC-03)

### Testing
- :white_check_mark: test(cmd): assert the REL-03 error wrap as one contiguous substring
Expand Down
8 changes: 6 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,21 @@ cd dist-verify
sha256sum -c checksums.txt

# 2) Cosign — per-archive bundles (keyless, GitHub Actions OIDC issuer)
# The identity pin is a REGEXP matched case-sensitively: the repository was
# renamed to PlatformRelay/Assent between v0.1.0 and v0.2.0, so v0.1.0's
# certificate says `assent` and v0.2.0+ say `Assent`. `[Aa]` covers both; the
# dots are escaped and the org/repo stays anchored, so a mirror still fails.
ARCHIVE=assent_X.Y.Z_linux_amd64.tar.gz # adjust OS/arch
cosign verify-blob \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp '^https://github.com/PlatformRelay/assent/' \
--certificate-identity-regexp '^https://github\.com/PlatformRelay/[Aa]ssent/' \
--bundle "${ARCHIVE}.sigstore.json" \
"${ARCHIVE}"

# Cosign — checksum manifest (covers archives + SBOMs listed inside)
cosign verify-blob \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp '^https://github.com/PlatformRelay/assent/' \
--certificate-identity-regexp '^https://github\.com/PlatformRelay/[Aa]ssent/' \
--bundle checksums.txt.sigstore.json \
checksums.txt

Expand Down
10 changes: 10 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ tasks:
# which is skipped on pull_request — without this a regression merges green and only
# reds main afterwards. Fast and offline (stubbed `gh`); needs jq.
- task: release-verify-tag-gate-test
# AUD2-S03 (SEC-03): hack/install.sh's cosign signer pin. Offline (stubbed
# `cosign`), and the only gate that reads hack/install.sh's verify path —
# without this line an unpinned `cosign verify-blob` ships silently again,
# and SECURITY.md's published issuer/identity pair can drift away from it.
- task: release-install-cosign-pin-test
# D-124: the AUD-S06 docs truth-lag gates shipped green and invoked by nothing,
# which is the same manual-gate defect they exist to close. Sequential (not a
# `deps:` entry) — go-task runs deps in parallel and readme_smoke_test.sh builds
Expand Down Expand Up @@ -257,6 +262,11 @@ tasks:
cmds:
- bash hack/release/verify_tag_gate_test.sh

release-install-cosign-pin-test:
desc: "AUD2-S03 gate: hack/install.sh pins the cosign signer identity + OIDC issuer, no drift from SECURITY.md (REQ-AUD2-S03-01..05)"
cmds:
- bash hack/release/install_cosign_pin_test.sh

audit-exitgate-test:
desc: "AUD-S18 exit gate: 2026-08-06 audit conditions closed + gates green at the new bar (REQ-AUD-S18-01/02). NOT in `check` — it runs `task check` itself."
cmds:
Expand Down
1 change: 1 addition & 0 deletions docs/decisions/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,4 @@ project/process decisions.
| D-150 | 2026-08-16 | **SONAR-GO-CX-TEST is RESOLVED: adopt a checked-in `sonar-project.properties` excluding `**/*_test.go` from the S3776 cognitive-complexity rule, accepting the loss of SonarCloud Automatic Analysis.** 66 CODE_SMELL issues, concentrated in 34 table-driven Go test files, against a Quality Gate that is otherwise OK (0 bugs/vulns/hotspots) — high cognitive complexity is the idiom in table-driven Go tests, not a defect. Chosen over the UI-only Won't-Fix route (keeps Automatic Analysis, but the suppression is not reviewable in-tree and does not survive a project re-import) and over refactoring the 34 files (churn on passing tests for a non-gating metric). Accepted cost: a scanner step must be added to CI and maintained going forward — Automatic Analysis stops once a `sonar-project.properties` is present. Revert: delete the properties file to restore Automatic Analysis; the 66 smells return. |
| D-151 | 2026-08-16 | **OpenSSF Best Practices project creation is RESOLVED: (a) — the operator will create the `bestpractices.dev` project for `PlatformRelay/assent`.** Unblocks SEC-SC-S02 (evidence page + README badge, once *passing*); SEC-SC-S01 (fuzzing) is not blocked by this and can proceed independently. Operator action only — requires a personal account at bestpractices.dev, not automatable in-tree. Current CII score is 0, which drags the OpenSSF Scorecard (~7); the repo already satisfies most criteria (pinned CodeQL, Dependabot, secret scanning, signed releases, SLSA provenance). Revert: leave CII at 0 indefinitely — SEC-SC-S01 is unaffected either way. |
| D-152 | 2026-08-18 | **The 2026-08-18 audit's "Next (risk reduction)" wave is decomposed as its own epic P5-AUD2 (`openspec/specs/p5-aud2-audit-remediation/spec.md`), five stories, spec-first.** Context: `agent-context/PROJECT-AUDIT-2026-08-18.md` closed both P1 conditions the same day and v0.3.0 shipped, leaving a named but untracked wave — exec-transport trio (REL-01/02/07), REL-03 `ErrNotFound` discrimination, SEC-03 cosign identity pin, TEST-02 mutant. Options considered: **(a)** hand-fix them as loose commits (rejected — AGENTS.md rule 4 is spec-first, and REL-01 is now byte-identical across *three* audits precisely because nothing ever tracked it); **(b)** append them to the existing P5-AUD epic (rejected — that epic's exit gate is CLOSED and reopening it would make "AUD complete" a moving claim); **(c)** a new AUD2 epic keyed to this audit **(chosen)**. Three consequences recorded here so they are not re-litigated: **(1)** AUD2 has **no release-condition story** — the audit had exactly two P1s and both are already closed; **(2)** **WG-S01 is deliberately excluded** — it carries the LGTM governance marker, and GOVERNANCE says such stories are surfaced to the maintainer, which an autonomous loop's decide-and-log does not override; **(3)** AUD2's exit gate is a **`task check` stage**, not a `release-exitgate` step, because that job is `pull_request`-skipped (RELSE-08) and wiring the gate there is how AUD-S18's own stale `CHECK_STAGES` pin survived four merges. A 2026-08-10-keyed AUD2 draft exists only in the local stash `leave-aud2-not-this-epic`, was never committed, and is superseded; its still-open items (F3/F5/F7) stay Later-wave. Revert: delete the spec + backlog section; the four findings return to the audit report untracked. |
| D-153 | 2026-08-19 | **The cosign signer-identity pin published in `SECURITY.md` was WRONG, not merely missing from `hack/install.sh`: the identity regexp is widened to `^https://github\.com/PlatformRelay/[Aa]ssent/` in both files (AUD2-S03 / SEC-03).** Substance first: `SECURITY.md`'s "Verify a tagged release" instructions pinned `--certificate-identity-regexp '^https://github.com/PlatformRelay/assent/'`, and an adopter who followed them on **v0.2.0 or v0.3.0 got a verification FAILURE on a genuine, correctly signed artifact** — the published recipe has been broken for every release since v0.2.0, and the natural reading of that failure is "this release was tampered with". Cause: the repository was renamed `PlatformRelay/assent` → `PlatformRelay/Assent` between v0.1.0 and v0.2.0; the keyless signing certificate's Subject Alternative Name carries GitHub's canonical casing; cosign compiles `--certificate-identity-regexp` as a Go RE2 pattern and matches it **case-sensitively**. Decoded from the published bundles: v0.3.0 and v0.2.0 sign as `https://github.com/PlatformRelay/Assent/.github/workflows/release.yaml@refs/tags/vX.Y.Z`, v0.1.0 as `https://github.com/PlatformRelay/assent/.github/workflows/release.yaml@refs/heads/main`. All three verify under the new value; independently re-confirmed with real cosign against the real v0.3.0 artifact (`Verified OK` under the new pin, exit 1 "expected SAN value to match regex" under the old). Options considered: **(a)** ship the pin byte-identical to the published-but-broken value and file the breakage as a follow-up (rejected — it would land a green gate asserting a guarantee that fails closed on this project's own releases, D-124's defect one level up); **(b)** case-insensitive `(?i)` (rejected — wider than the defect and it silently accepts casings GitHub never issues); **(c)** an explicit `[Aa]` class in both files, dots escaped (chosen). Not widened otherwise: the `^` anchor and the owner/repo scope stand, so another owner, an `assent-mirror` typosquat, another forge and an unescaped-dot host all still fail. Enforcement, per D-128: **one** published truth — `hack/release/install_cosign_pin_test.sh` extracts the pair from `hack/install.sh` and `SECURITY.md`, requires exactly one distinct value per file, reddens on drift, and (this is the assertion that would have caught the defect) matches the pin against the three **real** SANs above, committed as offline fixtures, plus six negatives. Extends D-110 (bundles beside archives) and D-109; the maintainer-path twin at `hack/release/verify-artifacts.sh:124` is still unpinned and is tracked in the backlog, not fixed here. Revert: restore `'^https://github.com/PlatformRelay/assent/'` in both files — which re-breaks verification of v0.2.0 and every later release, so revert only together with a repo rename back to lowercase. |
1 change: 1 addition & 0 deletions hack/audit/exitgate_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ CHECK_STAGES=(
changelog-verify
release-changelog-gate-test
release-verify-tag-gate-test
release-install-cosign-pin-test
docs-gates
lint-depguard-test
lint-workflow-pins-test
Expand Down
18 changes: 17 additions & 1 deletion hack/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,23 @@ done

if [[ -n "$bundle" ]]; then
command -v cosign >/dev/null 2>&1 || die "cosign required to verify ${bundle} but not found on PATH"
cosign verify-blob --bundle "$bundle" "$ARCHIVE" >/dev/null \
# SEC-03 / REQ-AUD2-S03-01: pin the signer. Keyless verify-blob without an
# identity pin accepts ANY Fulcio certificate, so a mirror-swapped archive
# shipped with its own validly signed bundle would verify clean. The issuer and
# identity regexp below are byte-identical to the pair SECURITY.md publishes;
# hack/release/install_cosign_pin_test.sh reddens if the two ever drift apart.
#
# The [Aa] class is not cosmetic: the repository was renamed to PlatformRelay/
# Assent between v0.1.0 and v0.2.0, and the Fulcio SAN carries GitHub's
# canonical casing — v0.1.0's certificate says `assent`, v0.2.0/v0.3.0's say
# `Assent`. cosign matches this regexp case-SENSITIVELY, so a lowercase-only
# pin rejects the project's own current releases. The dots are escaped because
# this is a regexp, not a literal. The pin is still anchored at the org/repo:
# any other owner, or an `assent-mirror`-style typosquat, fails.
cosign verify-blob \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp '^https://github\.com/PlatformRelay/[Aa]ssent/' \
--bundle "$bundle" "$ARCHIVE" >/dev/null \
|| die "cosign verification failed for ${ARCHIVE}"
elif [[ "$REQUIRE_SIGNATURE" -eq 1 ]]; then
die "--require-signature set but no .sigstore.json bundle found beside ${ARCHIVE}"
Expand Down
Loading