feat(centinela): capability firewall with MCP gateway and WhatsApp step-up - #462
Open
JacoboCamargo wants to merge 14 commits into
Open
feat(centinela): capability firewall with MCP gateway and WhatsApp step-up#462JacoboCamargo wants to merge 14 commits into
JacoboCamargo wants to merge 14 commits into
Conversation
…ep-up Centinela is a deterministic capability firewall for Houston agents. The LLM is a confused deputy, not a security boundary: this moves the trust decision out of the prompt and into code the model cannot bypass, no matter how persuasive the input. - engine/houston-centinela: the Policy Core, pure logic. evaluate() returns Allow | Deny | StepUp over capability scopes, taint tracking, an egress allowlist, the Rule of Two, step-up capabilities and a duress latch. Every DENY is checked before any STEP_UP, so the gate is fail-closed by construction (deny beats step-up beats allow). 25 tests. - engine/houston-centinela-mcp: an MCP gateway (JSON-RPC over stdio) wrapping the core. Every tool call the agent makes is gated before it runs; the model only ever sees this endpoint, so it cannot reach the tools except through the gate. Verdicts stream to a journal the Salvoconducto UI tails. A STEP_UP escalates to the owner over WhatsApp (Meta Cloud API template) and waits for SI or NO; deny and timeout both block. 17 tests. Grounded in CaMeL (Google DeepMind) and the Agents Rule of Two (Meta). Follows Houston conventions: fail-closed default DENY, typed decision reasons, no silent failures, files under 200 lines, no em dashes in user-facing copy. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The approval recipient is the trust anchor for the whole step-up channel: if any number could be set, an attacker points it at their own phone and self-approves. A number is now accepted only after a one-time code we send to it is echoed back, and the verified anchor lives server-side where the agent can never reach or change it. - enrollment.rs: OTP store, single-use codes with a TTL, the verified anchor. - the WhatsApp client now sends to an explicit `to`: approvals go to the verified number, codes go to the number being verified (WHATSAPP_OTP_TEMPLATE or free-form text inside the 24h window). - the webhook gains POST /enroll/start and /enroll/confirm, plus permissive CORS so the Salvoconducto UI can call them. - the UI gains a "verify your number" panel. - no verified number means every step-up is fail-closed. 23 tests, clippy clean, fmt clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Redo the Salvoconducto from a dark theme to Houston's light, ChatGPT-like look (near-black on white, monochrome with semantic accents, invisible borders, visible actions) per knowledge-base/design-system.md: rounded-full buttons, rounded-xl cards, soft semantic pills (success/warning/danger), clean type. The number-verification panel and the live decision log are included; layout is responsive. Rendered states verified by headless screenshot. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…empts
The gateway is the choke point every sensitive call already passes through. The
Auditor reviews each verdict and, on a real bypass attempt (a jailbreak for an
undeclared capability, the duress latch, or exfiltration via taint/egress),
alerts the verified owner out of band over WhatsApp. Step-ups (handled by the
approver) and allows raise no alert; fail-closed when no number is verified.
Outbound messaging now sits behind a `Notifier` trait, so the whole approval,
enrollment and audit flow is exercised against a `MockNotifier` with no network.
- notifier.rs: the `Notifier` trait + a recording `MockNotifier` for tests.
- auditor.rs: the Auditor and its bypass classification.
- whatsapp.rs: implements `Notifier`, adds `send_alert` (+ optional
`WHATSAPP_ALERT_TEMPLATE`).
- server.rs: `Hooks { approver, auditor }`; the gateway audits every verdict.
- approver/webhook/main wired through `Arc<dyn Notifier>`.
36 tests (mock-backed: approver yes/no/timeout/send-failure, auditor per bypass
type + allow/step-up/no-verified, gateway integration). clippy + fmt clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A Houston agent (houston.json + CLAUDE.md) that runs behind Centinela: reads and drafts flow freely, irreversible actions require WhatsApp approval, and undeclared capabilities are blocked and alerted. Seeds its own capabilities.json salvoconducto. ShieldCheck icon, Spanish copy, no em dashes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Demo tools now read from a believable profile (inbox with a poisoned email, bank balance, transactions) so the whole flow is visible end to end. demo-flow.sh runs the six-step sequence through the gateway over one session (taint persists), narrates it, and populates the Salvoconducto UI's live log. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a real "Salvoconducto" tab to the Houston desktop app (STANDARD_TABS +
tab-resolver + a React component using the design-system tokens). It shows the
agent's permissions in plain language and a live decision log polled from the
gateway's new GET /decisions endpoint. Typecheck passes (pnpm tsc).
- app/src/components/tabs/salvoconducto-tab.tsx: the tab component.
- app/src/agents/{standard-tabs,tab-resolver}.ts: register the tab.
- engine/houston-centinela-mcp/src/webhook.rs: GET /decisions (journal as JSON).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e toggle Even a permitted action can leak. An allowed email may carry an API key, a private key, a card number or a password. The new content-inspection layer scans the outbound payload and blocks the send when a secret is found, regardless of whether the capability is granted: not "what can the agent do" but "what is it taking out". It is a per-agent toggle the owner flips live, and a blocked leak also raises an Auditor alert. - houston-centinela: secrets.rs scanner (API key, AWS key, private key, JWT, card via Luhn, password), Reason::SensitiveContent, Session.inspect_content, ToolCall.payload, and the evaluate() check. 12 new tests. - houston-centinela-mcp: a shared Arc<AtomicBool> toggle, webhook GET /inspect + POST /toggle/inspect, the gateway syncs it into the session per call, and the Auditor alerts on a blocked leak. - the Salvoconducto UI (standalone + the native Houston tab) gain the switch. 73 tests, clippy + fmt clean, pnpm tsc passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ing) In a multi-agent system agents inevitably talk to each other, and that channel is a leak path: a poisoned email agent asks the bank agent for the accounts and mails them out. Centinela cuts it at the source: agent-to-agent relay is modeled as egress, so an agent can read sensitive data but cannot export it to an agent that is not a cleared destination. Access is not export. A tainted session cannot relay at all (taint -> egress), and even a cleared relay asks the human. - relay_to_agent tool mapped to capability agent:relay (is_egress). - demo salvoconducto declares agent:relay, allowlists only cleared agents, requires step-up for it. - 3 tests covering the data-laundering attack. 76 tests total. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…sApp creds The content-inspection toggle and /decisions only worked when WhatsApp credentials were set, because the webhook spawned inside the approver branch. The webhook now runs whenever the gateway runs, with an optional notifier: the UI endpoints (/inspect, /toggle/inspect, /decisions) always work, while the reply and enrollment endpoints return 503 without credentials. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Salvoconducto tab now lets the owner verify their approval number in-app (number -> code -> verify), calling the gateway's /enroll endpoints, matching the standalone UI. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…oconducto Each capability is now a switch the owner flips from the Salvoconducto UI: revoke a permission and the gate denies it on the next call, grant one and it takes effect immediately, no restart. The base salvoconducto stays immutable; live overrides are applied on top per verdict. - houston-centinela: Capabilities::set_capability (grant/revoke), + test. - houston-centinela-mcp: shared Arc<Mutex<HashMap>> overrides in ServerState, effective_caps() applied per verdict, webhook GET /permissions + POST /toggle/permission. The webhook deps are bundled in a public Web struct instead of a growing positional argument list. - the Salvoconducto UI (standalone + native Houston tab) render a switch per permission, reading /permissions and posting toggles. 79 tests, clippy + fmt clean, pnpm tsc passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A single terminal command now drives the whole step-up flow: POST /demo/request sends the WhatsApp approval to the verified owner and blocks until they reply SI or NO, returning the outcome. Approver::with_registry lets the HTTP-triggered request share the webhook's registry, so the same reply resolves it. The decision is journaled so the Salvoconducto UI shows it. pedir-permiso.sh wraps the curl with friendly output. 42 gateway tests, clippy + fmt clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Brings up the whole demo from a single command: builds and starts the gateway, the Salvoconducto UI, the cloudflared tunnel, and (given META_APP_ID/SECRET) points the Meta webhook at the fresh public URL. Makes the demo reproducible without hand-running each piece. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR: Centinela, a capability firewall for Houston agents
What this adds
Centinela is a deterministic capability firewall for Houston agents. The LLM is
a confused deputy, not a security boundary: this moves the trust decision out of
the prompt and into code the model cannot bypass.
Two new engine crates (frontend-agnostic, no Tauri, no React):
engine/houston-centinela— the Policy Core. Pure logic.evaluate()returns
Allow | Deny | StepUpover capability scopes, taint tracking, anegress allowlist, the Rule of Two, step-up capabilities and a duress latch.
Every DENY is checked before any STEP_UP, so the gate is fail-closed by
construction. 25 tests.
engine/houston-centinela-mcp— an MCP gateway (JSON-RPC over stdio) thatwraps the core. Every tool call the agent makes is gated before it runs; the
model only ever sees this endpoint. Verdicts stream to a journal a plain
language Salvoconducto UI tails. A STEP_UP escalates to the owner over
WhatsApp (Meta Cloud API) and waits for SI/NO; deny and timeout are
fail-closed. 17 tests.
Why
The same artifact serves both hackathon tracks: a plain-language permission
passport for non-technical users, and a code-enforced boundary for safe deploy.
Grounded in CaMeL (Google DeepMind) and the Agents Rule of Two (Meta);
blocks the Lethal Trifecta (Willison) by structure, not by filters.
Conventions followed
Fail-closed default DENY, typed decision reasons (enums with Display), no silent
failures (every verdict surfaces), files under 200 lines, no em dashes in
user-facing copy, secrets only via env vars.
Tests
cargo test -p houston-centinela -p houston-centinela-mcp— 42 passing.cargo clippyclean,cargo fmtclean.How to try it
See
engine/houston-centinela-mcp/README.md(gateway) andWHATSAPP-SETUP.md(the WhatsApp step-up demo).
cargo run -p houston-centinela --example demosruns the three blocking demos with no external setup.