Repository navigation
feat(aiguard): apply backend extra_tags and extra_metastruct to AI Guard spans - #10865
Conversation
The AI Guard evaluate response can carry an optional extra_tags object (data.attributes.extra_tags), so policies can add context to AI Guard spans without a tracer release per tag. - Scalar values (string, finite number, boolean) are set as tags on the ai_guard span under the key exactly as returned; booleans as 'true'/'false'. - Objects and arrays are added to the ai_guard meta_struct. - No prefix is added and keys are not filtered by namespace. - Tracer-computed tags and meta_struct keys, and span-control keys (sampling, service, resource, type, error, version, env, ...), are never overridden. - Applied before the block decision, so tags land on both allow and blocking paths. - Malformed payloads are skipped with a debug log and never change the evaluation result or telemetry. Jira: APPSEC-70769 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Overall package sizeSelf size: 9.73 MB Dependency sizes| name | version | self size | total size | |------|---------|-----------|------------| | import-in-the-middle | 3.5.3 | 131.25 kB | 531.86 kB | | opentracing | 0.14.7 | 194.81 kB | 194.81 kB | | dc-polyfill | 0.1.11 | 25.74 kB | 25.74 kB |🤖 This report was automatically generated by heaviest-objects-in-the-universe |
🎉 All green!🧪 All tests passed 🔄 Datadog retried 4 tests - 3 passed on retry 🎯 Code Coverage (details) 🔗 Commit SHA: 73fcee0 | Docs | View more details | Give us feedback! |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #10865 +/- ##
==========================================
+ Coverage 93.64% 98.97% +5.33%
==========================================
Files 1068 1072 +4
Lines 175397 176070 +673
Branches 72 72
==========================================
+ Hits 164252 174270 +10018
+ Misses 11144 1799 -9345
Partials 1 1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
BenchmarksBenchmark execution time: 2026-10-29 10:55:19 Comparing candidate commit 73fcee0 in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 2440 metrics, 17 unstable metrics.
|
|
@codex review |
1 similar comment
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Codex Review: Didn't find any major issues. Keep it up! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
The span formatter writes root and chunk keys (_dd.top_level, _dd.*_psr, _dd.p.*, _dd.tags.process) before copying span tags, so a backend extra tag with the same key could overwrite them, and _dd1.sr.eausr was writable directly. Reserve the whole tracer-internal _dd namespace through a single isReservedExtraTag predicate, plus the internal keys outside it (_sampling_priority_v1, process_id, language, runtime-id). This is not an AI Guard namespace filter: the backend still owns ai_guard.*. Jira: APPSEC-70769 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The AI Guard evaluate response now carries backend-provided span data
in two attributes, each a list of { key, value } objects, because
dotted keys such as ai_guard.evaluate_id are not valid JSON:API member
names and the default decoder would reject the response.
- extra_tags: each entry with a string value is set as a tag on the
ai_guard span under the key exactly as returned. Non-string values
are skipped and never reach the meta_struct.
- extra_metastruct: each entry with a non-null value (object, array or
scalar) is added to the ai_guard meta_struct under the key exactly
as returned.
- A repeated key keeps its last value. A field that is not a list, or
an entry that is not an object or has a missing, empty, non-string
or __proto__ key, is skipped with a debug log.
- Reserved keys and the fail-safe guarantees are unchanged.
The backend sends the evaluation ID as ai_guard.evaluate_id in
extra_tags only; extra_metastruct is empty for now.
Jira: APPSEC-70769
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
More details
Reserved-key handling protects tracer-owned span controls, and malformed optional fields do not change evaluation validity. Runtime confidence is limited to isolated checks because the checkout lacks the dependencies needed for the full Mocha suite.
🤖 Bits Code Review · Commit 28e0d15
CarlesDD
left a comment
There was a problem hiding this comment.
LGTM. one optional suggestion, not blocking
…sing the response
Parsing now owns the shape of both fields, like tags, sds_findings and
tag_probs: normalizeExtraEntries turns each into a list of { key, value }
entries, maps a non-list to an empty list and drops malformed entries,
so a malformed value never becomes a bad_response. Absent fields share
one frozen empty list.
applyExtraSpanData only applies the span policy: string tag values,
non-null meta_struct values and the reserved keys.
Jira: APPSEC-70769
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
/merge |
|
View all feedbacks in Devflow UI.
The expected merge time in
Tests failed on this commit ad90c0b: What to do next?
|
|
/merge |
|
View all feedbacks in Devflow UI.
The expected merge time in
Tests failed on this commit 40a10a3: What to do next?
|
|
/merge |
|
View all feedbacks in Devflow UI.
The expected merge time in
Tests failed on this commit c0f117e: What to do next?
|
|
/merge |
|
View all feedbacks in Devflow UI.
The expected merge time in
This pull request was merged directly. |
…ard spans (#10865) * feat(aiguard): add backend-provided extra_tags to AI Guard spans The AI Guard evaluate response can carry an optional extra_tags object (data.attributes.extra_tags), so policies can add context to AI Guard spans without a tracer release per tag. - Scalar values (string, finite number, boolean) are set as tags on the ai_guard span under the key exactly as returned; booleans as 'true'/'false'. - Objects and arrays are added to the ai_guard meta_struct. - No prefix is added and keys are not filtered by namespace. - Tracer-computed tags and meta_struct keys, and span-control keys (sampling, service, resource, type, error, version, env, ...), are never overridden. - Applied before the block decision, so tags land on both allow and blocking paths. - Malformed payloads are skipped with a debug log and never change the evaluation result or telemetry. Jira: APPSEC-70769 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(aiguard): reserve tracer-internal keys from backend extra_tags The span formatter writes root and chunk keys (_dd.top_level, _dd.*_psr, _dd.p.*, _dd.tags.process) before copying span tags, so a backend extra tag with the same key could overwrite them, and _dd1.sr.eausr was writable directly. Reserve the whole tracer-internal _dd namespace through a single isReservedExtraTag predicate, plus the internal keys outside it (_sampling_priority_v1, process_id, language, runtime-id). This is not an AI Guard namespace filter: the backend still owns ai_guard.*. Jira: APPSEC-70769 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(aiguard): read extra_tags and extra_metastruct as key/value lists The AI Guard evaluate response now carries backend-provided span data in two attributes, each a list of { key, value } objects, because dotted keys such as ai_guard.evaluate_id are not valid JSON:API member names and the default decoder would reject the response. - extra_tags: each entry with a string value is set as a tag on the ai_guard span under the key exactly as returned. Non-string values are skipped and never reach the meta_struct. - extra_metastruct: each entry with a non-null value (object, array or scalar) is added to the ai_guard meta_struct under the key exactly as returned. - A repeated key keeps its last value. A field that is not a list, or an entry that is not an object or has a missing, empty, non-string or __proto__ key, is skipped with a debug log. - Reserved keys and the fail-safe guarantees are unchanged. The backend sends the evaluation ID as ai_guard.evaluate_id in extra_tags only; extra_metastruct is empty for now. Jira: APPSEC-70769 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(aiguard): normalize extra_tags and extra_metastruct when parsing the response Parsing now owns the shape of both fields, like tags, sds_findings and tag_probs: normalizeExtraEntries turns each into a list of { key, value } entries, maps a non-list to an empty list and drops malformed entries, so a malformed value never becomes a bad_response. Absent fields share one frozen empty list. applyExtraSpanData only applies the span policy: string tag values, non-null meta_struct values and the reserved keys. Jira: APPSEC-70769 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ard spans (#10865) * feat(aiguard): add backend-provided extra_tags to AI Guard spans The AI Guard evaluate response can carry an optional extra_tags object (data.attributes.extra_tags), so policies can add context to AI Guard spans without a tracer release per tag. - Scalar values (string, finite number, boolean) are set as tags on the ai_guard span under the key exactly as returned; booleans as 'true'/'false'. - Objects and arrays are added to the ai_guard meta_struct. - No prefix is added and keys are not filtered by namespace. - Tracer-computed tags and meta_struct keys, and span-control keys (sampling, service, resource, type, error, version, env, ...), are never overridden. - Applied before the block decision, so tags land on both allow and blocking paths. - Malformed payloads are skipped with a debug log and never change the evaluation result or telemetry. Jira: APPSEC-70769 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(aiguard): reserve tracer-internal keys from backend extra_tags The span formatter writes root and chunk keys (_dd.top_level, _dd.*_psr, _dd.p.*, _dd.tags.process) before copying span tags, so a backend extra tag with the same key could overwrite them, and _dd1.sr.eausr was writable directly. Reserve the whole tracer-internal _dd namespace through a single isReservedExtraTag predicate, plus the internal keys outside it (_sampling_priority_v1, process_id, language, runtime-id). This is not an AI Guard namespace filter: the backend still owns ai_guard.*. Jira: APPSEC-70769 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(aiguard): read extra_tags and extra_metastruct as key/value lists The AI Guard evaluate response now carries backend-provided span data in two attributes, each a list of { key, value } objects, because dotted keys such as ai_guard.evaluate_id are not valid JSON:API member names and the default decoder would reject the response. - extra_tags: each entry with a string value is set as a tag on the ai_guard span under the key exactly as returned. Non-string values are skipped and never reach the meta_struct. - extra_metastruct: each entry with a non-null value (object, array or scalar) is added to the ai_guard meta_struct under the key exactly as returned. - A repeated key keeps its last value. A field that is not a list, or an entry that is not an object or has a missing, empty, non-string or __proto__ key, is skipped with a debug log. - Reserved keys and the fail-safe guarantees are unchanged. The backend sends the evaluation ID as ai_guard.evaluate_id in extra_tags only; extra_metastruct is empty for now. Jira: APPSEC-70769 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(aiguard): normalize extra_tags and extra_metastruct when parsing the response Parsing now owns the shape of both fields, like tags, sds_findings and tag_probs: normalizeExtraEntries turns each into a list of { key, value } entries, maps a non-list to an empty list and drops malformed entries, so a malformed value never becomes a bad_response. Absent fields share one frozen empty list. applyExtraSpanData only applies the span policy: string tag values, non-null meta_struct values and the reserved keys. Jira: APPSEC-70769 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
What does this PR do?
Jira: APPSEC-70769 (epic APPSEC-70724)
The AI Guard evaluate response can now carry backend-provided span data in two optional attributes, each a list of
{ key, value }entries. This applies them to theai_guardspan:What the backend sends today (the evaluation ID, as a span tag only):
extra_metastructis empty for now, but implemented so future structured values need no tracer release. Illustrative shape (examples only, not sent today):extra_tags: each entry with a string value is set as a tag on theai_guardspan, under the key exactly as returned. Non-string values are skipped and never reach the meta_struct.extra_metastruct: each entry with a non-null value (object, array or scalar) is added to theai_guardmeta_struct, under the key exactly as returned.ai_guard.prefix and does not filter by namespace: the backend owns it.extra_tags(singleisReservedExtraTagpredicate): every tracer-internal_dd*key (_dd.*,_dd1.*, e.g._dd.top_level,_dd.*_psr,_dd.p.*, which the span formatter writes before copying span tags), the tags the tracer computes itself (ai_guard.action,reason,target,tool_name,blocked,redacted,event, the HTTP/user entry tags), and keys with span-level side effects (manual.keep,manual.drop,sampling.priority,_sampling_priority_v1,service.name,resource.name,span.type,span.kind,error*,analytics.event,service.version,version,env,process_id,language,runtime-id). Reserving_dd*protects the tracer namespace; it is not an AI Guard namespace filter.extra_metastruct:messages,attack_categories,sds,tag_probs.__proto__key is skipped with a debug log. The evaluation result and telemetry never change, and a missing or malformed field never becomes a bad response.No public API change.
Motivation
Lets AI Guard policies attach their own context to traces without a tracer release per tag (APPSEC-69622); the first entry is the evaluation ID (
ai_guard.evaluate_id, a span tag only), so theai_guardspan can be correlated with the backend evaluation.The two fields are lists rather than maps because dotted keys such as
ai_guard.evaluate_idare not valid JSON:API member names, and the default DataDog/jsonapi decoder would reject the response (see APPSEC-70724).Additional Notes
_dd*key and the span-control keys thatspan_format/span.setTagtreat specially in dd-trace-js (sampling, top-level span fields, error flag, root/chunk metrics), and rejects__proto__keys. Worth aligning the cross-tracer list in the RFC.encode/0.4.js) drops booleans directly inside arrays andnullvalues. The backend definesextra_metastruct, so it avoids those shapes, as it already avoids dotted keys inside structured values.manifests/nodejs.ymlin system-tests needs flipping frommissing_featureonce this ships.mocha 'packages/dd-trace/test/aiguard/**/*.spec.js'412 passing;npm run lintclean; no newtscerrors insrc/aiguard.🤖 Generated with Claude Code