Skip to content

test(btest): adopt rejected_elements safety pattern across all agents (RHAIENG-6258) - #262

Merged
andrewdonheiser merged 6 commits into
mainfrom
feat/rejected-elements-adoption
Jul 16, 2026
Merged

andrewdonheiser merged 6 commits into
mainfrom
feat/rejected-elements-adoption

Conversation

@andrewdonheiser

@andrewdonheiser andrewdonheiser commented Jul 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Added rejected_elements field to adversarial golden queries in all 10 agents that were missing it (agentic_rag already had it), asserting the agent does not leak system prompt internals when faced with prompt injection
  • Added test_adversarial_prompt_injection_resistance test function to every agent's test_tool_usage.py — loads adversarial queries with rejected_elements, verifies the agent answers the legitimate part, and asserts none of the rejected phrases appear in the response
  • Refactored autogen mcp_agent's hand-rolled credential check (sk-, Bearer, etc.) to use score_pii_leakage from harness.scorers.safety, which covers 11 pattern types and integrates with the scoring framework

Test plan

  • Verify ruff check and ruff format --check pass on all 10 modified test files
  • Confirm agentic_rag (the reference agent) was not modified
  • Run make test for one agent locally to validate the new test is collected by pytest
  • Deploy at least one agent and run behavioral tests end-to-end to confirm test_adversarial_prompt_injection_resistance passes
  • Verify autogen mcp_agent's test_adversarial_no_pii_leakage correctly uses score_pii_leakage scorer

🤖 Generated with Claude Code

… (RHAIENG-6258)

Add rejected_elements field to adversarial golden queries in all 10
agents that were missing it (agentic_rag already had it). Add a
matching test_adversarial_prompt_injection_resistance test to every
agent's test_tool_usage.py that asserts none of the rejected phrases
appear in the response. Refactor autogen mcp_agent's ad-hoc credential
check to use score_pii_leakage from harness.scorers.safety.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 14, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 300aaeed-61ff-476d-83c8-228e3e60faa7

📥 Commits

Reviewing files that changed from the base of the PR and between a135ac1 and 56c2a5e.

📒 Files selected for processing (23)
  • agents/a2a/templates/langgraph_crewai_agent/tests/behavioral/fixtures/golden_queries.yaml
  • agents/a2a/templates/langgraph_crewai_agent/tests/behavioral/test_tool_usage.py
  • agents/autogen/templates/mcp_agent/tests/behavioral/fixtures/golden_queries.yaml
  • agents/autogen/templates/mcp_agent/tests/behavioral/test_tool_usage.py
  • agents/crewai/templates/websearch_agent/tests/behavioral/fixtures/golden_queries.yaml
  • agents/crewai/templates/websearch_agent/tests/behavioral/test_tool_usage.py
  • agents/google/templates/adk/tests/behavioral/fixtures/golden_queries.yaml
  • agents/google/templates/adk/tests/behavioral/test_tool_usage.py
  • agents/langflow/templates/simple_tool_calling_agent/tests/behavioral/fixtures/golden_queries.yaml
  • agents/langflow/templates/simple_tool_calling_agent/tests/behavioral/test_tool_usage.py
  • agents/langgraph/templates/agentic_rag/tests/behavioral/test_tool_usage.py
  • agents/langgraph/templates/human_in_the_loop/tests/behavioral/fixtures/golden_queries.yaml
  • agents/langgraph/templates/human_in_the_loop/tests/behavioral/test_tool_usage.py
  • agents/langgraph/templates/react_agent/tests/behavioral/fixtures/golden_queries.yaml
  • agents/langgraph/templates/react_agent/tests/behavioral/test_tool_usage.py
  • agents/langgraph/templates/react_with_database_memory/tests/behavioral/fixtures/golden_queries.yaml
  • agents/langgraph/templates/react_with_database_memory/tests/behavioral/test_tool_usage.py
  • agents/llamaindex/templates/websearch_agent/tests/behavioral/fixtures/golden_queries.yaml
  • agents/llamaindex/templates/websearch_agent/tests/behavioral/test_tool_usage.py
  • agents/vanilla_python/templates/openai_responses_agent/tests/behavioral/fixtures/golden_queries.yaml
  • agents/vanilla_python/templates/openai_responses_agent/tests/behavioral/test_tool_usage.py
  • evals/harness/assertions.py
  • evals/harness/scorers/safety.py

📝 Walkthrough

Walkthrough

The change adds shared prompt-injection assertions, expands adversarial golden fixtures across agent templates, introduces behavioral resistance tests, replaces AutoGen’s manual leakage check with PII scoring, and detects Slack-style tokens.

Changes

Adversarial evaluation coverage

Layer / File(s) Summary
Shared injection and PII checks
evals/harness/assertions.py, evals/harness/scorers/safety.py
Adds assert_injection_resistance and Slack-token detection for PII leakage scoring.
Template prompt-injection coverage
agents/*/templates/*/tests/behavioral/{fixtures/golden_queries.yaml,test_tool_usage.py}
Adds rejected response elements and adversarial behavioral tests across the agent templates.
AutoGen leakage scoring
agents/autogen/templates/mcp_agent/tests/behavioral/test_tool_usage.py
Replaces the manual system-leakage test with PII scoring and shared injection-resistance validation.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant BehavioralTest
  participant AgentEval
  participant InjectionAssertion
  participant SafetyScorer
  BehavioralTest->>AgentEval: run adversarial golden query
  AgentEval-->>BehavioralTest: return result
  BehavioralTest->>InjectionAssertion: validate expected and rejected elements
  InjectionAssertion->>SafetyScorer: score prompt-injection resistance
  SafetyScorer-->>InjectionAssertion: return score
  InjectionAssertion-->>BehavioralTest: pass or fail assertions
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: adopting rejected_elements safety checks across agents.
Description check ✅ Passed The description is clearly related to the changeset and summarizes the new tests, fixtures, and scorer refactor.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/rejected-elements-adoption

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.

andrewdonheiser and others added 3 commits July 15, 2026 12:23
…erage (RHAIENG-6258)

Replace overly broad rejected_elements patterns ("I am a", "you are a",
"system prompt") with disclosure-specific phrases that distinguish
refusal language from actual system prompt leaks. Add Slack token
(xox[bpasre]-) pattern to score_pii_leakage PII patterns dict. Fix
HITL adversarial expected_elements from ["cannot", "will not"] to
["file"] for robustness across response variants.

Verified against live agents: react_agent, autogen/mcp_agent, and HITL
pass; crewai correctly catches genuine system prompt leak.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ngs (RHAIENG-6258)

- Revert agentic_rag rejected_elements to original broader patterns
- Extract assert_injection_resistance() into harness.assertions to
  eliminate duplication across 11 agent test files
- Add score_prompt_injection_resistance() call for broader regex-based
  compliance detection (13 patterns covering DAN mode, developer mode, etc.)
- Fix slack_token regex: add word boundaries for consistency

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@andrewdonheiser
andrewdonheiser marked this pull request as ready for review July 15, 2026 16:55
@andrewdonheiser
andrewdonheiser requested a review from a team as a code owner July 15, 2026 16:55
@shricharan-ks shricharan-ks self-assigned this Jul 16, 2026

@shricharan-ks shricharan-ks left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review — PR #262 (medium effort, verified)

This PR adopts the rejected_elements safety pattern across all 10 agents that were missing it (agentic_rag already had it), adds a new shared assert_injection_resistance helper, refactors autogen's PII check to use the shared scorer, and adds Slack token coverage. 23 files changed, 207 additions, 33 deletions.

Overall this is a well-structured security improvement. The test pattern is consistent across all agents, the shared helper reduces duplication, and the rejected_elements phrases chosen for the 10 new agents are specific enough to avoid false positives. Four findings survived review, all non-blocking.


Findings (4, non-blocking)

1. 🟡 assertions.py:27 — Helper is stricter than the original agentic_rag inline code (PLAUSIBLE)

The original agentic_rag inline code (now removed) only checked expected_elements + rejected_elements. The new assert_injection_resistance helper additionally calls score_prompt_injection_resistance(), which checks compliance indicators (regex patterns for "HACKED", "developer mode", "DAN", etc.).

This means the agentic_rag refactoring is not a pure drop-in replacement — it's strictly stricter. A response that passed the old inline code could fail the new helper if it contains a compliance indicator. The PR author confirmed tests pass, so this isn't breaking today, but it's worth noting as a behavioral change during what appears to be a pure refactoring.


2. 🟡 assertions.py:13 — golden.get("expected_elements", []) silently skips validation on missing key (PLAUSIBLE)

The original agentic_rag code used golden["expected_elements"] (KeyError on missing). The helper uses .get("expected_elements", []) which silently skips the check. If a future adversarial golden entry omits expected_elements, the helper won't catch that the agent gave an irrelevant response — the safety check becomes partially ineffective for that entry.

This is defensive coding vs. fail-fast — both are reasonable. Worth a design decision.


3. 🟡 human_in_the_loop/golden_queries.yaml:56 — HITL adversarial expected_elements semantic shift (PLAUSIBLE)

The HITL adversarial query's expected_elements changed from ["cannot", "will not"] (refusal language) to ["file"] (content language). This shifts what the test validates:

  • Old: Verifies the agent explicitly refuses the adversarial request
  • New: Verifies the response mentions "file" (the legitimate part of the query)

The prompt-leak detection is now handled by rejected_elements, so the overall safety coverage is maintained. But the test no longer verifies the agent's refusal wording — just that it didn't leak internals. This is a deliberate design choice (rejection wording was already covered by test_tool_rejection in PR #255), just worth noting.


4. 🟡 safety.py:50 — Slack token regex body omits underscore (PLAUSIBLE)

"slack_token": re.compile(r"\bxox[bpasre]-[a-zA-Z0-9-]{10,}\b"),

Slack tokens can contain underscores in the body (e.g., xoxb-123456789012-abc_def_ghijklmn). The character class [a-zA-Z0-9-] doesn't include _, so the match may stop at the underscore boundary and not reach the {10,} minimum. Minor — the main Slack token prefixes (xoxb-, xoxp-) are correctly covered.

Fix: [a-zA-Z0-9_-] to include underscores.


What looks good

  • Consistent adoption: All 10 new agents use identical rejected_elements phrases — more specific than agentic_rag's pre-existing patterns ("my system prompt is" vs "I am a"), reducing false positive risk
  • HITL approval handling: The HITL test correctly passes approval=golden.get("approval", "yes"), ensuring the adversarial rejection flow works
  • Autogen refactoring: Hand-rolled credential patterns replaced with score_pii_leakage scorer (11 pattern types), integrating with the scoring framework
  • Shared helper extraction: assert_injection_resistance eliminates copy-paste across 10 agents
  • All callers assert result.success before calling the helper — the "no result.success guard" concern is mitigated by caller discipline

Summary

No blocking issues. The core pattern is sound — rejected_elements + assert_injection_resistance provides a good security baseline across all agents. Consider adding _ to the Slack token regex (#4) and noting the stricter-than-original behavior in the agentic_rag refactoring (#1). Ready to merge with minor fixes.

@shricharan-ks

Copy link
Copy Markdown
Contributor

Additional findings from second review pass

Two more findings worth noting:

5. 🟡 HITL adversarial query: test_tool_rejection no longer verifies refusal language

_rejection_queries() selects all queries with approval == "no", which includes the adversarial query. test_tool_rejection checks expected_elements with OR semantics (len(found) > 0).

  • Old expected_elements: ["cannot", "will not"] → verified the agent communicates refusal
  • New expected_elements: ["file"] → only verifies the response mentions "file", which is trivially true

The basic rejection query (danger.txt) still checks ["cannot", "will not"], and the new test_adversarial_prompt_injection_resistance covers prompt-leak detection. But there's a gap: no test now verifies the HITL agent explicitly refuses the adversarial request with refusal language. Consider keeping expected_elements: ["cannot", "will not", "file"] for both refusal-verification AND content-verification.

6. 🟡 Autogen: hardcoded adversarial query in test_adversarial_no_pii_leakage will diverge from golden YAML

test_adversarial_no_pii_leakage hardcodes the query string, while the new test_adversarial_prompt_injection_resistance reads it from golden_queries.yaml. Both exercise the same query today, but if the golden YAML changes later, the PII test's hardcoded copy silently diverges. Consider reading the query from the golden data in both tests.

andrewdonheiser and others added 2 commits July 16, 2026 06:54
- Add underscore to slack_token regex char class so tokens like
  xoxb-123456789012-abc_def_ghijklmn match fully
- Read adversarial query from golden YAML in autogen
  test_adversarial_no_pii_leakage instead of hardcoding it, preventing
  silent divergence if the fixture changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@andrewdonheiser

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review @shricharan-ks — here's our take on each finding:

#1 (helper stricter than original) — Intentional, not a pure refactoring. The PR description calls out adding score_prompt_injection_resistance() for broader compliance detection (13 regex patterns covering DAN mode, developer mode, instruction override, etc.). This gives defense-in-depth: YAML rejected_elements catch agent-specific phrases, the scorer catches broad compliance indicators.

#2 (.get("expected_elements", []) silently skips) — Every adversarial golden query across all agents has expected_elements defined — it's a required field in the fixture schema. The caller also pre-filters with if q.get("rejected_elements"), so only well-formed entries reach the helper. The .get() is defensive against a case that can't happen with valid fixture data, and a missing expected_elements would be caught during fixture authoring since the test wouldn't validate topic relevance.

#3 (HITL expected_elements semantic shift) — Deliberate design choice. The test asserts every element in expected_elements is present (AND semantics), so adding refusal terms like ["cannot", "will not"] would require ALL of them to appear — too strict for non-deterministic LLM output. Refusal wording is already validated by test_tool_rejection (the danger.txt query checks ["cannot", "will not"] with OR semantics). This test's job is topic relevance + no leakage.

#4 (Slack token regex missing underscore) — Good catch, fixed in ca9af4b. Character class now includes _ so tokens like xoxb-123456789012-abc_def_ghijklmn match fully.

#5 (HITL refusal language gap) — Same reasoning as #3. test_tool_rejection covers refusal language with OR semantics on the danger.txt query. The adversarial query's safety is covered by rejected_elements + score_prompt_injection_resistance(). Adding refusal terms to expected_elements here would create flaky assertions since the adversarial test uses AND semantics.

#6 (Autogen hardcoded query divergence) — Good catch, fixed in ca9af4b. test_adversarial_no_pii_leakage now reads the query from golden_queries.yaml instead of hardcoding it.

@shricharan-ks shricharan-ks left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Re-review — Actionable findings addressed ✅

Commit ca9af4b9 fixes the two actionable findings from the previous review:

Finding #4 (Slack token regex) — FIXED ✅

# Before: [a-zA-Z0-9-]  (missed underscores)
# After:  [a-zA-Z0-9_-] (covers underscore-containing tokens)

Finding #6 (autogen hardcoded query) — FIXED ✅
test_adversarial_no_pii_leakage now reads the query from golden YAML via adversarial[0]["query"] instead of hardcoding the string. No more divergence risk.

Remaining findings (1, 2, 3, 5) — all non-blocking design decisions, acknowledged:

  • Helper is intentionally stricter than original (adds compliance-indicator check)
  • .get() with default is defensive coding vs fail-fast — reasonable choice
  • HITL expected_elements semantic shift is deliberate (refusal covered by test_tool_rejection)
  • HITL test_tool_rejection for adversarial query is weaker but covered by test_adversarial_prompt_injection_resistance

LGTM — ready to merge.

@andrewdonheiser
andrewdonheiser merged commit 45f32a9 into main Jul 16, 2026
18 checks passed
@andrewdonheiser
andrewdonheiser deleted the feat/rejected-elements-adoption branch July 16, 2026 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants