Skip to content
Myrhex
Français

Mission file05 / 06

Bulwark

Security library2026Open source

Prompt injection defence for anything that feeds untrusted text to a model or lets a model call tools.

Seven layers, three languages

Bulwark sits between untrusted text and a language model, in seven layers: decode, sanitise, detect, spotlight, harden, validate and ground. It exists in Python, TypeScript and Swift with no required runtime dependencies, and its 131 injection signatures cover 15 languages.

The signatures are written once, in Python, and generated into the other two. The first time I compared the three implementations they disagreed on 529 documents. Generating from a single source cut that to 67 on the spot, and chasing the rest turned up real bugs.

Measured, not claimed

Since 1.0 it grades itself against 888 labelled documents, 539 attacks and 349 harmless ones, written independently of the code. Each label records what a careful security engineer would say, never what Bulwark currently outputs, so every miss is a reproducible bug report rather than a tautology.

On that deliberately hostile set it catches 39% of attacks with 87% precision, and wrongly flags 9% of harmless documents, 3% once the deliberately borderline ones are set aside. Those are honest numbers for a corpus built to beat it. The gate refuses any change that catches less, flags more harmless text, or lets through a single attack it used to catch.

One line break

A single line break once beat the flagship signatures. They join trigger words across short spans, and the dot in a regular expression does not cross a newline, so wrapping “ignore all previous instructions” over two lines took its score from 0.86 to zero. Detection now runs over four views of the text: as written, with lookalike letters and leetspeak folded, with whitespace flattened and broken words rejoined, and with accents stripped.

Bugs only a second language finds

In Swift, the Turkish dotless ı slipped past case-insensitive matching, so a tag spelled <ımg> was invisible to the output check: a one-character way to leak data. In TypeScript, text was measured in UTF-16 units, so a document made entirely of hostile characters from outside Unicode's basic plane read as 4.7% contaminated instead of 100%. Reading the code had found neither. Comparing the languages found both.

Guarding the agent loop

ToolGuard treats an agent as three choke points: what goes into a tool, what comes back out, and the loop itself. It reads hosts the way the C library does, because 0177.0.0.1, 0x7f.0.0.1 and 2130706433 all mean your own machine, and once a session has read injected content it holds anything above read-only until a person confirms.

Rampart redacts personal data before it reaches a model. Its placeholders carry a random nonce, so an attacker cannot plant [EMAIL_1] in a page and have a real address filled back in where they can read it.

What is not done

1.1.0 is tagged on GitHub, but npm and PyPI still serve 0.3.0. Swift's Rampart covers six kinds of personal data to the other two's twelve. And prompt injection is not a solved problem: Bulwark makes attacks harder and failures visible. It does not make them impossible.