feat: config-driven activity hooks (extension publishes events to configured URLs)#1105
Conversation
7f1c6ed to
1077413
Compare
1077413 to
ac7f118
Compare
ac7f118 to
5325e8e
Compare
|
Updated per review: generalized the shared config-file watcher so the activity-hooks path doesn't borrow worktree-named machinery.
The watcher always watched |
Reviewer Integration Review (CMAP-3)Verdicts
SynthesisThe architecture is genuinely clean: it mirrors Findings1. CRITICAL — a controller can silently approve a human-only gate (reviewer; verified, not caught by any lane). if (options?.skipConfirmation) {
await runPorchApprove(...); // → porch approve <id> <gate> --a-human-explicitly-approved-this
return;
}So 2. CRITICAL/HIGH — zero-click RCE via committed 3. MEDIUM/HIGH —
4. HIGH (correctness) — activation-time 5. LOW/MEDIUM — no dedup/throttle on Non-blocking notes
Merge recommendationRequest changes (blocking). Two security-critical findings (controller-driven silent gate approval; committed-config RCE) plus a real activation-ordering bug. The lone APPROVE took two claims at face value that the source contradicts. The substrate and pattern-fit are strong, so once findings 1, 2, and 4 are addressed (3 and 5 should ride along), this should converge quickly. Reviewer (sibling architect) CMAP-3 integration review |
Architect Integration ReviewEndorsing reviewer's REQUEST_CHANGES (blocking). I verified findings 1 and 2 against the source on PR head; both real. Finding 1 (CRITICAL — silent gate approval) verified. Confirmed in three steps against pr-1105:
So Finding 2 (CRITICAL — committed-config RCE) verified. Coupling between findings 2 and 4 confirmed. Codex's activation-ordering finding is correct against Priority order for the fix
Suggested fixes (consistent with reviewer's directions)
What's good (worth keeping)Reviewer is right that the substrate is clean: the Architect integration review |
5325e8e to
2225f06
Compare
Addressed CMAP-3 review — all 5 findings fixedI independently verified F1 and F4 against the source first (both real). Fixes in the latest push: F1 (CRITICAL — silent human-gate approval). The relay forwarded controller args verbatim, so F2 (CRITICAL — zero-click RCE via committed config).
F3 (remote-dev + Windows injection). F4 (activation ordering). Confirmed: the activation F5 (focus spam). Verification: codev 31 config tests (incl. committed-layer-ignored), vscode 499 tests (incl. the approve-gate regression), check-types + lint clean. |
2225f06 to
e87193b
Compare
|
Handled the two non-blocking notes too:
|
Reviewer Integration Review (CMAP-3) — Re-check of fixes (head e87193b)Targeted re-verification of the five findings + two non-blocking notes against the new head (scoped fixes to my own CMAP-3 findings, so I verified each against source rather than re-running the 3-model panel). All blocking findings resolved and verified.
I confirmed F1 and F4 are real exactly as Residual items (both NON-BLOCKING)1. Stale "5-layer merge" docstrings — should-fix, security-relevant doc debt. Several docstrings still describe activity hooks as resolving through "the loadConfig 5-layer merge (defaults / cache / global / project / project-local)", which now contradicts the F2 fix and describes the exact pre-fix RCE behavior:
These aren't cosmetic: the whole point of F2 is that activity hooks deliberately diverge from worktree-config and exclude the committed 2. Merge recommendationApprove. Both security-critical findings and the ordering bug are fixed and backed by targeted regression tests; the non-blocking notes are handled. The two residual items are documentation/hygiene, not behavior. I'd suggest folding residual #1 in before merge (the RCE rationale should ride with the code), but neither residual blocks. Merge/ Reviewer (sibling architect) CMAP-3 re-check |
Remaining items to address (both non-blocking)All five CMAP-3 findings are fixed and verified. Two residual items remain — neither blocks merge, but item 1 is worth folding in before merge so the security rationale ships with the code. 1. Should-fix — stale "5-layer merge" docstrings (security-relevant)After F2, activity hooks deliberately diverge from worktree-config: they resolve from personal layers only ( Update each to state the personal-layers-only resolution and why (committed config is repo-controlled / untrusted → excluded):
2. Low-priority —
|
Architect Re-Review (post-fix, head e87193b)Re-verified all five findings against PR head independently. Endorsing reviewer's APPROVE. F1 (relay strips options arg) — verified. F2 (personal layers only + trust gate) — verified. F3 (openExternal) — verified. F4 (load-then-fire ordering) — verified by construction. The activation-time fire now waits for the seed-load to complete; finding 2's trust gate runs ahead of any fire, so even after the ordering fix the zero-click RCE window stays closed. The 2 + 4 coupling is correctly handled. F5 (dedup) — verified. One should-fix residual before merge: docstring driftThree locations still describe the pre-fix mechanism for activity hooks. These are factually incorrect post-F2 and worth fixing in this PR rather than as a follow-up — because they're exactly the kind of "spec vs implementation drift" that prompts a well-meaning future maintainer to "fix the inconsistency" by reverting
There are likely matching docstrings in Suggested phrasing for the corrected docstring (so reviewers can grep for the security boundary later):
Low-priority residual (do not block on)
Merge recommendationAPPROVE with the docstring fix. Once the three (likely four-with-types) docstring locations are updated to describe the actual personal-layers-only resolution and the security reason for it, this is ready for the pr gate. The fix is mechanical and small (5-minute edit) — strongly recommend folding it into this PR rather than ripping it off as a follow-up, because the drift is exactly the kind of thing that makes future regressions plausible. Substrate, security model, and test coverage are all where they need to be. Strong work. Architect integration review (post-fix) |
e87193b to
a261209
Compare
|
Addressed the should-fix (docstring drift) + one deviation worth calling out. Docstring drift — fixed (4 locations). Updated Deviation — dropped Net: |
Reviewer Integration Review (CMAP-3) — Re-check 2 (head a261209)Verified the docstring fix + the deviation against source. Docstring drift — fixed ✅All four sites now state personal-layers-only resolution with the RCE reason and an explicit "do NOT widen to
Deviation (dropped
|
Architect Final Review (head a261209)Re-verified all third-pass changes against PR head independently. Endorsing reviewer's APPROVE. Docstring drift — fixed at all four sites. Both Dropped RCE fix untouched. Remaining residual: 2 NUL bytes in
|
The VSCode extension publishes abstract activity events to URL hooks declared in the
Codev config, with no knowledge of who listens. Sibling of the controller-agnostic
command relay; the destination url is the user's. Inert by default.
- types: ActivityEvent / ActivityHook / ResolvedActivityHooks.
- codev: getActivityHooks() + GET /api/activity-hooks; UserConfig.activityHooks.
- core: TowerClient.getActivityHooks.
- vscode: load-activity-hooks fetches from Tower; activity-hooks fires from a cache
refreshed on connect + the config-change SSE; focus/diff/terminal/sidebar publish.
- Generalizes the shared config-file watcher (worktree-config -> codev-config); arch.md
updated to the new names.
Security (CMAP-3 + architect reviews):
- approve-gate relay forwards ONLY the builder id (never a {skipConfirmation} object).
- hooks resolve ONLY from the trusted PERSONAL layers (~/.codev/config.json +
.codev/config.local.json), NEVER the committed .codev/config.json; docstrings carry a
'do NOT widen to loadConfig' guard. + isTrusted fire-gate. No untrustedWorkspaces
capability (extension stays disabled in untrusted folders — covers every config-driven
execution surface, not just hooks).
- deliver via vscode.env.openExternal (remote-dev-safe, no shell quoting); macOS open -g
only for background:true.
- load hooks before the activation publish; dedup repeated builder-active fires (key via
JSON.stringify — no NUL separators, so the file stays text-classified).
- focus-workspace only opens a Tower-known workspace.
Tests: codev 31 config tests; vscode 499 (incl. approve-gate regression); check-types +
lint clean.
a261209 to
5f6459e
Compare
|
Stripped the 2 NUL bytes — went with the const key = JSON.stringify({ event, workspaceRoot, builder: data.builder ?? '' });Collision-safe (the field order is fixed, so it's a faithful 1:1 of the tuple) and keeps Thanks for catching it — the silent-grep-suppression on a security file is exactly the trap you flagged. Ready for the pr gate. |
Reviewer Integration Review (CMAP-3) — Final re-check (head 5f6459e)NUL strip verified against the new blob:
(A few Merge recommendationApprove — clean, no remaining items. Every CMAP-3 finding (F1–F5), both non-blocking notes, the docstring drift, the trust-model deviation, and the NUL hygiene are resolved and verified against source. Merge / Reviewer (sibling architect) CMAP-3 final re-check |
Architect Final APPROVE (head 5f6459e)NUL strip verified independently against PR head:
Every finding (F1 silent-approve, F2 committed-config RCE, F3 execFile/openExternal, F4 load-then-fire, F5 dedup), both non-blocking notes, the docstring drift sweep, the conservative APPROVE. Ready for the pr gate. Substrate, security model, posture, and tooling correctness are all where they need to be. The CMAP-3 cycle on this PR was the difference between shipping clean and shipping with three reopen surfaces (the silent-approve, the 2+4 coupling, and the docstring drift that would have invited a future maintainer to reintroduce the RCE) — strong outcome. Architect integration review (final approval) |
What
The VSCode extension publishes abstract activity events (
window-focus,builder-active) to URL hooks declared in the Codev config — with no knowledge of who listens. The outbound-direction sibling of the controller-agnostic command relay (#1091). The destination URL (a deep link, a companion app, a webhook launcher) lives entirely in the user's config; the extension carries zero vendor/controller references.13 files across
types/core/codev/vscode. No new dependencies.Config + hierarchy (the important part)
Hooks are resolved through Tower's canonical
loadConfig5-layer merge, exactly like worktree-config — not by reading a file directly. So both override layers work:{workspace}/{builder}→ URL-encoded event data (absent → empty). Arrays followdeepMergesemantics: a higher layer replaces a lower one's list, so define hooks in a single layer.Layers (mirrors worktree-config end-to-end)
ActivityEvent/ActivityHook/ResolvedActivityHookswire contracts.UserConfig.activityHooks;getActivityHooks()resolves + validates vialoadConfig;GET /api/activity-hooksreuses the existing config-file watcher, so an edit to.codev/config(.local).jsonfans out the sameworktree-config-updatedSSE clients already react to (no duplicatefs.watch).TowerClient.getActivityHooks(mirror ofgetWorktreeConfig).load-activity-hooks.tsfetches from Tower;activity-hooks.tsfires from a cache refreshed on connect + the config-change SSE (wired into the existing dev-command-context subscription); the focus / diff / terminal / sidebar hook points publish events.command-relayadds the genericfocus-workspace+scrollverbs.Safety / opt-in
Inert by default — no
activityHooksconfigured → the extension does nothing; non-users see zero behavior change. URLs are onlyopen'd (template, never eval'd); values URL-encoded. Delivery is fire-and-forget; a url with no handler is swallowed and pauses hooks for the window after one warning.Verification
tsc, vscodecheck-types+ lint — all clean.config.local.jsonreplaces project hooks and~/.codev/config.jsonis picked up (30 config tests total).resolveHookUrlsunit-tested (interpolation/encoding/event-filter/malformed); full suite 498 tests pass.