diff --git a/kits/pii-sovereign-guardrail/.gitignore b/kits/pii-sovereign-guardrail/.gitignore new file mode 100644 index 000000000..246d974f3 --- /dev/null +++ b/kits/pii-sovereign-guardrail/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +.next/ +.env +.env.local +*.log +.vs/ diff --git a/kits/pii-sovereign-guardrail/README.md b/kits/pii-sovereign-guardrail/README.md new file mode 100644 index 000000000..3eab40617 --- /dev/null +++ b/kits/pii-sovereign-guardrail/README.md @@ -0,0 +1,89 @@ +# PII Sovereign Guardrail + +Enterprise middleware that masks personally identifiable information (PII) +before it reaches an external LLM provider (OpenAI, Anthropic, etc.), and +rehydrates it in the response — so raw personal data never leaves your +infrastructure in identifiable form. + +## The problem + +Enterprise legal and security teams routinely block GenAI rollouts because +raw user input — names, emails, phone numbers, addresses — ends up in a +third-party model vendor's logs. That's a real compliance blocker, not a +hypothetical one, for regulated industries like banking, healthcare-adjacent +SaaS, and legal tech. + +## How it works + +Two detection layers, because neither alone is honest coverage: + +1. **Layer 1 — deterministic (regex).** Catches structurally predictable + PII: emails, API keys/secrets, phone numbers, credit card numbers. Fast, + free, and reliable for well-formed matches. +2. **Layer 2 — probabilistic (LLM-based NER).** Catches unstructured PII + regex can't: names, addresses, free-text personal references. Each + detection is tagged with a confidence level. + +Both layers' detections are masked with indexed placeholders +(`[REDACTED_EMAIL_0]`, `[REDACTED_NAME_1]`, ...) before the prompt is sent +to the target model. After the model responds, the placeholders are swapped +back to their real values — a process that happens entirely within this +flow, never externally. + +See [`agent.md`](./agent.md) for the full architecture and an honest list +of what this does and does not cover, and +[`constitutions/default.md`](./constitutions/default.md) for the hard +guardrails the flow must never violate (e.g. fail closed, never persist +the token map). + +## Demo ![PII Guardrail demo](./docs/demo.png) *Left: what leaves your infrastructure (masked). Right: what the caller actually receives (rehydrated).* + +## Structure + +``` +pii-sovereign-guardrail/ +- lamatic.config.ts (kit metadata) +- agent.md (capability doc, read this first) +- constitutions/default.md (hard guardrails) +- flows/pii-sovereign-guardrail.ts (flow graph, real Lamatic Studio export) +- scripts/ (masking/rehydration logic, real, working +- prompts/ (Layer 2 NER prompts) +- model-configs/ (LLM configs per node) +- apps/ (Next.js demo, live masking visualization) + +``` + +This flow is built, deployed, and tested end-to-end in Lamatic Studio. +Everything in `flows/`, `scripts/`, `prompts/`, `model-configs/`, and +`constitutions/` is the real Studio export, not a scaffold — Layer 1 +(regex), Layer 2 (LLM NER), the target model call, and rehydration have +all been verified working together on live test runs. + +## Running the demo locally + +```bash +cd kits/pii-sovereign-guardrail/apps +cp .env.example .env.local # fill in your own Lamatic + model credentials +npm install +npm run dev +``` + +The app works out of the box in **demo mode** (Layer 1 only, no LLM call, +no API keys needed) so you can see the redaction pipeline immediately. +Once `.env.local` is filled in with a deployed flow's `LAMATIC_API_KEY`, +`LAMATIC_PROJECT_ID`, `LAMATIC_API_URL`, and `PII_GUARDRAIL_FLOW_ID`, it +automatically switches to the full two-layer pipeline against the real +deployed flow. + +## What this does NOT do + +- It is not a certified DLP system — for HIPAA/PCI-scope data, pair it with + dedicated compliance tooling. +- Layer 2 is probabilistic. It significantly improves coverage over regex + alone but is not a guarantee of catching every unstructured PII mention. +- It does not provide audit logging by itself — pair it with your existing + observability stack (e.g. Langfuse, which Lamatic already integrates with). + +## Tags + +`security` `compliance` `pii` `data-sovereignty` `middleware` `enterprise` diff --git a/kits/pii-sovereign-guardrail/agent.md b/kits/pii-sovereign-guardrail/agent.md new file mode 100644 index 000000000..40d25ae07 --- /dev/null +++ b/kits/pii-sovereign-guardrail/agent.md @@ -0,0 +1,32 @@ +# PII Sovereign Guardrail — Agent Identity + +## What this agent does + +The PII Sovereign Guardrail sits between your application and any external LLM +provider (OpenAI, Anthropic, etc.). It intercepts outbound prompts, masks +personally identifiable information (PII) before the prompt ever leaves your +infrastructure, sends the sanitized prompt to the target model, and then +rehydrates the original values back into the response before it's returned +to the caller. + +The goal isn't "detect 100% of PII" — no system honestly can, and any kit +that claims otherwise is overselling. The goal is to give engineering and +compliance teams a **documented, auditable boundary**: a clear answer to +"what does this catch, and what doesn't it catch" so they can make an +informed decision about what still needs a human review step. + +## Capabilities + +1. **Deterministic masking (Layer 1)** — pattern-based detection for + structurally predictable PII: email addresses, API keys / secret tokens, + phone numbers, credit card numbers. This layer is fast, free, and has + effectively zero false negatives for well-formed instances of these + patterns. +2. **Probabilistic masking (Layer 2)** — an LLM-based named-entity + recognition pass that catches unstructured PII regex structurally cannot: + personal names, physical addresses, and free-text personal references + ("my account under John Smith", "reach me at..."). Each detection carries + a confidence label. +3. **Token re-hydration** — a per-request token map (never persisted, + never sent externally) restores the original values into the model's + response before it reaches the caller. \ No newline at end of file diff --git a/kits/pii-sovereign-guardrail/apps/.env.example b/kits/pii-sovereign-guardrail/apps/.env.example new file mode 100644 index 000000000..845f4efc0 --- /dev/null +++ b/kits/pii-sovereign-guardrail/apps/.env.example @@ -0,0 +1,7 @@ +# Get these from Lamatic Studio → Settings → API Keys (see repo CONTRIBUTING.md) +LAMATIC_API_KEY= +LAMATIC_PROJECT_ID= +LAMATIC_API_URL= + +# Flow ID for the deployed pii-guardrail flow (Studio → Flow → Details panel) +PII_GUARDRAIL_FLOW_ID= diff --git a/kits/pii-sovereign-guardrail/apps/actions/orchestrate.ts b/kits/pii-sovereign-guardrail/apps/actions/orchestrate.ts new file mode 100644 index 000000000..d968684e3 --- /dev/null +++ b/kits/pii-sovereign-guardrail/apps/actions/orchestrate.ts @@ -0,0 +1,49 @@ +"use server"; +import { guardrailFlowId, createLamaticClient } from "../lib/lamatic-client"; + +export interface GuardrailResult { + secureResponse: string; + maskedPromptSent: string; + tokensRedacted: { + total: number; + deterministic: number; + probabilistic: number; + }; + // Populated only in local demo mode (no deployed flow ID yet), so + // reviewers can see the redaction pipeline working before wiring up + // a real Lamatic Studio deployment. + demoMode?: boolean; +} + +export async function runGuardrail( + rawUserPrompt: string, + targetModel: string +): Promise { + if (!guardrailFlowId) { + // No deployed flow yet — fall back to a local demo so the app is + // still usable out of the box. Swap this out once PII_GUARDRAIL_FLOW_ID + // is set in .env.local. + const { runLocalDemoGuardrail } = await import("../lib/local-demo"); + return { ...runLocalDemoGuardrail(rawUserPrompt), demoMode: true }; + } + + const lamatic = createLamaticClient(); + const response = await lamatic.executeFlow(guardrailFlowId, { + rawUserPrompt, + targetModel + }); + + const result = response.result; + if (!result) { + // Fail closed — if the flow didn't return a result, don't guess + // at partial data. Surface a clear error instead. + throw new Error("Lamatic flow returned no result — masking pipeline failed."); + } + + return { + secureResponse: result.secureResponse, + maskedPromptSent: result.maskedPromptSent, + tokensRedacted: result.tokensRedacted, + demoMode: false + }; +} \ No newline at end of file diff --git a/kits/pii-sovereign-guardrail/apps/app/globals.css b/kits/pii-sovereign-guardrail/apps/app/globals.css new file mode 100644 index 000000000..08831bf61 --- /dev/null +++ b/kits/pii-sovereign-guardrail/apps/app/globals.css @@ -0,0 +1,45 @@ +:root { + --bg: #0c0f12; + --panel: #14181d; + --panel-raised: #191e24; + --border: #262b31; + --text: #e9ecef; + --text-dim: #8b939c; + --accent: #34d8a6; + --accent-dim: #1f7a5c; + --redact: #eb6a4e; + --redact-bar: #05070a; + --font-ui: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; + --font-mono: ui-monospace, "SF Mono", "Cascadia Code", Menlo, Consolas, + monospace; +} + +* { + box-sizing: border-box; +} + +html, +body { + padding: 0; + margin: 0; + background: var(--bg); + color: var(--text); + font-family: var(--font-ui); +} + +body { + min-height: 100vh; +} + +::selection { + background: var(--accent-dim); + color: white; +} + +button { + font-family: inherit; +} + +a { + color: var(--accent); +} diff --git a/kits/pii-sovereign-guardrail/apps/app/layout.tsx b/kits/pii-sovereign-guardrail/apps/app/layout.tsx new file mode 100644 index 000000000..48836c2a4 --- /dev/null +++ b/kits/pii-sovereign-guardrail/apps/app/layout.tsx @@ -0,0 +1,19 @@ +import "./globals.css"; + +export const metadata = { + title: "PII Sovereign Guardrail — Lamatic AgentKit", + description: + "Enterprise middleware that masks PII before it reaches an external LLM, and rehydrates it in the response." +}; + +export default function RootLayout({ + children +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/kits/pii-sovereign-guardrail/apps/app/page.tsx b/kits/pii-sovereign-guardrail/apps/app/page.tsx new file mode 100644 index 000000000..1f69262a9 --- /dev/null +++ b/kits/pii-sovereign-guardrail/apps/app/page.tsx @@ -0,0 +1,311 @@ +"use client"; + +import { useState } from "react"; +import { runGuardrail, type GuardrailResult } from "../actions/orchestrate"; + +const EXAMPLE_PROMPT = + "Hi, this is John Whitfield. My email is john.whitfield@acme-corp.com and my number is (415) 555-0192. Can you draft a reply to my landlord about the lease at 42 Elm Street, Austin?"; + +const MODELS = ["gpt-4o-mini", "gpt-4o", "claude-sonnet-4-6"]; + +function RedactedText({ text }: { text: string }) { + // Splits on [REDACTED_TYPE_n] placeholders and renders them as literal + // redaction bars — the signature visual for this kit. Nothing under a + // bar left the building in its real form. + const parts = text.split(/(\[REDACTED_[A-Z_]+_\d+\])/g); + return ( + + {parts.map((part, i) => { + const match = part.match(/^\[REDACTED_([A-Z_]+)_\d+\]$/); + if (!match) return {part}; + return ( + + {match[1]} + + ); + })} + + ); +} + +export default function Page() { + const [rawPrompt, setRawPrompt] = useState(EXAMPLE_PROMPT); + const [targetModel, setTargetModel] = useState(MODELS[0]); + const [loading, setLoading] = useState(false); + const [result, setResult] = useState(null); + const [error, setError] = useState(null); + + async function handleRun() { + setLoading(true); + setError(null); + try { + const res = await runGuardrail(rawPrompt, targetModel); + setResult(res); + } catch (err) { + setError(err instanceof Error ? err.message : "Something went wrong."); + } finally { + setLoading(false); + } + } + + return ( +
+
+ LAMATIC AGENTKIT · SECURITY LAYER +
+

+ PII Sovereign Guardrail +

+

+ Raw personal data never leaves your infrastructure. Everything + under a bar below is masked before it reaches an external model, + and restored only in the final response. +

+ +
+ {/* Input panel */} +
+ +