Show HN: Prompt-scrub – local-first PII redaction for LLM prompts and responses Nano Collective released prompt-scrub, a local-first Node.js utility that redacts personally identifiable information from LLM prompts and responses before they leave the machine, replacing findings with stable placeholders and allowing local rehydration. The tool, available as an npm package and CLI, includes eight detectors (phone, postal address, path, secret, URL, and opt-in name and code-tell) and supports deterministic scrubbing with session-based mapping stored locally. Built by the Nano Collective https://nanocollective.org , a community collective building AI tooling not for profit, but for the community. This is the first public release of prompt-scrub , a small Node.js utility that runs entirely on your machine. It detects identifying content inside a prompt emails, paths, secrets, phone numbers, URLs, postal addresses, and a couple of opt-in categories , replaces each finding with a stable placeholder like Email 1 or Path 2 , and lets you rehydrate the model's response back to the original values locally after it comes back. The motivation is simple: most accidental identifier leakage to a cloud LLM lives in the text of the prompt and the text of its response. Stripping it there, deterministically, before the prompt leaves your machine is a useful layer in a privacy posture, and one that does not need a network round-trip, a new account, or a hosted service to work. What it actually does The package exposes two functions and a CLI. scrub takes either a plain string or an array of { role, content } messages, runs the configured detectors over the text, replaces each finding with a category-namespaced placeholder, and returns the scrubbed content plus a session id: js import { scrub, rehydrate } from '@nanocollective/prompt-scrub'; const prompt = "My key is sk-12345 and my email is email protected "; const { scrubbedContent, sessionId } = scrub { content: prompt } ; // scrubbedContent: "My key is Secret 1 and my email is Email 1" You send scrubbedContent to whatever LLM provider you already use. When the response comes back, rehydrate walks the text, looks up each placeholder in the session map, and swaps them back. Unknown placeholders placeholder text the model hallucinated, or placeholders from a previous session are passed through unchanged and surfaced as warnings so you can decide whether to trust them: js const response = "Your email Email 1 looks correct and your key Secret 1 is fine."; const { content, warnings } = rehydrate { content: response, sessionId } ; // content: "Your email email protected looks correct and your key sk-12345 is fine." The session id is the link between the two steps. It points at a small JSON file under your OS config directory that holds the placeholder-to-original mapping. The file is written atomically, with restrictive permissions, and includes a corrupt-file quarantine path so a half-written map does not silently disable rehydration. The location is overridable via the PROMPT SCRUB CONFIG DIR environment variable. Detectors Eight detectors ship in the box. On by default: - phone - postal address - path Unix-style and Windows-style - secret tuned for common API key and credential shapes, since missing a credential is worse than missing a name - url Off by default, opt-in: - name proper-noun detector, with a stricter allowlist mode - code-tell user-enumerated private identifiers, for things like internal project codenames URL detection also accepts a trusted-host allowlist with subdomain matching, so internal services you control can pass through without a placeholder if that is what you want. Overlapping detector findings an email that looks like a URL fragment, say resolve by a documented priority order, with longer span winning on ties. Determinism is deliberate: the same input and session always produce the same scrubbed output, which keeps provider prompt-cache prefixes byte-stable. The CLI A small command-line wrapper around the same logic. The recommended workflow is inspect first, then scrub : See exactly what would change without writing anything echo "My email is email protected " | prompt-scrub inspect Scrub stdin or a file , prints the session id to stderr echo "My email is email protected " | prompt-scrub scrub Rehydrate a response using --session-id echo "Contact Email 1 for details." | prompt-scrub rehydrate --session-id