anomalyco / anomalyco/opencode

[SECURITY] Context pruning silently drops instruction/constraint-bearing content from the context window (integrity, not just cost)

Open
#42,437 2 comments 0 reactions 1 assignee View on GitHub

@rekram1-node is already working on this.

Since Aug 13, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

[SECURITY] Context pruning silently drops instruction/constraint-bearing content from the context window (integrity, not just cost)

Description

Description

  • Severity: Medium-High (context/instruction integrity; constraint bypass for free once triggered)
  • Affected: session/compaction.ts (PRUNE_MINIMUM/PRUNE_PROTECT/PRUNE_PROTECTED_TOOLS) + session/prompt.ts:1850 (prune fired on every agent→user transition); default-ON in released builds (at current dev it is opt-in, core/src/config/compaction.ts)
  • Related: #21208/#21209 (closed, perf framing only), #37767, #39291

Plugins

None required

OpenCode version

Observed in source at commits baef5cd43b and 743f6410f2, current dev branch (code-verified; see References below)

Steps to reproduce

  1. Run a long session until messages exceed PRUNE_PROTECT = 40_000 tokens (every agent→user transition fires compaction.prune, packages/opencode/src/session/prompt.ts:1850).
  2. Early in the session, state a binding constraint (e.g. "never modify X") or have a permission denial logged.
  3. After pruning, the conversation window no longer contains that content (only skill output is protected, session/compaction.ts:37-39); a later request is evaluated without it.

Screenshot and/or share link

N/A — verified against source (details and file:line references below).

Operating System

All (cross-platform; verified on macOS Darwin)


Details

Pruning (compaction.prune) hard-deletes messages from the context window past PRUNE_PROTECT = 40_000 tokens on every transition to the primary (user) agent:

export const PRUNE_MINIMUM = 20_000
export const PRUNE_PROTECT = 40_000
const PRUNE_PROTECTED_TOOLS = ["skill"]
...
yield* compaction.prune({ sessionID }).pipe(Effect.ignore, Effect.forkIn(scope))   // prompt.ts:1850

The protection list contains only skill tool output (compaction.ts:39). Everything else past the protected window — including user messages, permission denials, prior instructions like "never touch production", and any constraint content that arrived mid-session (or was injected by untrusted content earlier in the conversation) — is dropped with no user notice.

Impact (why this is a security issue, not just a cost one)
  1. Constraint eviction = permission bypass: any future agent request to do something the user explicitly denied earlier in the session is judged against a window in which the denial no longer exists. For long sessions — exactly when such constraints matter — pruning removes the evidence of the constraint.
  2. Attacker-influenced shaping: content earlier in the window (from untrusted files/repos/instructions processed by tools) determines what survives via token volume; a long attacker-authored block naturally pushes later constraint-bearing user messages out of the protected tail (recent tokens preserved are only 2_000–8_000).
  3. Silent and periodic: fires on every user→agent transition; the user is never told which messages were dropped, and Effect.ignore swallows errors. Compaction (the summarize path) at least preserves a "Constraints & Preferences" summary — pruning preserves nothing.
  4. The choice of what sits in the protected tail is heuristic (tokens/turns), and PRUNE_PROTECTED_TOOLS encodes the assumption that only tool output is disposable — user/system constraints are treated as equally disposable.
Suggested fix
  • Either extend the protected set to instruction-bearing content (user messages containing rule-like text, all permission/denial records) or drop hard-pruning in favor of compaction-only summaries that carry forward a constraints section.
  • Surface pruning: log what was removed per transition (and raise a user-visible notice when protected-content removal occurs).
  • Default: with pruning now optional, prefer documenting/off by default — and when enabled, never prune user messages whose text contains constraint patterns (denials, imperative rules) — false positives are cheaper than evicted denials.

Plugins

None required

OpenCode version

Observed in source at commits baef5cd43b and 743f6410f2, current dev branch (code-verified; file:line references in Details).

Screenshot and/or share link

N/A — verified against source (details and file:line references below).

Operating System

All (cross-platform; verified on macOS Darwin).

Steps to reproduce

  1. Run a session long enough to exceed PRUNE_PROTECT = 40_000 tokens (every agent→user transition fires compaction.prune, packages/opencode/src/session/prompt.ts:1850).
  2. Early in the session, state a binding constraint (e.g. "never modify X") or have a permission denial logged.
  3. After pruning, the window no longer contains that content — only skill output is protected (session/compaction.ts:37-39) — so a later request is evaluated without it.

Details

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.