Conversation
…t string
Semgrep finding 979721020, javascript.lang.security.audit.unsafe-formatstring,
low:
console.error(` ${workflow}: pull_request paths the ${target} pattern
misses:`, missed);
A second argument is what makes this a finding rather than a template
literal. With `missed` passed alongside it, Node hands the first argument
to util.format, so it becomes a FORMAT string -- and `workflow` and
`target` are interpolated into it. A `%s` inside either would consume
`missed` and forge the line. The same interpolation on line 15 is not
flagged and should not be: single-argument console.error never processes
specifiers.
Not exploitable here. Both values come from the hardcoded SITES array four
lines above, so nothing outside this file reaches the format string. The
fix is one line and the assumption is the part worth removing: "the inputs
happen to be literals today" is what stops being true when someone derives
that list from a directory read.
Values move to arguments behind a constant format string, with %j for the
path list -- %o printed `[length]: 1` alongside the paths.
The guard still reports correctly. Mutating the ropa-site pattern to force
a failure:
.github/workflows/azure-ropa-site-prod.yml: pull_request paths the
ropa_site pattern misses: ["packages/ropa-site/probe"]
npm run test:scripts: 24 + 24 pass. check-format clean.
… two ReDoS findings it caused Semgrep's detect-non-literal-regexp flagged both RegExps in matchTag, which interpolate its `tag` parameter. The parameter was `string`, so nothing but the call sites stopped data reaching a pattern; all three pass literals — 'decision', 'uitvoeringsregel' and 'inputData'. The parameter is now a MeasuredTag union of exactly those three, so the interpolation is a compile-time constant and the compiler enforces it rather than a comment asserting it. Both lines carry a scoped nosemgrep naming the rule, why it is safe, and what would end that: widening the type back to string. Same shape as RonlAttr in the frontend's ronlAttributes.ts, which suppresses the same rule for the same reason. Neither pattern has a nested quantifier, so there was no backtracking risk either way; the finding was about the interpolation, and that is what this closes. The findings clear from the Semgrep dashboard on the next scan of acc.
…rep ReDoS findings (#219)
Every other gate here runs on a commit, so a new advisory against unchanged code is seen by nothing; Dependabot alerts watch the default branch, acc, and not the main that production deploys from. ICTU recommendation 10. .github/workflows/dependency-audit.yml runs at 05:17 UTC and on demand, reading each branch's lockfile with npm audit --package-lock-only, installing nothing. It fails on a high or critical advisory in production dependencies and reports the rest, and it opens, updates and closes one tracking issue so a scheduled failure reaches someone. scripts/audit-tree.mjs groups findings by ADVISORY rather than by package: npm audit reports one entry per affected package, and on 2026-09-24 linked-data-explorer's 28 moderate entries were three advisories, 24 of them the same @tiptap/core reached through its extensions. A count that overstates the problem by an order of magnitude is a count that gets ignored. An audit that cannot run exits 2 and is treated as a finding, never as a clean tree. Node is pinned as an exact literal rather than read from .nvmrc: this job audits both branches, and main need not carry the same .nvmrc as acc. Measured before merging -- linked-data-explorer 3 advisories, ttl-editor none, ronl-business-api 7 with one high -- and those match what Dependabot reports.
… remove The audit loop checks out origin/acc and origin/main in turn, which replaces the working tree — including scripts/audit-tree.mjs itself, which exists on the branch under review before it exists on either target. The first run lost the script at the first checkout and node exited 1 for a missing module, which the step then read as "a high or critical advisory was found": a failure reported as a finding that did not exist. The script is now copied to $RUNNER_TEMP before the loop and run from there. Each iteration resets its own status, and anything above 2 is clamped to 2, so "the audit could not run" can never be mistaken for a finding or for a clean tree. The reasoning is in the workflow, next to the loop that needs it.
`audit` is zizmor.yml's job and a required status check here. Required checks match by name, so this workflow's second job of that name made the required context ambiguous: one passing check and one failing check under one name, which no ruleset can satisfy. Its first run blocked sgort/ronl-business-api#206 outright. The job is now `dependency-audit`, with the reason recorded next to it, and SECURITY-PIPELINE.md names the check alongside the rest of the audit's specification. Same lesson as the two jobs that were both called "Build and Deploy Job", renamed for this reason in #184 — which is where the rule this broke was written down.
ICTU recommendation 7 asks for the risk of a major to be assessed, and for the first or second patch release to be waited for. Majors already wait for Dependency Dashboard approval here, which is where the assessment happens; nothing said which version may be offered once someone approves. A packageRules entry now sets allowedVersions to !/^\d+\.0\.0$/ for the npm manager, so Renovate never proposes X.0.0 and the earliest a major can arrive is X.0.1, the first patch. Scoped to npm deliberately: allowedVersions applies to every update type of a matched package, and the same pattern against Docker tags (18-alpine) or the github-runner datasource (ubuntu 26.04) would mean something else. The pattern matches X.0.0 alone, which no minor or patch release can look like. Verified before merging: renovate-config-validator --strict accepts the config, and the stored pattern was tested against fourteen versions with no mismatch. It blocks 1.0.0, 2.0.0, 3.0.0, 10.0.0 and 0.0.0, and allows everything else — including every major queued today (express 5.2.1, @tiptap 3.31.3, react-router 7.18.0), so nothing waiting is affected by this. The cost is recorded in the rule: a package that publishes X.0.0 and never a patch is never offered that major, and the remedy is a per-package exception carrying its reason.
ICTU recommendation 7 asks for a major to be assessed rather than taken or ignored, and for the decision to be recorded. These two were assessed on 24 September 2026 and held, each as a rule that disables the update and carries its reason and the condition that ends it — the shape ttl-editor already uses for Tailwind 4 and ESLint 10. ubuntu 26.04: the runner pins exist to stop the image drifting under us, not to be newest. `ubuntu-latest` still resolves to 24.04, so taking 26.04 would put every job ahead of GitHub's own default. Checked the same day: the ubuntu26 images are published and the label carries no beta marker, so this is a choice, not an impossibility. Revisit when `ubuntu-latest` moves. Not deferred by rule, and deliberately so: react-router-dom 7, vitest 5, jsdom 30, vite 8 with @vitejs/plugin-react 6, typescript 7, eslint 10, the React monorepo and the grouped workspace majors. Disabling those would hide them; they stay queued behind Dependency Dashboard approval, where a person sees them.
`npm ci` refuses to install when package-lock.json and package.json disagree, so this was already detectable — but only as an EUSAGE error inside "Install dependencies for the formatter", three steps into a job about pinning, where it does not read as a lockfile problem. It happened on 25 September 2026 in ronl-business-api: three dependency pull requests merged back to back, each with a lockfile computed against an older acc, produced a lockfile matching no package.json. Every pull request had been green against its own base. A `npm ci --dry-run` step now runs before that install. It resolves and validates without writing node_modules, costs seconds, and fails with the cause in its own name. Verified both ways before merging: it passes here, and it fails with EUSAGE against the broken tree that prompted it. What it cannot do is recorded next to it: the check runs on the pull request's own merge commit, so it proves the lockfile is consistent with THAT base. A pull request green against a stale base, merging into a base that has moved, is not caught by anything on the pull request. Merge dependency pull requests one at a time, and rebase each onto the merged acc first.
The new step failed on every pull request, and not for the reason it exists: npm ci --dry-run still runs lifecycle scripts, and the root postinstall (scripts/write-deps-marker.mjs) copies package-lock.json into node_modules, which a dry run never creates. On a clean checkout that is ENOENT, reported under a step named "Lockfile matches package.json" — precisely the confusion the step was added to remove. It passed when it was written because the machine it ran on already had node_modules. CI does not, which is the condition that mattered and the one that was not tested. --ignore-scripts skips the postinstall. Whether the lockfile satisfies package.json is decided before any script runs, so the check keeps its meaning.
ICTU recommendation 10 asks for SBOMs of released versions, kept analysable. When an advisory lands against something that shipped months ago, the question is what that version contained, and only a document written at the time answers it. scripts/write-sbom.mjs writes docs/sbom/<name>-<version>.cdx.json: CycloneDX, production dependencies only — that is what the released artifact holds — and --package-lock-only, so it needs no install and describes the lockfile rather than whatever is in node_modules. npm run sbom is a step in bump-release, after the version bump, since the filename carries the version. .github/workflows/sbom.yml runs on a push to main, on demand, and on a pull request that touches the tooling. A promotion to main IS the release here: there are no tags and no GitHub Releases. It uploads the document as an artifact and, on a promotion, asserts that the released version has one. Two copies, because neither suffices alone. This repository is public, so GitHub caps artifact retention at ninety days; the committed copy is what answers a question about a version that shipped a year ago. The artifact is what a scanner or a Dependency-Track import can fetch without a checkout. Three modes, and the distinction is the point: writing; --check, strict, which belongs where the release is cut; and --verify-release, which a promotion can honestly assert — a missing document fails, drift only warns, because a promotion carries every commit merged since the release and the lockfile may legitimately have moved. Both comparisons ignore serialNumber and metadata.timestamp, which change on every run. Verified before merging: the strict check fails on a stale document and passes on a fresh one; --verify-release warns on drift and fails on a missing file; Prettier reformats the document and both still pass, because they compare parsed JSON rather than bytes.
Flips the 2026.09.8 changelog entry to Released: eleven commits since v2026.09.7, scope backend (the only package change is quality.service.ts). Root and backend package.json and their package-lock.json entries move to 2026.09.8; the frontend stays at 2026.09.7. Adds the release SBOM, docs/sbom/linked-data-explorer-2026.09.8.cdx.json.
|
Azure Static Web Apps: Your stage site is ready! Visit it here: https://witty-beach-0a7cfa203-229.westeurope.3.azurestaticapps.net |
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promotes
acctomain, publishing v2026.09.8: 12 commits (11 in the release entry plus the bump itself, #228).What this release is
It is almost entirely the #119 CI-posture and dependency work, per the ICTU recommendations:
docs/sbom/and uploaded bysbom.yml(ci: generate a release SBOM, committed and uploaded (#119) #227).accandmain, which fails on a high or critical advisory in production dependencies and keeps one tracking issue.package.jsonfails under its own name.X.0.0, and a recorded deferral for ubuntu 26.04.matchTaginquality.service.tsnow takes a closed union, and the drift guard uses a constant format string.npm run dev:fullonly).The one runtime change in production is
quality.service.ts, which only tightens types. Behaviour is unchanged.What the sequencer will do with this range
I ran the same
scripts/promotion-targets.mjslocally againstorigin/main..origin/acc(16 changed files):The backend deploys first, then the frontend. The ROPA site is skipped because nothing in its filter changed.
frontend=truefollows from the release, not from frontend code. The only frontend file in the range ispackages/frontend/src/changelog.json, and the rootpackage.jsonandpackage-lock.jsonsit in its filter too. That is expected: the release entry is what the frontend shows.First promotion with the release SBOM
This is the first push to
mainsincesbom.ymllanded. On the promotion it runs--verify-release: it fails ifdocs/sbom/linked-data-explorer-2026.09.8.cdx.jsonis missing, and only warns if the lockfile has moved since. The document is committed, andwrite-sbom.mjs --checkpassed against it when the release was cut. Its uploaded artifact is the one to fetch for this version.Opening this pull request deploys a production preview
As decided in #210, the frontend matches its
pull_requestpath filter here, so it builds a preview on the production Static Web App, served fromacc. The ROPA site does not match, so it builds no preview.Verified before opening this
On
accat0143ea2, after #228 merged:/v1/healthversion 2026.09.8,build.sha 0143ea2…, healthy, TriplyDB and Operaton upGitLab mirror:
accfast-forwarded to0143ea2;mainmatches at99e29f9.Not in this release
Renovate #221 (node 24.21.0 in workflows), #223 (
@types/n3,yaml) and #226 (@bpmn-io/properties-panel) were left out on purpose and stay open for the next release.