I work on identity: who and what gets access, to what, for how long, and how you prove it to an auditor. Lately that means AI agents: giving them their own identity, a human owner, least privilege, and a full audit trail. I build the controls, then attack them, and write down every bypass I find.
AI Agent Spend Gate Β· Your AI agent has a company card. Three independent layers keep it honest, and only rules or a human can say yes.
How it works
flowchart LR
A["π€ Agent<br/>Amazon Bedrock<br/><i>proposes</i>"] --> B["π§ Decider 2B<br/>guardrail<br/><i>is it honest?</i>"]
B --> C["π Spend Gate<br/>Python rules<br/><i>is it allowed?</i>"]
C --> D["π³ Order"]
B -. "unsure" .-> H["π Human"]
C -. "over limit" .-> H
- Three layers of control. The LLM proposes, Strands Decider 2B checks honesty (grounded, injected, mislabeled), and a deny-by-default rules engine decides permission.
- Models can add friction, never approve. Only the deterministic rules, or a human, can say yes to spending money.
- Built for prompt injection. A poisoned note in a tool result tells the agent to buy $450 of gift cards and file them as office supplies. The rules alone would approve that lie. The guardrail catches it and denies it before the tool runs.
- Calibrated confidence. Uncertain calls go to a human, not to a guess.
- Reusable
#guardrailspattern that works with any Strands agent. - I break my own controls. The build journal records every bypass I found, like a
nanamount that slipped past every rule, and how I fixed it.
CareMap Β· What care would cost you, before the bill. 4,500+ US hospitals priced under the charity-care law where you live, with the math shown, and your income never leaves the browser.
How it works
flowchart LR
U["π€ You<br/>ZIP, household, income"] --> B["π Browser<br/>poverty-line math,<br/>state rule"]
B -- "tier % only" --> A["π€ Strands agent<br/>AWS Lambda"]
A --> T["π§Ύ Tools<br/>CMS + state law"]
T --> G["π Grounding guard<br/><i>every $ from a tool?</i>"]
G -. "no: rewrite" .-> A
G --> R["π¬ Reply + map"]
- Privacy by design. Household size and income are redacted in the browser before any network call; the agent only ever sees a tier and a ZIP.
- AI output is untrusted. Strands hooks keep a ledger of every number a tool returned; a reply that states a figure not in the ledger is sent back to be rewritten.
- Works without the AI. The map, prices and law math run client-side, so the app keeps working when the model is down or out of quota.
- Hardened endpoint. The Lambda Function URL has a CORS allow-list, a max message length and a per-IP rate limit.
- Tested and accessible. 262 tests (Python and TypeScript) and an automated WCAG 2.2 AA audit.
Security principles, applied
| Principle | Where it shows up in my work |
|---|---|
| Least privilege | The agent's AWS IAM policy can invoke one model and nothing else |
| Separation of duties | The AI agent proposes a purchase; a separate policy engine decides |
| Zero trust for inputs | LLM output and tool results are treated as untrusted, even from internal feeds |
| Defense in depth | Three independent layers: the agent, a Decider 2B guardrail, deterministic rules |
| Fail closed | Deny by default: unknown vendors, unknown categories and malformed amounts are blocked |
| Threat modeling | Prompt injection and excessive agency (OWASP LLM Top 10), business email compromise |
| Detection & response | Every guardrail verdict is recorded with a reason and confidence scores, so it can be sent to a SIEM |
| Adversarial testing | Found and fixed bypasses: NaN, true, and fraction-of-a-cent amounts |
| Data minimization | CareMap works out eligibility in the browser; the AI agent sees a tier percentage, never a salary |
| Output integrity | Every dollar figure an AI reply states must match a tool result, or the reply is forced to rewrite |
Least privilege for people, machines, and agents. π




