Phase log 82: Public trust round: Scorecard, SBOMs, ruleset, Best Practices and Baseline badges
OpenSSF Scorecard workflow, release SBOMs and provenance, pinned Docker bases, coverage floor and Codecov upload, community and policy documents, OpenVEX, protect-main ruleset, docs-only CI skipping, security.txt move, Best Practices passing and Baseline 1 and 2 earned, Baseline 3 at 95 percent.
public trust round
Date: 2026-10-17, after 81-ci-repairs-site-wrap-and-seo-pass-llms-txt-and-badges.md (CI repairs, site wrap and SEO pass). Version
0.55.1 at the end; v0.55.1 is published and is the first release with SBOMs. Everything
below is committed and pushed by the maintainer (commits listed under Current Progress),
except the items in "Not mine and not committed" at the end of this file.
Goal
The maintainer asked whether to adopt Codecov for the repo. That became: get public approval for a secrets manager. We settled on public security evidence (Scorecard, signed and attested releases with SBOMs, policy documents, branch protection) rather than a coverage percentage, then earned the OpenSSF Best Practices "passing" badge and the Baseline series. Mid-way the maintainer added: restyle the badges like their profile README, remove the four Scorecard vulnerability findings, stop README or website pushes from costing a full CI run, and (with "do everything for the full score") the Baseline forms. The goal changed twice: Codecov first became a minor item (the token and badge only), and "full score" turned out to be capped at Baseline 3 = 20 of 21 for a single maintainer.
Current Progress
Commits (all on main, all by the maintainer through the one-liners I handed over):
c4a6d35Scorecard workflow, attest and list release assets (also deleted the trackedunv-cli/examples/__pycache__/*.pyc, which was already staged).4c75cddVitest coverage floor and per-run coverage artifact.8bea3bfCONTRIBUTING, CODE_OF_CONDUCT, issue forms, PR template, Scorecard badge, SECURITY.md links.f61b5d3Docker bases pinned by digest, CodeQL token narrowed, pyc ignored,curl | pythonremoved fromexporters.yml.983d3cfUpdated to 0.55.1 (the maintainer's bump; first release with SBOMs).7853b0bCodecov upload step andcodecov.yml.5a50156osv-scanner.tomland the first badge restyle.ba20844CHANGELOG rewritten with versioned sections 0.42.7 to 0.55.1, Dependabot ignore forageminor bumps.8a1de20Best Practices badge in the README.be4598achangesjob,CI result,paths-ignoreon build.yml and docs.yml,tests/workflows.test.tsguards.1b4c6f8GOVERNANCE, ARCHITECTURE, THREAT_MODEL, SECURITY and CONTRIBUTING policy text.2e58535scripts/gen-vex.py,vex.openvex.json, VEX shipped in releases, README Project section.5f3e39asecurity.txt moved towebsite/security.txt.cc40636and061976aBaseline badge, then restyled.
Files and why:
.github/workflows/scorecard.yml(new): OpenSSF Scorecard (ossf/scorecard-action v2.5.0 pinned by SHA), weekly and on push to main,permissions: read-allat the top,security-events: writeandid-token: writeonly on the job, SARIF upload with the already pinned codeql-actionupload-sarif(v4.38.3 SHA)..github/workflows/build.ymlrelease job: new "Generate SBOMs" step before assembling assets (cargo-cyclonedx 0.5.9 forunv-cli,unv-server,src-tauri;npm sbom --omit devfor the frontend; each output checked withjq -e '.bomFormat == "CycloneDX"'),cp sbom/*.jsonandcp vex.openvex.jsonintorelease-assets/,actions/attest-build-provenance(v4.2.2 SHA) placed BEFORE cosign so.sig/.pemare not attested,attestations: writeadded, release body gainedgh attestation verifyand an SBOM and VEX paragraph. Alsopaths-ignoreon push (README, policy files, licence, screenshots, issue templates,website/**), with the long header comment corrected:paths-ignoreskips a push only when EVERY changed file matches, so a version bump still runs..github/workflows/ci.yml:changesjob (bash classifier, outputsrustandfrontend),needs: changesandif:ontest,lint,frontend, a newci-okjob namedCI result(if: always(), fails on any result other than success or skipped), weeklyschedulesocargo denystill runs without code changes, coverage step (npm run test:coverage), coverage artifact upload, and a Codecov step that runs only when job-levelCODECOV_TOKENis non-empty (job-level because a step's ownenvis not visible to its ownif)..github/workflows/docs.yml:paths-ignorefor policy and community files;website/is deliberately NOT ignored because this workflow deploys the site..github/workflows/codeql.yml:security-events: writemoved from workflow to job..github/workflows/exporters.yml: twocurl | python3pipes becamecurl -o fileplusjq(Scorecard "downloadThenRun").Dockerfile,tools/viewer/Dockerfile:rust:1.90-bookworm@sha256:3914072c...anddebian:bookworm-slim@sha256:7c7b2c96...(index digests fromskopeo inspect --raw)..github/dependabot.yml: docker ecosystem nowdirectories: ["/", "/tools/viewer"];ageminor bumps ignored with the reason (see What Didn't Work).vitest.config.ts: reporterstext, html, lcov, json-summary; thresholds statements 71, branches 60, functions 73, lines 74 (measured 71.85 / 61.53 / 73.63 / 74.24).codecov.yml: informational only; the enforced gate is the Vitest floor.osv-scanner.toml(new): four documented ignores (RUSTSEC-2023-0071 rsa, RUSTSEC-2024-0370 proc-macro-error, RUSTSEC-2026-0173 proc-macro-error2, RUSTSEC-2024-0429 glib). Scorecard honours it.deny.tomlstays the source of truth for cargo-deny.scripts/gen-vex.pyandvex.openvex.json: OpenVEX document (11 statements,not_affected) generated fromdeny.tomlplusosv-scanner.toml; not hand written.GOVERNANCE.md,ARCHITECTURE.md,THREAT_MODEL.md,SECURITY.md,CONTRIBUTING.md: see Decisions.CHANGELOG.md: versioned sections for 0.42.7, 0.43.0, 0.44.x, 0.54.0, 0.54.1, 0.55.0, 0.55.1, plus Unreleased; the Added bullets that sat under Unreleased now sit under 0.54.0.README.md: centered, dark for-the-badge badge block like the profile README (CI, CodeQL, Scorecard, Best Practices, Baseline, Codecov, Release, Downloads, then platforms, Rust, Tauri, licence, website), and a Project links section.website/security.txt(moved fromwebsite/.well-known/).tests/workflows.test.ts: new describe "documentation-only changes do not run the build" (5 tests).book/src/adr/0159-main-is-protected-by-a-ruleset-and-builds-follow-what-changed.md(this session's ADR) and its row inbook/src/SUMMARY.md.~/Code/unenverse-ruleset.json(outside the repo): the ruleset body, applied by the maintainer.~/Code/bestpractices-baseline.ps1(outside the repo, generated): opens three prefilled Baseline forms.
Verification (commands and summary lines):
npm run check(format, lint, typecheck, Vitest): 92 test files, 1,567 tests passed at the start of the session.npx vitest run --coverage: All files 71.85 / 61.53 / 73.63 / 74.24. Withstatements: 99it exited 1 with "Coverage for statements (71.85%) does not meet global threshold (99%)"; reverted. Then exit 0.npx vitest run tests/workflows.test.ts: 19 passed before, 24 passed after the new tests. Mutations checked: aggregator acceptingcancelled,package.jsonadded to the ignore list, therustgate removed fromtesteach produced "1 failed | 23 passed"; restored, 24 passed.- Classifier (bash extracted from ci.yml, run in a scratch git repo with output outside the
repo): README only -> rust=false frontend=false; README + screenshots + issue template
-> false/false; policy docs only -> false/false; website only -> rust=false frontend=true;
website + README -> false/true;
src/a.ts-> true/true; README + source -> true/true;package.jsonbump -> true/true; workflow file,Cargo.toml-> true/true; release gate, dispatch, schedule, zero-sha new branch, unknown base commit -> true/true. - Aggregator (
ci-okrun block,RESULTSenv): "success success success success" exit 0, "success skipped skipped success" exit 0, anyfailureorcancelledexit 1. actionlint .github/workflows/*.yml: clean except pre-existing SC2034 (unused retry loop variablei) inexporters.yml.scorecard --repo=github.com/darthdemono/UnENVerse(remote): 6.3 at the start of the session (Vulnerabilities 6, Pinned-Dependencies 8, Token-Permissions 9, Security-Policy 4, Binary-Artifacts 9); 7.5 after the changes were pushed (Branch-Protection 3, Code-Review 0, Maintained 0, Contributors 0, CII-Best-Practices 5 then passing, Signed-Releases 8). Local--local .run: 7.9; Pinned-Dependencies 10, Token-Permissions 10, Security-Policy 10, Dangerous-Workflow 10; Vulnerabilities went 6 -> 10 afterosv-scanner.tomlwas added.cargo check -p vault-core --lockedafter the ssh-key feature experiment (reverted);cargo test -p vault-core --lib generators: 3 passed;cargo check --workspace --locked --all-targetsfinished clean on the reverted tree.npm run format:check,npm run lint,npm run typecheckclean after each batch.- GitHub state: ruleset
protect-mainactive (id 25308031);CI result: successseen in the main CI run39045900172(all six jobs success); docs deploy succeeded andhttps://unenverse.darthdemono.com/security.txtreturned 200 about 150 s after the merge; release v0.55.1 lists four*.cdx.jsonassets with.sigand.pem. - OpenSSF: Best Practices "passing" earned (project 15623, all five sections full);
project JSON at the end:
badge_percentage_baseline_1: 100,_2: 100,_3: 95,achieved_baseline_1_at 2026-10-17T15:59:12Z,achieved_baseline_2_at 2026-10-17T15:59:53Z,achieved_baseline_3_at: null.
What did not run: the attest-build-provenance step and the Scorecard action have not been
seen to succeed in CI by me (the SBOM commands were run locally; v0.55.1 did publish SBOMs,
so the release job ran). I did not run cargo fuzz. The Codecov upload has not run (no
token). I did not re-run the full npm run preflight after the last docs-only commits.
The bugs that mattered
https://unenverse.darthdemono.com/.well-known/security.txtwas a 404 although the file was in the repo and the site build copied it.actions/upload-pages-artifactdrops dot-folders from the artifact. Impact: the published security contact did not exist, and the OpenSSF controls cite it. Fixed by serving/security.txt(RFC 9116 allows the root), commit5f3e39a. Checked live.- Every push, including a README edit, ran the full CI (about 18 minutes on Windows), a
Build & Release run and a full Doxygen, rustdoc and mdBook rebuild. Fixed by ADR-0159
(
changesjob,paths-ignore). The reason it is not a triggerpaths:filter is the deadlock described there. - Scorecard flagged four RUSTSEC findings as "existing vulnerabilities". Three are build-time or Tauri's GTK3 stack and cannot be removed, one (rsa) is reachable only with a decryption oracle UnENVerse never exposes. They are documented, not fixed.
- The restyle PR (#36) conflicted: the branch had been created from the old badge branch
after that branch had already been rebase-merged, so it carried a commit
mainalready had under a new hash. Fixed bygit switch -C <branch> origin/main; git cherry-pick. - A multi-line JSON body pasted into pwsh as a here-string with an indented
'@fails. Fixed by writing~/Code/unenverse-ruleset.jsonand handinggh api ... --input.
Decisions made
- Public evidence over a coverage number. Codecov alone is the weakest trust signal for a secrets manager. Done instead: Scorecard, signed assets with attestations and SBOMs, policy files, branch protection, Best Practices. Codecov is kept as an optional badge fed by the same lcov, with an enforced Vitest floor that needs no third party. Rejected: Codecov as the quality gate (adds a token and a third-party app to a secrets manager's CI for no safety gain).
- Keep RSA. The SFTP destination (
unv-app/src/destinations.rs) authenticates with RSA keys via russhbest_supported_rsa_hash; disabling russh'srsafeature would break RSA key SFTP and still leave three advisories. Rejected: droppingssh-key'scryptofeature invault-core(it only generates ed25519), which looked like the fix but does nothing because russh enablesssh-key/rsaitself. - Ignore with reasons rather than hide.
osv-scanner.tomlcarries one reason per advisory, mirrorsdeny.toml, andvex.openvex.jsonis generated from both so the three cannot drift. Rejected: patching a fork ofrsawith a bumped version number (supply chain risk, dishonest), and editing Cargo.lock by hand (cargo re-adds the entry). - Ruleset, not classic branch protection, with
bypass_mode: pull_requestfor the admin, 0 required approvals, required checkCI result, CodeQL gate medium or higher (ADR-0159). Rejected: requiring approvals (no second human), requiring code-owner review (GitHub forbids self approval, it would block every merge), strict up-to-date branches and no admin bypass (friction with no Scorecard gain: the next tier needs a reviewer). CI resultis the single required check so a skipped matrix job cannot leave a required context pending.- security.txt at the site root. Rejected: a custom Pages artifact tar to keep
.well-known(more machinery for the same RFC-compliant result). - Baseline answers. All Baseline 1 and 2 controls Met; Baseline 3 all Met except OSPS-QA-07.01 (Unmet with an honest note). Rejected: a bot or second personal account as the approver (the control says non-author human, it would be a false record). OSPS-QA-04.01 and QA-04.02 are N/A (single repository).
- The Baseline form was filled with prefilled-link URLs, built by a script from one
answer table and opened with
xdg-open, not typed by a browser agent. - The baseline README badge is a shields dynamic-JSON badge reading
badge_percentage_baseline_2from the project JSON, because the project's own SVG cannot be styled. The label says "level 2" by hand. - Policy thresholds are drafts for the maintainer (medium or higher fixed or documented before the next release; latest release only is supported; 7 day acknowledgement). They are commitments in a public file, so the maintainer owns them.
- Releases stay manual. A release needs a version bump and an absent
v<version>tag. No automatic version bump was built (CI committing to main fights the ruleset). - ADR written: ADR-0159.
What Worked / What Didn't Work
Worked: reading Scorecard's per-check details to find exactly which lines to fix;
skopeo inspect --raw | sha256sum for index digests without pulling images; the
osv-scanner.toml file for Scorecard (honoured, 6 -> 10); testing the bash classifier and
the aggregator in a scratch repo before committing; mutation-checking the new workflow
tests; prefilled-link URLs for the 64 Baseline controls (the maintainer confirmed the
badges were earned).
Did not work:
cargo update -p ssh-keyand dropping thecryptofeature: no effect on the lockfile, russh keepsssh-key/rsa. Reverted.- Upgrading
ageto 0.12.1 to clear proc-macro-error2: age 0.12 pullsml-kem 0.2.1, which does not compile againstkem 0.3.0(required by russh 0.64.1, via ml-kem 0.3.2). Forcingkemto0.3.0-pre.0was refused by cargo. Reverted; Dependabot PR #28 would have failed, soageminor bumps are ignored with a reason. Revisit when age or ml-kem agree with russh. scorecard --local .countsnode_modules/binaries as Binary-Artifacts 0; use the remote run.- A test harness that wrote its output file inside the scratch repo got it committed by
git add -Aand corrupted the first classifier run; output now goes outside the repo. actions/upload-artifact/Pages: dot-folders dropped (bug 1).- The first badge restyle branch (bug 4).
Next Steps
Only the maintainer can do:
gh secret set CODECOV_TOKEN --repo darthdemono/UnENVerse, then rotate the token in Codecov (it was pasted into a chat), then confirm the "Send coverage to Codecov" step ran (not skipped) on a main run. Until then the coverage badge reads "unknown".- Turn on Dependabot security updates (the repo setting reads "disabled").
- Triage the open Dependabot PRs (#33, #31, #30, #29, #25 vite 8, #20 cosign-installer 4,
#17, #16, #15, #14, #13, #4 typescript 7); #28 (age) was closed. Do not merge vite 8,
typescript 7 or cosign-installer 4 without a full
npm run preflightand a look at the release job. - Baseline 3 stays at 95 percent until a second human has write access; then set the
ruleset to 1 required approval, remove the admin bypass or accept the weaker claim,
add the person to
GOVERNANCE.md, and flip OSPS-QA-07.01. - Decide on dynamic analysis as a release gate (
cargo fuzz run60 s per target inbuild.yml); that would flip the Best Practicesdynamic_analysisanswer from Unmet to Met.
Doable by an agent next:
- After the next version bump, confirm the release has
vex.openvex.json, the four SBOMs and a build provenance attestation (gh attestation verify <asset> --repo darthdemono/UnENVerse); the provenance step has not been seen working yet. - When Baseline 3 is reached, change the README badge from
badge_percentage_baseline_2and "level 2" to level 3. - If the maintainer wants it, skip the Doxygen and rustdoc steps for
website/-only pushes indocs.yml. - Re-check
deny.tomlandosv-scanner.tomlwhenever Tauri,ageorrusshmove; thenpython3 -I scripts/gen-vex.pyto regenerate the VEX file.
Notes for whoever picks this up
- Main is protected: no direct push, not even by the maintainer. Commit one-liners must use
git fetch; git switch -c <topic> origin/main; git add -- <paths>; git commit -m '<one sentence>'; git push -u origin HEAD; gh pr create --fill; gh pr merge --rebase --admin(--autowaits for CI instead). Starting a branch from a stale local branch is what produced PR #36's conflict. Never run commit or push yourself. - Never put
paths:on ci.yml'spull_requesttrigger. The required check is the job namedCI result; renaming that job silently blocks every PR. website/changes redeploy the site through docs.yml and never release; a release needsnpm run version <x.y.z>and no existing tag.website/.well-known/must not be recreated: Pages drops dot-folders.- The policy files (
SECURITY.md,GOVERNANCE.md, thresholds, support window) are public commitments; edit them only on the maintainer's word. - The Baseline and Best Practices answers cite file paths and URLs in this repository. If a
cited file moves (
SECURITY.md,CONTRIBUTING.md,GOVERNANCE.md,THREAT_MODEL.md,ARCHITECTURE.md,vex.openvex.json), update the form text too. - The bypass is a pull-request bypass only; the ruleset can be removed with
gh api repos/darthdemono/UnENVerse/rulesets/25308031 --method DELETE. - MCP memory (entity "UnENVerse (EnvVault) working rules and lessons") and the git and process-lesson entities were updated this session; they hold the ruleset id, the CI design and the open items. No token or secret was stored anywhere.
Not mine and not committed
During the session the working tree gained edits I did not make: src/ts/bundle-import.ts,
src/ts/cookies.ts, src/ts/copy-profile.ts, src/ts/stack.ts, src/ts/totp.ts,
tests/uid-pane.test.ts and new tests/coverage-*.test.ts files (a coverage-raising effort
from another process or the maintainer). Treat them as that work's own; they are not part of
this handoff and nothing here depends on them. Earlier in the session
unv-server/src/lib.rs (the health-stall test) also changed under me; it has since been
committed as 80e3f5a.