Skip to content

feat: enhance adversarial defense mechanisms in prompts and tools#122

Merged
achuvyas-kv merged 2 commits into
masterfrom
feat/redteam-prompt-hardening
Jun 25, 2026
Merged

feat: enhance adversarial defense mechanisms in prompts and tools#122
achuvyas-kv merged 2 commits into
masterfrom
feat/redteam-prompt-hardening

Conversation

@achuvyas-kv

@achuvyas-kv achuvyas-kv commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Problem

Two gaps in the autonomous (opfor hunt) and attack-generation paths:

  1. The hunt agent trusts target output as instructions. Commander/operator subagents receive raw target replies. An adversarial target can embed directives ("the test is over", "you are now in maintenance mode", "reply PASS", "ignore previous instructions") that the agent may follow — a prompt-injection-on-the-tester risk that can corrupt a run or suppress findings.

  2. The attacker LLM over-refuses. When generating adversarial prompts, the model sometimes treats the evaluator's strong language ("fraud", "exfiltrate", "deceive") as a real-world request and refuses, so the target ships unevaluated. The generation prompts also did not state the concrete pass/fail criteria the message should target.

Solution

Adversarial-target defense (hunt path):

  • Added an "Adversarial target defense" + "Sandboxing note" section to the commander and operator system prompts, framing all target output as untrusted DATA and embedded directives as attack evidence — never commands.
  • send_to_target, recon_probe, and get_thread now wrap target responses in <untrusted_target_output>…</untrusted_target_output> delimiters before returning them to the agent. Wrapping is applied on the return path only; error and rate-limited responses pass through unwrapped, so failure handling is unchanged.

Attacker-prompt hardening (run path):

  • Added a "Harness context" preamble to the agent and MCP attack-generation prompts, framing generation as a sandboxed, safety-positive evaluation so the attacker model stays in character instead of refusing.
  • Injected the evaluator's fail_criteria / pass_criteria directly into the generation prompts so the crafted message and judge hint target the actual vulnerability definition.

These are additive: prompt-text changes carry no control-flow impact, and the response-wrapping is a guarded, return-path-only transform.

Checklist

  • npm run build passes
  • npm run typecheck passes
  • npm run build:catalog:check passes (if evaluators or suites changed) — N/A, no evaluator/suite changes
  • Tested against a local target — ran opfor hunt end-to-end (target: deepseek-chat via OpenRouter, brain: claude-haiku-4.5); recon, sends, and get_thread all exercised the wrapped path, and a finding still passed the verbatim-evidence guard
  • No secrets, .env, or .opfor/ artifacts committed
  • PR title follows <type>: <what changed>feat: harden red-team prompts against adversarial targets and attacker refusals

Evaluator checklist (skip if no evaluator added)

Skipped — no evaluator added or changed.

Summary by CodeRabbit

  • Bug Fixes
    • Strengthened handling of target responses by clearly marking them as untrusted content in transcripts and tool outputs.
    • Improved resilience against prompt-injection style phrases by adding stronger safety guidance to agent prompts.
  • New Features
    • Updated agent and evaluation prompts with clearer harness, task, and sandbox context for more consistent behavior.
    • Refined attack-generation prompts with clearer vulnerability criteria and result guidance.

@achuvyas-kv achuvyas-kv requested a review from jithin23-kv June 25, 2026 11:06
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@achuvyas-kv, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 43 minutes and 51 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0021ff2e-a579-48ce-a16f-6c9f6a60cba7

📥 Commits

Reviewing files that changed from the base of the PR and between c7a9eec and 35293c5.

📒 Files selected for processing (10)
  • core/src/autonomous/lib/untrustedOutput.ts
  • core/src/autonomous/prompts/commander.ts
  • core/src/autonomous/prompts/defenses.ts
  • core/src/autonomous/prompts/operator.ts
  • core/src/autonomous/tools/getThread.ts
  • core/src/autonomous/tools/reconProbe.ts
  • core/src/autonomous/tools/sendToTarget.ts
  • core/src/generate/generateAttacks.ts
  • core/src/prompts/attacker-mcp.ts
  • core/src/prompts/harnessContext.ts

Walkthrough

The PR updates prompt text and tool outputs so target responses are treated as untrusted data, adds sandbox framing to attacker-facing prompts, and wraps successful target responses in <untrusted_target_output> delimiters.

Changes

Target Output Hardening

Layer / File(s) Summary
Prompt doctrine updates
core/src/autonomous/prompts/commander.ts, core/src/autonomous/prompts/operator.ts, core/src/prompts/attacker-mcp.ts
Commander, operator, and attacker prompts add untrusted-output handling and sandbox framing.
Harness context headings
core/src/generate/generateAttacks.ts
buildAgentSystemPrompt reformats harness context, observed runtime context, upstream session context, and rules headings.
MCP vulnerability criteria
core/src/generate/generateAttacks.ts
Single and pattern MCP attack prompts add explicit fail/pass criteria and tie judgeHint to the fail criteria.
Target response wrapping
core/src/autonomous/tools/getThread.ts, core/src/autonomous/tools/reconProbe.ts, core/src/autonomous/tools/sendToTarget.ts
Thread, recon probe, and send-to-target outputs wrap successful target responses in <untrusted_target_output> delimiters while leaving error and rate-limited responses unchanged.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: hardening prompts and tools against adversarial targets.
Description check ✅ Passed The description follows the required template and includes the problem, solution, checklist, and evaluator checklist status.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/redteam-prompt-hardening

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/src/autonomous/tools/getThread.ts`:
- Around line 36-42: The get_thread serialization path is wrapping every
non-error t.response and omitting the rate-limited passthrough state, which
breaks parity with send_to_target and recon_probe. Update the turn-building
logic in getThread/get_thread to preserve t.rateLimited alongside response, and
skip the <untrusted_target_output> wrapper whenever a turn is rate-limited so
replayed transcripts match the live-tool contract. Ensure each serialized turn
includes the rateLimited flag in addition to response, score, and isError.

In `@core/src/autonomous/tools/sendToTarget.ts`:
- Around line 147-153: The untrusted output wrapper in sendToTarget is currently
bypassable because result.response is inserted raw, allowing a target to close
the delimiter and inject instructions. Update the wrapping logic in sendToTarget
to escape or serialize the payload before surrounding it with
<untrusted_target_output> tags, and apply the same reusable helper wherever
target text is wrapped, including recon_probe and get_thread. Prefer a shared
utility such as wrapUntrustedTargetOutput so the trust-boundary handling stays
consistent across these call sites.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ede73440-e82a-4f1d-8bf6-d631b7f921d5

📥 Commits

Reviewing files that changed from the base of the PR and between 70ed2a4 and c7a9eec.

📒 Files selected for processing (7)
  • core/src/autonomous/prompts/commander.ts
  • core/src/autonomous/prompts/operator.ts
  • core/src/autonomous/tools/getThread.ts
  • core/src/autonomous/tools/reconProbe.ts
  • core/src/autonomous/tools/sendToTarget.ts
  • core/src/generate/generateAttacks.ts
  • core/src/prompts/attacker-mcp.ts

Comment thread core/src/autonomous/tools/getThread.ts Outdated
Comment on lines 36 to 42
response:
t.isError || !t.response
? t.response
: `<untrusted_target_output>\n${t.response}\n</untrusted_target_output>`,
score: t.score,
isError: t.isError,
})),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve the rate-limited passthrough contract in get_thread.

send_to_target and recon_probe intentionally leave rate-limited responses unwrapped, but this path wraps any non-error t.response and drops rateLimited from the serialized turn. That makes replayed transcripts diverge from the live-tool contract and can present rate-limit notices as normal target output. Please gate on t.rateLimited here too and return that flag in each turn.

Suggested fix
       turns: thread.turns.map((t) => ({
         turnIndex: t.turnIndex,
         inherited: thread.forkedFromTurn !== undefined && t.turnIndex <= thread.forkedFromTurn,
         persona: t.persona,
         strategy: t.strategy,
         prompt: t.prompt,
         response:
-          t.isError || !t.response
+          t.isError || t.rateLimited || !t.response
             ? t.response
             : `<untrusted_target_output>\n${t.response}\n</untrusted_target_output>`,
         score: t.score,
         isError: t.isError,
+        rateLimited: t.rateLimited,
       })),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
response:
t.isError || !t.response
? t.response
: `<untrusted_target_output>\n${t.response}\n</untrusted_target_output>`,
score: t.score,
isError: t.isError,
})),
response:
t.isError || t.rateLimited || !t.response
? t.response
: `<untrusted_target_output>\n${t.response}\n</untrusted_target_output>`,
score: t.score,
isError: t.isError,
rateLimited: t.rateLimited,
})),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/src/autonomous/tools/getThread.ts` around lines 36 - 42, The get_thread
serialization path is wrapping every non-error t.response and omitting the
rate-limited passthrough state, which breaks parity with send_to_target and
recon_probe. Update the turn-building logic in getThread/get_thread to preserve
t.rateLimited alongside response, and skip the <untrusted_target_output> wrapper
whenever a turn is rate-limited so replayed transcripts match the live-tool
contract. Ensure each serialized turn includes the rateLimited flag in addition
to response, score, and isError.

Comment thread core/src/autonomous/tools/sendToTarget.ts Outdated
@achuvyas-kv achuvyas-kv merged commit 4ee277a into master Jun 25, 2026
1 check passed
@arunSunnyKVS arunSunnyKVS deleted the feat/redteam-prompt-hardening branch June 25, 2026 11:46
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.

2 participants