Skip to content

fix: redact secrets from every tool and resource response - #9

Merged
maverick0628 merged 1 commit into
mainfrom
fix/redact-secrets
Sep 18, 2026
Merged

maverick0628 merged 1 commit into
mainfrom
fix/redact-secrets

Conversation

@maverick0628

Copy link
Copy Markdown
Owner

cloudsync_list returned each task's credentials.provider block unredacted, so listing cloud sync tasks put the B2 application key and the S3 secret access key into the model transcript. The same was true of certificate and SSH private keys, API key and password hashes, bind passwords, CHAP secrets and alert service tokens. truenas_api_call could reach all of them.

What changed

  • src/redact.ts: one redaction pass. Values under secret-named keys become "[redacted]" at any depth. The match is case-insensitive and ignores separators (api_key, API-Key and apiKey are one name). Every string also gets a scrub for private key blocks, URL passwords and secret query parameters. Non-JSON text, such as error messages, gets the same scrub plus "name": "value" pair matching.
  • src/run-tool.ts: the dispatch logic moved out of index.ts so it can be tested. Every path out is redacted: handler results, the raw-result fallback and error messages.
  • src/index.ts: uses runTool and registers resources through a redacting wrapper, so new tools and resources are covered by default. The tool description tells the model values are redacted.
  • api_key_create and keychaincredential_generate_ssh_key descriptions now say the secret they create is not returned.

Redaction is output-only. Handlers and the client still see real values, so no update can write [redacted] back to the NAS.

Deliberate choices

  • Kept: credential id, name, provider type, bucket, folder, endpoint, region, public keys, certificates, *_path, key_type. Null, booleans, numbers and empty strings are never redacted, because they show whether something is set and leak nothing.
  • B2 account is redacted. TrueNAS labels it "Key ID". It is the same half of the pair as S3 access_key_id, which is also redacted. account on other providers is kept.
  • Known gap: a secret inside a field with an ordinary name, such as a Slack webhook url or a password typed into a cloud sync args string, is not caught.

Full rationale is in DECISIONS.md.

Verification

  • npm run build clean, npm test 78/78, including 29 new redaction tests. The fixtures mirror real TrueNAS 26 response shapes: cloud sync tasks with nested B2 and S3 credentials.provider, the credentials list, cloud backup, keychain SSH keypair and SSH credentials, a certificate, the general config's nested ui_certificate, API keys, users, alert services, iSCSI auth, LDAP, keytabs, VMs and proxies. Every secret value is an obvious fake, and one test proves no fake survives any fixture.
  • The integration tests run the real cloudsync_list, cloudsync_get, cloudsync_credentials_list, cloud_backup_list, keychaincredential_list and keychaincredential_generate_ssh_key handlers through the real registry and runTool.
  • A mutation check broke six rules one at a time, and each one turned tests red.
  • Compiled dist/ smoke test: a real MCP client talked to the built server, backed by a mock TrueNAS WebSocket. There were 0 leaks on cloudsync_list, truenas_api_call GET /cloudsync, the error path and the truenas://vms resource.
  • The diff was scanned for real keys, LAN addresses and literal PEM headers, and none were found. PEM armour in fixtures is built at runtime.

🤖 Generated with Claude Code

cloudsync_list passed the TrueNAS response through as-is, so each task's
provider block put the B2 application key and S3 secret access key into
the model transcript. Certificate and SSH private keys, API key and
password hashes, bind passwords and alert tokens had the same problem,
and truenas_api_call could reach all of them.

All tool results, error messages and resources now pass through one
redaction step in src/redact.ts on the way out. Matching is by key name,
case-insensitive and separator-blind, plus a string scrub for private key
blocks, URL passwords and secret query parameters. Identifiers, provider
type, bucket, region and public keys stay visible.

Handlers and the client still see real values, so no update can write
"[redacted]" back to the NAS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@maverick0628
maverick0628 merged commit 0d1dd71 into main Sep 18, 2026
3 checks passed
@maverick0628
maverick0628 deleted the fix/redact-secrets branch September 18, 2026 02:01
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.

1 participant