Reversibly mask secrets and PII before sending text to an LLM, then restore them in the response.
When you send text to a hosted model, anything sensitive in that text — emails,
API keys, card numbers, private keys — leaves your machine. prompt-cloak
swaps those values for stable placeholders before the request, keeps the
mapping in a local vault, and swaps the real values back after the model
replies. The model sees [EMAIL_1]; you keep jane@corp.io.
- Reversible.
cloak()→ call the model →uncloak()restores everything. - Zero dependencies. Pure ESM JavaScript, runs anywhere Node 18+ runs.
- Deterministic. The same value always maps to the same placeholder, so the
model can reason about
[EMAIL_1]and you can map its answer back. - Local-first. No network, no API keys, nothing phones home.
npm install prompt-cloak
# or run the CLI without installing:
npx prompt-cloak --helpimport { cloak, uncloak } from 'prompt-cloak';
const { text, vault } = cloak('Email jane@corp.io, card 4111 1111 1111 1111');
// text -> 'Email [EMAIL_1], card [CREDIT_CARD_1]'
// vault -> { '[EMAIL_1]': 'jane@corp.io', '[CREDIT_CARD_1]': '4111 1111 1111 1111' }
// ...send `text` to your LLM, get a reply that references the placeholders...
const restored = uncloak(modelReply, vault);Round-trip is exact: uncloak(cloak(s).text, cloak(s).vault) === s.
See examples/llm-roundtrip.js for a full flow.
cloak(text, {
only: ['email', 'creditCard'], // apply only these detectors
except: ['phone'], // apply all detectors except these
});# Mask a file and save the vault for later
prompt-cloak mask notes.txt --vault notes.vault.json > masked.txt
# Restore from the masked text + vault
prompt-cloak unmask masked.txt --vault notes.vault.json
# One-shot JSON (text + vault together), reading stdin
echo "ping me at a@b.com" | prompt-cloak mask --json
# List what it can detect
prompt-cloak detectors| Type | Example |
|---|---|
email |
jane@corp.io |
creditCard |
4111 1111 1111 1111 (Luhn-checked) |
ipv4 |
10.0.0.5 |
macAddress |
3D:F2:C9:A6:B3:4F |
jwt |
eyJhbGci... |
awsAccessKey |
AKIAIOSFODNN7EXAMPLE |
githubToken |
ghp_... |
apiKey |
sk-..., sk-ant-... |
ssn |
123-45-6789 |
phone |
+1 415-555-0132 |
privateKey |
-----BEGIN RSA PRIVATE KEY----- ... |
Overlapping matches are resolved by detector priority (e.g. a 16-digit card is never carved up by the phone-number rule), and Luhn validation keeps random 16-digit IDs from being mistaken for cards.
| Export | Description |
|---|---|
cloak(text, options?) |
Returns { text, vault }. |
uncloak(text, vault) |
Returns the text with placeholders restored. |
detectors |
The array of built-in detector definitions. |
findMatches(text, detectors) |
Low-level: raw matches before overlap resolution. |
resolveOverlaps(matches) |
Low-level: pick non-overlapping matches by priority. |
isLuhnValid(value) |
The Luhn checksum used by the card detector. |
prompt-cloak uses pattern matching, not magic. It catches well-structured
secrets and common PII formats, but it cannot recognize a person's name in free
prose or a secret that looks like ordinary text. Treat it as a strong first
layer of defense, not a guarantee. Review the masked output for anything
sensitive the patterns missed before sending it onward.
npm test # runs node --testMIT © 2026 Ayubjon
If this project is useful to you, you can support its development with a crypto tip — thank you!
USDT — Ethereum (ERC-20):
0xad39bdf2df0b8dd6991150fcea0a156150ed19b8
Send only on the Ethereum (ERC-20) network.