From 0eeb1df4a21bef473d98f48ddd870eadcd2f257b Mon Sep 17 00:00:00 2001 From: Mathias Date: Sun, 14 Jun 2026 00:28:47 +0200 Subject: [PATCH] fix(claudewatcher): scrub bare 1Password service-account tokens (ops_) A ~/.zshrc read surfaced OP_SERVICE_ACCOUNT_TOKEN into a transcript. The _TOKEN= form was already caught by homelab-env-token, but a bare ops_ value was not. Add an op-service-account rule (ordered early). Tests cover both env-assigned and bare forms. Co-Authored-By: Claude Opus 4.8 (1M context) --- ingestion/internal/claudewatcher/scrubber.go | 3 +++ ingestion/internal/claudewatcher/scrubber_test.go | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/ingestion/internal/claudewatcher/scrubber.go b/ingestion/internal/claudewatcher/scrubber.go index 575d408..7344eff 100644 --- a/ingestion/internal/claudewatcher/scrubber.go +++ b/ingestion/internal/claudewatcher/scrubber.go @@ -46,6 +46,9 @@ var DefaultRules = []Rule{ {Name: "private-key", RE: regexp.MustCompile(`-----BEGIN[^-]*PRIVATE KEY-----`)}, {Name: "ssh-key", RE: regexp.MustCompile(`ssh-(?:rsa|ed25519|ecdsa)\s+[A-Za-z0-9+/=]{40,}`)}, {Name: "github-pat", RE: regexp.MustCompile(`\b(?:ghp|gho|ghu|ghr|gha)_[A-Za-z0-9]{30,}\b`)}, + // 1Password service-account token (ops_). Long, high-value root + // credential; guard the bare value (the _TOKEN= form also hits homelab-env-token). + {Name: "op-service-account", RE: regexp.MustCompile(`\bops_[A-Za-z0-9_\-]{40,}`)}, // No leading \b: a shell mangle can glue the key to a preceding word // ("yes"+"sk-...") which has no word boundary, and that exact case // leaked a LiteLLM master key past this rule (2026-06-11). Match the diff --git a/ingestion/internal/claudewatcher/scrubber_test.go b/ingestion/internal/claudewatcher/scrubber_test.go index 3a129f4..15b7e89 100644 --- a/ingestion/internal/claudewatcher/scrubber_test.go +++ b/ingestion/internal/claudewatcher/scrubber_test.go @@ -32,6 +32,11 @@ func TestScrub_PoisonedFixtures(t *testing.T) { {"sk-standalone-hex", "sk-7181ca984603239d8c4819361bf33b94b9c3c07018791868", "openai-sk"}, // Bare JWT not preceded by "Bearer" (e.g. a Dex token dumped to stdout). {"jwt-bare", "token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dQw4w9WgXcQabcdef", "jwt"}, + // 1Password service-account token (ops_), env-assigned and bare. + // Both hit the dedicated op-service-account rule (ordered before the + // generic homelab-env-token). Guards ~/.zshrc reads etc. (2026-06-14). + {"op-sa-env", "export OP_SERVICE_ACCOUNT_TOKEN=ops_eyJzaWduSW5BZGRyZXNzIjoibXkuMXBhc3N3b3JkLmNvbSJ9", "op-service-account"}, + {"op-sa-bare", "ops_eyJzaWduSW5BZGRyZXNzIjoibXkuMXBhc3N3b3JkLmNvbSIsInVzZXJBdXRoIjp7fX0aGVsbG8", "op-service-account"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) {