Skip to content

feat(capture): mask secrets (tokens, passwords, API keys) with stars before persist/embed#307

Open
khustup2 wants to merge 1 commit into
mainfrom
feat/redact-secrets-on-capture
Open

feat(capture): mask secrets (tokens, passwords, API keys) with stars before persist/embed#307
khustup2 wants to merge 1 commit into
mainfrom
feat/redact-secrets-on-capture

Conversation

@khustup2

@khustup2 khustup2 commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

What

Hivemind captures prompts, tool inputs, tool outputs and assistant messages, then embeds and stores them. Those payloads routinely contain credentials. This masks secrets with stars before anything is embedded or written to the store.

One shared helper (src/hooks/shared/redact.ts), applied at every capture chokepoint (6 sites): the serialized line in all four capturers (capture.ts, cursor/, codex/, hermes/) and the text in the shared uploadSummary(). Redacting the serialized JSON covers content/tool_input/tool_response and both the embedding and the DB write at once, star-safe (stays valid JSON).

Detection coverage (layered, most-specific first)

  1. Private-key blocks — PEM / OpenSSH.
  2. ~30 provider token schemes, keeping the scheme prefix as a hint (ghp_********): GitHub, OpenAI/Anthropic, Stripe (secret/restricted/webhook), Slack (bot/user/app + incoming webhooks), AWS access-key ids, Google API key + ya29. OAuth, HuggingFace, GitLab, npm, PyPI, Shopify, DigitalOcean, Doppler, Databricks, Linear, Postman, SendGrid, Atlassian, Notion, Groq, xAI, Replicate, Mailgun, Telegram bot, Sentry DSN, JWTs.
  3. Structured secretsAuthorization/Proxy-Authorization (Bearer/Basic/Digest), bare Bearer, scheme://user:password@host URL creds.
  4. Generic labeled assignmentsKEY=VALUE / "key":"value" where KEY is a secret-ish word.
  5. High-entropy backstop — a bare, unlabeled, random-looking token with no known prefix (e.g. an AWS secret access key, a raw key echoed in JSON). Gated by length ≥24, mixed character classes, and Shannon entropy ≥3.5 bits/char.

Precision (don't shred traces)

  • Fixed-length mask — never reveals a secret's length.
  • Won't match look-alikes (tokenizer, max_tokens); leaves literal true/false/null values readable.
  • The entropy backstop excludes UUIDs, git SHAs, hex hashes, decimal numbers, single-case words and filesystem paths (=/+// excluded from the candidate charset so key=value and base64/paths break into short segments rather than gluing into one blob).
  • Idempotent, pure, dependency-free.

Tests

  • tests/shared/redact.test.ts49 cases: every provider scheme, structured secrets, labeled assignments, the entropy backstop (recall + full precision-exclusion set), idempotency, multi-secret blobs. 100% line coverage of the helper.
  • tests/claude-code/capture-hook.test.ts — end-to-end: a token + password in a real Bash tool call are masked in the emitted INSERT (and therefore in the embedding).
  • Full suite: no regressions (only pre-existing graph/* tree-sitter + flush-memory network failures, identical on main).

Note

Test fixtures build vendor tokens from split literals at runtime so GitHub push protection doesn't flag this PR.

Follow-up (tracked separately)

  • Cowork transcript ingest (src/mcp/cowork-ingest.ts) redaction is deferred to a focused follow-up PR that ships it with a proper test suite (the file sits at ~35% coverage today). Tracked in Redact secrets on the Cowork transcript ingest path (+ tests) #308.
  • uploadSummary redacts the stored summary text; the summary embedding is computed by each wiki worker before the call, so full parity could redact at the worker's embed site too. Raw-capture embeddings are already fully covered.

Summary by CodeRabbit

  • Security Improvements
    • Added automatic secret redaction across captured session events, summaries, and wiki/memory uploads.
    • Masking is applied before persistence and embedding, covering common tokens, API keys, passwords (including in URLs), authorization headers, private keys, webhooks, and JWTs.
  • Tests
    • Added extensive unit tests for secret detection and idempotency.
    • Added a capture hook test verifying redacted values appear in generated SQL for tool-call event branches.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Captured session events, OpenClaw messages, and uploaded wiki summaries now pass through shared secret redaction before storage, embedding, size calculation, and derived metadata processing. Tests cover provider tokens, credentials, structured values, precision safeguards, entropy detection, idempotency, and capture integration.

Changes

Session secret redaction

Layer / File(s) Summary
Redaction rules and coverage
src/hooks/shared/redact.ts, tests/shared/redact.test.ts
Adds ordered masking rules for token formats, credentials, headers, URLs, assignments, CLI flags, private keys, and high-entropy values, with exclusions and broad tests.
Capture payload redaction
src/hooks/capture.ts, src/hooks/codex/capture.ts, src/hooks/cursor/capture.ts, src/hooks/hermes/capture.ts, harnesses/openclaw/src/index.ts, tests/claude-code/capture-hook.test.ts
Capture hooks and OpenClaw use redacted serialized entries for SQL persistence and embedding, with integration coverage verifying secrets are absent from generated SQL.
Summary text redaction
src/hooks/upload-summary.ts, src/hooks/*/wiki-worker.ts, src/hooks/wiki-worker.ts
Uploaded and wiki-generated summaries are redacted before stamping, persistence, embedding, and derived metadata calculations.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CaptureOrWikiWorker
  participant redactSecrets
  participant Storage
  participant Embedding
  CaptureOrWikiWorker->>redactSecrets: serialize or transform captured content
  redactSecrets-->>CaptureOrWikiWorker: return masked content
  CaptureOrWikiWorker->>Storage: persist redacted payload or summary
  CaptureOrWikiWorker->>Embedding: embed redacted content
Loading

Suggested reviewers: efenocchi, kaghni

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it does not follow the required template and is missing explicit Version Bump and Test plan sections. Rewrite it to use the repo template with Summary, Version Bump, and Test plan sections, and state whether the version was bumped or no release is needed.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: redacting secrets before persistence and embedding.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/redact-secrets-on-capture

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Scope: files changed in this PR. Enforced threshold: 90% per metric (per file via vitest.config.ts).

Status Category Percentage Covered / Total
🟢 Lines 92.72% (🎯 90%) 828 / 893
🟢 Statements 90.71% (🎯 90%) 908 / 1001
🟢 Functions 96.70% (🎯 90%) 88 / 91
🔴 Branches 85.78% (🎯 90%) 531 / 619
File Coverage — 11 files changed
File Stmts Branches Functions Lines
src/hooks/capture.ts 🟢 95.2% 🔴 82.7% 🟢 100.0% 🟢 100.0%
src/hooks/codex/capture.ts 🟢 97.2% 🔴 84.2% 🟢 100.0% 🟢 100.0%
src/hooks/codex/wiki-worker.ts 🟢 94.7% 🔴 84.1% 🟢 100.0% 🟢 96.6%
src/hooks/cursor/capture.ts 🟢 91.9% 🔴 88.3% 🟢 100.0% 🟢 95.9%
src/hooks/cursor/wiki-worker.ts 🔴 85.0% 🔴 82.5% 🟢 90.0% 🔴 86.7%
src/hooks/hermes/capture.ts 🔴 84.9% 🔴 82.5% 🟢 100.0% 🔴 87.0%
src/hooks/hermes/wiki-worker.ts 🔴 84.8% 🔴 82.5% 🟢 90.0% 🔴 86.5%
src/hooks/pi/wiki-worker.ts 🔴 84.8% 🔴 82.5% 🟢 90.0% 🔴 86.5%
src/hooks/shared/redact.ts 🟢 97.0% 🟢 92.3% 🟢 100.0% 🟢 100.0%
src/hooks/upload-summary.ts 🟢 100.0% 🟢 100.0% 🟢 100.0% 🟢 100.0%
src/hooks/wiki-worker.ts 🟢 94.6% 🟢 91.8% 🟢 100.0% 🟢 94.9%

Generated for commit d54e602.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/hooks/shared/redact.ts (2)

135-137: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the as string cast — it's incorrect when rule.replace is a function.

The Rule.replace type is string | ((match: string, ...groups: string[]) => string), but the as string cast forces TypeScript to use the string overload of String.replace. This works at runtime because JavaScript dispatches dynamically, but it suppresses type-checking for the function replacements in rules 7 and 8.

♻️ Suggested fix: type-narrow with typeof
   for (const rule of RULES) {
-    out = out.replace(rule.re, rule.replace as string);
+    if (typeof rule.replace === "string") {
+      out = out.replace(rule.re, rule.replace);
+    } else {
+      out = out.replace(rule.re, rule.replace);
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/shared/redact.ts` around lines 135 - 137, Remove the `as string`
cast in the `RULES` loop and type-narrow `rule.replace` with `typeof`: pass it
directly when it is a string, otherwise invoke or pass it as the replacement
callback so both `Rule.replace` variants remain type-checked, including rules 7
and 8.

77-86: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Scheme prefix hint is lost when known tokens appear in key=value assignments.

When a known token like ghp_… appears in an assignment with a secret-ish key (e.g., GITHUB_TOKEN=ghp_…), rule 2 correctly keeps the prefix (ghp_********), but rule 7 subsequently matches TOKEN=ghp_******** and replaces the entire value with ********, discarding the ghp_ hint. The secret remains fully masked and the key name still identifies the secret type, so this is not a security concern — just a minor precision trade-off worth being aware of.

If preserving the scheme prefix in this case is desired, rule 7's replace function could skip values that are already partially masked (e.g., contain ********).

Also applies to: 110-117

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/shared/redact.ts` around lines 77 - 86, The redact rules cause the
generic key=value masking rule to remove scheme prefixes already preserved by
token-specific rules. Update the rule 7 replacement logic in the shared
redaction rules to detect values containing the MASK marker and leave those
assignments unchanged, while continuing to fully mask unrecognized secret
values; apply the same change to the corresponding duplicate rule.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/hooks/shared/redact.ts`:
- Around line 135-137: Remove the `as string` cast in the `RULES` loop and
type-narrow `rule.replace` with `typeof`: pass it directly when it is a string,
otherwise invoke or pass it as the replacement callback so both `Rule.replace`
variants remain type-checked, including rules 7 and 8.
- Around line 77-86: The redact rules cause the generic key=value masking rule
to remove scheme prefixes already preserved by token-specific rules. Update the
rule 7 replacement logic in the shared redaction rules to detect values
containing the MASK marker and leave those assignments unchanged, while
continuing to fully mask unrecognized secret values; apply the same change to
the corresponding duplicate rule.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 87d2787b-58f9-433f-825d-b1c9dec2e3b4

📥 Commits

Reviewing files that changed from the base of the PR and between abd3771 and df7a218.

📒 Files selected for processing (9)
  • src/hooks/capture.ts
  • src/hooks/codex/capture.ts
  • src/hooks/cursor/capture.ts
  • src/hooks/hermes/capture.ts
  • src/hooks/shared/redact.ts
  • src/hooks/upload-summary.ts
  • src/mcp/cowork-ingest.ts
  • tests/claude-code/capture-hook.test.ts
  • tests/shared/redact.test.ts

@khustup2 khustup2 force-pushed the feat/redact-secrets-on-capture branch from df7a218 to 05537dd Compare July 11, 2026 07:21
Comment thread src/hooks/shared/redact.ts Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hooks/shared/redact.ts`:
- Around line 166-179: Update the secret redaction regexes and replacement logic
in the shared redaction rules so quoted assignment and CLI values are consumed
through their closing quote, masking the entire secret while preserving
surrounding syntax. Extend the CLI pattern to recognize the documented short -p
form, including -p=VALUE, and keep unquoted value handling unchanged.
- Around line 56-66: Rework the looksLikeSecret heuristic so long benign
identifiers such as CustomerAuthenticationServiceV2 are not redacted, while
secrets containing characters like / or + remain protected when tokenized into
shorter fragments. Apply the improved detection consistently across the
redaction flow before output is persisted or embedded, and add regression cases
covering both false-positive and split-secret scenarios.
- Around line 155-161: Update the authorization redaction rules in the shared
redaction pattern set to mask the complete supported Digest credential,
including parameters such as username and response, rather than only its initial
fragment. Make the bare Bearer rule case-insensitive so lowercase and mixed-case
schemes are redacted, while preserving the existing token character and length
constraints.

In `@tests/shared/redact.test.ts`:
- Around line 26-31: Update the test case around redactSecrets to assert the
complete expected transformed string, including the surrounding text and fully
masked secret, rather than using toContain checks. Apply the same exact-output
assertion to the additional covered case, while retaining verification that the
original secret is absent.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 820aea27-62bb-4fd4-ae45-f8b1e628ae87

📥 Commits

Reviewing files that changed from the base of the PR and between df7a218 and 05537dd.

📒 Files selected for processing (9)
  • src/hooks/capture.ts
  • src/hooks/codex/capture.ts
  • src/hooks/cursor/capture.ts
  • src/hooks/hermes/capture.ts
  • src/hooks/shared/redact.ts
  • src/hooks/upload-summary.ts
  • src/mcp/cowork-ingest.ts
  • tests/claude-code/capture-hook.test.ts
  • tests/shared/redact.test.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/hooks/hermes/capture.ts
  • src/hooks/codex/capture.ts
  • src/hooks/upload-summary.ts
  • tests/claude-code/capture-hook.test.ts
  • src/hooks/capture.ts
  • src/mcp/cowork-ingest.ts
  • src/hooks/cursor/capture.ts

Comment thread src/hooks/shared/redact.ts
Comment on lines +155 to +161
// Authorization / Proxy-Authorization headers.
{
re: /((?:proxy-)?authorization["']?\s*[:=]\s*["']?(?:bearer|basic|token|digest)\s+)([A-Za-z0-9._~+/=-]{8,})/gi,
replace: `$1${MASK}`,
},
// Bare `Bearer <token>`.
{ re: /\b(Bearer\s+)([A-Za-z0-9._~+/=-]{12,})/g, replace: `$1${MASK}` },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Fully redact supported Authorization variants.

A Digest header only has its initial username= fragment replaced, leaving parameters such as a hexadecimal response exposed. Bare bearer is also case-sensitive, so lowercase schemes with hex tokens bypass both this rule and the entropy backstop.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/shared/redact.ts` around lines 155 - 161, Update the authorization
redaction rules in the shared redaction pattern set to mask the complete
supported Digest credential, including parameters such as username and response,
rather than only its initial fragment. Make the bare Bearer rule
case-insensitive so lowercase and mixed-case schemes are redacted, while
preserving the existing token character and length constraints.

Comment thread src/hooks/shared/redact.ts
Comment thread tests/shared/redact.test.ts
@khustup2 khustup2 force-pushed the feat/redact-secrets-on-capture branch 2 times, most recently from 96d9e92 to a7167c6 Compare July 11, 2026 17:12

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/shared/redact.test.ts (1)

136-141: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

This case doesn't exercise the entropy threshold it claims to.

"Ab1".repeat(10) contains only A, b, 1 — all hex digits — so looksLikeSecret returns early at the hex guard (Line 59: /^[0-9a-f]+$/i) and never reaches the shannonEntropy(...) >= 3.5 check. The test passes for the wrong reason. Use non-hex characters so the token clears the hex/UUID guards and genuinely tests the low-entropy path.

💚 Proposed fixture change
-    const repeated = "Ab1".repeat(10); // 30 chars, entropy ~1.58 bits/char
+    const repeated = "Xy9".repeat(10); // 30 chars, mixed non-hex classes, entropy ~1.58 bits/char
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/shared/redact.test.ts` around lines 136 - 141, Update the
repeated-token fixture in the “does NOT mask a long but low-entropy repeated
token” test to include non-hex characters while retaining mixed character
classes and low repetition entropy, so it bypasses the hex/UUID guards and
exercises the shannonEntropy threshold in looksLikeSecret.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/shared/redact.test.ts`:
- Around line 136-141: Update the repeated-token fixture in the “does NOT mask a
long but low-entropy repeated token” test to include non-hex characters while
retaining mixed character classes and low repetition entropy, so it bypasses the
hex/UUID guards and exercises the shannonEntropy threshold in looksLikeSecret.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3b8fa630-a4fb-4b82-a85d-8ccb54a35c9d

📥 Commits

Reviewing files that changed from the base of the PR and between 96d9e92 and a7167c6.

📒 Files selected for processing (8)
  • src/hooks/capture.ts
  • src/hooks/codex/capture.ts
  • src/hooks/cursor/capture.ts
  • src/hooks/hermes/capture.ts
  • src/hooks/shared/redact.ts
  • src/hooks/upload-summary.ts
  • tests/claude-code/capture-hook.test.ts
  • tests/shared/redact.test.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/hooks/cursor/capture.ts
  • tests/claude-code/capture-hook.test.ts
  • src/hooks/hermes/capture.ts
  • src/hooks/codex/capture.ts
  • src/hooks/capture.ts
  • src/hooks/upload-summary.ts

@khustup2 khustup2 force-pushed the feat/redact-secrets-on-capture branch from a7167c6 to ee61a8d Compare July 11, 2026 22:18
Comment thread src/hooks/shared/redact.ts Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/hooks/wiki-worker.ts (1)

288-288: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Consider stamping the offset after redaction, not before.

The **JSONL offset**: ${jsonlLines} marker is stamped first, then passed through redactSecrets(...). On resume this exact line is parsed back with /\*\*JSONL offset\*\*:\s*(\d+)/ (Line 195). If any labeled-assignment or entropy rule ever matches the marker, the persisted offset is corrupted and resume slicing breaks. Redacting raw first and stamping afterward keeps the authoritative bookkeeping line outside the redactor's reach (the offset line carries no secrets). This pattern is repeated in the codex/cursor/hermes/pi wiki-workers.

♻️ Suggested reorder
-        const text = redactSecrets(stampOffset(raw, jsonlLines));
+        const text = stampOffset(redactSecrets(raw), jsonlLines);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/wiki-worker.ts` at line 288, In the wiki-worker processing flow,
update the expression around stampOffset and redactSecrets so raw content is
redacted before the JSONL offset is stamped. Preserve the existing offset value
and ensure the authoritative “JSONL offset” marker remains outside
redactSecrets, matching the ordering used by the related worker implementations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hooks/shared/redact.ts`:
- Around line 165-166: Update the URL credential pattern in the redaction rules
to allow colons within the password while still identifying the username
separator and trailing @ delimiter. Preserve masking behavior for the password
and ensure URLs such as postgres://user:pa:ss@host/db are redacted.

---

Nitpick comments:
In `@src/hooks/wiki-worker.ts`:
- Line 288: In the wiki-worker processing flow, update the expression around
stampOffset and redactSecrets so raw content is redacted before the JSONL offset
is stamped. Preserve the existing offset value and ensure the authoritative
“JSONL offset” marker remains outside redactSecrets, matching the ordering used
by the related worker implementations.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 04aefb8e-f3ba-43e6-9224-f66e108891ce

📥 Commits

Reviewing files that changed from the base of the PR and between a7167c6 and ee61a8d.

📒 Files selected for processing (14)
  • harnesses/openclaw/src/index.ts
  • src/hooks/capture.ts
  • src/hooks/codex/capture.ts
  • src/hooks/codex/wiki-worker.ts
  • src/hooks/cursor/capture.ts
  • src/hooks/cursor/wiki-worker.ts
  • src/hooks/hermes/capture.ts
  • src/hooks/hermes/wiki-worker.ts
  • src/hooks/pi/wiki-worker.ts
  • src/hooks/shared/redact.ts
  • src/hooks/upload-summary.ts
  • src/hooks/wiki-worker.ts
  • tests/claude-code/capture-hook.test.ts
  • tests/shared/redact.test.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/hooks/capture.ts
  • src/hooks/cursor/capture.ts
  • src/hooks/codex/capture.ts
  • tests/claude-code/capture-hook.test.ts
  • src/hooks/hermes/capture.ts
  • src/hooks/upload-summary.ts

Comment thread src/hooks/shared/redact.ts
@khustup2 khustup2 force-pushed the feat/redact-secrets-on-capture branch from ee61a8d to 8719711 Compare July 11, 2026 22:25
…before persist/embed

Captured prompts, tool inputs, tool outputs and assistant messages routinely
contain credentials — a Bash `export GITHUB_TOKEN=…`, an `Authorization:
Bearer …` echoed by curl, a `postgres://user:pass@host` connection string. We
were embedding and storing those verbatim.

Add a shared, pure `redactSecrets()` helper (src/hooks/shared/redact.ts) and
apply it at every capture chokepoint, before the payload is embedded or written
to the store:
- raw events: redact the serialized `line` in all four capturers (claude,
  cursor, codex, hermes) + the cowork transcript ingest. Redacting the
  serialized JSON covers content / tool_input / tool_response and both the
  embedding and the DB write in one shot, and is star-safe (keeps the JSON
  parseable).
- summaries: redact the text at the top of the shared uploadSummary().

Redactor design:
- Fixed-length star mask — never reveals the secret's length.
- Keeps a non-secret hint where useful: the scheme prefix of a known token
  (`ghp_********`) or the key name of an assignment (`password=********`).
- Covers: PEM private keys; GitHub/OpenAI/Anthropic/Stripe/Slack/AWS/Google/
  HuggingFace/GitLab tokens; JWTs; Authorization Bearer/Basic; bare Bearer;
  URL basic-auth creds; generic KEY=VALUE / "key":"value" assignments; and
  --password/--token CLI flags.
- Precision-first: won't match look-alikes (`tokenizer`, `max_tokens`) and
  leaves literal true/false/null values readable. Idempotent.

Tests: 31-case unit suite for the redactor + an end-to-end capture-hook test
proving a token/password in a Bash tool call is masked in the emitted INSERT
(and therefore in the embedding, which is derived from the same line).
@khustup2 khustup2 force-pushed the feat/redact-secrets-on-capture branch from 8719711 to 1631f2d Compare July 11, 2026 22:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants