Skip to content

Use cases

Add on-device protection to the AI features you build.

The SDK swaps sensitive values in your own process before your model call, then restores the streamed reply as it arrives.

Three engineers discussing a simple box-and-arrow diagram

The task

You are building a feature that sends user text to a language model, such as support drafts or an internal assistant. That text will contain names, emails and phone numbers you would rather not send to a model provider.

The SDK runs inside your own process. You call protect before the request and keep the swap table in memory. As the reply streams back, a restorer swaps the placeholders for the real values, so your users see a normal answer.

Before and after

We ran a fictional prompt through the engine with its developer profile, then streamed a fixed reply through the restorer. Every output below is real engine output.

Fictional example

What you start with

Write a two-line welcome message for our new customer Tomasz Wierzbicki (tomasz.w@example.com).
Mention that his account manager, Grace Olumide, will call him at (312) 555-0129 this week.

What the AI receives

Write a two-line welcome message for our new customer [PERSON_1] ([EMAIL_1]).
Mention that his account manager, [PERSON_2], will call him at [PHONE_1] this week.
Names
2
Email addresses
1
Phone numbers
1

Restoring the placeholders on the device gave back the original text exactly in this run.

The code

Three calls carry the round trip. Your own model call sits between protect and the restorer: it sends prompt.text with the instruction and returns the stream the loop reads.

The engine's internal name is @tightlip/core. The current SDK tarball installs as the package tightlip during the transition.

import {protect, createStreamRestorer, PLACEHOLDER_INSTRUCTION, ProtectError} from 'tightlip';

let prompt;
try {
  prompt = protect(userText, {profile: 'developer'});
} catch (err) {
  if (err instanceof ProtectError) return; // fail closed: do not send
  throw err;
}

// Send PLACEHOLDER_INSTRUCTION as the system prompt and prompt.text as the user message.
const restorer = createStreamRestorer(prompt.vault);
for await (const chunk of modelStream) render(restorer.push(chunk));
render(restorer.flush());
Read the PLACEHOLDER_INSTRUCTION text

Some details in this conversation were replaced with placeholders in square brackets, such as [PERSON_1], [ORG_2], [EMAIL_1] or [CASE_NUMBER_1]. Each placeholder stands for one real value that is kept private. Treat each placeholder as the real name, number or detail it stands for. [PERSON_1_FIRST] and [PERSON_1_LAST] are the first and last name of [PERSON_1]. Whenever you refer to one, copy the placeholder exactly as written, including the square brackets, capital letters, underscores and number. Do not translate, reformat, merge or split placeholders, and do not guess or comment on the hidden values.

The streamed reply, restored

No model was called to build this page. A fixed reply string stands in for the model’s answer. We cut it into 9-character chunks, so some chunks end halfway through a placeholder, and fed them to createStreamRestorer.

Welcome aboard, [PERSON_1]. Your account manager [PERSON_2] will call you at [PHONE_1] this week.

Each chunk pushed into the restorer, and the text it released.
Chunk receivedText released
Welcome aWelcome
board, [Paboard,
ERSON_1].Tomasz Wierzbicki.
Your acc Your acc
ount manaount mana
ger [PERSger
ON_2] wilGrace Olumide wil
l call yol call yo
u at [PHOu at
NE_1] thi(312) 555-0129 thi
s week.s week.
(flush)(nothing left)

The restorer held back each partial placeholder until it was complete, then released the real value. The joined output was:

Welcome aboard, Tomasz Wierzbicki. Your account manager Grace Olumide will call you at (312) 555-0129 this week.

What you review before sending

  1. 1Log what you send. Check that protected prompts, not originals, reach your model provider and your own logs.
  2. 2Treat ProtectError as "do not send". protect throws instead of returning half-protected text.
  3. 3Decide where the swap table lives. It stays in memory unless you serialise it with vault.toJSON().
  4. 4Reuse one vault across a conversation so the same person keeps the same placeholder.
  5. 5Add customTerms for names your users mention often, such as client or project names.
  6. 6Test replies that rename or drop placeholders. Unknown placeholders are left as they are.

Limits

  • Detection uses rules, checksums and name lists. It has no language model. A name with no cue, such as a title, a role or a label, can be missed. Add names you know matter to your always-protect list.
  • A vault holds at most 50 records. Plan for long conversations with many different people.
  • The stream restorer holds back a partial placeholder for up to 40 characters before it releases the text.
  • Addresses are found in US style only. Non-Latin scripts are not covered.
  • The SDK protects the text you pass to it. It does not read files, and the browser extension does not scan attachments either.
  • A chat site's page can read text while you type it, before anything is swapped. That applies to the browser extension. In your own app, protect runs before the text leaves your process.
  • Review stays necessary. Test your feature with realistic fictional data and read what your app sends.

Try it

The free check runs the same engine in your browser and sends no text to an AI model. The SDK is not published on npm yet. Get the current SDK tarball from the download page.

Remove sensitive details before your next AI prompt.

Try the free check ↗