CI fix round 1: realliance annotations, member text as data, publish hardening #2

Merged
chris merged 3 commits from fix-round-1 into main 2026-11-05 00:35:44 +00:00
Owner

Fix round 1 for the forge CI (Task 4 review), before 2.4.0 gets tagged.

  • Manifest annotations (I1). crane append kept nginx's manifest annotations: its source, revision, version, created time and base image. Renovate reads those before the config labels. crane mutate -a now sets source and revision (the labels' values), url, and created from SOURCE_DATE_EPOCH (the commit time), and blanks every other key the base sets. check-image.sh requires the two annotations to equal the labels, and no annotation may name nginx. The published sha-d0eb467 keeps nginx's annotations: tags never move, and nothing deploys it.
  • Member text is data (M2). build.ts pasted about.toml strings into pug mixin calls, so a quote turned a bio into code that ran in the publishing job. The connorjc bio did exactly that: it injected a script (the "(Ethical) Hacking" marquee) and linked to an OWASP page on injection flaws. Members now reach pug as locals, which pug escapes, and links must be https://. The connorjc card keeps its text and links, and only the marquee script goes. scripts/test-members.sh builds a copy with a hostile member and runs in CI.
  • Narrower build (M2). The build script (scripts/deno-build.sh) reads this directory and writes build/ only. Its network and imports reach deno.land and esm.sh only, and it can read only the environment variables the libraries probe. It can start no processes and read no system information.
  • Publish trusts nothing the build touched (M1, M2, M3, M8).
    • It refuses a dirty tree and fetches crane fresh, checked by sha256.
    • The Docker config lives in the job's RUNNER_TEMP, which the runner deletes at job end (probed).
    • A tag counts as absent only on MANIFEST_UNKNOWN or NAME_UNKNOWN.
    • It no longer writes a job summary.
    • Checkout uses persist-credentials: false.
  • Smaller fixes (M4, M5). EXPECT_REVISION defaults to HEAD, and the build log prints tar's version next to the digest.

Testing:

  • Two clean laptop builds of 18376d6 gave the same digest, sha256:9696208d…a177, and check-image: PASS.
  • Never-move tests against scratch registries passed again: publish refuses a dirty tree and never moves a pre-pushed tag.
  • check-image.sh against the published sha-d0eb467 fails, flagging nginx's annotations.
  • The page is the same for the current members, except the connorjc script and the .filter utility it implied.

Never approve workflow runs from fork pull requests.

Fix round 1 for the forge CI (Task 4 review), before `2.4.0` gets tagged. - **Manifest annotations (I1).** `crane append` kept nginx's manifest annotations: its source, revision, version, created time and base image. Renovate reads those before the config labels. `crane mutate -a` now sets `source` and `revision` (the labels' values), `url`, and `created` from `SOURCE_DATE_EPOCH` (the commit time), and blanks every other key the base sets. `check-image.sh` requires the two annotations to equal the labels, and no annotation may name nginx. The published `sha-d0eb467` keeps nginx's annotations: tags never move, and nothing deploys it. - **Member text is data (M2).** `build.ts` pasted `about.toml` strings into pug `mixin` calls, so a quote turned a bio into code that ran in the publishing job. The `connorjc` bio did exactly that: it injected a script (the "(Ethical) Hacking" marquee) and linked to an OWASP page on injection flaws. Members now reach pug as locals, which pug escapes, and links must be `https://`. The `connorjc` card keeps its text and links, and only the marquee script goes. `scripts/test-members.sh` builds a copy with a hostile member and runs in CI. - **Narrower build (M2).** The build script (`scripts/deno-build.sh`) reads this directory and writes `build/` only. Its network and imports reach `deno.land` and `esm.sh` only, and it can read only the environment variables the libraries probe. It can start no processes and read no system information. - **Publish trusts nothing the build touched (M1, M2, M3, M8).** - It refuses a dirty tree and fetches crane fresh, checked by sha256. - The Docker config lives in the job's `RUNNER_TEMP`, which the runner deletes at job end (probed). - A tag counts as absent only on `MANIFEST_UNKNOWN` or `NAME_UNKNOWN`. - It no longer writes a job summary. - Checkout uses `persist-credentials: false`. - **Smaller fixes (M4, M5).** `EXPECT_REVISION` defaults to `HEAD`, and the build log prints tar's version next to the digest. Testing: - Two clean laptop builds of `18376d6` gave the same digest, `sha256:9696208d…a177`, and `check-image: PASS`. - Never-move tests against scratch registries passed again: publish refuses a dirty tree and never moves a pre-pushed tag. - `check-image.sh` against the published `sha-d0eb467` fails, flagging nginx's annotations. - The page is the same for the current members, except the `connorjc` script and the `.filter` utility it implied. Never approve workflow runs from fork pull requests.
ConnorJC's description closed the generated mixin call and appended its own
pug: a script (the "(Ethical) Hacking" marquee) and a replacement website
list, linking OWASP's "Injection Flaws". The next commit passes about.toml
values to pug as data, which ends that, so this keeps the card's visible
text and links as data: the description, Github and Website. The marquee
script goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWhaDeswMqYvQaVdpL8oit
build.ts pasted about.toml strings into pug mixin calls, so a value with a
quote became pug and JavaScript that ran at build time, in the job that later
publishes. Members now reach src/index.pug as locals (each m in members), pug
escapes them on output, values must be strings, links must be https://, and
an icon must sit next to its about.toml. scripts/test-members.sh builds a
copy with a hostile member (quotes, #{}, </h3><script>, a closing mixin call)
and expects it all escaped and no <script>; a javascript: link must fail.

scripts/deno-build.sh is the one build command (CI, just build, the test):
read this directory, write build/ only, net and imports to deno.land/esm.sh,
and only the environment variables the libraries probe, with the ones that
change the output unset. No --allow-run or --allow-sys. autoprefixer gets
browserslist's default query explicitly and BROWSERSLIST_ROOT_PATH bounds
its config search, so no read leaves the directory. The intermediate/ folder
goes. For the current members the page is the same except ConnorJC's script
(previous commit), and tailwind.css drops the `.filter` utility it implied.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWhaDeswMqYvQaVdpL8oit
image: realliance manifest annotations; publish trusts nothing the build touched
All checks were successful
ci / build (pull_request) Successful in 10s
ci / build (push) Successful in 11s
18376d6f96
- build-image: crane append kept nginx's manifest annotations (source,
  revision, version, created, base.*), which Renovate reads before the
  labels. mutate -a now sets source and revision (the labels' values), url,
  created from SOURCE_DATE_EPOCH (the commit time, also the layer mtime), and
  blanks the rest. The build log prints tar's version next to the digest.
- check-image: the source and revision annotations must equal the labels,
  and no annotation may name nginx. EXPECT_REVISION defaults to HEAD, so a
  standalone check is against the checkout's own commit.
- publish: refuses a tree that differs from the commit; fetches crane fresh
  (sha256-checked) into the job's RUNNER_TEMP, which the runner deletes at
  job end, and keeps DOCKER_CONFIG there; treats a tag as absent only on
  MANIFEST_UNKNOWN/NAME_UNKNOWN; drops the job-summary write (Forgejo 15
  has none).
- ci.yaml: checkout with persist-credentials: false.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWhaDeswMqYvQaVdpL8oit
chris merged commit 18376d6f96 into main 2026-11-05 00:35:44 +00:00
chris deleted branch fix-round-1 2026-11-05 00:35:44 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
realliance/realliance.net!2
No description provided.