security: scan the supply chain, publish an SBOM, and type-check the build (Phase 2) - #322
Conversation
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
There was a problem hiding this comment.
Pull request overview
Adds Phase 2 supply-chain security controls, release SBOM generation, and production build type-checking.
Changes:
- Adds secret, dependency, and container-image scanning workflows.
- Generates, attests, and requires a CycloneDX release SBOM.
- Enables Next.js build type-checking with policy tests and documentation.
Reviewed changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
.github/workflows/security-scan.yml |
Adds supply-chain scanning jobs. |
.github/workflows/release-artifacts.yml |
Adds the release SBOM gate. |
.gitleaks.toml |
Configures secret-scan allowlists. |
.trivyignore.yaml |
Defines vulnerability-suppression policy. |
.gitignore |
Ignores local scanner outputs. |
next.config.ts |
Enables build-time type-checking. |
SECURITY.md |
Documents supply-chain controls and SBOMs. |
CONTRIBUTING.md |
Documents local scanner usage. |
docs/BACKLOG.md |
Records deferred security work. |
tests/unit/gitleaks-config.test.ts |
Tests Gitleaks policy invariants. |
tests/unit/trivyignore-policy.test.ts |
Tests suppression requirements. |
tests/unit/security-scan-workflow.test.ts |
Tests scan-workflow invariants. |
tests/unit/release-sbom.test.ts |
Tests SBOM release wiring. |
tests/unit/release-provenance.test.ts |
Requires SBOM attestation. |
tests/unit/next-config-typecheck.test.ts |
Guards build type-checking. |
Suppressed comments (3)
.gitleaks.toml:44
- This regex allows every future
StrongPassvalue followed by digits anywhere in the repository, rather than only the documented examples that produced the historical findings. Enumerate the exact known literals so a newly committed credential such asStrongPass999is still reported.
regexes = ['''^StrongPass\d+$''']
.gitleaks.toml:53
- Scoping by file still hides any real private key later committed to
ConnectionModal.tsx; it does not limit the suppression to the two placeholder strings. Remove the PEM-shaped placeholder match or suppress the exact extracted placeholder values instead.
paths = ['''^src/components/ConnectionModal\.tsx$''']
.gitleaks.toml:62
- This path exemption hides any future real
curl -ucredential in the compose file, not only the intended Compose-variable placeholder. Narrow the suppression to the exact known finding (for example with a match-level regex or fingerprint) so other basic-auth credentials remain reportable.
paths = ['''^database-compose\.yml$''']
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 15 changed files in this pull request and generated no new comments.
Suppressed comments (9)
.gitleaks.toml:34
- This path allowlist suppresses the
generic-api-key,jwt, andprivate-keyrules for every future file undertests/. A real non-provider JWT, private key, or generic credential accidentally committed as a fixture would therefore pass the new blocking scan. Scope these exceptions to the exact 13 historical fabricated values/locations rather than the entire test tree, and regression-test with a generic secret as well as a provider-specific token.
targetRules = ["generic-api-key", "jwt", "private-key"]
paths = ['''^tests/''']
.gitleaks.toml:53
- Allowlisting this whole source file for
private-keymeans a real key later pasted intoConnectionModal.tsxis invisible to the blocking scan. Match only the two placeholder findings (by exact value/line) so unrelated private-key findings in this file remain actionable.
targetRules = ["private-key"]
paths = ['''^src/components/ConnectionModal\.tsx$''']
.gitleaks.toml:62
- This exempts every future
curl-auth-userfinding indatabase-compose.yml, not only the Compose interpolation described above it. A newly hard-codedcurl -u user:passwordcredential in this file would pass. Allowlist the exact interpolation value instead of the whole file.
targetRules = ["curl-auth-user"]
paths = ['''^database-compose\.yml$''']
.github/workflows/security-scan.yml:108
--no-mergesdrops conflict-resolution additions made in a merge commit. Such content can exist in neither parent, so this removes the only patch containing it whilecommit_countcan remain nonzero from other commits; Gitleaks then reports clean. Preserve combined merge diffs (or add a current-tree scan) while avoiding the duplicated parent-side changes this comment is concerned about, and update the workflow guard test accordingly.
range="--no-merges $BASE_SHA..$HEAD_SHA"
SECURITY.md:209
- The text promises regeneration from an immutable digest, but the command uses a mutable tag. Retagging can make this produce an SBOM for different bytes than the intended release. Show a digest-qualified reference (
ghcr.io/libredb/libredb-studio@sha256:…) instead.
trivy image --format cyclonedx --scanners license \
--output libredb-studio-image.cdx.json \
ghcr.io/libredb/libredb-studio:0.9.67
.gitleaks.toml:45
^StrongPass\d+$is a pattern family, not an allowlist of the historical examples: it silently suppresses any futureStrongPassplus digits credential in any file. Enumerate the exact fabricated literals found by the sweep so a new value still fails the scan.
targetRules = ["generic-api-key"]
regexes = ['''^StrongPass\d+$''']
regexTarget = "secret"
.github/workflows/security-scan.yml:445
- The vulnerability report and image SBOM resolve mutable
:latestin separate Trivy invocations. If a release retagslatestbetween them, this run uploads a report and SBOM for different images and records neither shared identity. Resolvelatestto one digest once, then pass that digest-qualified reference to both scans (and include it in the summary/artifact metadata).
"$TRIVY_IMAGE" image \
--scanners vuln \
--format json \
--output /out/image.json \
ghcr.io/libredb/libredb-studio:latest
.github/workflows/release-artifacts.yml:616
- Only changing the root component's name leaves its version empty. CycloneDX consumers such as Dependency-Track key projects by name/version, so successive release SBOMs can collapse into the same unversioned
libredb-studioproject. Setmetadata.component.versionfromVERSIONand verify both root fields before attesting.
if (doc.metadata && doc.metadata.component) {
doc.metadata.component.name = "libredb-studio";
}
tests/unit/trivyignore-policy.test.ts:47
- This assertion enforces that suppressions remain empty even though the comment and
.trivyignore.yamlexplicitly permit a justified, expiring suppression. The first legitimate entry will fail here before the policy checks below can validate it. Remove this current-state assertion and leave the per-entry policy tests as the durable contract.
test("is empty today - the CRITICAL/fixable threshold covers everything else", () => {
// Not a requirement that this file must stay empty forever. A record of
// the current, expected state, so a reader knows the loops below are
// vacuous by design rather than by accident.
expect(vulnerabilities.length).toBe(0);
…anner gaps SonarCloud (typescript:S5906): tests/unit/trivyignore-policy.test.ts's length assertion now reports the actual length on failure. Copilot review of #322, five findings, all fixed: - .gitleaks.toml's four path-scoped allowlists exempted every future generic-api-key/jwt/private-key/curl-auth-user finding under their path, not just the 24 historical fabricated ones. Replaced with exact commit:file:rule:startline fingerprints in a new .gitleaksignore (the actual gitleaks 8.30.1 mechanism for this - its [[allowlists]] TOML schema has no fingerprint field). Verified live: a real-shaped private key and generic API key planted under tests/ are now reported; the full history still scans clean. - security-scan.yml's --no-merges left a secret introduced only while resolving a merge conflict unscanned in both parents. Switched the pull_request range to --diff-merges=first-parent, which surfaces that resolution without re-flooding with the other side's unrelated history. Verified live against a synthetic conflict-resolution commit. - The pull request and the workflow's own section header used 'blocks' for a check that is not yet a required branch-protection check. Reworded the workflow header; PR body wording proposed separately. - SECURITY.md's image-SBOM regeneration command used a mutable tag while the prose promised an immutable digest. Resolves the digest first. - CONTRIBUTING.md and SECURITY.md called bun.lock, Cargo.lock and go.mod 'three non-npm lockfiles' - bun.lock is the npm lockfile and go.mod is a manifest, not a lockfile. Named the three ecosystems directly. tests/unit/gitleaks-config.test.ts rewritten for the new split (fingerprint classification moved out; the file now guards the shape of any future value-scoped allowlist). New tests/unit/gitleaksignore.test.ts guards the fingerprint file's shape and count.
… pin the allowlist
…l fixable advisories on main
…s errors Remove typescript.ignoreBuildErrors from next.config.ts (control 2.3). Measured before deleting: with the flag off, `bun run build` exits 0 today (zero hidden errors), and a deliberately injected type error in src/lib/security/headers.ts does fail the build with the expected "Type error: Type 'string' is not assignable to type 'number'" - so the check the flag was suppressing is real, not vacuous. What this buys is narrow, not "catches type errors in general" - both `bun run typecheck` and the build read the same tsconfig.json, so most errors were already caught by the required typecheck gate. The one gap is that `next build` regenerates .next/types/validator.ts from the current route tree before checking, while `tsc --noEmit` reads whatever the last build left on disk. A route added, renamed or removed can leave typecheck passing against stale generated types while the build's check would not. Removing the flag closes that window. Add tests/unit/next-config-typecheck.test.ts to guard against the flag being restored - it is a two-line block that looks harmless to re-add "just to see the build finish."
…se 2 deferrals Resyncs the operator Helm mirror (bun run chart:bump, no version change): this branch forked before phase-1-hardening's own resync commit, so chart:check failed on the pre-existing README/NOTES drift before any Phase 2 edit.
The review's structural finding: three of the four things it could break were guards, not scanners. Apply the same discipline to the YAML that protects the scanners' own configuration. - secret-scan resolves the commit range with `git rev-list --count` under `set -e`, so an unresolvable range (unreachable base.sha, shallow checkout, `.git` as a file) fails loudly instead of reaching gitleaks, which logs that same failure at ERROR and still exits 0. A new step asserts the scanned count is non-zero on a pull request. - the "never runs a container by tag" test matched only the physical `docker run --rm \` line; every image reference sits on a continuation line, so substituting a mutable tag for $TRIVY_IMAGE/$GITLEAKS_IMAGE stayed green. Replaced with a positive, whole-block assertion. - added tests/unit/trivyignore-policy.test.ts: mechanical backing for the suppression policy .trivyignore.yaml's own header and SECURITY.md promise (a statement of meaningful length, an expired_at that parses and is no more than 90 days out). - keyed the security-scan concurrency group by event as well as ref, so a push to main can no longer cancel the daily cron's image scan (same ref, different event). - the dependency gate's failure explainer now checks the gate step's own outcome instead of a bare `if: failure()`, so a Trivy DB timeout or a bun install flake no longer gets told a CRITICAL advisory is present. - the release sbom job checks for Docker Hub credentials and logs in when configured, and retries its trivy pull three times - it now sits on the release path (publish-release needs it) and had no retry. - the sbom's root component is renamed away from Trivy's default ".". - SECURITY.md now says the SBOM covers "the dependency closure of" the packaged artefacts, not the artefacts themselves, and names the bundled Node.js runtime (packaging/*/fetch-node.sh) as an undescribed gap. - docs/BACKLOG.md: relettered the Phase 2 deferrals S1-S7 to C1-C7 (the SQL section already owns S1-S8), amended C7 to name the three Next.js middleware/authorization-bypass CVEs against src/proxy.ts and record the 0.10.0 decision, and added C8 for the Node-runtime SBOM gap. - minor corrections: the gh attestation verify example now uses the <version> placeholder; the security-scan.yml comment blaming GHCR rate limits for DB downloads now names Trivy's actual mirror.gcr.io source; .gitignore no longer points at CONTRIBUTING.md commands that write no report files; the publish-release comment no longer claims release:published fires npm-publish/docker-build-push directly; added a continue-on-error absence test for dependency-scan. Every guard touched here was sabotaged and confirmed red before being reverted; see .superpowers/sdd/2026-08-09-security-phase-2/fix-wave-report.md for the full record.
… blind spots Response to the re-review of be5754d. - security-scan.yml: `if: steps.gate.outcome == 'failure'` has no status-check function, so GitHub Actions implicitly prepends `success() &&` - which is already false once the gate step has failed, so the explainer could never fire, including on a genuine CRITICAL-advisory failure on main. Fixed to `if: failure() && steps.gate.outcome == 'failure'`. - security-scan.yml: log_opts and commit_count were two independent reconstructions of "$BASE_SHA..$HEAD_SHA"; a sabotage touching only log_opts could leave commit_count non-zero while the scanner read a different, reversed range. Refactored to a single `range=` assignment that both outputs are derived from. - tests/unit/security-scan-workflow.test.ts: the pinned-image guard checked whether $TRIVY_IMAGE/$GITLEAKS_IMAGE occurred anywhere in a docker run block, so a hardcoded tag plus an unused decoy reference to the variable still read as pinned. Replaced with `imageArgument()`, which finds the token docker actually reads as the image (the first non-flag token after `docker run --rm \`) and checks that specifically. - added a general test asserting any step conditioned on another step's outcome begins with an explicit status function - the same class of bug as the explainer regression, not a patch for that one line. - added the requested one-sentence comment noting that push/schedule/ workflow_dispatch have no non-zero-scan backstop by decision (--all has no equivalent range to miscount), and a pointer to tests/unit/trivyignore-policy.test.ts from both .trivyignore.yaml's header and the dependency gate's failure explainer. Every guard touched was sabotaged and confirmed red before being reverted; see .superpowers/sdd/2026-08-09-security-phase-2/fix-wave-report.md (round 2 section) for the full record.
…anner gaps SonarCloud (typescript:S5906): tests/unit/trivyignore-policy.test.ts's length assertion now reports the actual length on failure. Copilot review of #322, five findings, all fixed: - .gitleaks.toml's four path-scoped allowlists exempted every future generic-api-key/jwt/private-key/curl-auth-user finding under their path, not just the 24 historical fabricated ones. Replaced with exact commit:file:rule:startline fingerprints in a new .gitleaksignore (the actual gitleaks 8.30.1 mechanism for this - its [[allowlists]] TOML schema has no fingerprint field). Verified live: a real-shaped private key and generic API key planted under tests/ are now reported; the full history still scans clean. - security-scan.yml's --no-merges left a secret introduced only while resolving a merge conflict unscanned in both parents. Switched the pull_request range to --diff-merges=first-parent, which surfaces that resolution without re-flooding with the other side's unrelated history. Verified live against a synthetic conflict-resolution commit. - The pull request and the workflow's own section header used 'blocks' for a check that is not yet a required branch-protection check. Reworded the workflow header; PR body wording proposed separately. - SECURITY.md's image-SBOM regeneration command used a mutable tag while the prose promised an immutable digest. Resolves the digest first. - CONTRIBUTING.md and SECURITY.md called bun.lock, Cargo.lock and go.mod 'three non-npm lockfiles' - bun.lock is the npm lockfile and go.mod is a manifest, not a lockfile. Named the three ecosystems directly. tests/unit/gitleaks-config.test.ts rewritten for the new split (fingerprint classification moved out; the file now guards the shape of any future value-scoped allowlist). New tests/unit/gitleaksignore.test.ts guards the fingerprint file's shape and count.
…ose two more scanner gaps CodeQL (js/redos): tests/unit/gitleaksignore.test.ts's FINGERPRINT regex had a redundant, ambiguous slash-delimited group - '[^:\\s]+' already matches '/', so '(?:\\/[^:\\s]+)*' let the same character be consumed by either alternative, exponential backtracking on a long run of '/'. Removed the redundant group; the colon delimiters between fingerprint fields already make one '[^:\\s]+' per segment unambiguous. docs/BACKLOG.md C7: resolved all five 'Middleware / Proxy bypass' GHSAs against the next advisories affecting next@16.1.6 against the GitHub advisory database directly rather than trusting a copied CVE number. CVE-2026-44573 (GHSA-36qx-fr4f-26g5) was cited as one of the three that apply here; it does not - it is a Pages Router + i18n advisory and this app has no pages/ directory. The one actually missing was CVE-2026-45109 (GHSA-26hh-7cqf-hhc6), an incomplete-fix follow-up scoped to middleware.ts. Also verified CVE-2026-64642 (GHSA-6gpp-xcg3-4w24, Turbopack + single-locale i18n) does not apply - this app has no i18n config. Recorded the full table with fixed versions so a future reader does not need to re-derive it. security-scan.yml: added a direct 'git rev-parse --is-shallow-repository' guard before the --all full-history scan. That scan skips the pull-request range's non-zero-commit assertion because there is no equivalent range to miscount, but a shallow checkout would make --all scan a truncated slice and report 'no leaks found' indistinguishable from a real clean scan. Sabotage-verified: removing the guard makes its test fail. image-scan: the vuln scan and the SBOM generation each independently resolved the mutable ':latest' tag in separate docker run invocations minutes apart; a release retagging ':latest' mid-run would make them describe two different images with no shared identity. Resolved the digest once and reused it for both. release-artifacts.yml: the SBOM root-component rename set 'name' but left 'version' empty, so a CycloneDX consumer that keys a project by name+version would collapse every release's SBOM into the same unversioned project. Now sets both from the release version. tests/unit/security-scan-workflow.test.ts and tests/unit/release-sbom.test.ts updated for the new steps; tests/unit/gitleaksignore.test.ts unchanged in shape, just its regex.
The rebase onto main pulled in #323 (next 16.1.6 -> 16.3.0), which is exactly the deferred work C7 described. All three applicable middleware- bypass advisories (CVE-2026-44575, CVE-2026-45109, CVE-2026-44574) are fixed by 16.2.5/16.2.6, both below the now-locked 16.3.0 - verified against bun.lock. Deleted the entry per this file's own convention (delete an entry when the work lands) rather than leave a corrected-but- already-resolved decision record; #323's own post-merge correction note carries the CVE/GHSA mapping history. Renumbered the former C8 to C7.
|
Follow-up round covering findings not tied to a postable review thread, plus a few items surfaced independently. Fixed, from Copilot's second review (suppressed comments, no thread to reply on):
Declined, with reasons:
Also fixed this round:
All of the above is on top of the rebase onto |
4228c46 to
2c90972
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 17 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
SECURITY.md:173
- The historical classifications now live as exact fingerprints in
.gitleaksignore;.gitleaks.tomlonly enables the default rules and contains no classifications. Pointing readers to the TOML file sends them to the wrong audit record.
reason. The classification is `.gitleaks.toml`, and the full sweep runs again on
.github/workflows/security-scan.yml:122
- For a branch-update merge (
featuremerges currentmain), the feature branch is the first parent, so this emits the merge result versus the old feature tip—including every newly mergedmainchange. Those base changes are therefore rescanned under the merge commit and can produce new fingerprints for already-classified findings, failing an unrelated pull request. Use a combined merge diff (--cc/--diff-merges=combined) that exposes conflict-resolution-only additions, and cover the branch-update case while keeping the local command and guard test synchronized.
range="--diff-merges=first-parent $BASE_SHA..$HEAD_SHA"
… user, not root Copilot found a real defect that would have broken the first 0.10.0 release attempt: aquasec/trivy runs as root by default, so the SBOM it writes into the bind-mounted workspace lands on the host owned by root, mode 644. The very next step patches that same file in place with Node's fs.writeFileSync, which for an existing file opens it for write - permission a non-root runner does not have on a root-owned file. release-artifacts.yml never runs on a pull request, so none of this branch's 18 green checks exercised this path; the first execution would have been the real release, and a failed release burns a patch version rather than retrying the same tag. Reproduced against the real pinned image and real repository lockfiles (bun.lock, Cargo.lock, go.mod): the unpatched command produces a root-owned SBOM, and the exact 'Name and version the SBOM's root component' step's fs.writeFileSync then fails EACCES. Fixed with --user "$(id -u):$(id -g)" on the Trivy invocation - this scan uses --scanners license only, so it needs no vulnerability-DB cache and has no root-owned-cache fallout to work around, unlike the /root/.cache/ trivy-mounted scanners elsewhere in this repository. Verified end-to-end by extracting the actual committed step scripts from the YAML and running them in sequence against the real image: output is now owned by the invoking user, and the patch step succeeds and sets name and version correctly. Comment explains why the other two shapes from review were not taken: sudo chown is reactive and assumes passwordless sudo a self-hosted runner may not grant; a write-sibling-then-rename in the Node step needs no privilege either, but moves the fix somewhere a future editor could 'simplify' back into an in-place write without realising a permission fix depends on it. Audited all docker run invocations across security-scan.yml and release-artifacts.yml for the same class of defect (output written by a container later modified by a host-side step). This is the only one: every other scanner's output is either uploaded/cat'd read-only downstream, rewritten by another container rather than the host, or produces no file at all. tests/unit/release-sbom.test.ts: new guard test, matching the flag immediately after 'docker run --rm' rather than a bare substring - this step's own comment also contains the literal flag text in prose, so a substring check alone would still pass with the flag removed from the actual command. Sabotage-verified: removing the flag from the command (leaving the comment) fails the test; restoring it passes.
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 17 changed files in this pull request and generated no new comments.
Suppressed comments (5)
SECURITY.md:173
- This points readers to the wrong file:
.gitleaks.tomlcontains no classifications; the 24 classified findings are the exact fingerprints and explanations in.gitleaksignore. Name that file so the security documentation matches the implemented suppression mechanism.
reason. The classification is `.gitleaks.toml`, and the full sweep runs again on
.github/workflows/release-artifacts.yml:27
go.modis a Go module manifest, not a lockfile. Calling all three inputs lockfiles repeats the terminology error already corrected inCONTRIBUTING.mdandSECURITY.md; describe them as ecosystem/dependency inputs instead.
# The sbom job (security programme control 2.2) generates one CycloneDX
# document from the repository's three lockfiles (bun.lock,
# packaging/windows/launcher/go.mod, desktop/src-tauri/Cargo.lock) and
.github/workflows/release-artifacts.yml:503
- This second comment again calls
go.moda lockfile and says the inputs themselves "are the closure." They are manifests/lockfiles from which Trivy derives an inventory. Reword this to avoid documenting the same inaccurate model beside the release-critical implementation.
# One SBOM covers everything this release ships: Trivy finds bun.lock,
# packaging/windows/launcher/go.mod and desktop/src-tauri/Cargo.lock, which
# between them are the closure of the npm package, the four standalone
# tarballs, the win32 zip, the .deb and .rpm packages, the snap, the AppImage
# and the desktop .deb - all built from these lockfiles at this commit.
tests/unit/trivyignore-policy.test.ts:16
- “non-empty” contradicts both this paragraph and
gitleaks-config.test.ts, which assert that the allowlist is empty today.
* same argument tests/unit/gitleaks-config.test.ts already makes for its own
* allowlists staying non-empty. Vacuous is fine: the point is that the FIRST
SECURITY.md:185
- This is not a production-only dependency closure. The scanned
bun.lockexplicitly containsdevDependencies(including@types/node, which this document later acknowledges is present), and the workflow performs a fullbun installbefore scanning. Describe it as the repository's resolved dependency inventory, or generate a production-only SBOM, so consumers do not mistake build/test packages for shipped runtime dependencies; update the wording quoted indocs/BACKLOG.mdat the same time.
Every release carries `libredb-studio-<version>.cdx.json`, a CycloneDX 1.7 SBOM
of the production dependency closure, attached as a release asset and signed with
a GitHub build-provenance attestation. It covers all three ecosystems the release
is built from — npm, Go and Rust — and therefore describes **the dependency
closure of** the npm package, the standalone tarballs, the Windows zip, the



Phase 2 of the security maturity programme: the supply chain.
Stacked after Phase 0 (#320) and Phase 1 (#321), both merged. Phase 3 — credential encryption at rest and a published security posture — follows on its own branch and carries the version bump to 0.10.0.
What this adds
.gitleaks.tomland a fingerprint allowlist, plus an incremental scan that fails a pull request, and a full-history sweep onmainand a schedulemainonly, and only on a CRITICAL advisory that has a fix and is not suppressedtypescript.ignoreBuildErrorsremoved, with a guard testThe secret-scanning result
The sweep was run against this repository's real history before any configuration was written: 753 commits, 24 matches, every one read and classified as fabricated — jwt.io's published sample token, hex filler,
StrongPass123documentation examples, PEM bodies whose content literally readsfake, and$$COUCHBASE_USER:$$COUCHBASE_PASSWORDcompose interpolation.No credential has ever been committed to this repository, and no rotation is required.
Each of those 24 is suppressed by its fingerprint — the exact
commit:file:rule:startlinegitleaks reports — in.gitleaksignore. A fingerprint suppresses one already-classified historical occurrence and nothing else: a real secret added later in the same file, or one matching the same fabricated literal in a new commit, produces a different fingerprint and is still reported..gitleaks.tomlcarries no allowlist entries at all, and apaths-based one is now rejected outright by its test.This is the second design. The first used four rule-scoped
paths = ^tests/allowlists, and review caught what that costs: it would have covered every future generic-credential, JWT and private-key finding anywhere undertests/, not only the fabricated fixtures it was written for. That gap was not argued about but reproduced — a real-shaped secret planted in a brand-new file undertests/was silently swallowed by the old configuration, and is reported by the new one. The sweep still scans clean across the full history, now 774 commits.Worth recording plainly, because this pull request's own text asserted the principle and the implementation then broke it: a broad path exclusion that happens to cover today's findings will cover tomorrow's real one. Stating a rule is not the same as enforcing it, which is why the test now rejects the shape rather than trusting the comment.
What gates and what does not
Only one thing in this pull request can fail a merge that could not before: the production build now type-checks, because
typescript.ignoreBuildErrorsis gone.Of the scanners:
continue-on-errorline. The scan exists for trend and evidence, not enforcement.Suppressions live in
.trivyignore.yamland must carry a written statement and an expiry no more than 90 days out. An expired entry is re-reported — verified, not assumed — and a policy test enforces both requirements.The SBOM, and what it does not cover
Every release from the next tag onward carries
libredb-studio-<version>.cdx.json: CycloneDX 1.7, 755 components, attested, built from the three ecosystem inputs —bun.lock,desktop/src-tauri/Cargo.lockand the launcher'sgo.mod.It describes the dependency closure of the npm package, the tarballs, the Windows zip, the deb and rpm, the snap, the AppImage and the desktop package. It does not describe the pinned Node runtime those artefacts bundle, which appears in no lockfile, and it is not the container image's SBOM.
The image cannot have a release asset:
release-artifacts.ymlpublishes the release before it dispatches the image build, and releases here are immutable. The image SBOM is regenerable from any immutable digest with the command documented inSECURITY.md.publish-releasenow requires the SBOM asset by name before flipping the draft to published. That is deliberate: a release without its bill of materials should not exist.The Next.js advisories this branch deferred, and no longer defers
This section originally deferred the
nextadvisories to a separate pull request, on the grounds that a Next minor can change middleware and CSP behaviour — exactly the surface Phase 1 verified in a real browser. That happened: #323 landed the bump tonext16.3.0 and this branch is rebased onto it, so the deferral is resolved rather than outstanding, and BACKLOG C7 has been deleted (the later entries renumbered).Two claims in the original wording were also wrong, and are corrected here rather than quietly deleted.
The middleware bypasses were counted as three. Resolved against the GitHub advisory database, five high-severity Middleware/Proxy bypass advisories affect
next@16.1.6, all published before this branch existed — so the count was wrong when written, not overtaken by a later disclosure. Of the five, CVE-2026-44573 (GHSA-36qx-fr4f-26g5) is scoped to the Pages Router with i18n and does not apply here: this application has nopages/directory. The four that do apply are CVE-2026-44574, CVE-2026-44575, CVE-2026-45109 and CVE-2026-64642.CVE-2026-45109 is the one worth naming, because the original text did not have it: it is the incomplete-fix follow-up scoped to middleware, and
src/proxy.tsis Next 16's middleware — the single file Phase 1 put RBAC, the Origin check, rate limiting, security-header delivery and audit emission into.next@16.3.0carries zero advisories, so the bump closes all of them regardless of how they were counted.For contributors
CONTRIBUTING.md..gitleaksignore— the exactcommit:file:rule:startlinegitleaks reports — which suppresses that one classified occurrence and nothing else..gitleaks.tomlcarries no allowlist entries at all today, andpaths-based ones are rejected outright bytests/unit/gitleaks-config.test.ts.bun auditis the local zero-container view, but it carries no fixed-version data, so it cannot tell you whether a fix exists.Verification
CI is green at
2c90972, the head of this branch after the rebase ontomain: 18 checks pass and one is skipped. Among the 18 areUnit & Integration Tests, which is where the 100 percent line-coverage gate lives, andLint, Typecheck and Build. The skipped one isImage Scan, which does not run on a pull request by design.The ten local gates were run in the foreground before each push and were green:
format,lint(0 errors, 76 pre-existing warnings),typecheck,knip,test(23/23 groups),build,test:coverage+coverage:check(100.00 percent),build:lib+attw,readme:check, andchart:check(chart 0.1.30, appVersion 0.9.67, in sync).And the phase's own claims were re-run rather than restated: the full-history sweep found zero findings over 694 commits; the CRITICAL gate exited 0 while the same scan one severity lower exited 1 with 17 findings, proving the scanner is live rather than silently vacuous; and the SBOM regenerated at 755 components across three ecosystems.
A note on how this phase was reviewed
The branch review's structural finding was that three of the four things it could break were guards, not scanners — the mechanisms protecting the scanners' configuration rather than the scanners themselves. The secret scan exited 0 when a commit range failed to resolve. The container-tag guard stayed green when every image was switched to a mutable tag. The suppression policy's statement-and-expiry rules had no mechanical backing at all.
All three are fixed, and each fix was proved by sabotage: reverting it turns a specific test red. That habit is now the phase's standard — a control is not delivered until something fails when it is broken.