Deliverd Screen: an open-source check before every prompt
21 October 2026 · Deliverd Engineering Team · 5 min read
In October we shipped data screening: a check an agent runs before it sends text to a model, so the email addresses, account numbers and keys in a prompt don't go along with the question. The detectors behind it now have a life of their own. Deliverd Screen is an open-source library for TypeScript, Python and Rust. It finds and redacts personal data, secrets and your own confidential terms before text goes to any model. There's no account and no key, and it makes no network call.
npm install @deliverd/screen
pip install deliverd-screen
cargo add deliverd-screen
import { screen } from "@deliverd/screen";
const checked = await screen("Reply to mary.byrne@example.ie about IBAN IE29AIBK93115212345678");
checked.text; // "Reply to [EMAIL_1] about IBAN [IBAN_1]"
const answer = await model.generate(checked.text);
checked.restore(answer); // the real values, back in the answer
Why give it away
Checking a prompt shouldn't need a sales call. Every team that sends text to a model has the same problem, and most of them write the same handful of regular expressions to deal with it, on a Friday, with no tests. A check that runs on your own machine, that you can read line by line, is easier to trust than one you have to take on faith, and it is the right default for a step that sees your most sensitive text.
We also think the hard part of this problem isn't finding an email address. It's deciding what happens next. That part is where Deliverd earns its money, and we come back to it below.
What it finds
Email addresses, phone numbers, IBANs, payment card numbers (Luhn-checked), UK National Insurance numbers, Irish PPS numbers (check character), US Social Security numbers, IP addresses, dates of birth, API keys and tokens (AWS, GitHub, Slack, Stripe, Anthropic, OpenAI, Google, JWTs and more), words that mark special-category data such as health or religion, and your own terms: client names, code names, deals.
Each check gives one of four answers:
- Allow: nothing the model may not receive, so send the text as it is.
- Redact: swap what the model may not receive for placeholders such as
[EMAIL_1], send the rest, and put the values back in the answer withrestore(). The same value gets the same placeholder throughout, so the model can still tell two people apart. - Ask: a person has to decide, special-category data by default. The library can't ask anyone, so it says no and tells you why.
- Deny: send nothing. Secrets are refused by default, because a key sent to a model is a key disclosed.
The TypeScript, Python and Rust packages find exactly the same things. All three are tested against one shared set of cases, the same set our own SDKs and servers are held to, so a prompt checked in a Python notebook and again in a Rust service gets the same answer.
Names: bring your own model
A pattern can find an IBAN, because an IBAN has a shape and a checksum. It can't find a person's name, because a name looks like any other word. So Screen doesn't pretend to. Instead it takes a detector: any function from the text to the places a model found something. Adapters turn a Hugging Face token classifier's or a Presidio analyzer's output into those places, and any model with the same output works, GLiNER included.
import { tokenClassifier } from "@deliverd/screen/adapters";
const checked = await screen(prompt, { detectors: [tokenClassifier((t) => ner(t))] });
Run the model where the text already is. A detector that sends the text to a hosted service has already sent it. And check a model's licence before you ship it.
Where it fits
- In code, as above, before you call a model.
- In the Vercel AI SDK, as middleware:
screenMiddleware()wraps a model, screens the whole conversation, restores the answer (a streamed one too) and stops the call on a deny or an ask. - On the command line, in pre-commit hooks and CI:
npx @deliverd/screen --fail evals/*.jsonlfails the build if an eval set has a customer's email address in it. The Python and Rust packages install the samedeliverd-screencommand. - In your coding agent, as a local MCP server with one tool,
screen_text, for Claude Code, Cursor or Codex. It returns the verdict and the redacted text, never the values it replaced, so they don't end up in the agent's context through the tool.
The TypeScript and Python packages have no runtime dependencies. The Rust crate's library has one, regex, which matches in time linear in the text, so no prompt can make a check slow.
When a person has to decide
A library can tell you to stop. It can't decide for you, because there's nobody in it to ask.
That's Data Gate, in Deliverd. deliverd.screen() in our SDKs runs the same detectors on your machine and sends Deliverd only the counts: two email addresses, one IBAN. The verdict comes back from your organisation's policy and from what your AI register approves each model to receive, so a model approved for confidential client data gets more than one approved only for internal notes. On an ask, the question goes to the right person in Slack, Teams, email or on their phone, with the redacted text in front of them. Every check is recorded: which agent, which model, what kinds of data and how many, and the verdict.
Data Gate is included in every paid plan, from Team up. On Free it runs report-only: checks are recorded and nothing is held back, so you can see what it would have stopped before you pay for it to stop anything.
What it won't do
Structured identifiers are found well. People's names are not found reliably without a model plugged in, and only as well as that model with one. Redacted text about a person is still personal data under GDPR. Screen means fewer identifiers sent, never proof that a prompt was clean.
Get it
The source is on GitHub at deliverd-dev/deliverd-sdk, under the MIT licence, with the npm, PyPI and crates.io packages published from it. The developer docs cover every option, and deliverd.dev/screen has the short version. Issues and pull requests are welcome, especially a case it gets wrong.