Home / Guides / mask-pii-before-claude
How to mask PII before calling the Claude API (Node.js)
Run deterministic PII masking with @noeticguard/core in your Node backend before anthropic.messages.create — local vault tokens, no cloud scrubbing endpoint.
Published 2026-08-28 · NoeticGuard engineering notes
Live masking playground
Paste text — PII and API keys become vault tokens before any LLM sees them.
Raw Prompt
Untrusted user input
Type sensitive data or pick a scenario. NoeticGuard Core tokenizes PII and high-precision secrets into a vault ([EMAIL_n], [SECRET_n]), routes safe placeholders to the LLM, then re-identifies on the way back to the user.
Masked Prompt (To LLM)
Vault tokens ([EMAIL_n], [SECRET_n]) + Brand Guard — reversible PII/secrets only
Masked prompt will appear here.
Raw LLM Output
Model echoes vault tokens (never raw PII)
Run the pipeline to simulate the LLM response.
Final Unmasked Output (To User)
unmaskPii restores vault PII & secrets · competitors stay blocked
Run the pipeline to see PII restored for the end user.
Anthropic's Claude API is a common backend choice for agents, support copilots, and document Q&A. If user text contains emails, payment details, or national IDs, those values can appear in provider-side logs and retention systems. The fix is identical to OpenAI: run maskPii in your Node service before anthropic.messages.create.
Claude-specific notes
- Messages API shape — mask the string inside
messages[].contentbefore the SDK call; unmask the text block in the response if your UI needs originals. - Multi-turn agents — reuse one vault per conversation so
[EMAIL_1]stays stable across tool loops. On serverless, persist the vault (Redis/DB). - Policy from dashboard — fetch GET /v1/config with an
ng_pub_key so PCI/HIPAA packs and Brand Guard lists match workspace policy.
Pattern
- Install
@noeticguard/core(see SDK quickstart). - Call
maskPiiwith a per-session vault. - Pass only the masked content to Claude.
- Optionally
unmaskPiithe assistant reply for end users. - Report usage via POST /v1/telemetry.
import Anthropic from '@anthropic-ai/sdk';
import { maskPii, unmaskPii } from '@noeticguard/core';
const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const vault = new Map();
export async function chatWithClaudeMasked(userText: string) {
const { output: masked, matches } = maskPii(userText, {
vault,
kinds: ['email', 'creditCard', 'phone', 'nationalId', 'iban'],
});
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 1024,
messages: [{ role: 'user', content: masked }],
});
const raw =
message.content[0]?.type === 'text' ? message.content[0].text : '';
return {
reply: unmaskPii(raw, vault),
entitiesMasked: matches.length,
};
}Common failure modes
- Masking only the latest user turn while prior messages in the Claude thread still contain raw emails — mask every user/tool string that leaves your trust boundary.
- Creating a new vault per tool call — entity IDs renumber and the model loses continuity. One vault per conversation (or ticket).
- Skipping telemetry —
maskPiiworks locally, but dashboard usage stays at 0 untilPOST /v1/telemetry. - Shipping
ng_secret_keys to browsers or mobile — use publishableng_pub_for config + telemetry only.
When Claude is not the only model
The same vault pattern works for OpenAI, Gemini, and DeepSeek — only the client SDK changes. Prefer one shared maskPii helper in your backend so detector packs and Brand Guard lists stay consistent. Employees pasting into the Claude web UI need Browser Shield, not the API wrapper above.
Also shipping OpenAI? See the OpenAI masking guide — the vault and telemetry pattern is the same; only the SDK client changes. For vendor and category comparisons, see our comparison hub.