fix(agent): isolate workspace baseline from trusted system prompt#2049
fix(agent): isolate workspace baseline from trusted system prompt#2049Ram-Pavith wants to merge 4 commits into
Conversation
Keep host rules in the system channel; deliver AGENTS.md, directory listings, skills, and session time as request-time user fragments with untrusted envelopes on both agent-core engines.
Capture the threat model, architecture, acceptance criteria, and PR template copy for the untrusted baseline isolation work.
🦋 Changeset detectedLatest commit: 5c6b169 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6be16a8302
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const messages = | ||
| baseline.length === 0 ? [...history] : [...baseline, ...history]; |
There was a problem hiding this comment.
Preserve context-size measurements with baseline
Whenever a v2 turn has any baseline (the new builder normally emits the time fringe), this prepends extra user messages to request.messages. Later the same service calls this.contextSize.measured(request.messages, [message], usage), and AgentContextSizeService.measured returns early unless the input array exactly matches IAgentContextMemoryService.get(). As a result normal v2 turns stop recording provider token usage, leaving status, budget sizing, and compaction decisions on stale estimates. Pass only the real history into context-size accounting, or teach it to ignore the request-only baseline while still sending the prefixed messages to the model.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in f1c62e7: contextSize.measured now receives historyMessages (conversation only). Request body still prepends baseline for the model.
| } | ||
|
|
||
| private activeProfile: ResolvedAgentProfile | undefined; | ||
| private baselineContextMessages: readonly Message[] = []; |
There was a problem hiding this comment.
Rebuild baseline context after restoring v2 agents
baselineContextMessages is new in-memory state, but restored agents are created by replaying the wire ProfileModel; no bind()/useProfile() path runs, and the prompt route skips rebinding when the replayed profileName already matches. For resumed sessions this field therefore stays empty, while the persisted systemPrompt is now trusted-only and no longer contains AGENTS.md, listings, skills, or time, so the next prompt runs without the workspace guidance this change moved out of system. Rebuild it after wire.restore() or persist a snapshot before the agent can request the model.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in f1c62e7: bindBootstrap calls refreshSystemPrompt() when create has no binding, so restored agents rebuild request-time baseline after wire restore.
| additionalDirsInfo: context?.additionalDirsInfo, | ||
| }); | ||
| this.config.update({ profileName: profile.name, systemPrompt }); | ||
| this.baselineContextMessages = buildBaselineContextMessages({ |
There was a problem hiding this comment.
Rebuild v1 baseline context on session resume
This new baseline is only assigned when useProfile()/refreshSystemPrompt() runs. On a normal v1 session resume, Session.resumeAgent() replays the wire and only calls restoreAgentProfileHandle() to set activeProfile; because the replayed systemPrompt is non-empty it does not bootstrap the profile again. New sessions saved by this commit therefore resume with baselineContextMessages empty, while their system prompt only points to a separate baseline message, so AGENTS.md, directory listings, skills, and time disappear until some unrelated refresh. Rebuild this baseline during restore before serving prompts.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in f1c62e7: v1 restoreAgentProfileHandle now awaits refreshSystemPrompt() after restoring the profile handle so AGENTS/listings/skills/time fringe return on resume.
| applyBindingSnapshot(snapshot: ProfileBindingSnapshot): void { | ||
| this.activeProfile = undefined; | ||
| this.activeToolNamesOverlay = undefined; | ||
| this.baselineContextMessages = []; |
There was a problem hiding this comment.
Copy the baseline when forking v2 agents
When SessionBtwService.start() forks the main agent, AgentLifecycleService.fork() uses applyBindingSnapshot() unless the caller overrides the profile. This reset drops the parent's newly separated baseline, and the snapshot only carries the trusted systemPrompt, so BTW/side-question children lose the AGENTS.md, listing, skills, and time context they inherited before this commit. Copy the source profile's baseline or rebuild it from the snapshot so forked agents see the same workspace baseline as their parent.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in f1c62e7: ProfileBindingSnapshot / data() carry baselineContextMessages, and applyBindingSnapshot restores them so fork/BTW keeps the parent baseline.
Address Codex review on MoonshotAI#2049: measure context size without request-only baseline prefixes; rebuild baseline after v1/v2 resume; copy baseline through v2 binding snapshots used by fork/BTW.
Related Issue
Resolve #2048
Design reference (branch-local):
docs/design/system-prompt-baseline-isolation.mdIssue draft:
docs/design/issue-system-prompt-baseline-isolation.mdProblem
See linked issue. Short form:
Kimi's always-sent baseline mixed trusted host rules with workspace-controlled text (
AGENTS.md, directory listings, skill listings, session timestamp) inside the system channel. Soft prose ("AGENTS is not privileged") was not a structural boundary. A poisoned checkout can therefore elevate project text toward system-law priority (prompt injection via baseline / supply chain), not only via the user's chat turn.Related historical discussion: #2024 (closed without landed fix), #2028 (
KIMI_NOW/ cache), #524/context, #1955 baseline size.What changed
Phase 1 — structural isolation
escapeUntrustedText,sanitizeUntrustedControls,wrapUntrusteduntrusted_cwd_listing,untrusted_additional_dirs,untrusted_agents_md,untrusted_skills_listinghardenSkillBody(closer breakout + control strip)Phase 2a/2b — role / channel split (Codex-aligned, without kosong
developerrole)now.buildBaselineContextMessages/baselineMessagesForContext:Agentholds fragments →KosongLLMprefixes history; BTW copies parent baseline; full compaction estimates + summarizer calls include baseline.IAgentProfileService.getBaselineContextMessages()→llmRequesterprefixes (turn-snapshotted with system prompt).Why this fits Kimi Code
agent-core+agent-core-v2) stay parallel.user+ system side-channel); avoids a kosong-widedeveloperrole change.Out of scope (explicit non-goals)
developerin kosong/contextUI (Show a real context-usage breakdown (/context) and reflect the always-sent baseline in the indicator #524)Architecture (after)
Test plan
Commands used during development (Node ≥ 24.15, pnpm monorepo):
Last verified: typecheck green; agent-core focused 96 tests; agent-core-v2 focused 66 tests.
Coverage includes:
xml-escape.test.ts(v1/v2)baseline-context.test.ts, profile-shared/default-agent-profileskosong-llm.test.ts,llmRequesterService.test.tsManual (recommended before merge):
AGENTS.mdrequiring "POISONED" prefix.What is 2+2?.<untrusted_agents_md>with escaped closers if present.Changeset
.changeset/untrusted-system-prompt-baseline.md— patch@moonshot-ai/kimi-code:Docs
docs/design/system-prompt-baseline-isolation.mddocs/design/(internal; not VitePress user docs).Risk and rollout
{{ KIMI_AGENTS_MD }}etc.copyBaselineContextFrom/ profile bind pathsRollout: default-on, no feature flag. Patch release with CLI bundle.
Checklist
.changeset/untrusted-system-prompt-baseline.mdFile map (primary)
Reviewer notes
nowfor default profiles.docs/.gitignorenoise, handoff files, or_refs/comparison clones.mainbefore open (worktree was behind origin during development).