Skip to content

AI Guard: redaction tests for the full provider-bound context (multi-turn history) - #7595

Draft
avara1986 wants to merge 2 commits into
mainfrom
avara/APPSEC-68867-ai-guard-redaction-multi-turn
Draft

AI Guard: redaction tests for the full provider-bound context (multi-turn history)#7595
avara1986 wants to merge 2 commits into
mainfrom
avara/APPSEC-68867-ai-guard-redaction-multi-turn

Conversation

@avara1986

@avara1986 avara1986 commented Aug 26, 2026

Copy link
Copy Markdown
Member

Motivation

Jira: APPSEC-68867
RFCs: RFC AI Guard Sensitive Data Redaction (v1.2) and RFC AI Guard Sensitive Data Redaction. Backend (v1.1)
Follow-up to #7385, which introduced the redaction corpus.

Both RFCs were revised to make one point normative:

AI Guard attack analysis evaluates the latest logical message. SDS sensitive-data scanning MUST inspect every model-visible string in every message included in the current /evaluate request. redaction_replacements MUST therefore contain an entry for every path in the current provider-bound context that SDS mutates, not only paths belonging to the last message.

This matters because redaction is copy-on-write: the tracer sends a redacted copy to the provider and leaves the caller's list alone, so an earlier message still holds its original value on the next turn.

The gap

Of the 24 redaction scenarios on main, only REDACT_MIXED redacted a non-last message alongside the latest one, and REDACT_SYSTEM_PROMPT redacted a two-message system prompt. A tracer that redacted only the latest message passed 22 of 24 and would still ship the entire conversation history to the provider on turn 2 — the exact failure the RFC revision was written to prevent.

What this PR adds

Test_RedactionMultiTurnContext (9 tests) and 9 scenarios in gen_redaction_cassettes.py:

Scenario What it pins down
REDACT_TURN_1 + REDACT_HISTORY_AND_LATEST The RFC multi-turn example verbatim: historical SSN at messages[1] plus a new email at messages[3], with the already-redacted assistant message at messages[2] surviving byte for byte (no double redaction)
REDACT_HISTORY_ONLY Sensitive data only in the history, latest message benign — the case a last-message-only tracer cannot fake
REDACT_EVERY_ROLE_IN_HISTORY Five replacements in one call: system, historical user, tool-call arguments, tool result, latest user. Also asserts the redacted arguments still parse as JSON
REDACT_HISTORICAL_TOOL_CALL Tool call and result several turns back, latest message benign
REDACT_HISTORY_CONTENT_PART messages[1].content[0].text in a historical multimodal message
REDACT_DEEP_HISTORY Non-contiguous replacements at indices 1/3/5/7 of an eight-message conversation, each with benign neighbours so an off-by-one shows up as a mismatch
REDACT_SAME_VALUE_ACROSS_TURNS One value restated in a later turn: one entry per path, each applied on its own
REDACT_REORDERED_CONTEXT Drives test_redact_paths_are_request_local, which replays turn 1 → turn 2 → reordered turn 2 across three calls. The reorder swaps the strings behind messages[1]/messages[3], so a tracer reusing the previous response's paths writes the SSN replacement over the email message and fails

Plus test_redacted_history_in_sdk_response, asserting the list handed back to the caller — and therefore sent to the provider — has no history left. That is the guarantee redaction exists for.

Keeping fixtures in sync

Unchanged from #7385: scenarios live in SCENARIOS in utils/scripts/gen_redaction_cassettes.py, which writes both the cassettes and tests/ai_guard/redaction_scenarios.json, and cross-checks every declared expect_redacted/expect_removed against its reference implementation of the RFC algorithm. ./format.sh runs it, so the fixtures in the repo are always the fixtures the script produces.

Test results

  • ./run.sh AI_GUARD tests/ai_guard/test_ai_guard_sdk.py::Test_RedactionMultiTurnContext9 passed against python@4.14.0-rc1. Python's redact_messages is index- and cardinality-agnostic, so it passes unchanged — the new tests document a contract it already satisfies rather than asking for new work.
  • Full AI Guard suite → 49 passed, 2 xfailed (the 2 are the pre-existing Test_RedactionInSDKResponse gap).
  • TEST_THE_TEST437 passed.
  • ./format.sh → mypy, ruff, yamlfmt, yamllint, parser checks and the redaction-fixture drift check all clean.

Notes for reviewers

  • Test_RedactionMultiTurnContext is activated for python (flask-poc: v4.14.0-dev) and declared at the existing redaction ref for nodejs (express4: *ref_6_13_0); missing_feature for cpp_httpd, cpp_nginx, dotnet, golang, java, php, ruby.
  • It is a separate class rather than more methods on Test_Redaction on purpose: full-context scope is its own normative concern, so a tracer that implements last-message redaction first can be declared separately.
  • docs/understand/scenarios/ai_guard.md gains a subsection explaining the scope distinction and why the class exists.

🤖 Generated with Claude Code

…message

APPSEC-68867

Backend RFC v1.1 and RFC v1.2 make the scope distinction normative: attack
analysis targets the latest logical message, but SDS scans every model-visible
string in the messages array of the current /evaluate call, so
redaction_replacements may target the system prompt and historical user,
assistant and tool messages as well as the latest one.

Only REDACT_MIXED redacted a non-last message alongside the latest, so a tracer
that redacted only the latest message passed 22 of the 24 existing scenarios
while still shipping the whole history to the provider on turn 2.

Adds Test_RedactionMultiTurnContext and nine generator scenarios: the RFC
multi-turn example (historical SSN plus a new email, with an already redacted
assistant message that must survive byte for byte), history-only redaction with
a benign latest message, one replacement per role in a single call, historical
tool call/result and content part, non-contiguous replacements in an
eight-message conversation, a value restated across turns, and request-local
paths replayed over three calls that grow then reorder the conversation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

CODEOWNERS have been resolved as:

utils/build/docker/vcr/cassettes/aiguard/aiguard_evaluate_post_0acfa129.json  @DataDog/k9-ai-guard @DataDog/system-tests-core
utils/build/docker/vcr/cassettes/aiguard/aiguard_evaluate_post_1c682a42.json  @DataDog/k9-ai-guard @DataDog/system-tests-core
utils/build/docker/vcr/cassettes/aiguard/aiguard_evaluate_post_2fb44ba7.json  @DataDog/k9-ai-guard @DataDog/system-tests-core
utils/build/docker/vcr/cassettes/aiguard/aiguard_evaluate_post_4f716ee4.json  @DataDog/k9-ai-guard @DataDog/system-tests-core
utils/build/docker/vcr/cassettes/aiguard/aiguard_evaluate_post_5302857d.json  @DataDog/k9-ai-guard @DataDog/system-tests-core
utils/build/docker/vcr/cassettes/aiguard/aiguard_evaluate_post_5784ecb9.json  @DataDog/k9-ai-guard @DataDog/system-tests-core
utils/build/docker/vcr/cassettes/aiguard/aiguard_evaluate_post_942c4dec.json  @DataDog/k9-ai-guard @DataDog/system-tests-core
utils/build/docker/vcr/cassettes/aiguard/aiguard_evaluate_post_cd60ada5.json  @DataDog/k9-ai-guard @DataDog/system-tests-core
utils/build/docker/vcr/cassettes/aiguard/aiguard_evaluate_post_d7bf6a54.json  @DataDog/k9-ai-guard @DataDog/system-tests-core
docs/understand/scenarios/ai_guard.md                                   @DataDog/k9-ai-guard @DataDog/system-tests-core
manifests/cpp_httpd.yml                                                 @DataDog/system-tests-reviewers
manifests/cpp_nginx.yml                                                 @DataDog/system-tests-reviewers
manifests/dotnet.yml                                                    @DataDog/system-tests-reviewers
manifests/golang.yml                                                    @DataDog/system-tests-reviewers
manifests/java.yml                                                      @DataDog/system-tests-reviewers
manifests/nodejs.yml                                                    @DataDog/system-tests-reviewers
manifests/php.yml                                                       @DataDog/system-tests-reviewers
manifests/python.yml                                                    @DataDog/system-tests-reviewers
manifests/ruby.yml                                                      @DataDog/system-tests-reviewers
tests/ai_guard/redaction_scenarios.json                                 @DataDog/k9-ai-guard @DataDog/system-tests-core
tests/ai_guard/test_ai_guard_sdk.py                                     @DataDog/k9-ai-guard @DataDog/system-tests-core
utils/scripts/gen_redaction_cassettes.py                                @DataDog/system-tests-core

@datadog-prod-us1-6

datadog-prod-us1-6 Bot commented Aug 26, 2026

Copy link
Copy Markdown

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: c0b0b65 | Docs | View more details | Give us feedback!

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