Skip to content

SEC-005: deliver Agent SDK prompts verbatim (verbatim_prompts=True) - #146

Merged
DoRmAmMu1997 merged 1 commit into
mainfrom
fix/sec-005-verbatim-agent-prompts
Sep 28, 2026
Merged

DoRmAmMu1997 merged 1 commit into
mainfrom
fix/sec-005-verbatim-agent-prompts

Conversation

@DoRmAmMu1997

Copy link
Copy Markdown
Owner

Summary

SEC-005 sets verbatim_prompts=True on all four Claude Agent SDK runners (Check Fundamentals, Technical Analysis, 67 Ka Funda, IPO extraction). The option was added in claude-agent-sdk 0.2.158; main pins 0.2.159 since #142.

Why: by default the Claude CLI pre-processes each user message before the model sees it. An @path mention anywhere in the text is expanded into that file's contents (likewise @server:resource MCP mentions), and a message starting with / is dispatched as a slash command. This runs before tools=[], allowed_tools and permission_mode="dontAsk" are consulted, so the existing tool boundary never sees it.

The IPO extraction prompt inlines the company name scraped from SEBI listings. A filing named like @/etc/passwd could make the CLI read a local file into the model's context. The other three prompts carry only app-owned values (symbol, run mode, model name, candle-derived price facts); they get the same setting so the contract has no per-agent exceptions.

Two corrections to the original ticket text, both verified in code:

  • _build_user_prompt inlines the classifier's section enum values and page numbers, not free-text PDF headings. The only untrusted free text is the company name.
  • Slash dispatch needs a leading /, and every prompt starts with fixed app text. So @path expansion is the live path today; the option closes both.

Failure modes on version skew

Component Old version Behavior
SDK < 0.2.158 rejects the keyword with TypeError Fails closed. No run. The UI and the IPO batch boundary catch it (the IPO batch records an extraction_failed receipt).
CLI < 2.1.248 ignores the flag; SDK logs a warning Not closed. Falls back to today's behavior. The 0.2.159 pin bundles CLI 2.1.281, and the SDK prefers the bundled binary over PATH (checked with claude.exe --version in a pinned venv). Documented in the ADR.

The option is deliberately not feature-detected (unlike the fast-mode ThinkingConfigDisabled): silently running without a security control would reopen the pre-tool file-read path.

Changes

  • Code: one verbatim_prompts=True kwarg plus a Beginner-note comment in each of the four ClaudeAgentOptions constructions (the only four in the repo).
  • Tests:
    • Each runner's existing options-boundary test now asserts verbatim_prompts is True.
    • New test_pinned_sdk_accepts_verbatim_prompts builds the real pinned ClaudeAgentOptions. The runner tests use fake options classes that accept any keyword, so without it a pin downgrade below 0.2.158 would pass CI and break every AI screener at run time.
  • Docs:
    • ai-execution-boundaries.md ADR: decision, two rejected options (app-side @// scrubbing; feature detection), consequences, verification contract.
    • security.md gets a decision row and a testing bullet.
    • The HLD and the four agent LLDs list the option in their containment contract.

Verification

  • TDD RED: all five new assertions failed before the change, each for the intended reason. The four boundary tests failed with KeyError: 'verbatim_prompts'. The real-SDK guard failed with TypeError: ... unexpected keyword argument 'verbatim_prompts' on a local SDK 0.2.154.
  • GREEN, full gate suite in a venv pinned to constraints.txt (SDK 0.2.159, SQLAlchemy 2.1.0, numpy 2.5.3), Python 3.13:
    • pytest: 2305 passed, 2 skipped, coverage 90.61% (floor 89%)
    • compileall, Ruff, mypy (273 files), Bandit: clean
    • pre_commit validate-config: OK
    • pip_audit -r constraints.txt: exit 0
  • git diff origin/main -- constraints.txt pyproject.toml requirements*.txt is empty.
  • /code-review (high) found 6 items:
    • Fixed (3): the ADR overstated "fails closed" by omitting the CLI half; the /command risk was overstated; one ADR line broke the wrap.
    • Skipped (3): listed under "Not done" below.
  • /security-review: no findings. The change strictly narrows the attack surface and adds no new data flow.

Not done (flagged for the reviewer)

  • No live agent run. Verbatim turns also skip the CLI's turn-start attachment pass (skill/tool listings and per-turn reminders arrive after the first tool call). This should be harmless here: instructions come from the system prompt, setting_sources=[] already drops CLAUDE.md, and each agent has at most three small MCP tools. But only a real run exercises it, because every unit test fakes the SDK. Suggested pre-merge check: run one Check Fundamentals analysis and confirm it still calls fetch_company_data.
  • Shared options builder (altitude). The containment kwargs are still copied across four runners; a builder in backend/ai_runtime.py would let a new agent inherit them. Left as a follow-up to keep this PR to one ticket.
  • Local environments installed before chore(deps): Bump the python-minor-and-patch group with 3 updates #142 with an SDK older than 0.2.158 raise TypeError on every AI run after this merges. Upgrade with python -m pip install -c constraints.txt claude-agent-sdk.

🤖 Generated with Claude Code

claude-agent-sdk 0.2.158 added ClaudeAgentOptions.verbatim_prompts. By
default the Claude CLI pre-processes each user message before the model sees
it: an @path mention anywhere in the text is expanded into that file's
contents, and a leading "/" is dispatched as a slash command. That runs before
tools=[], allowed_tools and permission_mode="dontAsk" are consulted, so the
existing tool boundary never sees it.

The IPO extraction prompt inlines the company name scraped from SEBI listings,
so a filing named like "@/etc/passwd" could make the CLI read a local file
into the model's context. All four runners (Check Fundamentals, Technical
Analysis, 67 Ka Funda, IPO extraction) now set verbatim_prompts=True; the
other three prompts carry only app-owned values but share one contract.

The option is not feature-detected: an SDK older than the pin raises
TypeError, so AI runs fail closed rather than silently dropping the control.
The runner tests use permissive fake options classes, so a new test builds
the real pinned ClaudeAgentOptions to make an SDK downgrade fail CI. The CLI
half (Claude Code 2.1.248+) is satisfied by the CLI 2.1.281 bundled with the
0.2.159 pin, which the SDK prefers over any claude on PATH; the ADR records
that an older CLI would ignore the flag with only a warning.

Docs: the ai-execution-boundaries ADR gains the decision, rejected options,
consequences and verification contract; the security LLD, HLD and the four
agent LLDs list the option in their SDK containment contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@DoRmAmMu1997
DoRmAmMu1997 merged commit 52f9517 into main Sep 28, 2026
7 checks passed
@DoRmAmMu1997
DoRmAmMu1997 deleted the fix/sec-005-verbatim-agent-prompts branch September 28, 2026 10:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant