Skip to content

Commit e54fcd0

Browse files
committed
Redact sensitive tool argument values in audit payloads
- Add SENSITIVE_ARGUMENT_KEYS frozenset with command, content, new, old - Route arguments dict through redact_tool_arguments to redact both sensitive key patterns and tool argument content values, preserving non-sensitive keys like path - Keep payload-level content and other keys unaffected
1 parent cbfced2 commit e54fcd0

2 files changed

Lines changed: 58 additions & 1 deletion

File tree

teaagent/audit.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
'secret',
2121
'token',
2222
)
23+
SENSITIVE_ARGUMENT_KEYS = frozenset({'command', 'content', 'new', 'old'})
2324

2425

2526
def utc_now() -> str:
@@ -77,7 +78,35 @@ def record(self, event_type: str, run_id: str, **payload: Any) -> AuditEvent:
7778

7879

7980
def redact_audit_payload(payload: dict[str, Any]) -> dict[str, Any]:
80-
return {key: redact_audit_value(key, value) for key, value in payload.items()}
81+
redacted: dict[str, Any] = {}
82+
for key, value in payload.items():
83+
if key == 'arguments' and isinstance(value, dict):
84+
redacted[key] = redact_tool_arguments(value)
85+
else:
86+
redacted[key] = redact_audit_value(key, value)
87+
return redacted
88+
89+
90+
def redact_tool_arguments(arguments: dict[str, Any]) -> dict[str, Any]:
91+
return {
92+
str(key): redact_tool_argument_value(str(key), value)
93+
for key, value in arguments.items()
94+
}
95+
96+
97+
def redact_tool_argument_value(key: str, value: Any) -> Any:
98+
if is_sensitive_key(key) or key in SENSITIVE_ARGUMENT_KEYS:
99+
return AUDIT_REDACTED
100+
if isinstance(value, dict):
101+
return {
102+
str(child_key): redact_tool_argument_value(str(child_key), child_value)
103+
for child_key, child_value in value.items()
104+
}
105+
if isinstance(value, list):
106+
return [redact_tool_argument_value('', item) for item in value]
107+
if isinstance(value, tuple):
108+
return [redact_tool_argument_value('', item) for item in value]
109+
return redact_audit_value(key, value)
81110

82111

83112
def redact_audit_value(key: str, value: Any) -> Any:

tests/test_audit.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,34 @@ def test_record_redacts_sensitive_payload_keys(self) -> None:
131131
)
132132
self.assertEqual(event.payload['arguments']['path'], 'file.txt')
133133

134+
def test_record_redacts_sensitive_tool_argument_values(self) -> None:
135+
logger = AuditLogger()
136+
137+
event = logger.record(
138+
'tool_call_started',
139+
'run-1',
140+
arguments={
141+
'path': 'file.txt',
142+
'content': 'secret file body',
143+
'old': 'previous secret',
144+
'new': 'new secret',
145+
'command': 'export TOKEN=secret',
146+
},
147+
)
148+
149+
self.assertEqual(event.payload['arguments']['path'], 'file.txt')
150+
self.assertEqual(event.payload['arguments']['content'], AUDIT_REDACTED)
151+
self.assertEqual(event.payload['arguments']['old'], AUDIT_REDACTED)
152+
self.assertEqual(event.payload['arguments']['new'], AUDIT_REDACTED)
153+
self.assertEqual(event.payload['arguments']['command'], AUDIT_REDACTED)
154+
155+
def test_record_preserves_non_argument_content(self) -> None:
156+
logger = AuditLogger()
157+
158+
event = logger.record('tool_call_completed', 'run-1', content='read result')
159+
160+
self.assertEqual(event.payload['content'], 'read result')
161+
134162
def test_record_truncates_large_strings(self) -> None:
135163
logger = AuditLogger()
136164

0 commit comments

Comments
 (0)