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.1.0 — first public release. 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 |
✅ (SQLite watcher) |
❌ audit-only — no pre-send middleware exists |
| VSCode Copilot Chat |
✅ (Chat Participant) |
❌ 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.
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) → aiwatch: Install Claude Code hooks. This writes signed stub scripts to
~/.claude/hooks/ and registers them in ~/.claude/settings.json.
- Open the Policy tab and write your
policy.yaml, or install a community rule pack via the panel.
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).
- 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.