AI data protection · rated Medium
Redacted, except for the card number
Before an AI app sends user text to a model, it is supposed to strip out card numbers and personal data. This library does that job, from a vendor that sells AI data-loss prevention. On plain English it looked fine. Put an accented letter, a Russian greeting or a few emoji in front of a card number, and part of the number, then all of it, stayed in output the library called redacted. Silently. It is public as GHSA-gwjv-qpf9-g36x, rated Medium, credited to RIA Labs. Fixing it, the maintainer found more in the same engine and published advisories for the Go and Python SDKs too.
Read this part first
If your product scrubs personal or payment data out of text before it goes to a model, a log or a support tool, read the advisory. It names the packages and the affected versions. A fix shipped in version 1.12.0 of the three npm packages it lists, on 8 September 2026. Update to it or later.
On another language’s SDK, read its own advisory: Go (fixed in 1.0.0-rc.2) and Python (fixed in 1.1.0). Both come from the same engine change.
The rest of this is about the pattern, not the package. There is no exploit code below.
How bad it is
The record rates it Medium. The 6.5 is ours: it is the score in our report, which the project’s maintainer published unchanged, and the record itself carries only the severity word. Our vector is CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N: reachable over the network, no account, no click. An earlier draft of ours claimed a scope change, which would have made it 7.2. We dropped it before submitting: the data is the app’s own, and it is the app that sends it on, not the library.
Medium is the right rating for what it measures. What a rating cannot show is how this fails: silently. The function returns normally, the output has redaction placeholders in it, and a complete 16-digit card number can still be sitting right in front of them. Any check that asks “did redaction run?” passes. The input that triggers it is not exotic either. A message that opens in Russian or Chinese and then gives a card number can leave the whole number in place.
Why it matters more in an AI pipeline
Redaction exists for one moment: text is about to leave for somewhere less trusted. In an AI app, that moment is every prompt. The library’s own usage example is a round trip: redact the text, hand it on, then restore the originals in whatever comes back. That is the shape of a call to a model.
The case with a real attacker is mixed authorship. A chat transcript, a support thread or a prompt built from several turns gets redacted in one pass. Whoever writes the earlier text controls how much non-ASCII text sits in front of someone else’s card number, and so how much of that number survives. Our report named a multi-turn LLM prompt as one of those cases, and said plainly that it is a deployment assumption we did not observe in any specific product.
The restore step made it worse. It found its placeholders by what they looked like, not by where the text had been cut, so once the leak covered the whole number, the restored text came back with the card number in it twice.
Two ways of counting the same string
The detector reported where a secret started and ended in bytes. The code that cut the secret out counted in the string’s own 16-bit units. For plain ASCII text, one byte and one unit are the same thing, so the two agreed by coincidence, not by design. Put one accented letter, Cyrillic character or emoji before the secret and they stop agreeing.
Each such character pushes the cut further right than it should go. The first digits of the secret stay in front of the placeholder, still in the output, and the placeholder swallows ordinary text after it instead. With enough of those characters in front, the whole secret survives, untouched, inside a string the caller was told had been cleaned.
What the maintainer found next
Fixing it, the maintainer found the same root cause in more places and wrote it up at the top of the advisory. Two more kinds of input shift the cut the same way using plain ASCII, so English-only input is not safe either.
The bigger one: a crafted input could make the detection step itself fail, in the core SDK as well as the redaction package. A rule that fails returns an error, not allow or deny. The vendor documents that rule for scanning AI prompts before they reach the model provider, and its own docs say the SDK is “designed to fail open”, with example code that logs the error and allows the request. Follow that example and a prompt that trips the failure goes through unscanned. In the Python SDK the same failure was caught and detection was simply skipped for the request.
Those are the maintainer’s findings, not ours, and we did not score them. The record’s extra CWEs, CWE-674 and CWE-693, cover the detection failure. The Python advisory covers only that failure and still lists RIA Labs as finder. That credit is the maintainer’s call; the note on the JavaScript advisory says the failure came out of their remediation.
How we know
A description is not a finding. We ran the published package in a container, offline, against reserved test data only: the standard Visa test card number and an RFC 2606 example domain, never a real cardholder or person. Every run had a control beside it, the same call on plain ASCII text, which redacted correctly every time. Only the rows with non-ASCII text before the secret leaked. Putting non-ASCII text after the secret instead left the output clean, which is exactly what a byte-versus-unit mismatch predicts, and why this reads as the mechanism and not a flaky harness. A second script checked the restore step the same way: on the control it gave back the input exactly, and once the whole number leaked, it produced a second copy of it.
The record credits RIA Labs as the reporter here, and as the finder on the Go and Python advisories. Our report asked for a CVE. As of 27 September 2026 none is on the record.
Three questions for anything that guards a model’s input
None of this is specific to one package. If anything in your stack strips sensitive data out of text before a model sees it, ask these of whatever does the work.
- Do the code that finds a secret and the code that cuts it out count characters the same way? A detector and a splicer built in different layers, or across a boundary like WebAssembly, can each pick bytes or units on their own, and nothing forces them to agree.
- Does your test data look like your users’ data? Fixtures written in plain English cannot surface a bug that only shows up with other scripts, encoded text or odd punctuation, however thorough they are otherwise.
- When the guardrail errors, does the prompt still go out? A check that only acts on a clear “deny” fails open the moment the check itself breaks, and an attacker who can break it on purpose gets to skip it.
None of it needs exotic tooling. It needs test fixtures that look like real users, in more than one language, and a second look at what happens when the safety step fails.
What it proves, and what it doesn’t
It proves we test the code AI apps put between users and a model, not only web apps: an npm package, a mismatch across a WebAssembly boundary, and the same positive and negative controls a web finding gets. The bug sits in a control whose whole job is the moment text leaves for somewhere less trusted.
It does not prove every redaction library has this bug, and it does not show how any specific app assembles its prompts before redacting them. The detection failure and the plain-ASCII variants are the maintainer’s findings, not ours, and they are credited that way above.
If your product puts a guardrail between users and a model, we’ll read it. Nominate a target and the first scan costs nothing.
Published 8 September 2026. GHSA-gwjv-qpf9-g36x · severity Medium on the record · CVSS 3.1 6.5 from our report (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N) · CWE-212 (Improper Removal of Sensitive Information Before Storage or Transfer), CWE-674, CWE-693 · credited to RIA Labs, published by the project’s maintainer. Sibling advisories: GHSA-g926-hpg7-9wvv (Go), GHSA-9vc5-fp9m-33m5 (Python). Status as of 27 September 2026. Corrections, or a package you maintain and want looked at: info@ria-labs.com.
See what a scan turns up
Nominate a target. The first scan costs nothing, and we’ll walk you through it.