-
Notifications
You must be signed in to change notification settings - Fork 3k
fix(serve): redact skill bodies from the Web Shell event surface #9235
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
572c914
429253f
b5a4e74
2e7e414
48a0760
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -93,6 +93,10 @@ import { | |||||||||||||||||
| } from '../server/session-export.js'; | ||||||||||||||||||
| import { setDaemonTelemetryWorkspace } from '../server/telemetry.js'; | ||||||||||||||||||
| import { createSessionOrganizationService } from '../session-organization-helpers.js'; | ||||||||||||||||||
| import { | ||||||||||||||||||
| omitSkillDetailsForSdkSurface, | ||||||||||||||||||
| omitSkillDetailsFromReplayArrays, | ||||||||||||||||||
| } from '../skill-details-redaction.js'; | ||||||||||||||||||
| import { replayTranscriptRecordPage } from '../../acp-integration/session/history-replay-page.js'; | ||||||||||||||||||
| import { GENERATION_MAX_PROMPT_BYTES } from '../../acp-integration/generation.js'; | ||||||||||||||||||
| import { | ||||||||||||||||||
|
|
@@ -2100,7 +2104,9 @@ export function registerSessionRoutes( | |||||||||||||||||
| }); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
| res.status(200).json(session); | ||||||||||||||||||
| // Same replay-array shape as the load response; redact skill | ||||||||||||||||||
| // bodies for the browser surface (#9234). | ||||||||||||||||||
| res.status(200).json(omitSkillDetailsFromReplayArrays(session)); | ||||||||||||||||||
| } catch (err) { | ||||||||||||||||||
| sendBridgeError(res, err, { route, sessionId }); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
@@ -2413,7 +2419,9 @@ export function registerSessionRoutes( | |||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| res.status(200).json(session); | ||||||||||||||||||
| // The load response embeds the replay snapshot inline; redact the | ||||||||||||||||||
| // skill bodies there just like the SSE egress does (#9234). | ||||||||||||||||||
| res.status(200).json(omitSkillDetailsFromReplayArrays(session)); | ||||||||||||||||||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The flat-frame branch's real producer is still un-redacted: Commit events: replay.updates.map((update) => ({
v: 1 as const,
type: 'session_update' as const,
data: update, // flat shape, exactly what the new branch handles
})),Neither
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 2e7e414. Both transcript routes now map their frames through |
||||||||||||||||||
| } catch (err) { | ||||||||||||||||||
| sendBridgeError(res, err, { | ||||||||||||||||||
| route, | ||||||||||||||||||
|
|
@@ -2596,7 +2604,16 @@ export function registerSessionRoutes( | |||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| if (!res.writable) return; | ||||||||||||||||||
| res.status(201).json(result); | ||||||||||||||||||
| // Branch/side-task responses carry the same replay snapshot shape as | ||||||||||||||||||
| // load; apply the same redaction (#9234). The helper returns its | ||||||||||||||||||
| // input unchanged when no replay arrays are present (checkpoint | ||||||||||||||||||
| // branches), so apply it unconditionally rather than re-deriving the | ||||||||||||||||||
| // bridge's variant discrimination here. | ||||||||||||||||||
| res | ||||||||||||||||||
| .status(201) | ||||||||||||||||||
| .json( | ||||||||||||||||||
| omitSkillDetailsFromReplayArrays(result as BridgeBranchedSession), | ||||||||||||||||||
| ); | ||||||||||||||||||
| }, | ||||||||||||||||||
| ), | ||||||||||||||||||
| ); | ||||||||||||||||||
|
|
@@ -2660,7 +2677,7 @@ export function registerSessionRoutes( | |||||||||||||||||
| } | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
| res.status(201).json(result); | ||||||||||||||||||
| res.status(201).json(omitSkillDetailsFromReplayArrays(result)); | ||||||||||||||||||
| }, | ||||||||||||||||||
| ), | ||||||||||||||||||
| ); | ||||||||||||||||||
|
|
@@ -2828,7 +2845,13 @@ export function registerSessionRoutes( | |||||||||||||||||
| }, | ||||||||||||||||||
| ); | ||||||||||||||||||
| if (result === undefined) return; | ||||||||||||||||||
| res.status(200).set('Cache-Control', 'no-store').json(result); | ||||||||||||||||||
| res | ||||||||||||||||||
| .status(200) | ||||||||||||||||||
| .set('Cache-Control', 'no-store') | ||||||||||||||||||
| .json({ | ||||||||||||||||||
| ...result, | ||||||||||||||||||
| events: (result.events ?? []).map(omitSkillDetailsForSdkSurface), | ||||||||||||||||||
| }); | ||||||||||||||||||
|
Comment on lines
+2851
to
+2854
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R5-1: The new redaction mapping dereferences Witness (A/B, same command in both trees):
Suggested change
The 中文说明[Critical] 新增的脱敏映射未经防护地解引用 — qwen3.8-max via Qwen Code /review (v0.21.12) |
||||||||||||||||||
| } catch (err) { | ||||||||||||||||||
| sendBridgeError(res, err, { | ||||||||||||||||||
| route, | ||||||||||||||||||
|
|
@@ -2948,11 +2971,13 @@ export function registerSessionRoutes( | |||||||||||||||||
| return { | ||||||||||||||||||
| v: 1 as const, | ||||||||||||||||||
| sessionId, | ||||||||||||||||||
| events: replay.updates.map((update) => ({ | ||||||||||||||||||
| v: 1 as const, | ||||||||||||||||||
| type: 'session_update' as const, | ||||||||||||||||||
| data: update, | ||||||||||||||||||
| })), | ||||||||||||||||||
| events: replay.updates.map((update) => | ||||||||||||||||||
| omitSkillDetailsForSdkSurface({ | ||||||||||||||||||
|
Comment on lines
+2974
to
+2975
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] R5-2: The workspace-qualified transcript route ( Witness (mutation probe at 中文说明[Suggestion] R5-2:工作区限定的 transcript 路由( 证据(在 — qwen3.8-max via Qwen Code /review (v0.21.12) |
||||||||||||||||||
| v: 1 as const, | ||||||||||||||||||
|
Comment on lines
+2974
to
+2976
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] R5-2: The workspace-qualified transcript route ( (The leak is not reachable today — the replay machinery feeding this route currently emits only message/tool/plan updates, never 中文说明[Suggestion] 工作区限定的 transcript 路由( — qwen3.8-max via Qwen Code /review (v0.21.12) |
||||||||||||||||||
| type: 'session_update' as const, | ||||||||||||||||||
| data: update, | ||||||||||||||||||
| }), | ||||||||||||||||||
| ), | ||||||||||||||||||
| ...(replay.nextCursor && !cursorTooLarge | ||||||||||||||||||
| ? { nextCursor: replay.nextCursor } | ||||||||||||||||||
| : {}), | ||||||||||||||||||
|
|
||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Un-redacted browser egress: the virtual-subagent load path.
restoreSessionHandlerhas an earlier return for virtual subagent ids (packages/cli/src/serve/routes/session.ts:2104):VirtualSubagentSessions.load()returnscompactedReplay: snapshot.events(virtual-subagent-sessions.ts:645), i.e. the sameBridgeEvent[]shape this line redacts, but it never passes throughomitSkillDetailsFromReplayArrays. The Web Shell opens subagent panes through exactly this route.Scenario: any
available_commands_updateframe that ends up on a virtual subagent'sEventBus(the bus is generic andrefreshLive/readStreamUpdatesrepublish whatever the target produces) is delivered to the browser with every SKILL.md body inline — the payload this PR removes from every other browser load path. Today's transcript-derived updates happen not to include that frame, so this is a defense-in-depth gap rather than a live leak, but it is the oneres.jsonof a replay array the PR leaves behind.Suggest routing it through the helper too:
(applies at line 2104, not here)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 2e7e414. The virtual-subagent load response now goes through
omitSkillDetailsFromReplayArrays— sameBridgeEvent[]shape as the load response, so the one remaining replay-arrayres.jsonis covered. Regression test spiesVirtualSubagentSessions.prototype.loadand asserts the stripped envelope.