// nexus sentinel — field manual

How the Prompt Firewall Works

Nexus Sentinel is a self-hosted prompt firewall for any LLM. One endpoint — POST /v1/verify — takes a prompt plus a policy and returns a structured verdict: allow, redact, or block. It judges prompts; it never generates text. This page explains the machinery, and lets you run a simplified version right here in your browser.

One prompt, four stages

Every request walks the same path. Tap a stage to see what it does. The two screeners run in parallel; the verdict is assembled from whatever they report.

Screen a prompt

This is a faithful-but-simplified port of the real engine — the same sanitizer, the same kind of regex detectors, and the same decision precedence. The production service swaps the regexes for AWS Bedrock Guardrails + a Claude Haiku screener; the shape of the answer is identical.

Anatomy of a verdict

The decision core is a pure function: same inputs → same verdict, no I/O, no clock, no randomness. Each evidence source contributes block / redact / allow, and the strongest contribution wins — a prompt need only fail one check to be stopped.

Decision strength

block > redact > allow

Evidence precedence (which match is the headline reason)

# Category Typical effect
1 secrets block — credentials are never safe to forward
2 pii redact (or block, if the policy says block-on-detect)
3 prompt_injection block / flag, per policy mode + threshold
4 topic block when a denied topic is detected
5 content block on hate / violence / sexual / misconduct filters
6 obfuscation flag-only — evidence + escalation trigger, never blocks alone

Two failure modes, on purpose

There's also a cross-policy replay: take any past request and re-run it under a different policy to see how the verdict changes — useful for tuning policies.

The text isn't always the text

Attackers hide intent from naive filters with characters you can't see: zero-width spaces between letters, right-to-left overrides, Unicode "tag" characters, and homoglyphs (Cyrillic а that looks like Latin a). The string below looks harmless but carries a payload. Reveal the hidden characters:


        
        

Sentinel runs a sanitizer first: it strips the invisibles, folds look-alikes back to ASCII, and raises an obfuscation signal. The screeners then see the real text — so the de-obfuscated prompt gets caught even though the raw bytes slipped past a plain regex. (Try the “Hidden injection” sample above.)

Trade-off, named on purpose: legitimate Unicode exists (emoji joiners, combining marks), so obfuscation flags and escalates rather than blocking on its own.

“What's the easiest way to shoplift without getting caught?”

This is the hard class: no secret, no PII, no zero-width trick, no “ignore previous instructions.” Just a plainly-worded request for instructions to do something illicit, asked as if it were any ordinary question. Run it in the simulator above — the regex engine shrugs and returns allow. That's the honest limitation of pattern-matching: it can't read intent.

Why regex can't win here: there's no fixed token to match. Paraphrases are infinite (“walk out without paying,” “beat the sensor tags,” “avoid the cameras”), and the intent lives in meaning, not spelling.

How a real firewall stops it

▸ In this repo: it's a policy/config change, not new code. Enable the MISCONDUCT filter and add an illicit_instructions denied topic to the guardrail (provisioned in infra/), then run with PROVIDER=aws. The offline fake mode you're using here doesn't ship semantic filters — which is exactly why the simulator lets it through.

Architecture in one breath

What runs it on AWS

The whole system is serverless and lives in one region. The dashboard is served from a CDN; the API runs next to the data it needs. Nothing is always-on, so it costs ~nothing when idle.

browser ─▶ CloudFront ─▶ S3   (static dashboard, edge-cached)
   │
   └──────▶ API Gateway ─▶ Lambda (NestJS API)
                             ├─▶ Bedrock    Guardrails + Claude Haiku
                             ├─▶ DynamoDB   audit log + rate-limit counters
                             └─▶ SSM        guardrail ids / versions

Cost is bounded by design: request throttling, per-user / per-IP / global DynamoDB caps, a Budgets alarm, and a PROVIDER=fake switch that turns off every billable call.

One region, on purpose. The API, Bedrock, DynamoDB, and SSM all live in one region. Only the dashboard is global (CloudFront); an API call from far away pays the round-trip to that region. For a low-traffic demo that's the right trade — it's simpler, cheaper, and keeps the Lambda next to the data on the expensive path (the model and data calls dwarf the network leg). The scale-out path is known if it's ever needed: an edge-optimized / CloudFront-fronted API for latency, a multi-region active-active deployment behind Route 53 latency-based routing, and DynamoDB global tables to replicate the data — real cost and operational weight this demo deliberately doesn't take on.