aiwatch
Local-first visibility and policy guardrails for AI coding assistants.
aiwatch records every chat your VSCode/Cursor/Claude Code AI sends, applies your policy rules (regex, file-glob, AST, optional local LLM classifier), and blocks matching prompts to Claude Code before they leave your machine. All data stays on disk, encrypted with a key in your OS keychain (or a passphrase you choose).
Status: v0.3.0. Single architecture-independent .vsix: no native
binaries, so the same package runs on macOS, Linux and Windows, x64 and arm64,
against any Electron ABI VSCode ships.
Hosts
| Host |
Visibility |
Enforcement before send |
| Claude Code |
✅ |
✅ block prompts, block or rewrite tool calls, via the official hooks |
| Cursor |
✅ (polls its SQLite chat store) |
❌ audit-only — no pre-send middleware exists |
| VSCode Copilot Chat |
⚠️ only @aiwatch prompts |
❌ audit-only — no pre-send API exists |
What enforcement means, precisely. Claude Code exposes two hooks aiwatch
uses, and they have different powers
(docs):
On your prompt (UserPromptSubmit) — the hook can allow or refuse, but the API
has no way to rewrite prompt text:
block → the prompt is refused and erased before it is sent. ✅
redact / encrypt-placeholder → also blocked, with a message naming the
rule. Letting the prompt through would send exactly the secret the rule
redacts, so aiwatch fails closed and asks you to edit the prompt. The redacted
version is shown in the panel.
warn-and-allow → sent, with the warning attached as context.
log-only → sent, recorded silently.
On a tool call Claude makes (PreToolUse) — the hook can replace the tool's
arguments outright, so here redaction really redacts:
block → the call never runs. ✅
redact / encrypt-placeholder → the matching span is rewritten in place
and the call proceeds with the secret removed; you are told what was changed. ✅
warn-and-allow → runs, with the warning attached as context.
This covers the calls that actually move data off the machine — WebFetch,
WebSearch, and Bash running curl or git push. A key in a curl -H "Authorization: …" is stripped rather than merely refused.
On Cursor and Copilot every action degrades to a recorded hit plus a toast: the
prompt has already left by the time the adapter sees it.
What each host actually captures
- Claude Code — everything, once you install the hooks (the panel offers a
button, and aiwatch asks once on startup). Without them nothing is captured
and nothing is blocked.
- Cursor — aiwatch re-reads Cursor's SQLite chat store on startup and every
60 seconds, because Cursor exposes no API and emits no events. Chats appear
shortly after Cursor writes them, not instantly. If Cursor changes its storage
layout, capture stops and the panel says so rather than failing quietly.
- Copilot Chat — only prompts you address to
@aiwatch. VS Code gives
extensions no way to observe ordinary Copilot requests, so normal Copilot use
is invisible to aiwatch, and to any other extension.
Quick start
- Install from VSCode Marketplace (or build from source — see
BUILD.md).
- Open the command palette → aiwatch: Open Panel.
- Run the onboarding wizard — pick how the encryption key is stored.
- (Claude Code only) press Install Claude Code hooks in the panel, or run the
command of the same name. aiwatch also offers this once on startup. This writes
signed stub scripts to
~/.claude/hooks/ and registers them in
~/.claude/settings.json, merging with any hooks you already have.
- Open the Policy tab and write your
policy.yaml, or install a community rule pack via the panel.
The panel's status strip tells you, per host, whether capture is actually
running — and if not, which button fixes it.
Example policy
version: 1
rules:
- id: no-aws-keys
detect: { regex: 'AKIA[0-9A-Z]{16}' }
action: block
severity: high
- id: redact-customers
detect: { regex: 'cust_[a-z0-9]{6,}' }
action: redact
placeholder: '[CUST]'
- id: no-billing-src
detect: { path_glob: 'src/billing/**' }
action: block
severity: high
- id: warn-emails
detect: { regex: '[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}' }
action: warn-and-allow
Save → the next prompt to Claude Code is checked against these rules.
Architecture
TL;DR:
- Single VSCode extension, no daemon. Webview UI inside the IDE.
- Storage: libsodium secretstream envelopes (XChaCha20-Poly1305) over JSON.
- Key: VS Code SecretStorage by default, passphrase (argon2id 64MiB / 3 iter) optional.
- Loopback HTTP server on
127.0.0.1:<random> with rotating 32-byte token for Claude Code hooks.
- Regex vetted by SafeRegex: patterns that can backtrack catastrophically are
rejected at load time, and every match runs under a time budget.
- Cursor's SQLite history is read through sql.js (WebAssembly, read-only copy in
memory — Cursor's own database is never opened for writing or locked), polled on
a timer with an mtime filter, and de-duplicated per turn so re-reads are free.
- Captured turns are buffered briefly and written as a batch, so recording a
prompt costs the same whether the conversation has ten turns or a thousand.
- Rule packs: declarative-only schema, SHA-256 pinned, never
eval.
Data inventory & retention
| What |
Where |
Encrypted |
Default retention |
| Chat turns + context file metadata |
~/.aiwatch/global/<workspace-hash>/sessions/ or <repo>/.aiwatch/sessions/ |
yes |
30 days |
| Search index |
index.enc beside the sessions |
yes |
rebuilt on demand |
| Audit log (policy hits, key-mode changes, hook installs) |
~/.aiwatch/audit.log.enc |
yes |
1 year |
encrypt-placeholder mappings |
per-session, beside the session file |
yes |
24 h unless pinned |
| Hook token / current port |
~/.aiwatch/hook.token, ~/.aiwatch/port (mode 0600) |
no (rotates per IDE start) |
session |
| Config (key custody mode, rule-pack pins) |
~/.aiwatch/config.json |
no (contains no secrets) |
until removed |
aiwatch: Uninstall (cleanup hooks & data) removes the Claude Code hook stubs, the
~/.claude/settings.json entries aiwatch added, and — with an explicit confirmation —
~/.aiwatch/ itself.
Security
See SECURITY.md (threat model + reporting). The short version:
- Bound to
127.0.0.1 only; constant-time token compare; DNS-rebind defended.
- Webview has strict CSP, no inline scripts, no remote loads, DOMPurify for any markdown.
- No telemetry. No crash reporting. No phone-home. SBOM ships with each release.
Privacy
See PRIVACY.md. Nothing leaves your machine by default. The only network calls are:
- Rule-pack updates (only with explicit URL + SHA-256 pin).
- Ollama (only if you enable the LLM classifier; hardcoded to localhost; remote requires explicit toggle).
Development
npm ci
npm run build # builds dist/extension.js + dist/webview/ + dist/sql-wasm.wasm
npm run verify # typecheck + tests (incl. security corpora) + prod npm audit
npm run package # aiwatch-<version>.vsix
npm run release:artifacts runs the gates and produces the .vsix, the CycloneDX
SBOM, and RELEASE-MANIFEST.txt (source→bundle SHA mapping — see BUILD.md).
Then open in VSCode, press F5 to launch an Extension Development Host.
License
MIT. See LICENSE.