From 92d9aa3f289ab82098306c826d2dafa212a61ed9 Mon Sep 17 00:00:00 2001 From: dhruv8sh Date: Thu, 8 Oct 2026 01:35:56 +0530 Subject: [PATCH 1/6] Gate PRs on non-allowlisted URL hosts in changed lines Add a lint-domains job that runs ts dev lint domains --changed-vs HEAD^1 on every pull request, so the pre-commit check cannot be skipped with --no-verify or by contributors who never installed the hook. A depth-2 checkout fetches the merge commit and its parents, which makes HEAD^1 the exact base commit without cloning full history. Part of #1215 Signed-off-by: dhruv8sh --- .github/workflows/test.yml | 33 +++++++++++++++++++++++++++++++++ AGENTS.md | 1 + 2 files changed, 34 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d06a1b8ff0..a2b17e62dd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -289,6 +289,39 @@ jobs: cargo test --manifest-path crates/trusted-server-cli/Cargo.toml --target "$TARGET" \ --test proxy_trust_linux -- --ignored + lint-domains: + # Server-side counterpart to the `ts dev install-hooks` pre-commit hook, + # which `--no-verify` skips and fresh clones never install: fail the PR + # when an added line introduces a non-allowlisted URL host. PR-only, so + # direct pushes to main are not linted. + name: ts dev lint domains (changed lines) + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + # A PR checkout is GitHub's merge commit; depth 2 also fetches its + # parents, so `HEAD^1` is the exact base commit the PR merges into. + - uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Retrieve Rust version + id: rust-version + run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT + shell: bash + + - name: Set up Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: ${{ steps.rust-version.outputs.rust-version }} + cache-shared-key: cargo-lint-domains-${{ runner.os }} + + # The `ts` CLI is host-target only; the workspace default is wasm32-wasip1. + - name: Lint URL hosts on lines added by this PR + run: | + cargo run --manifest-path crates/trusted-server-cli/Cargo.toml \ + --target "$(rustc -vV | sed -n 's/host: //p')" --bin ts -- \ + dev lint domains --changed-vs HEAD^1 + test-typescript: name: vitest runs-on: ubuntu-latest diff --git a/AGENTS.md b/AGENTS.md index 6ff981ebfd..a6687b4e68 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -355,6 +355,7 @@ Every PR must pass: 6. JS format (`cd crates/trusted-server-js/lib && npm run format`) 7. Docs format (`cd docs && npm run format`) 8. Markdown format outside `docs/` (requires `cd docs && npm ci` first): `docs/node_modules/.bin/prettier --config docs/.prettierrc --check "*.md" ".claude/**/*.md" ".github/**/*.md" "crates/**/*.md" "scripts/**/*.md" "tinybird/**/*.md"`; fix with `--write` in place of `--check` +9. URL-host linter on lines the PR adds (`lint-domains` job, PRs only); run it locally with `cargo run --manifest-path crates/trusted-server-cli/Cargo.toml --target --bin ts -- dev lint domains --changed-vs origin/main` --- From 7fecdbdd1df3a79501b1d6fb299a197453915396 Mon Sep 17 00:00:00 2001 From: Dhruvesh Surolia Date: Tue, 13 Oct 2026 14:12:48 +0530 Subject: [PATCH 2/6] Update .github/workflows/test.yml Co-authored-by: prk-Jr <49094961+prk-Jr@users.noreply.github.com> --- .github/workflows/test.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 35ba4fb67f..f32ea4311e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -316,7 +316,11 @@ jobs: uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ steps.rust-version.outputs.rust-version }} - cache-shared-key: cargo-lint-domains-${{ runner.os }} + # Restore `test-cli`'s cache read-only. This job never runs on main, + # so a key of its own is only ever saved in PR scope, where no other + # PR can restore it. + cache-shared-key: cargo-cli-${{ runner.os }} + cache-save-if: false # The `ts` CLI is host-target only; the workspace default is wasm32-wasip1. - name: Lint URL hosts on lines added by this PR From d0d74cc966845a806cdd1a2da6576a88f04053c9 Mon Sep 17 00:00:00 2001 From: dhruv8sh Date: Thu, 15 Oct 2026 14:40:29 +0530 Subject: [PATCH 3/6] Pin Node before building ts and log scanned lines in lint-domains job Building the ts CLI compiles trusted-server-core, whose trusted-server-js build script runs npm, so set up Node from .tool-versions first (see #1204). Pass --verbose so a vacuous pass is visible in the job log. Signed-off-by: dhruv8sh --- .github/workflows/test.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f32ea4311e..6b2a4d8996 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -322,12 +322,20 @@ jobs: cache-shared-key: cargo-cli-${{ runner.os }} cache-save-if: false + # Building `ts` compiles trusted-server-core, whose trusted-server-js + # build script runs the TSJS build with the `npm` on PATH, so pin Node + # first (see #1204). + - name: Use Node.js for the TSJS build + uses: actions/setup-node@v4 + with: + node-version-file: .tool-versions + # The `ts` CLI is host-target only; the workspace default is wasm32-wasip1. - name: Lint URL hosts on lines added by this PR run: | cargo run --manifest-path crates/trusted-server-cli/Cargo.toml \ --target "$(rustc -vV | sed -n 's/host: //p')" --bin ts -- \ - dev lint domains --changed-vs HEAD^1 + dev lint domains --changed-vs HEAD^1 --verbose test-typescript: name: vitest From 36797df7c162748a960f693adf655745b3822678 Mon Sep 17 00:00:00 2001 From: dhruv8sh Date: Mon, 19 Oct 2026 14:23:39 +0530 Subject: [PATCH 4/6] Document the URL-host CI gate and its base-branch caveat Add the lint-domains job to CONTRIBUTING.md and /check-ci, and record the job's HEAD^1, depth-2, own-job design in the spec's Stage 2. Note in AGENTS.md and CONTRIBUTING.md that a PR into a branch other than main should use that branch as the --changed-vs base. Signed-off-by: dhruv8sh --- .claude/commands/check-ci.md | 1 + AGENTS.md | 2 +- CONTRIBUTING.md | 10 +++++++++- .../specs/2026-05-18-check-domains-design.md | 8 ++++++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.claude/commands/check-ci.md b/.claude/commands/check-ci.md index 62b6281400..5f258925ec 100644 --- a/.claude/commands/check-ci.md +++ b/.claude/commands/check-ci.md @@ -7,5 +7,6 @@ Run all CI checks locally, in order. Stop and report if any step fails. 5. `cd crates/trusted-server-js/lib && npx vitest run` 6. `cd crates/trusted-server-js/lib && npm run format` 7. `cd docs && npm run format` +8. `cargo run --manifest-path crates/trusted-server-cli/Cargo.toml --target "$(rustc -vV | sed -n 's/host: //p')" --bin ts -- dev lint domains --changed-vs origin/main` (use the PR's base branch in place of `main` when it targets another branch) Report a summary of all results when done. diff --git a/AGENTS.md b/AGENTS.md index 51ebc945fe..a40afe2532 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -364,7 +364,7 @@ Every PR must pass: 8. Markdown format outside `docs/` (requires `cd docs && npm ci` first): `docs/node_modules/.bin/prettier --config docs/.prettierrc --check "*.md" ".claude/**/*.md" ".github/**/*.md" "crates/**/*.md" "scripts/**/*.md" "tinybird/**/*.md"`; fix with `--write` in place of `--check` 9. Native core build-digest test and lint (`cargo test -p trusted-server-core --test template_build_digest` and `cargo clippy -p trusted-server-core --test template_build_digest -- -D warnings`) 10. Wasm clock lint: `scripts/lint-wasm-clock.sh` (crates built into the Cloudflare Worker must use `web_time`, not the `std::time::{Instant,SystemTime}` `now`/`elapsed` methods, which panic there) -11. URL-host linter on lines the PR adds (`lint-domains` job, PRs only); run it locally with `cargo run --manifest-path crates/trusted-server-cli/Cargo.toml --target --bin ts -- dev lint domains --changed-vs origin/main` +11. URL-host linter on lines the PR adds (`lint-domains` job, PRs only); run it locally with `cargo run --manifest-path crates/trusted-server-cli/Cargo.toml --target --bin ts -- dev lint domains --changed-vs origin/main`, using the PR's base branch in place of `main` when it targets another branch --- diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 50a7261528..17cd1b8442 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -176,9 +176,17 @@ To scan on demand, `ts dev lint domains` audits the whole working tree and `--changed-vs ` limits it to lines added against a ref. The full audit reports pre-existing violations, so a non-zero exit is expected today. +CI runs the same check on every pull request: the `lint-domains` job scans the +lines the PR adds against its base branch, so `--no-verify` or a missing hook +does not let a host through. Editing or moving a line that already contains a +non-allowlisted host counts as adding it. Reproduce the job locally with +`ts dev lint domains --changed-vs origin/main`, using the PR's base branch in +place of `main` when it targets another branch. + To allow a new host, add it to the allowlist constants in `crates/trusted-server-cli/src/commands/dev/lint/domains.rs`. To suppress a -single line, append `// allow-domain: ` in a comment. +single line, append `// allow-domain: ` in a comment +(`` in Markdown). ## Commits diff --git a/docs/superpowers/specs/2026-05-18-check-domains-design.md b/docs/superpowers/specs/2026-05-18-check-domains-design.md index 4ab3c49903..7aadafa2bd 100644 --- a/docs/superpowers/specs/2026-05-18-check-domains-design.md +++ b/docs/superpowers/specs/2026-05-18-check-domains-design.md @@ -1838,6 +1838,14 @@ delta-only enforcement, unbypassable. Requirements: - Reuse the host-target CI lane introduced by PR #669 (since `ts` binary is host-target only). +As implemented in #1254, Stage 2 differs from the plan above. It is +its own `lint-domains` job rather than a step in the host-target lane, +so branch protection can require it by name. It runs +`--changed-vs HEAD^1` on a `fetch-depth: 2` checkout rather than +`$GITHUB_BASE_REF` on a full clone: a PR checkout is GitHub's merge +commit, so its first parent is the exact base commit, and depth 1 +fails closed (exit 2). + **Stage 3 (optional, deferred):** Either (a) clean existing violations and add full-repo audit as a CI gate, or (b) snapshot a baseline file and run full-repo audit with baseline subtraction. Choice deferred From d2ac22179ca99217748d20654ef7b81cd75820ac Mon Sep 17 00:00:00 2001 From: dhruv8sh Date: Thu, 22 Oct 2026 20:15:41 +0530 Subject: [PATCH 5/6] Set up Rust in lint-domains through the shared action Signed-off-by: dhruv8sh --- .github/workflows/test.yml | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dbac9d1295..cf7935309a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -271,20 +271,14 @@ jobs: with: fetch-depth: 2 - - name: Retrieve Rust version - id: rust-version - run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT - shell: bash - - name: Set up Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v1 + uses: ./.github/actions/setup-rust with: - toolchain: ${{ steps.rust-version.outputs.rust-version }} - # Restore `test-cli`'s cache read-only. This job never runs on main, - # so a key of its own is only ever saved in PR scope, where no other - # PR can restore it. - cache-shared-key: cargo-cli-${{ runner.os }} - cache-save-if: false + # Restore `test-cli`'s cache read-only. The action only saves on + # main and this job only runs on PRs; `save-cache` keeps it + # restore-only if either changes. + cache-key: cargo-cli + save-cache: "false" # Building `ts` compiles trusted-server-core, whose trusted-server-js # build script runs the TSJS build with the `npm` on PATH, so pin Node From 4f1b8c6dc6df794197e0dd9014cd5f1f3df914c1 Mon Sep 17 00:00:00 2001 From: dhruv8sh Date: Thu, 22 Oct 2026 20:15:41 +0530 Subject: [PATCH 6/6] List every allow-domain comment form in CONTRIBUTING Signed-off-by: dhruv8sh --- CONTRIBUTING.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 505f86db4f..84b1d58a07 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -187,8 +187,9 @@ place of `main` when it targets another branch. To allow a new host, add it to the allowlist constants in `crates/trusted-server-cli/src/commands/dev/lint/domains.rs`. To suppress a -single line, append `// allow-domain: ` in a comment -(`` in Markdown). +single line, append `allow-domain: ` in the file's comment syntax: +`// allow-domain: `, `# allow-domain: `, +``, or `* allow-domain: `. ## Commits