Scrub captured turns locally before they enter an upload queue or reach a memory-extraction model. Parse the transcript, retain only approved fields, remove harness noise, and redact credentials using structured rules plus maintained secret detectors. If parsing or scanning fails, skip capture; pattern matching reduces exposure but cannot prove that a transcript contains no secrets.
Put the scrubber before the first outbound copy
A developer pastes a bearer token while debugging an API call. The assistant repeats it in a command, and a failed request echoes it in tool output. Capturing only user messages misses two copies; asking a hosted extraction model to remove secrets sends all three across the boundary first.
Make the local capture process responsible for every exported text field. Its output should be a new, sanitized batch, never the original transcript with a promise to clean it later. Keep the harness's existing local transcript separate from this export: sanitizing capture does not erase earlier messages or undo transmission to the agent's model provider.
OWASP's Logging Cheat Sheet identifies access tokens, passwords, connection strings and encryption keys as data that should usually be excluded or protected in logs. Apply that exclusion principle to memory capture. Design the exporter so neither its uploader nor its extraction client can read the original transcript. Review the complete path: retry queues, error reporting and debug output can otherwise create additional transcript copies.
Wire a local command into completed turns
Claude Code's hooks reference documents command hooks receiving JSON on standard input. In .claude/settings.json, a Stop hook can invoke your installed local capture wrapper:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "/usr/local/bin/memory-capture-local"
}
]
}
]
}
}memory-capture-local is an executable you supply, not a built-in command or a complete scrubber provided by this configuration. It reads session_id and transcript_path, reconciles complete transcript records with last_assistant_message, and sanitizes selected turns before handing anything to a background uploader. The documentation warns that the transcript can lag the final response.
Stop does not run after a user interrupt. Use a local cursor and reconciliation rather than assuming every turn arrived exactly once. Never pass transcript text through shell interpolation. Keep raw event payloads off stdout and stderr, and emit only a fixed status when capture fails. For lifecycle details, see using Claude Code hooks for automatic memory.
Normalize records before removing noise
Use a versioned adapter for each harness's transcript format. Parse complete records and build an allowlisted export containing a turn identifier, speaker, relevant text and, where useful, a tool name. Drop unused metadata instead of recursively forwarding every property. Reject unsupported record types; retain an incomplete final record locally for a later read.
Decode JSON string escapes before scanning so a private key represented with escaped newlines becomes a multiline value. For nested JSON tool results, inspect parsed values when the format is known. Scan command arguments, tool errors and assistant quotations as well as ordinary prose. A database URL in an exception deserves the same treatment as one pasted by a person.
Remove harness-injected reminders using provenance where available. If only text markers are available, use narrow, versioned rules and test literal examples quoted by users. Do not delete arbitrary text between broadly matching tags. Separately omit binary blocks, terminal decoration and repeated progress updates that offer no durable memory value.
Apply size limits by withholding oversized records, or scan them completely before shortening them. Truncating first can remove a private-key delimiter and leave an unrecognizable fragment eligible for upload.
Redact by structure, then by credential patterns
Start with fields whose meaning makes the decision straightforward: replace authorization headers, cookies, passwords, client secrets, access tokens and refresh tokens in parsed objects. Treat names case-insensitively and handle nested arrays. For a credential-bearing database URI, withhold the entire URI unless a tested parser can safely preserve the useful components.
Then scan retained text with maintained provider patterns and multiline private-key rules. GitHub's supported-pattern documentation distinguishes provider-specific secrets from generic patterns such as bearer headers, private keys and credential-bearing database strings. It also documents differing detection capabilities and changing token formats. Use that as evidence for maintaining multiple detector families, not as proof that GitHub scans your local transcript exporter.
Replace the entire sensitive span with a typed marker. This is an illustrative result in your export schema:
{
"speaker": "tool",
"text": "Request failed: Authorization: [REDACTED:AUTH_HEADER]"
}Preserve the failure description without retaining a token suffix. Match known credential values locally across the selected batch to catch repeated copies, without dumping the environment or persisting a secret lookup table. Treat entropy as a review signal: commit hashes and generated identifiers can look random too. Withhold ambiguous records when the remaining text cannot be confidently separated from credentials.
Gate upload on a completed local scan
Add an independent scan of the serialized export. Gitleaks documents a stdin mode, bounded recursive decoding and a --redact option for its own output. Its redaction flag does not rewrite your input into a sanitized transcript. Use it as a gate after your transformation, not as a replacement for that transformation.
For an installed, trusted policy file and a private sanitized batch, a shell wrapper can use:
if gitleaks stdin --config /etc/agent-memory/gitleaks.toml \
--ignore-gitleaks-allow --redact=100 --max-decode-depth=2 \
--timeout=5 --no-banner < sanitized-batch.json >/dev/null 2>&1
then
memory-upload sanitized-batch.json
else
printf '%s\n' 'memory capture skipped' >&2
fimemory-upload is your uploader. Run the scanner outside an untrusted repository's configuration context; transcript comments must not create exemptions. Every nonzero result here withholds upload, including scanner errors. Record a separate, content-free health status so a broken scanner becomes visible.
Queue only the exact bytes that passed. Prevent modification between scanning and sending, restrict queue access to the capturing account, and expire retries. Revalidate queued batches after policy changes. Never recover from a timeout by uploading the unsanitized original. A remote model used to judge suspicious text would itself become another outbound destination.
How SPYN3 handles transcript capture
SPYN3's Claude Code and Codex plugin strips system reminders and common secret patterns, including API keys, tokens and private keys, before uploading finished turns in the background. Capture is a separate admin setting, off by default, requiring the workspace's own model key and write:memory permission. Raw turns are deleted after extraction; extracted memories remain. SPYN3_MEMORY_OFF=1 disables the plugin for a session. Keep capture disabled until its filtering matches the transcript data your team permits to leave the machine.
Test the bytes that actually leave
Build synthetic fixtures that place the same fake credential in a user prompt, an assistant quotation, a command argument, nested tool output and an exception. Include multiline keys, escaped newlines, percent-encoded URLs, bounded base64 examples and secrets split across adjacent text blocks. Reassemble known text continuations before detection; withhold unsupported fragmentation rather than silently treating it as clean.
Send test exports to a local receiver and inspect the serialized request body, not just the scrubber's return value. Assert that seeded credential bytes and their tested encodings are absent, useful context survives, and typed markers remain. Check queue files and failure logs too. A passing detector test says little if an exception handler uploads the original input.
Exercise malformed JSON, unknown transcript versions, a killed scanner, unavailable upload service and an expired retry. Each should produce either a verified sanitized export or no capture. Track withheld batches and false positives using counts and rule identifiers, without recording matched values.
Pattern coverage is finite. A plain-language password or confidential customer detail can escape credential rules. Keep sensitive sessions out of automatic capture, and review memory extraction from transcripts only after this boundary works. If a real credential already left the machine, follow agent incident response: revoke it and trace its copies; editing the next export cannot retract the previous one.
Built for the next step
Put the boundary in one place
One endpoint holds the credentials, scopes and audit log, so agents only ever hold a revocable key.
Read the developer guideSPYN3 is a consolidated MCP layer: one hosted endpoint in front of every system you connect — MCP servers, Postgres, REST APIs, Google Drive — with tools foldered by source and read/write permissions per key. Switching agents means moving one URL. Free plan available; current capabilities are documented on the product facts page.
Sources
- Claude Code documentation: Hooks reference — Command-hook settings schema and stdin JSON; Stop, session_id, transcript_path, last_assistant_message, asynchronous transcript writes and user-interrupt behavior.. Verified 27 Oct 2026
- OWASP: Logging Cheat Sheet — Event identity, timestamps, actions and outcomes; exclusion of access tokens, passwords and other secrets from logs. Verified 27 Oct 2026
- GitHub Docs: Supported secret scanning patterns — Provider and generic pattern categories, private keys, bearer headers, database credentials, differing capabilities and changing token formats.. Verified 27 Oct 2026
- Gitleaks project: Usage and configuration — stdin scanning, explicit configuration, ignore-gitleaks-allow, redaction of scanner output, timeout, nonzero finding status and bounded recursive decoding.. Verified 27 Oct 2026
