fix: consult live message tree for wrapped-attribute reads when dirty - #296
Open
detail-app[bot] wants to merge 3 commits into
Open
detail-app[bot] wants to merge 3 commits into
detail-app[bot] wants to merge 3 commits into
Conversation
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This focused fix changes production Datadog redaction and attribute-resolution behavior, including whether previously scrubbed log data can be forwarded. Because the affected path handles potentially sensitive data, human review is warranted despite the added regression coverage. You can add or adjust custom eligibility rules. Learn more. |
…nwrappedAttribute
When the tree parent is an object and the leaf value is a non-string
(e.g. an array), the previous code returned null immediately.
flattenValue stores the first string element of such arrays under the
parent key in message_flat, so the null short-circuit caused a later
redact to see no value and leave the secret in the forwarded record.
Change else => return null to else => {} so the non-string-leaf case
falls through to message_flat, which stays authoritative for
never-edited and array-flattened paths.
Add a regression test covering an array containing a string after an
unrelated sibling edit dirties the tree.
When message_dirty is set and an ancestor key was deleted from the message_tree via deleteWrapped, unwrappedAttribute previously fell through to the stale message_flat snapshot, surfacing data that a policy had removed. Replace the single navigateParent call with a manual per-segment walk: - object ancestor with key absent → return null (deleted ancestor) - non-object ancestor (array) → defer to message_flat (array-flattened path) Add a regression test covering deleteWrapped on an ancestor object node followed by a read through that ancestor.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Detail bug report: View on Detail
Bug
For Datadog logs with a JSON-wrapped
message(GCP/Cloud Run shape), wrapped-attribute reads go throughDatadogLog.unwrappedAttribute, which serves a one-shotmessage_flatsnapshot built from the originalmessageon first read. Wrapped writes (setWrapped/deleteWrapped) mutate the livemessage_treeand flipmessage_dirty, but never refreshmessage_flat.The policy engine re-reads a field via the accessor before each transform write (remove → redact → rename → add), so for a field edited by an earlier rule, a later rule reads the stale pre-transform snapshot and overwrites the prior edit. The realistic impact: a redaction policy with more than one regex
redactrule on the same wrapped path silently loses every earlier rule's substitution — only the last rule's pattern is reliably scrubbed, and the PII the earlier rules targeted reappears in the forwarded record.Introduced in PR #203 (
f40cc7e), which added the edit primitives and the one-shot read cache but only wired the whole-body-replace path throughclearWrappedRewrite; thelog_attribute/resource_attribute/scope_attributebranches route tosetWrapped/deleteWrappedand never invalidate the read side.Fixes ENG-697
Fix
In
DatadogLog.unwrappedAttribute(src/signals/datadog/log.zig), consult the livemessage_treefirst whenmessage_dirtyis true, using the existingnavigateParent+obj.getPtrprimitives:null(adeleteWrappedremoval reads as absent, not the stale value — this is the primitive backingcallExists, so remove→add and remove→redact now compose correctly).null(matches the flat, which stores strings only).navigateParentfails, or the parent isn't an object — e.g. an array-of-objects the flattener reaches butnavigateParentwon't descend into, or no edit happened yet) → fall through tomessage_flat, which stays authoritative for never-edited and array-flattened paths.This keeps reads and writes consistent within a single transform pass.
setWrapped/deleteWrapped,ensureUnwrapped,clearWrappedRewrite,bodyForMatch,findExtraString, and the whole-body and top-level set/delete paths are unchanged.bodyForMatchis unaffected because matchers run in Phase 1, before any transform flipsmessage_dirty.Testing
Added 4 inline regression tests (the repo keeps tests inline in the same file):
log.zig:unwrappedAttribute observes a setWrapped edit on the same path;unwrappedAttribute returns null after deleteWrapped removes the leaf;unwrappedAttribute still resolves array-of-objects paths after an unrelated edit(guards against an over-eager fix regressing the array-of-objects flatten path).logs.zig:processLogs - two regex redacts on the same wrapped path compose— the primary leak end-to-end through the policy engine + re-serialization.Verified the tests are genuine regression guards by disabling the fix's
if (self.message_dirty)branch: exactly the 4 new tests fail (and nothing else), with the failure detail showing the stalealice@example.comread after rule 1's edit. Restoring the fix flips them back to pass.Routine checks all pass:
zig build,zig build test --summary all(523 pass, 1 skip, 0 fail),zig fmt --check src/ build.zig, andziglint.Also verified three un-versioned end-to-end scenarios live via temporary inline tests (since removed): three composing regex redacts on the same path (
alice→ALICE_R,example→EXAMPLE_R,\.→-→ALICE_R@EXAMPLE_R-com); a regex redact followed by a whole-value redact on the same path (whole-valueXwins, all prior tokens gone); and remove then regex-redact on the same path (the later redact readscallExists→null and no-ops, no PII leak). Thedatadog-log-benchbenchmark also still completes without error, including thewrapped/wrapped_rewritepath that exercisessetWrapped/deleteWrapped.Automatic Fixes PRs can be configured here.