feat(core): add global audit config and add configured JWT claims to audit logs#3429
feat(core): add global audit config and add configured JWT claims to audit logs#3429jakedoublev wants to merge 20 commits intomainfrom
Conversation
Signed-off-by: jakedoublev <jake.vanvorhis@virtru.com>
Signed-off-by: jakedoublev <jake.vanvorhis@virtru.com>
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds an optional audit configuration and JWT-to-audit-field enrichment: config model and validation, schema for allowed audit paths, dot-notation helpers, logger wiring to store/apply audit config, claim extraction/normalization into audit events, IPC auth-context rehydration from propagated metadata, and related tests and YAML examples. ChangesAudit, Auth, and Logging Feature
Sequence Diagram(s)sequenceDiagram
participant Client
participant IPC as IPC Interceptor
participant AuthCtx as Auth Context
participant Handler as Service Handler
participant Audit as Audit Logger
Client->>IPC: gRPC request (metadata: access_token / Authorization)
IPC->>AuthCtx: ipcReauthCheck (route reauthorization)
IPC->>AuthCtx: rehydrateIPCAuthContext (extract raw token)
AuthCtx->>AuthCtx: parse JWT (no verification) -> store token + raw bytes
IPC->>Handler: invoke handler with rehydrated context
Handler->>Audit: emit audit event
Audit->>AuthCtx: read claims from token in context
Audit->>Audit: apply JWTClaimMappings (dot-notation Get/Set)
Audit->>Audit: normalize values and log enriched audit entry
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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. Review rate limit: 0/1 reviews remaining, refill in 29 minutes and 30 seconds.Comment |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request enhances the platform's audit logging capabilities by enabling the enrichment of audit logs with claims extracted from JWTs. It introduces a configurable mapping system that allows administrators to specify which JWT claims should be included in audit logs and where they should be placed within the log structure. This provides better traceability and context for audit events without requiring changes to the core business logic. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Logs flow deep with claims in tow, Mapping paths where secrets go. Dot notation guides the way, To keep our audit records gray. Footnotes
|
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
There was a problem hiding this comment.
Code Review
This pull request introduces audit log enrichment, allowing JWT claims to be mapped to specific audit log fields using a new dot notation utility. It also updates the authentication package to support token retrieval from gRPC metadata as a fallback. Feedback was provided to expand the list of reserved audit paths to include all core fields, ensuring that custom mappings cannot overwrite critical audit information and compromise log integrity.
Signed-off-by: jakedoublev <jake.vanvorhis@virtru.com>
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Signed-off-by: jakedoublev <jake.vanvorhis@virtru.com>
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Signed-off-by: jakedoublev <jake.vanvorhis@virtru.com>
X-Test Failure Reportcukes-report |
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Signed-off-by: jakedoublev <jake.vanvorhis@virtru.com>
X-Test Failure Report |
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Signed-off-by: jakedoublev <jake.vanvorhis@virtru.com>
| // We should probably return an error here? | ||
| l.ErrorContext(ctx, "invalid authContext") | ||
| if l != nil { | ||
| l.ErrorContext(ctx, "invalid authContext") |
There was a problem hiding this comment.
The actual logged message is just "invalid authContext", so I think this is not necessary?
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Signed-off-by: jakedoublev <jake.vanvorhis@virtru.com>
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Signed-off-by: jakedoublev <jake.vanvorhis@virtru.com>
Signed-off-by: jakedoublev <jake.vanvorhis@virtru.com>
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Signed-off-by: jakedoublev <jake.vanvorhis@virtru.com>
X-Test Failure Reportcukes-report |
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Signed-off-by: jakedoublev <jake.vanvorhis@virtru.com>
| Timestamp string `json:"timestamp"` | ||
| Original map[string]any `json:"original,omitempty" audit:"extensible"` | ||
| Updated map[string]any `json:"updated,omitempty" audit:"extensible"` | ||
| RequestID uuid.UUID `json:"requestID" audit:"reserved"` |
There was a problem hiding this comment.
This is to align with the existing LogValue() implementation which actually drove the shape and syntax of the emitted logs, rather than marshaled JSON.
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
service/pkg/auth/context_auth.go (1)
46-57:⚠️ Potential issue | 🟠 MajorAdd defensive nil check for interface-typed logger to prevent potential panic.
Line 55's
if l != nilcheck is insufficient in Go: a typed nil pointer assigned to theoptionalErrorLoggerinterface will still evaluate as non-nil, allowing line 56'sl.ErrorContext()call to panic. Either use a more robust check (e.g., reflection to verify the underlying pointer is nil) or pass a no-op logger implementation instead of nil.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@service/internal/auth/authn.go`:
- Around line 515-518: The handler currently treats failures from
rehydrateIPCAuthContext as internal server errors; instead, convert those
authentication failures into an unauthenticated response by returning
connect.NewError with connect.CodeUnauthenticated when
rehydrateIPCAuthContext(nextCtx, a.logger) returns an error. Update the error
return in the block that calls rehydrateIPCAuthContext so it uses
CodeUnauthenticated and a clear auth-failure message (refer to the call site of
rehydrateIPCAuthContext in authn.go).
In `@service/logger/audit/enrichment.go`:
- Around line 50-56: ApplyConfig is allowing overlapping mapping paths (e.g.
"banana" vs "banana.kiwi.mango") which causes dotnotation.Set to fail at
runtime; add a validation step in Config.Validate() (or call from ApplyConfig)
that scans all mapping.Path values and rejects the config if any path is a
prefix of another path (or vice versa). Implement this by comparing normalized
path tokens for each pair of mappings (use mapping.Path and mapping.Claim to
construct clear error messages) and return a validation error instead of
accepting the config so the service fails fast rather than emitting per-request
enrichment errors from dotnotation.Set.
In `@service/logger/audit/schema.go`:
- Around line 114-124: The parseAuditTag function should fail fast on unknown
tag values: change parseAuditTag signature to return (bool, bool, error), make
the switch default return a descriptive error for unrecognized tag strings
(e.g., "unknown audit tag: ..."), update every call site in schema construction
code to handle and propagate that error (so schema building returns an error
instead of silently treating unknown tags as unset), and update any tests to
expect error propagation from parseAuditTag during schema construction.
🪄 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: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: ff57d941-f246-4516-97c9-9e60e4a9bf17
📒 Files selected for processing (15)
service/internal/auth/authn.goservice/internal/auth/authn_ipc_metadata_interceptor_test.goservice/internal/dotnotation/dotnotation.goservice/internal/dotnotation/dotnotation_test.goservice/logger/audit/enrichment.goservice/logger/audit/logger.goservice/logger/audit/logger_test.goservice/logger/audit/schema.goservice/logger/audit/schema_test.goservice/logger/audit/utils.goservice/logger/logger.goservice/pkg/auth/context_auth.goservice/pkg/auth/context_auth_test.goservice/pkg/server/services.goservice/pkg/server/services_test.go
…ct unknown audit tags Return connect.CodeUnauthenticated instead of CodeInternal when IPC token rehydration fails, since a malformed token is an auth failure not a server fault. Fail fast on unrecognized audit struct tag values (e.g. a typo like audit:"resreved") by returning an error from parseAuditTag, propagated through schema construction to panic at startup. Signed-off-by: jakedoublev <jake.vanvorhis@virtru.com>
Two mappings where one path is a prefix of another (e.g. "banana" and "banana.kiwi.mango") would pass individual validation but cause dotnotation.Set collisions at runtime. Detect this during Config.Validate() so the service fails fast at startup instead of emitting partially enriched audit logs per-request. Signed-off-by: jakedoublev <jake.vanvorhis@virtru.com>
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
service/logger/audit/enrichment.go (1)
50-56:⚠️ Potential issue | 🟠 Major | ⚡ Quick winReject overlapping destination paths at config-validate time to prevent partial runtime enrichment.
Line 50 still handles
dotnotation.Setfailures per-request. If mappings overlap (e.g.,bananaandbanana.kiwi.mango), enrichment can be partially applied at runtime. This should be rejected duringConfig.Validate()so startup fails fast.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@service/logger/audit/enrichment.go` around lines 50 - 56, Add a config-time check in Config.Validate() that scans all audit JWT-to-path mappings and rejects any overlapping destination paths (prefix/child relationships) so runtime calls to dotnotation.Set cannot partially apply enrichment; implement this by comparing each mapping.Path against others (or building a simple prefix trie) and return a validation error that references the two conflicting mappings (use mapping.Path and mapping.Claim to form the message). Keep the per-request dotnotation.Set error handling but ensure overlapping cases are caught and cause startup validation to fail fast.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@service/internal/auth/authn.go`:
- Around line 476-484: The IPC token extraction currently trims only exact-cased
prefixes using strings.TrimPrefix on authHeaders[0], which fails for
mixed/lowercase schemes; update the logic (in the function that reads md.Get and
returns the token) to perform case-insensitive scheme detection and removal:
read authHeaders[0] into a variable, lower-case a prefix slice (e.g.,
strings.ToLower(header[:n])) or use strings.HasPrefix on strings.ToLower(header)
to check for "bearer " and "dpop " and then return the original header with the
corresponding prefix length removed and trimmed; ensure you still handle both
"authorization" and "Authorization" via md.Get and return "" when header is
empty.
In `@service/logger/audit/schema.go`:
- Around line 166-177: Ensure the full dot-path syntax is validated before
allowing an extensible-node early success: before splitting/iterating (where
auditClaimDestinationSchema, segments, current, and current.extensible are used)
check for malformed paths like trailing dot or consecutive dots (e.g.,
strings.HasSuffix(path, ".") or strings.Contains(path, "..")) and return
ErrUnknownAuditPath if found; this prevents returning nil on the first unknown
segment when current.extensible would otherwise short-circuit and accept
malformed inputs.
---
Duplicate comments:
In `@service/logger/audit/enrichment.go`:
- Around line 50-56: Add a config-time check in Config.Validate() that scans all
audit JWT-to-path mappings and rejects any overlapping destination paths
(prefix/child relationships) so runtime calls to dotnotation.Set cannot
partially apply enrichment; implement this by comparing each mapping.Path
against others (or building a simple prefix trie) and return a validation error
that references the two conflicting mappings (use mapping.Path and mapping.Claim
to form the message). Keep the per-request dotnotation.Set error handling but
ensure overlapping cases are caught and cause startup validation to fail fast.
🪄 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: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 6798ee0e-a717-4ee0-918c-61800dd82ab7
📒 Files selected for processing (5)
service/internal/auth/authn.goservice/internal/auth/authn_ipc_metadata_interceptor_test.goservice/logger/audit/enrichment.goservice/logger/audit/schema.goservice/logger/audit/schema_test.go
X-Test Failure Reportcukes-report |
Return (auditFieldOptions, error) instead of (*auditFieldOptions, error) to avoid nil-nil returns flagged by nilnil linter. A zero-value opts (empty name) signals "skip this field." Signed-off-by: jakedoublev <jake.vanvorhis@virtru.com>
X-Test Failure Report |
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Hoist empty-segment validation before the schema traversal loop so
malformed paths like "banana..mango" or "banana." are rejected even
when the first segment triggers an extensible early return.
Also remove redundant md.Get("Authorization") fallback in IPC token
extraction since gRPC metadata keys are case-insensitive.
Signed-off-by: jakedoublev <jake.vanvorhis@virtru.com>
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@service/internal/auth/authn.go`:
- Around line 445-468: rehydrateIPCAuthContext currently restores pkg/auth state
but does not re-establish audit.ActorID, so IPC hops lose ActorID even when the
JWT 'sub' is present; update rehydrateIPCAuthContext to extract the actor id
(e.g., from parsed.Subject or parsed claims) after successful jwt.Parse and call
audit.ContextWithActorID(ctx, actorID) before returning the context (preserve
existing ctxAuth.ContextWithAuthNInfo usage and use
ctxAuth.GetJWKFromContext(ctx, l) and the parsed token to derive the subject),
ensuring IPCMetadataClientInterceptor/checkToken behavior is preserved and
audit.ActorID is rehydrated for later audit events.
In `@service/logger/audit/config.go`:
- Around line 57-67: The current isPathPrefix(short, long []string) ignores
identical paths because it returns false when len(short) >= len(long); change
the logic so identical slices are treated as a prefix conflict: update
isPathPrefix to return true when lengths are equal and all elements match (or
alternatively change the initial check to only return false when len(short) >
len(long)), so two mappings with the same Path (same slice contents) are
detected as conflicts and can be rejected by the caller that validates mappings;
keep the element-by-element comparison loop and ensure callers of isPathPrefix
(the mapping validation code) will reject duplicate destinations rather than
allowing silent overwrite.
In `@service/logger/audit/enrichment.go`:
- Around line 121-124: Change the call that discards the parse error to capture
and log it: replace "opts, _ := parseAuditFieldOptions(field)" with "opts, err
:= parseAuditFieldOptions(field)" (or equivalent) and if err != nil emit a
descriptive log entry including the field identifier and err before continuing;
keep the existing behavior of continuing when opts.name == "" but ensure
parseAuditFieldOptions errors are logged for observability. Use the existing
package/logger utility available in this file (or the nearest logger) so the log
includes context (function in enrichment.go, the field value, and the error).
In `@service/logger/audit/schema_test.go`:
- Around line 80-112: Add a unit test to cover identical destination paths by
calling validateNoOverlappingPaths with two JWTClaimMapping entries that have
the same Path (e.g., both "eventMetaData.requester.sub") and assert the intended
behavior (either require.NoError if duplicates are allowed or
require.ErrorIs(..., ErrOverlappingAuditPaths) if duplicates should be
rejected); reference validateNoOverlappingPaths, JWTClaimMapping, and
ErrOverlappingAuditPaths to locate where to add the new t.Run case in
TestValidateNoOverlappingPaths.
In `@service/logger/audit/schema.go`:
- Around line 176-201: Remove the unreachable "return nil" that follows the for
loop (the one after iterating over segments) in this function: the loop always
returns on the last segment via the switch that checks child.reserved,
child.extensible, and child.isLeaf, so delete that final return to clarify
control flow; if you prefer a defensive artifact, replace it with a short
comment stating the return is unreachable instead of keeping dead code.
🪄 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: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 2966a3ce-b3dd-40b3-ac1e-34db60e85549
📒 Files selected for processing (5)
service/internal/auth/authn.goservice/logger/audit/config.goservice/logger/audit/enrichment.goservice/logger/audit/schema.goservice/logger/audit/schema_test.go
Two claims mapped to the same path would silently overwrite each other at runtime. Detect this in validateNoOverlappingPaths and reject at config time. Also refactored pair iteration to check each pair once. Signed-off-by: jakedoublev <jake.vanvorhis@virtru.com>
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
|
Proposed Changes
reserved(immutable via configured JWT claim audit paths) orextensible(leaves are mutable) with unit testsexternal request -> registered PEP -> IPC authorization.v2.GetDecisionValidated with this config:
Decision audit log during rewrap flow (note
.banana.kiwi.mango):{ "time": "2026-05-01T14:35:31.065374-07:00", "level": "AUDIT", "msg": "decision", "audit": { "action": { "result": "failure", "type": "read" }, "actor": { "attributes": [ { "entitlements_relevant_to_decision": {} } ], "id": "jwtentity-1-clientid-opentdf" }, "banana": { "kiwi": { "mango": "service-account-opentdf" } }, "clientInfo": { "platform": "authorization.v2", "requestIP": "", "userAgent": "connect-go/1.19.1 (go1.26.2)" }, "eventMetaData": { "fulfillable_obligation_value_fqns": [], "obligations_satisfied": true, "resource_decisions": [ { "passed": false, "obligations_satisfied": true, "entitled": false, "resource_id": "rewrap-0", "data_rule_results": [ { "passed": false, "resource_value_fqns": [ "https://example.com/attr/attr1/value/value1" ], "attribute": { "id": "6a261d68-0899-4e17-bb2f-124abba7c09c", "rule": 2, "fqn": "https://example.com/attr/attr1" }, "entitlement_failures": [ { "attribute_value": "https://example.com/attr/attr1/value/value1", "action": "read" } ] } ], "required_obligation_value_fqns": null } ] }, "object": { "attributes": { "assertions": null, "attrs": null, "permissions": null }, "id": "jwtentity-1-clientid-opentdf-read", "name": "decisionRequest-read", "type": "entity_object" }, "original": null, "requestID": "05a9040f-12b6-4768-8399-f97b58a671bb", "timestamp": "2026-05-01T14:35:31-07:00", "updated": null } }KAS Rewrap audit log (note
.banana.kiwi.mango):{ "time": "2026-05-01T14:35:31.065701-07:00", "level": "AUDIT", "msg": "rewrap", "audit": { "action": { "result": "error", "type": "rewrap" }, "actor": { "attributes": [], "id": "" }, "banana": { "kiwi": { "mango": "service-account-opentdf" } }, "clientInfo": { "platform": "kas", "requestIP": "", "userAgent": "connect-go/1.19.1 (go1.26.2)" }, "eventMetaData": { "algorithm": "rsa:2048", "keyID": "r1", "policyBinding": "ZWZiNTBlNDA3MjgyYjQ4OWIxZTMwODA1MjYxYTU2YWNiMzAwZjhhNWQzODA0MDc3MmVhN2Y3YmFjYmU4NTVkYg==", "tdfFormat": "tdf3" }, "object": { "attributes": { "assertions": [], "attrs": [ "https://example.com/attr/attr1/value/value1" ], "permissions": [] }, "id": "ab63e64a-45a5-11f1-aded-da43b5bed1af", "name": "", "type": "key_object" }, "original": null, "requestID": "25fc8dae-af84-4c3f-916c-d815adc2a252", "timestamp": "2026-05-01T14:35:31-07:00", "updated": null } }Summary by CodeRabbit
New Features
Improvements
Tests