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/.github/workflows/test.yml b/.github/workflows/test.yml index 6cd9953281..6aa5de7605 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -335,6 +335,45 @@ 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: Set up Rust toolchain + uses: ./.github/actions/setup-rust + with: + # 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 + # 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 --verbose + test-typescript: name: vitest runs-on: ubuntu-latest diff --git a/AGENTS.md b/AGENTS.md index a71272db08..0aa07ba2dc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -373,6 +373,7 @@ Every PR must pass: 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. Documentation proposal scripts: `shellcheck scripts/docs-proposal/*.sh && node --test scripts/docs-proposal/hunks.test.mjs && scripts/docs-proposal/test.sh` +12. 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 CI also runs these HTML EOF checks. Run them locally only when changing EOF finalization, publisher or HTML-processor code: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cde75feaca..84b1d58a07 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -178,9 +178,18 @@ 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 the file's comment syntax: +`// allow-domain: `, `# allow-domain: `, +``, or `* allow-domain: `. ## 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