CAIN-42 CAIN Studio

Recipe · TypeScript / JavaScript

TypeScript / JavaScript: guard http requests with CAIN

Step by step, for an agent that fetches URLs: install, wrap the tool, see the live decision, write and test a rule, handle approvals.

Last reviewed 2026-10-03

1 Get a key

No key yet? Create a free account (every new account starts with a 7-day trial), then copy the key from the console. Requests go from your browser straight to this site.

Give each agent its own key in production (console → Agent keys): decisions, trust history and revocation are then per agent.

2 Install

Copy cain.ts from the TypeScript page into your project and set CAIN_API_KEY.

3 Put CAIN in front of any async function

There is no npm package yet: copy the helper from the TypeScript page (Node 18+, Deno, Bun, edge runtimes, no dependencies). guard() throws unless the verdict is ALLOWED and not blocked; a timeout or error is a refusal.

import { guard } from "./cain";   // the ~30-line helper from /docs/sdk-typescript

export const httpGet = guard('http_get', async (args: { url: string }) => {
  // your existing code, unchanged
}, 'http-agent');

// throws CainRefused unless CAIN answers ALLOWED for these exact arguments

On a refusal the agent gets a CainRefused error carrying the decision. The tool body never runs: not on a refusal, not on a hold, not when CAIN cannot be reached.

4 See what CAIN decides for this exact call

This is the request the integration sends when the model calls http_get with these arguments. record: false evaluates it without writing an evidence record. A new agent has no trust history, so expect REQUIRE_APPROVAL at first; destinations outside your egress allowlist are refused by the egress stage.

5 Write the rule, and test it before you save it

The cloud metadata address, the classic server-side request forgery target, is refused. For everything else, an egress allowlist of the hosts the agent may reach is the stronger control.

The panel sends the draft rule to the rule tester: it says which rule would decide the call and saves nothing. Swap in the safe arguments below to see it pass through. matches is a case-insensitive glob (*, ?), not a regular expression; conditions on one rule must all hold; the first matching rule by priority wins.

Safe arguments: {"url": "https://api.github.com/repos/acme/app"}

When it does what you want, save it from the console or with POST /fabric/tool-rules:

curl -sS -X POST https://cainstudio.online/fabric/tool-rules \
  -H "X-API-Key: $CAIN_API_KEY" -H 'Content-Type: application/json' \
  -d '{"name": "http-guard", "effect": "deny", "priority": 10, "match_path": "/tools/http_get", "conditions": [{"field": "url", "op": "contains", "value": "169.254.169.254"}]}'

6 Handle the calls that wait for a person

A held call is not executed. It appears in the approval queue with its exact arguments; an approval lets that one call run once. Adapters can wait instead of returning: wait_for_approval=120.

GET /fabric/approvals

7 Check it before you ship

cainstudio redteam --agent http-agent   # dry-runs dangerous calls; exit 2 if any would run
cainstudio decisions                 # what was decided, and why
cainstudio explain <decision_id>

Every recorded decision is signed; Cain().verify(decision) checks its quorum certificate on your machine (Python SDK).

More

Try CAIN-42 on your own agents

Create a free account and every new account starts with a 7-day trial of the full platform. Or try the sandbox first, with no account at all.

Create a free account →  ·  Try the sandbox  ·  See the whole ecosystem