Invock
Inspiration
I used to think the hard problem with AI agents was getting them to pick the right tool. I don't believe that anymore.
Agents aren't just writing text now. They're executing MCP tools, editing files, hitting APIs, querying databases, deploying software, and touching production infrastructure. Once a model has that kind of reach, the interesting failure isn't a bad sentence, it's a bad side effect.
Here's the part that bothered me. Almost every agent stack out there treats a model-generated tool call as authorization by itself. The model said to do it, so it gets done. But the same model reading your carefully written system prompt is also reading the webpage, the email, the retrieved document, and the upstream agent's output, and any one of those can be poisoned. Prompt injection, ambiguous schemas, a misleading tool description, or a plain reasoning error. One wrong inference becomes one real, irreversible action.
So the question I actually cared about wasn't "can the model choose the correct tool?"
It was: should this exact action, with these exact arguments, be permitted at this exact moment?
That's what Invock (short for InvokeLock) answers, at runtime, deterministically.
It sits between the agent and whatever the agent is trying to touch. Every action gets intercepted, evaluated against explicit policy, gated behind a cryptographically bound approval when it's risky, and recorded as evidence someone else can independently verify.
The whole thing runs on one principle: the model can propose an action, but it never gets to be the thing that permits it.
What it does
Invock intercepts tool calls before they reach the MCP server, API, filesystem, or whatever protected runtime is behind it.
Every request gets normalized into a canonical ActionEnvelope, which carries the stuff that actually matters for security: tool and operation identity, canonicalized arguments, session and transport metadata, provenance, taint indicators, policy version, protocol generation, stable request hashes, approval state, and execution/receipt IDs.
Then a fail-closed policy engine, running completely outside the model, decides one of five things:
ALLOW— go aheadDENY— blockedREQUIRE_APPROVAL— frozen until a real authorization shows upQUARANTINE— the schema is unknown or incompatible, so nothing runsERROR— fail safely, forward nothing
Fail-closed policy
Policies live in explicit YAML, not in a prompt. Rules can key off tool name, operation, argument structure, resource path, network destination, taint classification, session identity, transport type, protocol era, approval requirement, or risk level.
The important part is what happens when something doesn't match. Nothing slips through. Invalid policy, missing fields, incompatible schemas, unsupported protocol behavior, all of it fails closed. An agent doesn't earn authority just by producing syntactically valid JSON-RPC.
Taint-aware control
Invock tracks whether a sensitive argument came from somewhere untrusted. An email, a scraped page, a retrieved doc, or another agent can quietly hand you a filesystem path, a shell command, a deployment target, a recipient, an API endpoint, a credential reference, or a destructive flag.
Worth being precise about what this does not do. Invock never tries to determine whether the model "understood" a malicious instruction, since that is unknowable from the outside and any answer would be a guess. It evaluates the resulting action and the provenance of its values, which is deterministic and checkable.
Atomic approvals
An Invock approval is not a reusable yes. It's bound to the exact canonical action it authorizes.
That kills a specific set of attacks: reusing an approval for a different action, mutating arguments after review, executing an approved action twice, replaying something expired, carrying approval across sessions, or approving under one policy version and executing under another.
Consumption is atomic, so once it's used it's dead. This is the classic time-of-check/time-of-use gap in agent systems, where you approve the thing you saw on screen and the runtime executes something slightly different.
Signed receipts
Every execution produces an Ed25519-signed receipt recording action identity, canonical request hash, policy decision, approval relationship, result, timestamp, sequence number, and state identifiers.
So an operator, or an outside verifier who trusts nothing about your stack, can confirm the action was intercepted, evaluated, approved when required, executed with the approved arguments, and recorded without silent edits. The model doesn't hold the signing key and can't forge one.
MCP interception
Invock guards local stdio traffic and Streamable HTTP POST, plus initialization and capability negotiation, tool discovery, tool invocation, and structured responses and errors.
AI Agent
↓
MCP Request
↓
Invock Transport Boundary
↓
ActionEnvelope Normalization
↓
Taint + Schema Validation
↓
Fail-Closed Policy Evaluation
↓
Allow / Deny / Approve / Quarantine
↓
Protected MCP Server
↓
Signed Receipt + Audit State
The downstream tool never has to trust the model. It only ever sees actions that already cleared the boundary.
Schema-drift quarantine
Tool schemas move fast, and a security layer that assumes they won't is quietly broken. If you write a policy against yesterday's schema and tomorrow's request adds a field, permissive parsing gives you this:
Unknown field → parser ignores field → policy misses behavior → action executes
Invock treats drift as a security event instead:
Unknown security-relevant structure → quarantine → no execution
Local control plane
Strict TypeScript CLI, local control-plane init, policy loading and validation, approval management, receipt verification, privacy onboarding, a loopback-only API, a local dashboard, and demo/certification workflows.
Privacy was not bolted on at the end either. Invock was built toward local, zero-data-retention-compatible operation, leaning on hashes, identifiers, policy metadata, and signed evidence instead of storing plaintext.
How I built it
Strict TypeScript, structured as deterministic security components rather than yet another autonomous agent.
1. Transport boundary. Parses protocol messages, enforces supported methods, validates framing, separates control messages from actions, keeps malformed traffic away from protected servers, and preserves request/response correlation. It never authorizes anything on its own.
2. Action normalization. Two clients can serialize the same arguments differently, and you cannot build policy on an unstable representation. Invock strips out object-key ordering, optional-field quirks, transport wrappers, equivalent encodings, protocol-version differences, and non-security metadata, then derives a stable action identity. That canonical form is what policy, approval binding, replay detection, and receipts all agree on.
3. Policy engine. No semantic guessing, no LLM involvement. Just invariants:
No matching allow rule → deny
Invalid policy → deny
Unknown tool schema → quarantine
Missing approval → do not execute
Expired approval → deny
Changed action after approval → deny
Replayed action → deny
4. Approval state machine. Approval is state, not a chat message. Each record ties to a canonical action hash, session context, policy state, expiration, and consumption status.
PROPOSED
↓
POLICY_REQUIRES_APPROVAL
↓
PENDING_APPROVAL
↓
APPROVED
↓
ATOMICALLY_CONSUMED
↓
EXECUTED or FAILED
Invalid transitions fail closed, and concurrent requests can't both eat the same authorization.
5. Signed receipts. SQLite-backed local control plane holding action state, approval records, receipt sequencing, replay detection, policy snapshots, outcomes, and verification metadata, all signed with a key the model can't reach.
6. Protocol-era boundary. Clients behave according to different MCP generations, so Invock tracks era as part of guarded context and validates against the right boundary rules instead of accepting a request just because it vaguely resembles a shape it knows.
7. Containment. Docker plus network-boundary checks. Dashboard and API bind to loopback only, protected services aren't accidentally exposed, there's no alternate route around Invock, and security state and signing authority stay off the model's tool surface. Writing correct authorization code isn't enough if the deployment topology lets you walk around it.
8. Certification. I treated the security model as invariants that had to survive adversarial testing, and the final run gave me 122/122 tests passing, full arena validation, 3/3 mutation tests, requirements INV-41 through INV-85, Docker containment verified, and MCP transport regression, approval replay, schema-drift, fail-closed policy, and receipt integrity all green. I ran the whole certification twice and got the same result both times.
Verdict:
READY
That verdict does not mean the model looked well behaved in a demo. It means the deterministic enforcement mechanisms passed the defined runtime security gates.
Challenges I ran into
Two transports, one security model. stdio and Streamable HTTP differ in framing, lifecycle, error handling, and connection behavior, and I did not want transport quirks producing inconsistent decisions. I terminated transport-specific logic at the adapters and normalized everything into one envelope.
Approval replay and mutation. An approval button is trivial to implement and difficult to secure. I had to guarantee one approval could never authorize a different tool, different arguments, a different session, a later replay, multiple executions, or a request edited after review. That took exact binding, expiration, transactional consumption, and revalidation at execution time.
Staying fail-closed without being useless. A control plane that blocks everything is secure and unusable. A system that guesses when something "looks fine" is usable and unreliable. I ended up with a layered model separating clearly allowed, clearly prohibited, approval-gated, and unknown-so-quarantine.
Auditability versus privacy. Security wants detailed logs, privacy wants almost none. Invock needed enough evidence to prove what happened without becoming another database full of prompts, personal content, and tool arguments. That pushed me toward canonical hashes, compact metadata, local storage, and bounded retention.
Testing things that should never work. Normal tests check that valid input produces expected output. Agent security means proving that invalid, stale, concurrent, replayed, or malicious input produces no side effect at all, which is a much stranger thing to write tests for and also the most important part of the project.
Accomplishments that I'm proud of
Invock ended up being a real runtime security boundary and not a prompt wrapper with a logger attached: ActionEnvelope normalization, deterministic YAML policy, default-deny, taint matching, single-use approvals, replay resistance, Ed25519 receipts, SQLite control plane, stdio interception, guarded Streamable HTTP, protocol-era enforcement, schema-drift quarantine, loopback-only dashboard and API, CLI workflows, Docker containment, privacy onboarding, and deterministic certification.
Tests: 122 / 122 passed
Arena: PASS
Mutation suite: 3 / 3 passed
Requirements: INV-41 through INV-85 passed
Docker containment: PASS
Repeated certification: deterministic
Final verdict: READY
The result I care about most is quieter than the numbers: useful agent workflows do not require granting the model unrestricted authority. MCP's flexibility stays intact while authorization, approval, identity, replay protection, and audit integrity move into deterministic software.
What I learned
Prompts are not a security boundary. The model reading your rules is the same model reading attacker-controlled context. Anything enforced only in natural language can be misread, overridden, or argued out of.
Normalization is a security primitive. Canonicalization looked like plumbing at the start. It turned out to be the foundation, because without a stable action identity you cannot bind policy, approvals, replay protection, execution, or receipts to anything. Same request, same identity. Different request, different identity. Everything depends on that.
Approval authorizes bytes, not intentions. No user can safely approve an abstract statement like "let the agent deploy." Approval has to name the operation, the arguments, the resource, the context, and the window, and any change after the fact has to void it.
Signed evidence beats logs. A log is your application's claim about itself. A signed receipt lets someone who doesn't trust you detect tampering and confirm the action really went through the control plane. That gap matters a lot once agents span multiple processes.
Compatibility features become vulnerabilities. Protocol fallback, ignored fields, permissive parsing, and automatic schema adaptation are conveniences everywhere else in software. At an authorization boundary they're bypasses. Compatibility has to be explicit, versioned, and tested.
Zero-data-retention is a design constraint, not a cleanup step. You can't get there by deleting logs at the end. It changes what enters the control plane, how requests normalize, what persists, how receipts are built, how you debug, how long state lives, and what can leave the local boundary.
Adversarial certification tells you more than a demo. A demo proves one path works. Mutation testing, replay scenarios, concurrency, malformed input, protocol drift, containment checks, and deterministic reruns prove the paths that shouldn't work still don't.
What's next for Invock
Turning a certified hackathon control plane into something teams can actually deploy.
Enterprise policy management: org-wide distribution, inheritance, environment-specific rules, linting, simulation, policy-diff review, signed bundles, rollback, and role-based approval authority.
Stronger identity: SSO, workload identity, hardware-backed keys, KMS/HSM signing, multi-party and threshold approval, tenant-isolated control planes, and short-lived capability tokens.
More ecosystems: REST and OpenAPI tools, shell execution, CI/CD, database clients, cloud infra, browser automation, agent-to-agent protocols, and custom function-calling runtimes. The ActionEnvelope was built transport-independent on purpose, so the same policy and approval concepts port over.
Formal analysis: property-based testing, stateful fuzzing, concurrency model checking, formal approval-state specs, policy conflict analysis, receipt-chain verification, differential testing across protocol versions, continuous adversarial certification.
Observability without plaintext: HMAC-correlated identifiers, local-only forensic bundles, redacted event streams, retention-controlled metadata, customer-owned keys, exportable receipts, SIEM-compatible events.
SDKs: TypeScript, Python, MCP host middleware, agent-framework adapters, CI policy checks, local dev proxies, and test fixtures for protected calls.
Progressive autonomy. This is the real goal. I'm not trying to stop agents from acting, I'm trying to make acting defensible:
Observe
→ Propose
→ Require approval
→ Execute constrained actions
→ Automate narrow proven workflows
Every step up that ladder gets backed by policy, bounded capability, replay protection, cryptographic evidence, and certification you can rerun. Invock wants to be the runtime trust layer for agentic software, the thing that lets agents move fast without anyone having to blindly trust every action the model generates.
Built With
- agents
- claude
- cli
- codex
- cryptography
- dashboard
- docker
- ed25519
- hmac
- jsonrpc
- mcp
- node.js
- pnpm
- policy
- privacy
- rest
- sandbox
- security
- sqlite
- telemetry
- typescript
- vitest
- websockets
- yaml
- zdr
Log in or sign up for Devpost to join the conversation.