# Security policy — `@sentinel-ai-solutions/agent-kit`

## The principle

**Business agents must never hold credentials for personal financial
accounts. Personal agents must never hold credentials for business
systems.**

This is non-negotiable. It is the line that lets `agent-kit` be safely
consumed by both BW Advisory's Command Centre (a consulting back-office)
and FrankiAI (a family wealth portal that touches mortgage, super, and
banking data). The two products share infrastructure on purpose — they
must never share credentials.

The motivating incident: an early version of Archer had a Wise API key
available to its tool surface for "convenience." Wise is a personal
financial account. The credential was removed and the rule was codified
in `Polish Batch 2`. `agent-kit` v0.3 carries that rule forward as a
runtime guardrail so the next consumer cannot re-introduce the same
class of mistake.

## How the kit enforces it

`@sentinel-ai-solutions/agent-kit/tools` ships a `ToolCredentialScope` type with
three values:

- `business` — tools that read or write business-side systems (HubSpot,
  Command Centre database, Brad's BW Advisory Gmail, business invoices).
- `personal` — tools that read or write personal financial systems
  (FrankiAI database, personal banking, super, mortgage providers,
  personal email).
- `public` — tools that touch no credentialed system at all (currency
  conversion via Frankfurter, public weather APIs, public web search).
  These register cleanly on any agent.

Every `ToolDefinition` declares its `scope`. Every `ToolRegistry` is
constructed with an `agentScope`. At registration time, the registry
throws `ToolScopeMismatchError` if:

- a `personal`-scope tool tries to register on a `business`-scope
  registry, or
- a `business`-scope tool tries to register on a `personal`-scope
  registry.

The check is in `ToolRegistry.register()` — see `src/tools/registry.ts`.
It is a single `if`, but it is the codified version of "Wise API key
should not be on the business agent." Fail loud, fail early — at
boot-time, before any Claude conversation begins.

## Why this is at registration time, not runtime

Catching the mistake at runtime means a tool is already wired into the
agent's tool surface — Claude has already seen it as available — by the
time the dispatcher refuses to call it. That is too late. The boundary
must be drawn before Claude is told the tool exists.

Registration-time enforcement also makes the mistake impossible to ship
by accident. A typo (`scope: "personal"` on a business tool) crashes
the agent at boot, which CI catches, which prevents the binary from
deploying.

## What this does not protect

- **Credentials inside a single scope.** If your `business`-scope agent
  has both `send_invoice` and `delete_all_invoices` registered, the
  scope check will not stop Claude from calling the destructive one.
  Within a scope, granularity is the consumer's responsibility —
  consider approval flows (`@sentinel-ai-solutions/agent-kit/approval`, F0.6) for
  destructive actions.
- **Prompt-injected tool calls within scope.** A hostile prompt cannot
  invoke a tool outside the agent's scope (the tool isn't in the
  registry), but it can request any in-scope tool the registry holds.
  Same answer as above: scope is the coarse guardrail; approval flows
  are the fine guardrail.
- **Tools that intentionally cross domains.** If a tool needs to read
  from both business and personal systems (e.g. a tax-deductibility
  synthesiser), it should not live in either agent. Build it as a
  separate process with its own scope and a deliberate API surface.

## Reporting vulnerabilities

Email `brad@bwadvisorysolutions.com.au`. Do not file public issues for
security-sensitive reports.
