Dev.to Security πŸ” Cybersecurity πŸ‘ 0 πŸ“– 5 min read

Stop Pasting ***REDACTED*** into LLMs: Building a Zero-Latency Reversible Secret Vault in Vanilla JS

Every week, an engineer somewhere highlights a failing Kubernetes manifest, .env file, Terraform plan, or curl trace, pastes it into ChatGPT or Claude to debug an edge case, and realizes half a second too late that a liv

Every week, an engineer somewhere highlights a failing Kubernetes manifest, .env file, Terraform plan, or curl trace, pastes it into ChatGPT or Claude to debug an edge case, and realizes half a second too late that a live sk-proj-... key, AWS AKIA... credential, or production Postgres URI just left their machine.

Most security-conscious developers try to solve this manually by doing a quick find-and-replace to turn credentials into ***REDACTED*** before prompting.

Unfortunately, naive ***REDACTED*** scrubbing silently breaks two critical parts of an AI-assisted debugging loop:

  1. Destroying Cross-Variable Identity (Topology Loss): Suppose a distributed trace references two internal nodes (10.24.8.15 and 10.24.8.99) and passes a bearer token across three microservices, where service C accidentally receives a stale token. If every IP and token becomes ***REDACTED***, the LLM loses equivalence relationshipsβ€”it can no longer tell whether two masked strings were identical or distinct.
  2. Roundtrip Re-Insertion Friction: When Claude or ChatGPT hands back a 60-line refactored TypeScript or YAML block filled with ***REDACTED*** literals, pasting it back into your editor breaks your local build until you manually hunt down and restore every single credential without fat-fingering a quote.

To fix both problems without routing sensitive clipboard contents through any cloud proxy or bloated native binary, I built and open-sourced PasteShieldβ€”a zero-dependency, <1ms reversible secret sanitizer running 100% in local RAM across VS Code / Cursor, Node/Bun CLI pipelines, and an offline browser sandbox.

Here is a look under the hood at the three architectural problems we had to solve: deterministic bidirectional interning, ordered multi-pass regex precedence, and zero-server offline license verification using 32-bit FNV-1a checksums.

1. Designing a Prototype-Safe Bidirectional SessionVault

Instead of destroying entropy with static asterisks, PasteShield maps each unique (category, rawSecret) pair to a numbered semantic token such as <OPENAI_KEY_1>, <DB_URI_1>, <INTERNAL_IP_1>, and <INTERNAL_IP_2>.

Inside sanitizer-core.js, the vault lives strictly in volatile process memory using null-prototype dictionaries (Object.create(null)) so maliciously crafted log strings like __proto__ or constructor can never trigger prototype pollution:

function SessionVault() {
  this.secretToToken = Object.create(null);
  this.tokenToSecret = Object.create(null);
  this.categoryCounters = Object.create(null);
}

SessionVault.prototype.intern = function (category, rawSecret) {
  var compositeKey = category + '\u0000' + rawSecret;
  if (this.secretToToken[compositeKey]) {
    return this.secretToToken[compositeKey];
  }
  var nextIndex = (this.categoryCounters[category] || 0) + 1;
  var candidate = '<' + category + '_' + nextIndex + '>';
  while (this.tokenToSecret[candidate] && this.tokenToSecret[candidate] !== rawSecret) {
    nextIndex += 1;
    candidate = '<' + category + '_' + nextIndex + '>';
  }
  this.categoryCounters[category] = nextIndex;
  this.secretToToken[compositeKey] = candidate;
  this.tokenToSecret[candidate] = rawSecret;
  return candidate;
};

Notice two subtle invariants here:

  • Referential Stability Across Multi-File Prompts: Because secretToToken keys on category + '\u0000' + rawSecret, copying .env first and docker-compose.yml five minutes later guarantees that the same Redis password resolves to <DB_URI_1> in both snippets. The LLM sees exact structural isomorphism while never seeing a single character of the credential.
  • Collision-Proof Angle-Bracket Syntax: Tokens follow /^<[A-Z][A-Z0-9_]{1,40}_[1-9]\d*>$/. LLMs naturally treat <DB_URI_1> as an opaque symbolic variable and preserve it verbatim inside refactored string templates, JSON payloads, and TypeScript configs. When you copy the AI's response and press Cmd+Alt+R (Ctrl+Alt+R), a single regex pass over /<([A-Z][A-Z0-9_]{1,40})_([1-9]\d*)>/g swaps every placeholder back to its exact original value in under a millisecond.

2. Why Regex Execution Order Makes or Breaks Secret Scrubbers

If you run 10 independent regular expressions in arbitrary order, smaller patterns will cannibalize larger structured secrets and corrupt restoration!

Consider a production PostgreSQL connection URI:

DATABASE_URL="postgresql://svc_billing:[email protected]:5432/ledger"

If your INTERNAL_IP detector (10.24.8.15) or generic .env key-value detector fires before your DB_URI detector, the URI gets partially mutated into:

postgresql://svc_billing:sk-live-secret99@<INTERNAL_IP_1>:5432/ledger

Now the DB_URI regex fails to match, leaving svc_billing:sk-live-secret99 exposed in plain text!

PasteShield prevents partial-token cannibalization through strict coarse-to-fine pass ordering paired with an idempotency guard (hasExistingPlaceholder):

  1. Pass 1 β€” Multi-Line PEM / OpenSSH / PGP Private Key Blocks: Captures entire -----BEGIN ... PRIVATE KEY----- blocks across newlines before line-oriented matchers touch base64 lines.
  2. Pass 2 β€” Composite Cloud Connection Strings: Matches multi-segment Azure Storage, Service Bus, Cosmos DB, IoT Hub, and SQL connection strings (DefaultEndpointsProtocol=...;AccountKey=...).
  3. Pass 3 β€” Authenticated Database & Broker URIs: Captures full postgres://, mysql://, mongodb+srv://, rediss://, amqps://, and clickhouse:// URIs containing user:pass@host.
  4. Pass 4 β€” High-Prefix Vendor Tokens: Captures sk-proj-... / sk-ant-... (OpenAI & Anthropic), AKIA... / ASIA... (AWS IAM), ghp_ / github_pat_ (GitHub), Slack xoxb-, Stripe rk_live_, and 3-segment eyJ... signed JWTs.
  5. Pass 5 β€” Network & Identity Primitives: Captures RFC1918 (10.x, 172.16-31.x, 192.168.x) plus Tailscale/CGNAT (100.64-127.x) private IPv4s and email addresses.
  6. Pass 6 β€” Contextual Key-Value Assignments: Captures .env, YAML, and JSON assignments matching *_SECRET, *_PASSWORD, *_TOKEN, or *_API_KEY, while explicitly skipping benign placeholders (true, false, null, changeme, $ENV_REF, {{handlebars}}, and already-interned <TOKEN_1> markers).

Because every replacement checks if (hasExistingPlaceholder(match)) return match;, already-sanitized regions are completely immune to double-wrapping.

3. Zero-Backend Offline Pro Verification with 32-Bit FNV-1a

A non-negotiable security promise for developer security tools is zero outbound network traffic. If an extension claims to keep secrets local, opening fetch() sockets to a custom licensing server undermines enterprise trust and fails inside air-gapped corporate VPNs.

Our core 10 detectors, bidirectional VS Code commands, and Node/Bun CLI are 100% free and MIT-licensed forever. For teams wanting optional power featuresβ€”like custom corporate domain redaction (*.corp.acme.internal β†’ <COMPANY_DOMAIN_1>) and multi-file Markdown AI Context Packsβ€”we needed a purely offline license key format (SHIELDPRO-XXXX-XXXX-XXXXXX) verifiable in 15 lines of dependency-free JavaScript across Node, browsers, and macOS JavaScriptCore (jsc).

We used a 32-bit FNV-1a non-cryptographic checksum with 32-bit unsigned integer multiplication (Math.imul):

function fnv1aHex6(str) {
  var h = 0x811c9dc5;
  for (var i = 0; i < str.length; i++) {
    h ^= str.charCodeAt(i);
    h = Math.imul(h, 0x01000193) >>> 0;
  }
  return ('00000000' + h.toString(16).toUpperCase()).slice(-8).slice(0, 6);
}

Why Math.imul(h, 0x01000193) >>> 0 instead of h * 0x01000193? In JavaScript, standard multiplication uses IEEE-754 64-bit floats with 53 bits of mantissa precision; multiplying a 32-bit integer near 0xFFFFFFFF by the FNV prime 16777619 (0x01000193) can exceed $2^{53} - 1$ and silently drop low bits! Math.imul performs exact C-style 32-bit two's-complement multiplication, and >>> 0 coerces the result back to an unsigned 32-bit integerβ€”producing byte-for-byte identical hex signatures between Python build scripts and JavaScript runtimes with zero WebCrypto async ceremony.

Try It in Your Workflow (100% Local & MIT Open Source)

Whether you live inside VS Code, Cursor, or terminal pbpaste | ... | pbcopy pipelines, you can wire reversible sanitization into your daily AI loop in 30 seconds:

If there is a cloud vendor token format or log pattern your team hits frequently, open an issue or PR on GitHubβ€”happy to add first-class detectors for it!

πŸ“° Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.