Skip to content
Merged
25 changes: 25 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ jobs:
shellcheck --shell=bash --severity=error scripts/tests/pin-version-verify.sh
bash -n scripts/check-pin-version.sh
bash -n scripts/tests/pin-version-verify.sh
shellcheck --shell=bash --severity=warning scripts/publish-guard.sh
shellcheck --shell=bash --severity=warning scripts/publish-mirror.sh
shellcheck --shell=bash --severity=error scripts/tests/publish-guard-verify.sh
shellcheck --shell=bash --severity=error scripts/tests/publish-mirror-verify.sh
shellcheck --shell=bash --severity=error scripts/tests/mirror-publish-workflow-verify.sh
bash -n scripts/tests/mirror-publish-workflow-verify.sh
bash -n scripts/publish-guard.sh
bash -n scripts/publish-mirror.sh
# format.sh's own fail-closed properties. Formatters are stubbed, so this is
# hermetic and needs no Go toolchain — which is why it lives in this job
# rather than Lint. It exists because the first cut of format.sh reported
Expand Down Expand Up @@ -127,6 +135,23 @@ jobs:
# pin-version-drift.yml, which must never gate a PR (backend#2704 / #1009).
- name: Pin-version watcher harness (drift reddens / fail-closed)
run: bash scripts/tests/pin-version-verify.sh
# The mirror-publish guard (scripts/publish-guard.sh) and publisher
# (scripts/publish-mirror.sh): each guard reddens on the thing it claims
# to catch (a forbidden path, a forbidden string, a missing scanner), an
# empty or unreadable list is "could not tell", and the publisher refuses
# an unset or self-pointing mirror. gitleaks is a PATH shim here, so this
# is hermetic; the workflow installs the real, pinned binary.
- name: Mirror-publish guard harness (refusals named / fail-closed)
run: bash scripts/tests/publish-guard-verify.sh
- name: Mirror-publish publisher harness (target / tree / release)
run: bash scripts/tests/publish-mirror-verify.sh
# The decisions mirror-publish.yml takes in its own step bodies — a
# prerelease keeps the mirror's default branch, the release tag is fetched
# as data only at the expected commit, no checkout takes an untrusted
# ref, a publisher refusal reaches the step log. The step bodies are read
# out of the YAML and executed with `gh` shimmed, so this is hermetic.
- name: Mirror-publish workflow harness (step bodies / shape / mutations)
run: bash scripts/tests/mirror-publish-workflow-verify.sh

test:
timeout-minutes: 15
Expand Down
452 changes: 452 additions & 0 deletions .github/workflows/mirror-publish.yml

Large diffs are not rendered by default.

79 changes: 79 additions & 0 deletions .publish-forbidden
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# .publish-forbidden — what must never reach the public mirror, even if the
# allowlist (.publish-include) let it through by mistake.
#
# Read by scripts/publish-guard.sh. Four sections; `#` starts a comment. A
# header the guard does not know, a needle listed in both string tiers, or an
# empty [strings-refuse] is refused as "could not tell" (exit 2).
#
# [paths] gitignore-style names. A pattern containing `/` is
# anchored to the staged root; one without matches ANY path
# component; a trailing `/` means "as a directory".
# [strings-refuse] extended regexes, matched case-insensitively against
# every staged TEXT file. A hit REFUSES the publish and
# names the file and line (never the matched text).
# [strings-report] same syntax. Hits are COUNTED and printed (per-needle
# totals, ten most-hit files) but do not refuse — unless
# the guard runs with --strict, which promotes this tier
# to refusal. A needle moves up to [strings-refuse] the
# day it is decided the mirror must never carry it.
# [allow] exact tokens removed from a line before a needle is
# re-tested, so a line is spared only when the allowed
# token was the whole reason it hit.
#
# This file is ONE list read by both the guard and its tests; the tests write
# their own inputs and never iterate this file to check itself.

[paths]
tests/
scripts/tests/
ci/
.github/
docs/rfcs/
docs/migration-tools/
CLAUDE.md
STYLE.md
Makefile
.cursor/
*.go
go.mod
go.sum
__pycache__
.DS_Store
.env*
*.pem
*.key
kubeconfig*

[strings-refuse]
# Mailboxes (the public support address is spared under [allow]).
[A-Za-z0-9._%+-]+@tracebloc\.io
# AWS account identifiers and ARNs.
arn:aws:
[0-9]{12}\.dkr\.ecr\.
#
# CUSTOMER AND TENANT IDENTIFIERS ARE DELIBERATELY NOT LISTED HERE. This file
# is public, and a list of customer names would itself be the disclosure the
# scan exists to prevent. Those needles are supplied privately at publish time:
# the workflow writes the PUBLISH_FORBIDDEN_TENANTS secret (one needle per line,
# same regex syntax) to a file and passes it as --extra-forbidden; they join
# this tier. The guard refuses to run the scan when that list is missing or
# empty.

[strings-report]
# Internal tracker and RFC identifiers — a reader of the mirror cannot open
# them. Counted until the decision to strip them from the deliverable (or to
# accept them) is taken; --strict refuses them.
backend#
rfcs#
RFC-0
RFC-BACKEND
e2e-test-agent#
tracebloc/backend
# Non-production tracebloc hosts; same decision pending.
dev-api\.tracebloc\.io
stg-api\.tracebloc\.io
dev\.tracebloc\.io
stg\.tracebloc\.io

[allow]
support@tracebloc\.io
16 changes: 16 additions & 0 deletions .publish-include
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# .publish-include — what the public mirror of this repo MAY carry.
#
# Read by scripts/publish-guard.sh. One glob per line; `#` starts a comment.
# `*` and `?` do not cross `/`, `**` does; a leading `!` takes matching files
# back out. Only tracked files are considered. Anything not matched here is
# excluded by construction — .publish-forbidden is the second lock.
#
# The mirror is README + releases. The binaries, SHA256SUMS, signatures and
# the two installers travel as RELEASE ASSETS (copied from this repo's release
# by the publish workflow and scanned by the same guard), never as tree files.
# No Go source, no Makefile, no workflows: the forbidden list refuses each of
# those by name should a line here ever widen.
README.md
LICENSE
# The user docs README links to. One level only: docs/rfcs/ stays home.
docs/*.md
20 changes: 20 additions & 0 deletions scripts/RELEASE_CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,26 @@ have to reverse-engineer the surface area on release day.
and all artifacts attached. `prerelease=true` if the tag
contains a `-` (e.g. `v0.1.0-rc1`).

7. `.github/workflows/mirror-publish.yml` fires when the Release
workflow completes. It stages the public deliverable (README,
LICENSE, `docs/*.md` per `.publish-include`; the release assets)
through `scripts/publish-guard.sh` — allowlist, forbidden paths,
forbidden strings, gitleaks, all fail-closed — and pushes it, plus
a copy of the release, to the public mirror named by the
`MIRROR_REPO` variable. Until that variable is set the job refuses
to publish; `Actions → Mirror publish → Run workflow` with
`dry-run: true` shows what would ship. The string scan has two
tiers: `[strings-refuse]` hits refuse; `[strings-report]` hits
(internal ticket references, non-production hostnames) are counted
and printed with the most-hit files, and refuse only under the
`strict` input or the `PUBLISH_STRICT=true` repository variable.
The guard and publisher run from the workflow's own commit; the
release tag is fetched separately as data and refused unless it
resolves to the commit the Release run ran on. A prerelease
(`-rc.N`) mirrors only its GitHub release, marked prerelease and
pinned to the mirror's current default-branch head — the mirror's
default branch keeps the last stable release.

GitHub Releases plus the cosign-verified `install.sh` are the
install path — a Homebrew tap and the `install.tracebloc.io`
vanity URL were considered and dropped
Expand Down
Loading
Loading