Repository navigation
Remove Team Token PR2.1: Host-route admission security prerequisite - #797
josue-tech-94 merged 25 commits into
Conversation
5f3bd3c to
2bfe0b8
Compare
PR Review — Remove Team Token PR2.1: Host-route admission security prerequisite (Spec 65)Review verdictNO — a merge blocker and confirmed regressions remain. Reviewed the complete diff at base PR-introduced issue count
FindingsRP797-001 — Blocker —
|
2bfe0b8 to
ceaed99
Compare
ceaed99 to
c869d9f
Compare
Add a durable verified-subject budget before protected Host-RPC work. The same 300-per-fixed-minute bucket is shared by legacy adapters and v2 checkpoints; Host messages and artifacts retain their existing policies. - Add strict PostgreSQL fixed-window admission and a narrow legacy adapter. - Charge the closed v2 non-message operation set before live authorization. - Add canonical request-local route preflight before checkpoints and admission. - Keep exact local binding and remote checkpoint as separate reused stages. - Preserve stream concurrency and honor Retry-After on reconnects. Class-check: Spec 65 Host-RPC preflight/admission registrations -> 18 sites; all 18 non-message routes use staged preflight; messages remain Spec 62 and artifact list/download remain Spec 48. - Control API: 511 files, 6,163 tests passed; 365 skipped; build passed. - RPC Proxy: 56 files, 693 tests passed; build passed. - Desktop: Electron verified; 74 focused tests and full build passed. - Action-context contracts: 11 tests and build passed. - Code review SAFE; security review SAFE; collateral CLEAN. - Local Real PostgreSQL DSN unavailable; exact-head CI Real-PG is required. - Changed-file Prettier, style-rules, and git diff --check passed. - Fixed-window boundary bursts are accepted by Spec 65. - Exact-head Real PostgreSQL remains a publication acceptance gate.
Keep generic #766 backend-unavailable results separate from strict Spec 65 admission. Both paths use one bounded, rateLimitPool-backed fixed-window SQL primitive. Class-check: generic pool, strict callers, cleanup, and limiter tests.
Share model and rename validators across RPC Proxy and MCP Host. Reject deterministic invalid input before admission, checkpoint, or resolver work. Class-check: Spec 65 model writes and session renames on legacy and v2 paths. Repro-test: buggy head 1ba67d6 returned 403 after admission → live-resolver for an invalid legacy rename; the permanent assertion is green.
Class-check: shared session-title preflight across context-breakdown and rename; validate only rename while context-breakdown remains path-only. Repro-test: exact-head CI at d5d0b7e failed nine RPC Proxy assertions; focused mounted suites now pass 111/111 and the full suite passes 697/697. R2-B1: restore context-breakdown behavior and retain canonical rename validation.
Class-check: approval ID fallback -> 2 routes -> both preflight before admission; v2 binding first. Repro-test: rpc-proxy/src/__tests__/host-rpc-admission-order.producer.test.ts fails at e28816b (400 expected, 403 observed).
Class-check: host-rpc-admission gateway routes -> 1 production location -> exact POST only. Default deny is preserved; no broad internal route is exposed. Repro-test: scripts/tests/test-mcp-host-action-checkpoint-gateway.sh fails at 36085ac with the expected missing exact admission location, then passes after correction.
Class-check: host-rpc-admission scope allowlist -> 1 route registration -> include the required wake scope while preserving all other scope checks. Repro-test: control-api/test/hostRpcAdmissionCheckpoint.test.ts fails at 2d87246 (204 expected, 403 observed).
Class-check: Host-RPC admission 429 header coercions -> 4 headers -> require explicit canonical numeric values for each allowlisted field. Repro-test: rpc-proxy/src/__tests__/host-rpc-admission-transport.test.ts fails at d5a2826 (missing/hex values returned 429 instead of typed 503).
Class-check: sessions cursor decode/scope implementations -> 1 canonical contract and 2 consumers -> share one parser across MCP Host and RPC Proxy. Repro-test: rpc-proxy/src/__tests__/host-rpc-admission-order.producer.test.ts fails at c87a378 (malformed cursor returned 403 after admission instead of the existing 400).
Class-check: Host activity limit parsing in RPC Proxy and MCP Host -> 2 boundaries -> use one shared positive-finite validator and retain the validated value. Repro-test: rpc-proxy/src/__tests__/host-rpc-admission-order.producer.test.ts fails at ea2764f (invalid limits charged admission and reached Host resolution).
Class-check: Host-ref preflight checks -> 8 route classes -> apply the existing safe path rule and wildcard/percent rejection to task progress streams; leave other route policies unchanged. Repro-test: rpc-proxy/src/__tests__/host-rpc-admission-order.producer.test.ts fails at 713a4f1 (invalid refs reached admission and resolver, returning 403 instead of 400).
Class-check: strict admission uses checkAndIncrementStrict; generic callers keep their existing fallback. Repro-test: test/services.rateLimiter.test.ts fails at a5604b3 because strict store failure records no metric/log.
Class-check: session-message Host, agent, and chat path identity are checked before pagination; valid paths retain the pagination error. Repro-test: host-rpc-admission-order.producer.test.ts fails at d90830a because a malformed Host path plus limit returns the pagination error instead of the established path error.
Class-check: status, activity, and progress read-only stream preflight all use the shared body guard; reject any transfer-encoded request body before admission. Repro-test: host-rpc-admission-order.producer.test.ts fails at a15e0fa because a raw chunked GET without Content-Length reaches admission and live Host resolution instead of the established 400.
Class-check: normalize first, then preserve the 120-code-point/512-byte title contract; reject normalized UTF-16 length above 240 before allocating an unbounded code-point array. No raw-length cutoff. Repro-test: action-context-contracts/index.test.cjs fails at 1368c8f because the 121-code-point title calls Array.from instead of taking the bounded fast path.
Class-check: valid passthrough cursor fixtures use the shared producer; malformed cursors stay 400. Repro-test: passthrough.test.ts fails at cb4038d: literal cursor current is rejected. The stale fixture gets 400 instead of its expected 200 forwarding response.
Reconcile the replayed child tree to the explicit three-way integration oracle.
c869d9f to
65182d3
Compare
Stable-ID: R27-M1 Class-check: /rpc/hosts/:hostRef public class -> 21 registrations -> one shared W1 validator covers all 21; Desktop and internal Control API routes remain outside Spec 61. Repro-test: mounted invalid_host assertions fail on 18/20 legacy-capable registrations at 730a6eb; both artifact registrations were already-400 positive controls.
Separate the existing local v2 route binding from remote checkpoint execution for the 21 Spec 61 Host-ref routes so shared W1 validation runs between them. Non-Host v2 callers retain the original checkpoint path. Class-check: requireScope calls in the 21 Host-ref registrations -> 21 sites -> all defer only the remote checkpoint until after shared W1; non-Host callers remain unchanged. Repro-test: rpc-proxy/src/__tests__/host-ref-validation.test.ts rejects a producer-signed target with a whitespace Host ref before checkpoint I/O; RED at 490b0e3.
Class-check: w1PayloadRoutes in host-ref-validation.test.ts → 4 route classes (approval approve/deny, model write, session rename). Valid bodies exercise W1; valid-host controls cover all four. Malformed-body precedence remains separate under Spec 61. Repro-test: rpc-proxy/src/__tests__/host-ref-validation.test.ts:197 (fails at 6173730 when W1 is bypassed; each assertion expects exact 400 but receives the downstream result).
PR2.1 — Host-route admission security prerequisite
Stack position / purpose
PR1 (#334) → PR2 (#594) → PR2.1 (this PR) → PR2.2 (#795) → PR3 → PR4
PR2 is the direct base at 2d6f34b. This prerequisite preserves Spec 65 in PR2.1: the standalone PR2 security analysis exposed 17 genuine pre-resolution non-message Host-route admission gaps assigned to this child. Those gaps predate PR #795; they are not defects introduced by its W1 change, and they are not false positives. PR2 is intentionally not independently landable until composed with this security-completion layer. PR2.2 / #795 remains the downstream Host-ref/W1 unit and is now based on this PR2.1 head.
Problem
Several non-message
/rpc/hosts/:hostRef...operations could reach live Host resolution/checkpoint work without a durable request-frequency admission boundary ahead of that work. These were R0/base-equivalent relative to PR #795. Host messages were already protected by Spec 62, and artifact routes remain governed by Spec 48.Frozen Spec 65 contract
CONTROL_API_HOST_RPC_ADMISSION_RL_PER_MIN; positive finite integer only.{"error":"host_rpc_admission_unavailable"}503.Retry-After,X-RateLimit-Limit,X-RateLimit-Remaining, andX-RateLimit-Resetheaders.Ordering
Legacy requests preserve existing authentication and scope/body precedence, then run canonical route preflight, Spec 65 admission, live resolver/authority, and effect.
V2 requests run authentication/parser gates, exact local v2 binding, canonical route preflight, remote checkpoint, strict checkpoint validation, Spec 65 admission, live authorization, destination resolution, and effect.
Deterministically invalid route input consumes zero units. Exact v2 binding precedes preflight; valid input later denied by current live authority consumes one unit. Route validation is single-sourced and its normalized result is retained for the handler. Non-Spec-65
requireScopecallers retain their existing atomic behavior.In the current PR2.2 composition, its W1 stage runs as exact local binding → canonical preflight → W1 → remote checkpoint → applicable admission → live authority/destination → effect. W1 is implemented in downstream PR2.2, not in this PR.
Scope
Covered non-message classes: wake; approval writes; session search/read/rename; model read/write; task result/cancel; activity/status/health reads; and stream opens.
Excluded: Host messages / Spec 62; artifact list/download / Spec 48; #674/W1 implementation; unrelated Control API rate-limit behavior; PR3; broad refactors.
Implementation
The implementation lineage is R27-M1
ee05bd3404d4d0744d5f00a9832507819171dc1f, pure PR2 integration0803435a12d73730ee0035ddf9027db695c6325b, generic-limiter reconciliation1ba67d6202e224cf792bbdd486a23b08084b18b7, R1-B1 correctiond5d0b7eea1b333a0ac60dc681daa2fdbd4f27232, and R2-B1 correctione28816ba58d1fcc25573519d34513ad3b5c36377. R1-B1 shares model-selection and session-title validators between RPC Proxy preflight and MCP Host runtime validation; R2-B1 keeps title validation on rename while context-breakdown remains path-only. The current diff from accepted PR2 contains 62 files, including canonical hook-generated version/lock and release-manifest coordinates.Test / security proof
d5d0b7eea1b333a0ac60dc681daa2fdbd4f27232: shared model/title validation rejects deterministic invalid input before admission/checkpoint/resolver work. Mounted tests prove legacy invalid model/title requests return 400 with zero admission/resolver calls; valid v2 binding plus invalid title returns 400 before checkpoint/admission/resolver; invalid signed model target remainsinvalid_bindingbefore preflight. Bypassing canonical title preflight turns the permanent mounted assertion RED (expected 400, observed 403). Exact local binding denial retains precedence and valid input later denied by live authority still consumes admission.Local validation
git diff --checkpassed.Recovered linear exact-head status
c869d9f1a70752ff29cb3958790cd26e7e218a09, based on PR2Remove Team Token PR2: Runtime actions use validated access paths #594 at
2d6f34b4021a994afd38033192fcde6885ea0204. PR2 is based on PR13a9d2d1dc75d79800e5847df5c19dd97e9c68961, whose base is frozen latestdev
21050a2abc459e6c3a8441c024187eb3e3987f04.the downstream PR2.2 head is
0fbfc9a3aa00ea878f4c2698cb8f61a692ebcb27.Remote bases and ancestry agree; the complete stack has zero merge commits
from the frozen dev snapshot.
62 files. Spec 65 still covers the 17 genuine standalone-PR2 admission gaps;
messages remain Spec 62 and artifacts remain Spec 48.
36480967121, including Real PostgreSQL job109126211249, passed. CodeQL36480966777/ Analyze109126210790passed, and required GHAS109133412086passed with zeroannotations. The required result reports no new alerts in code changed by
this PR.
branch; no current changed-code alert remains for this head. No alert was
dismissed or otherwise changed in this sync.
objection at current head
c869d9f1a70752ff29cb3958790cd26e7e218a09; this carries forward thesemantic acceptance for the synchronized layer. GitHub still marks this PR REVIEW_REQUIRED,
so human approval remains outstanding. No GitHub review or approval was published by this task.
not started. Effective deployment configuration remains a deployment-only
verification gate.
Historical exact-head status (superseded by the current exact-head status above)
Issue #801 remains a verified inherited pre-PR3 behavior from PR1; its lifecycle remains open pending coordinated landing in
dev.The current published head is
e28816ba58d1fcc25573519d34513ad3b5c36377on accepted PR2 base0b40a4301d2c1fa459135c240e66c39497451022. The prior head2ed638a003a297a94d5302540a9e2d6be63f4646CI and CodeQL/GHAS results remain historical.The intermediate head d5d0b7e CI run 36086705879 failed RPC Proxy job 107920095814; its Real PostgreSQL job 107920095256 succeeded. R2-B1 corrected the misplaced rename validator in e28816b. Current-head CI 36087548373 passed all jobs, including Real PostgreSQL 107922683455. CodeQL run 36087657660 / Analyze 107923015181 succeeded for branch fix/host-rpc-admission-spec65, ref refs/heads/fix/host-rpc-admission-spec65, and SHA e28816b, category /language:javascript-typescript; uploaded analysis 1837033301. Required CodeQL check 107927008949 succeeded with zero annotations and reports no new alerts in code changed by this PR. Existing branch-wide alerts were not dismissed in this pass.
Prior-head exact gate history (historical only): CI
36011314158succeeded, including Shell syntax107672448732, Real PostgreSQL107672448891, Control API107672449786, RPC Proxy107672449461, Desktop107672449429, shared action-context contracts107672449650, and repository hygiene107672448804; the reported matrix was green. CodeQL36011521542/ Analyze107673169072succeeded forfix/host-rpc-admission-spec65, refrefs/heads/fix/host-rpc-admission-spec65, category/language:javascript-typescript. Required check107680576170succeeded. Alert 1521 (js/request-forgery, Critical) atrpc-proxy/src/routes/rpcHostProgressStream.ts:110-120was individually dismissed as a structural false positive after fixed-destination, producer-backed validation and mutation evidence. Alerts 1522–1539 were not generated changed-code annotations and were not dispositioned. All of this applies only to prior head2ed638a003a297a94d5302540a9e2d6be63f4646, not current acceptance.For older history, CodeQL workflow
35956661657succeeded atee05bd3404d4d0744d5f00a9832507819171dc1f, but required check107500130065failed on a critical SSRF annotation atrpc-proxy/src/routes/rpcHostProgressStream.ts:110-120. That old-head alert remains historical/unadjudicated and is not current evidence.At that historical stage, PR2.1 was not accepted, merged, or landed and its Round 2 formal review was pending; the current status above supersedes that snapshot.
Landing / stack sequence