Skip to content

Add ts dev lint domains and ts dev install-hooks to block non-allowlisted URL hosts - #733

Merged
aram356 merged 33 commits into
mainfrom
feature/check-domains-spec
Oct 8, 2026
Merged

aram356 merged 33 commits into
mainfrom
feature/check-domains-spec

Conversation

@aram356

@aram356 aram356 commented May 23, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #1160

Summary

  • Adds ts dev lint domains — a pure-Rust source/config/docs linter that flags non-allowlisted URL hosts in four modes (--staged, --changed-vs <ref>, full-repo, explicit paths). All git operations go through gitoxide (no shelling out to git). Captured URL authorities are canonicalised through the url crate (percent-decoding, IDNA, case, port) before the allowlist check, so lookalike authorities cannot pass on a prefix match.
  • Adds ts dev install-hooks — writes a managed pre-commit hook that runs ts dev lint domains --staged into git's own .git/hooks directory (the main repository's from a linked worktree). It never edits git configuration or the working tree; it refuses when core.hooksPath is set in any scope, refuses to clobber an unmanaged hook, and with --force backs the old hook up untouched.
  • Refactors the existing ts dev leaf into a subcommand group; lint and install-hooks sit next to proxy.

Design

Allowlists: EXACT_HOSTS, SUBDOMAIN_HOSTS, REFERENCE_HOSTS, plus RFC 2606 reserved TLDs. Suppression marker: // allow-domain: host (and #, <!--, * comment forms). Scanned extensions cover Rust, TS/JS, configs, Markdown, CSS, HTML, env files, and Dockerfiles, matched case-insensitively. Only the linter's own source, E2E suite, spec and plan are exempt. Exit codes: 0 clean, 1 violations, 2 environment error.

Evasion and bypass hardening

Review found four ways the tool could be defeated while reporting success. All are fixed, each with a regression test:

Issue Fix
P1 The hook read <git dir>/index, but git supplies a temporary index via GIT_INDEX_FILE for git commit -a and git commit -- <path>, so both forms committed violations that git add + git commit rejected Honour GIT_INDEX_FILE, falling back to repo.index_path()
P1 Host capture stopped early, missing //github.com%2eevil%2ecom, https://github.com­.com (an invisible soft hyphen) and https://%65vil.com — the first two reported an allowlisted prefix, the third nothing at all Capture the authority up to a real delimiter, then canonicalise; the protocol-relative pattern no longer ends in a suffix anchor that backtracks into an allowlisted prefix
P2 The JSON report echoed credentials from URLs with escaped soliduses (https:\/\/user:pw@host) Redactor accepts escaped soliduses
P2 An empty core.hooksPath was treated as unset, so installation reported success while git never read .git/hooks An empty value is refused like any other override

The wider authority capture is the change most likely to cause false positives, so it was checked against the whole repository: a full-repo audit reports exactly the same 484 violations as the pre-change build, byte-identical — no false positives, no lost detections.

Test plan

  • cargo fmt --all -- --check
  • cargo clippy --package trusted-server-cli --target <host> --all-targets --all-features -- -D warnings
  • cargo clippy-fastly, cargo clippy-axum
  • cargo test --package trusted-server-cli --target <host> — 351 tests across 8 suites, including real-git end-to-end tests for the installer (branch-planted .githooks/post-checkout stays inert, linked worktree shares the hook, global and empty core.hooksPath refused, temporary-index commit forms blocked)
  • Each bypass regression test confirmed to fail against the unfixed code
  • ts dev lint domains --changed-vs origin/main on this branch: clean
  • ts dev lint domains full audit: diagnostic only, reports the repo's pre-existing violations (no CI gate in v1)
  • cd docs && npm run format

@aram356
aram356 marked this pull request as draft May 23, 2026 20:19
@aram356 aram356 self-assigned this May 27, 2026
@aram356
aram356 deleted the branch main July 7, 2026 21:43
@aram356 aram356 closed this Jul 7, 2026
@aram356
aram356 deleted the feature/check-domains-spec branch July 7, 2026 21:43
@aram356
aram356 restored the feature/check-domains-spec branch July 9, 2026 22:07
@aram356 aram356 reopened this Jul 9, 2026
@aram356
aram356 changed the base branch from feature/ts-cli to main July 9, 2026 22:12
@aram356
aram356 force-pushed the feature/check-domains-spec branch from fcff9ed to 30f2cc1 Compare July 10, 2026 16:38
Port the pure-Rust URL-host linter (`ts dev lint domains`) and the
pre-commit hook installer (`ts dev install-hooks`) onto main's
restructured CLI. Both are cross-host (all git access via gitoxide, no
`git` subprocess) and register as siblings of the macOS-only
`ts dev proxy`.

Adapt to main's layout:
- Move the feature under `src/commands/dev/{lint,install_hooks}` and add
  the `Lint` / `InstallHooks` variants to `DevCommand`, replacing the
  prior empty-enum handling.
- Reintroduce a small `CliError` (`Io`, `Json`, `EnvironmentError`,
  `ViolationsFound`) plus `output::write_{stdout,stderr}_line` /
  `write_json`; `output` becomes cross-host while `info` / `warn` stay
  macOS-only.
- Map the linter's results to the exit contract (0 clean, 1 violations,
  2 environment error) in `commands::dev::run`.
- Scope `error-stack` / `derive_more` cross-host and add `gix` /
  `gix-config`; add `assert_cmd` / `predicates` / `temp-env` dev-deps.

The former `serve` subcommand is not restored — main replaced it with
`ts dev proxy` independently of this work.
@aram356
aram356 force-pushed the feature/check-domains-spec branch from 30f2cc1 to b0b6bd5 Compare July 15, 2026 00:30
aram356 added 4 commits July 16, 2026 20:13
Pin a fixed `user.name` / `user.email` in the repo-local config of the
git fixtures. `create_and_checkout_branch` writes a ref through
`repo.reference(...)`, whose reflog needs a committer identity; CI
machines have no ambient identity, so both `changed_vs` tests failed
with `CreateOrUpdateRefLog(MissingCommitter)`. Developer machines
passed only because they inherited a global git identity.

Repair the "Resolved by the Phase 2 spike" list in the design spec.
Its sub-bullets were glued onto preceding lines and its inline code
spans were split across line breaks, so prettier re-indented the block
deeper on every run and never converged, failing `format-docs`.
@aram356
aram356 marked this pull request as ready for review August 18, 2026 21:30

@ChristianPavilonis ChristianPavilonis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Review:

Summary

Reviewed the domain-lint and hook-installation changes. I found one confirmed self-exclusion defect, submitted inline.

Findings by priority

  • P1: 1 inline finding.

CI

All currently reported PR checks are passing. git diff --check is clean.

Existing Reviews

No existing submitted reviews were returned when checked; this review does not duplicate prior feedback.

Comment thread crates/trusted-server-cli/src/commands/dev/lint/domains.rs Outdated

@prk-Jr prk-Jr left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Adds ts dev lint domains (four scan modes, gitoxide-only) and ts dev install-hooks. The pure-function layer — host extraction, allowlist matching, suppression markers — is well factored and unusually well tested, including userinfo-bypass and rename regressions. Three defects block: the self-exclusion constant points at a path that no longer exists, so the linter flags its own source and this PR fails its own --changed-vs gate; install-hooks silently disables any hooks already in .git/hooks; and both commands error out when run from a subdirectory.

Verified against the PR head in an isolated worktree with a binary built from it (aarch64-apple-darwin): cargo fmt --all -- --check clean, cargo clippy -p trusted-server-cli --all-targets -- -D warnings clean, cargo test -p trusted-server-cli 236 + 28 + 4 + 29 + 1 pass. Evidence for each defect is in the inline comments.

1 of the inline comments below carries a one-click GitHub suggestion — use Commit suggestion to apply it as a commit on the PR branch. The remaining comments describe the fix in prose because the change spans several files, touches Cargo.lock, or needs a new helper, and can't be auto-applied.

Blocking

🔧 wrench

  • SELF_PATH points at a path that does not exist — self-exclusion is dead — see inline at crates/trusted-server-cli/src/commands/dev/lint/domains.rs:883
  • install-hooks silently disables every hook already in .git/hooks — see inline at crates/trusted-server-cli/src/commands/dev/install_hooks.rs:229
  • Both commands fail from any subdirectory (gix::open does not discover the repo root) — see inline at crates/trusted-server-cli/src/commands/dev/lint/domains.rs:995

❓ question

  • Stage-2 dogfooding policy for the linter's own test file and the two design docs — see the cross-cutting section below.

Non-blocking

♻️ refactor / 🤔 thinking / 🌱 seedling / ⛏ nitpick

  • Violation report prints the stale src/dev/lint/domains.rs hint path (suggestion) — see inline at crates/trusted-server-cli/src/commands/dev/lint/domains.rs:2054
  • temp-env dev-dependency added but never used — see inline at crates/trusted-server-cli/Cargo.toml:81
  • Four new deps bypass [workspace.dependencies] — see inline at crates/trusted-server-cli/Cargo.toml:41
  • Absolute-URL regex accepts single-label hosts; protocol-relative one does not — see inline at crates/trusted-server-cli/src/commands/dev/lint/domains.rs:341
  • test_support.rs and tests/common/mod.rs are byte-identical, kept in sync by comment — see inline at crates/trusted-server-cli/tests/common/mod.rs:8
  • Diff modes don't skip binary blobs, full-repo mode does — see inline at crates/trusted-server-cli/src/commands/dev/lint/domains.rs:950
  • --staged writes a tree object into the object database on every run — see inline at crates/trusted-server-cli/src/commands/dev/lint/domains.rs:1025

Cross-cutting / body-level findings

  • ❓ Stage-2 dogfooding policy. After the SELF_PATH fix, ts dev lint domains --changed-vs origin/main on this head still reports 91 violations across 3 of this PR's own new files: crates/trusted-server-cli/tests/lint_domains_cli.rs (30), docs/superpowers/specs/2026-05-18-check-domains-design.md (34), docs/superpowers/plans/2026-05-18-ts-dev-lint-domains.md (27) — all from the deliberate test.com / evil.com / cdn.example.evil fixtures the spec's own test cases require. What is the intended resolution: extend the exclusion list to the linter's test file and docs/superpowers/**, sprinkle allow-domain: markers, or accept that the Stage 2 changed-lines gate cannot be turned on until the Stage 1 cleanup covers these? Worth answering in this PR, because it decides whether the exclusion policy needs to grow before the gate exists.

  • 📌 Plan Phase 8 (documentation) did not ship. The plan's Task 8.1 (CONTRIBUTING.md — "Pre-commit URL-host linter" install steps) and Task 8.2 (README.md mention) have no counterpart in the diff, so nothing in the repo tells a contributor that ts dev install-hooks exists or that a pre-commit linter is expected. Either land those two doc edits here or open a follow-up so the install step isn't discoverable only from the design doc.

  • 📝 PR description's exit-code contract overstates the implementation. The description lists "130 cancelled", but there is no CliError::Cancelled variant and no 130 mapping — finish() in commands/dev/mod.rs maps ViolationsFound → 1, EnvironmentError → 2, everything else → 1. The spec leaves the 130 model explicitly undecided ("Pick one model"), so the code is self-consistent; only the description is ahead of it.

  • 👍 Genuinely strong adversarial testing. The userinfo-bypass regressions (https://github.com@test.com/path → test.com, https://a@b@c.evil → c.evil) close a real allowlist bypass; the pure-rename and rename+edit cases pin a bug that a naive path-map walk would reintroduce; and works_without_git_on_path (env_clear + empty PATH) actually proves the no-subprocess claim rather than asserting it. The suppression-marker bypass tests (allow-domain inside a URL path, host literally named allow-domain) are the kind of tests reviewers usually have to ask for.

CI Status

  • integration tests (Fastly EC lifecycle): PASS
  • integration tests: PASS
  • browser integration tests: PASS
  • CodeQL: PASS
  • Analyze (actions): PASS
  • Analyze (rust): PASS
  • Analyze (javascript-typescript): PASS
  • prepare integration artifacts: PASS
  • vitest: PASS
  • cargo check/build/test (spin native + wasm32-wasip1): PASS
  • cargo test: PASS (required)
  • cargo test (ts CLI, native): PASS
  • cargo test (axum native): PASS
  • cargo test (cross-adapter parity): PASS
  • cargo check (cloudflare native + wasm32-unknown-unknown): PASS
  • format-typescript: PASS (required)
  • format-docs: PASS (required)
  • cargo fmt: PASS (required)

Comment thread crates/trusted-server-cli/src/commands/dev/lint/domains.rs Outdated
Comment thread crates/trusted-server-cli/src/commands/dev/install_hooks.rs Outdated
Comment thread crates/trusted-server-cli/src/commands/dev/lint/domains.rs Outdated
Comment thread crates/trusted-server-cli/src/commands/dev/lint/domains.rs Outdated
Comment thread crates/trusted-server-cli/Cargo.toml
Comment thread crates/trusted-server-cli/Cargo.toml Outdated
Comment thread crates/trusted-server-cli/src/commands/dev/lint/domains.rs Outdated
Comment thread crates/trusted-server-cli/tests/common/mod.rs Outdated
Comment thread crates/trusted-server-cli/src/commands/dev/lint/domains.rs
Comment thread crates/trusted-server-cli/src/commands/dev/lint/domains.rs
Blocking fixes from review of #733:

- SELF_PATH pointed at the pre-move `src/dev/lint/domains.rs`, so the
  component-aware `Path::ends_with` never matched and the linter flagged
  its own fixtures (self-scan 38 violations, `--changed-vs origin/main`
  129). Correct the path and sweep every stale spelling across code,
  tests, and the two design docs. Extend the self-exclusion to the E2E
  test file and the `docs/superpowers/` design docs, whose deliberate
  `test.com` / `evil.com` fixtures are the remaining 91 — `--changed-vs
  origin/main` now reports 0 on this PR's own files.
- `install-hooks` set `core.hooksPath` without noticing executable hooks
  already in `.git/hooks`, silently disabling them. Add a preflight that
  refuses without `--force` (and prints a note under it), plus tests.
- `gix::open` does not discover the repo root, so every mode failed from
  a subdirectory with a misleading "not a git repository". Switch the
  three collectors and `install_hooks` to `gix::discover`; add a
  nested-subdirectory regression test.

Non-blocking cleanups:

- Skip non-UTF-8 blobs in diff modes, matching full-repo's binary skip.
- Drop the unused `temp-env` dev-dependency.
- Move the `gix` / `gix-config` / `assert_cmd` / `predicates` version
  pins into `[workspace.dependencies]` so the pin is enforceable.
- De-duplicate the fixture helpers: `tests/common/mod.rs` now includes
  the single `test_support.rs` via `#[path]` instead of a byte-identical
  copy kept in sync by comment.
- Document the `--staged` tree-object write and the intentional
  single-label-host asymmetry in the spec.
- Land the Phase 8 docs: `ts dev install-hooks` steps in CONTRIBUTING.md
  and a mention in README.md.
aram356 added a commit that referenced this pull request Sep 6, 2026
Blocking fixes from review of #733:

- SELF_PATH pointed at the pre-move `src/dev/lint/domains.rs`, so the
  component-aware `Path::ends_with` never matched and the linter flagged
  its own fixtures (self-scan 38 violations, `--changed-vs origin/main`
  129). Correct the path and sweep every stale spelling across code,
  tests, and the two design docs. Extend the self-exclusion to the E2E
  test file and the `docs/superpowers/` design docs, whose deliberate
  `test.com` / `evil.com` fixtures are the remaining 91 — `--changed-vs
  origin/main` now reports 0 on this PR's own files.
- `install-hooks` set `core.hooksPath` without noticing executable hooks
  already in `.git/hooks`, silently disabling them. Add a preflight that
  refuses without `--force` (and prints a note under it), plus tests.
- `gix::open` does not discover the repo root, so every mode failed from
  a subdirectory with a misleading "not a git repository". Switch the
  three collectors and `install_hooks` to `gix::discover`; add a
  nested-subdirectory regression test.

Non-blocking cleanups:

- Skip non-UTF-8 blobs in diff modes, matching full-repo's binary skip.
- Drop the unused `temp-env` dev-dependency.
- Move the `gix` / `gix-config` / `assert_cmd` / `predicates` version
  pins into `[workspace.dependencies]` so the pin is enforceable.
- De-duplicate the fixture helpers: `tests/common/mod.rs` now includes
  the single `test_support.rs` via `#[path]` instead of a byte-identical
  copy kept in sync by comment.
- Document the `--staged` tree-object write and the intentional
  single-label-host asymmetry in the spec.
- Land the Phase 8 docs: `ts dev install-hooks` steps in CONTRIBUTING.md
  and a mention in README.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@aram356

aram356 commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator Author

Review addressed — a05cf2a

Thanks for the thorough pass. All three blocking findings and the non-blocking cleanups are resolved; each inline thread has a reply. Summary of the cross-cutting / body-level items:

Stage-2 dogfooding policy (❓). Resolved by extending the self-exclusion rather than sprinkling markers. After the SELF_PATH fix, the remaining 91 violations were all deliberate fixtures in the linter's own E2E test file and its two design docs, so path_is_scanned now also skips crates/trusted-server-cli/tests/lint_domains_cli.rs and the docs/superpowers/ tree (internal design prose that quotes test.com/evil.com as examples of what the linter catches). Result: ts dev lint domains --changed-vs origin/main on this head reports 0 — the Stage-2 changed-lines gate can be turned on without a separate Stage-1 cleanup.

Plan Phase 8 docs (📌). Landed here: CONTRIBUTING.md gains a "Pre-commit URL-host linter" section with the ts dev install-hooks / ts dev lint domains steps, and README.md now points contributors at it.

PR description exit-code (📝). Corrected — the description no longer claims "130 cancelled"; it now matches finish() (0 clean / 1 violations / 2 environment error). The 130 model stays explicitly undecided in the spec.

Verified on aarch64-apple-darwin: cargo fmt --all -- --check clean, cargo clippy -p trusted-server-cli --all-targets -- -D warnings clean, cargo test -p trusted-server-cli green (239 lib + 28 + 29 + 1 + 4), docs prettier clean, and the self-scan / subdirectory / changed-vs behaviors checked against a binary built from this head.

Resolve the Cargo.lock conflict by re-resolving against the merged
manifests. Pin kstring to 2.0.2: 2.0.4 raises its MSRV to rustc 1.96,
which the workspace's pinned 1.95.0 toolchain cannot build. kstring
arrives transitively via gix-config.
The plan linked the spec as a sibling, but it lives in `specs/`, not
`plans/`. VitePress builds with the dead-link gate main added in #1066,
so this failed the `format-docs` job. Use the `../specs/` form the rest
of `docs/superpowers/` already uses.
@aram356
aram356 force-pushed the feature/check-domains-spec branch from 7c0cc50 to 1c2914a Compare September 8, 2026 05:28
@aram356

aram356 commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator Author

Round 4 feedback addressed in 944df4d — both findings from @prk-Jr. Each was reproduced against a built CLI before any change and re-verified against the same reproduction afterwards; replies are on the individual threads.

Host extraction (; , ( ) ') — all are accepted inside a hostname by the WHATWG parser, so terminating the capture on them handed the allowlist an example.com prefix.

The review warned that deleting delimiters would trade the bypass for Markdown and source-punctuation false positives. I measured rather than guessed: removing every soft delimiter added 115 new violations across a full-repo scan (almost all format!("https://{host}{path}")), and narrowing to just these five still left 33. So the split is by position, not by character:

  • { } [ ] stay terminators — braces enclose interpolation, and the URL parser rejects brackets outside the IPv6 branch the regex captures separately.
  • The rest are admitted into the capture and handled after it: everything from a ' onwards is cut (a quote inside an authority marks where the source string closed and the rest is code), then a trailing $#*(),;!&= run is trimmed, extending the previous round's interpolation trim.

Only the leading label and trailing run are affected, so embedded occurrences still reach the allowlist — tests assert example.com;unapproved.internal, example.com(unapproved.internal and github.com$unapproved.internal are each reported whole, so the trims cannot become a new bypass.

IDNA TLDs — applied the suggestion. Canonicalisation maps a Unicode TLD to punycode, whose xn-- prefix carries hyphens and digits, so the ASCII-alphabetic-only test discarded it. A test asserts the protocol-relative result equals the absolute form so the two paths cannot drift again.

Effect on the repo: net zero. The violation count is unchanged at 486, with two lines now reporting the more accurate origin.(example).com where the old class truncated to origin.

Also in this push: merged main, which had rewritten CONTRIBUTING.md from 174 lines to 61 — I took that rewrite wholesale and re-added the pre-commit-linter documentation condensed into its new Verify section. The PR is MERGEABLE again.

Verification — every CI gate run locally, all green: cargo fmt --all --check; all eight clippy gates including the new clippy-cli / clippy-codegen; all four adapter suites; parity; the CLI suite — 3876 tests, 0 failures; plus 1130 vitest, JS and docs format. Coverage added for both findings in unit tests and in staged mode (the pre-commit path), plus a multi-byte-host test since the quote cut slices by byte index and IDNA makes that reachable.

@aram356
aram356 requested a review from prk-Jr September 24, 2026 07:07

@prk-Jr prk-Jr left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One blocking correctness issue: consecutive single-quoted URLs can cause the linter to silently skip a disallowed host, including in staged mode.

Validation: reproduced against the reviewed commit; the suggested one-line replacement makes the reproduction report the violation and exit 1. All 104 linter unit tests and 38 CLI integration tests pass with the replacement. Scratch changes were restored. All 20 GitHub checks passed when inspected during review.

Comment thread crates/trusted-server-cli/src/commands/dev/lint/domains.rs Outdated
Address review round 5 on PR #733, applying the suggested one-character
change to the terminator class.

`'` was left out of the class in the previous round because it is legal
inside a WHATWG hostname, with a trailing one trimmed after capture
instead. That was wrong in a way the trim could not repair: a match that
does not stop at the quote runs past the end of its own string and
consumes the next URL's scheme, and `captures_iter` cannot revisit input
an earlier match already consumed.

    const urls = ['https://example.com','https://unapproved.internal'];

captured `example.com','https:` as a single authority. Trimming at the
quote recovered `example.com`, but the second host was never examined, so
the linter exited 0 on a disallowed host in explicit-path and staged
modes. Reproduced first: a list of two disallowed hosts reported only the
first, which is a silent false negative and the one outcome this linter
must not have.

Terminating at the quote costs nothing. Measured on a full-repo scan with
only this change applied: 486 violations before and after, no additions
and no removals. The trailing-quote shapes that motivated the previous
round's approach (`expect(new URL('https://host').href)`) are now handled
by the terminator rather than the trim, which is the stronger guarantee
since it also bounds the match.

With the quote terminating the capture, the trim's leading `find('\'')`
cut became unreachable. Verified rather than assumed, by asserting on it
across a full-repo scan and all unit tests before deleting it, and
renamed the multi-byte test that referred to it. A URL inside a quoted
string cannot contain the quote anyway.

Regression coverage for consecutive single-quoted URLs in unit tests,
including three-host lists, double quotes, and mixed quoting, plus a
staged-mode CLI test since that is the path the pre-commit hook runs.
Spec updated to record why the quote is a terminator despite being
host-legal.
@aram356

aram356 commented Sep 25, 2026

Copy link
Copy Markdown
Collaborator Author

Round 5 feedback addressed in fcba41d — the single blocking finding from @prk-Jr, applying the suggested one-character change.

The bug was mine from round 4. I had removed ' from the terminator class because it is legal inside a WHATWG hostname, trimming a trailing one after capture instead. That was wrong in a way the trim could not repair: a match that does not stop at the quote runs past the end of its own string and consumes the next URL's scheme, and captures_iter cannot revisit consumed input.

Reproduced before changing anything, and it was slightly worse than reported — a list of two disallowed hosts reported only one:

['https://unapproved.internal','https://other.internal']  ->  ['unapproved.internal']

Both are reported now. A silent false negative is the one outcome this linter must not have.

Cost of the fix: zero. Since ' was removed last round specifically to avoid false positives, I measured before accepting: a full-repo scan with only this character changed gives 486 violations before and after, no additions, no removals. The trailing-quote shapes that motivated the trim (expect(new URL('https://host').href)) are now handled by the terminator, which is strictly better — it bounds the match instead of repairing it afterwards.

Dead code removed. With the quote terminating the capture, the trim's leading find('\'') cut became unreachable. I verified that by asserting on it across a full-repo scan and all unit tests before deleting, rather than assuming, and renamed the multi-byte test that referenced it.

Verification — all gates green locally: cargo fmt --all --check; all eight clippy gates; all four adapter suites; parity; CLI suite — 3878 tests, 0 failures; plus 1130 vitest and JS/docs format. Regression coverage for consecutive single-quoted URLs (three-host lists, double quotes, mixed quoting) and a staged-mode CLI test. Re-confirmed the earlier rounds' reproductions still pass. Spec records why the quote terminates despite being host-legal, so this does not get re-removed in a future round.

@aram356
aram356 requested a review from prk-Jr September 25, 2026 20:59
Three conflicts, all in the `ts` CLI crate: main added a `ts dev
sandbox-probe` subcommand that touches the same files as this branch's
`ts dev lint` and `ts dev install-hooks`. Every conflict was additive,
so each resolved to the union.

- `Cargo.toml`: kept this branch's `gix` block in the cross-host
  `cfg(not(target_arch = "wasm32"))` section, since the linter runs on
  every host, and took main's new `cfg(not(target_family = "wasm"))`
  probe section and its widened macOS+Linux proxy section verbatim.
  Dev-dependencies merged to the union of `assert_cmd`, `predicates` and
  main's `flate2`.
- `src/commands/dev/mod.rs`: union of the `DevCommand` variants and
  their match arms, so `proxy`, `sandbox-probe`, `lint` and
  `install-hooks` all remain. Took main's widened
  `cfg(any(target_os = "macos", target_os = "linux"))` gate for the
  proxy module and updated the comment that still called it macOS-only.
- `src/lib.rs`: both sides rewrote the same module comments. Merged them
  into one description covering the proxy's widened targets and the
  cross-host `output` helpers that both the linter and the probe need.

Git also auto-merged both sides' identical addition of `error-stack` to
the cross-host dependency block into two lines, which is a duplicate key
`cargo` rejects rather than a conflict git reports. Caught by
`cargo metadata --locked` and de-duplicated; a scan for other doubled
keys found none.

Verified the merge changed no linter behaviour: running the pre-merge
binary and the merged binary over the merged tree gives identical
results (518 violations, no additions, no removals). The rise from 486
is main's new source, not this branch. `ts dev --help` lists all four
subcommands, and the reproductions from every earlier review round still
report their hosts.
The `main` merge broke the Linux CLI build: `cargo clippy -D warnings`
failed with 17 errors, all variants of `cannot find function 'info' /
'warn' in module 'output'`.

This branch added `#[cfg(target_os = "macos")]` to `output::info` and
`output::warn` in an earlier round, when `ts dev proxy` was their only
caller and was macOS-only, so an ungated pair warned as dead code
elsewhere. Main has since widened the proxy to macOS and Linux and added
`ts dev sandbox-probe`, which builds on every host target and calls both
helpers. The gates became wrong, and the premise that justified them no
longer holds.

Git reported no conflict in `output.rs` because only this branch had
touched it since the merge-base, so the stale gated version won silently.
The merge compiled on macOS and could not compile on Linux, which is why
the local gate run came back clean. The `lib.rs` comment is corrected to
match: both helpers are cross-host now.

Verified on the target that failed rather than by inference, using the
pinned 1.95.0 toolchain in a linux/amd64 container:
`cargo clippy -p trusted-server-cli --all-targets -- -D warnings` exits 0,
and the library test suite goes from a compile failure to 721 passing.
Two remaining failures in that container are artifacts of it rather than
defects: `permission_denied_is_hard_error` needs a non-root user to make
`chmod 000` deny a read, and
`missing_node_modules_fails_with_npm_ci_instruction` needs npm on PATH.
Both pass on main's own ubuntu CI job, and `prebid_bundle.rs` is not part
of this branch's diff. macOS remains clean: `cargo clippy-cli` passes and
887 CLI tests pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Block non-allowlisted URL hosts from entering the repository

3 participants