# Claude Code message filter — a `UserPromptSubmit` hook prototype

The same classifier as the Codex bundle, wired into **Claude Code**'s
`UserPromptSubmit` hook. When you submit a message as a prompt, the hook
classifies it as `fraud`, `political`, or `neutral`, and (optionally) blocks the
ones you choose.

> Claude Code `UserPromptSubmit`: a command hook receives the submitted prompt
> as JSON on STDIN; `exit 2` blocks the prompt and the hook's STDERR is shown as
> the reason. Docs: https://docs.claude.com/en/docs/claude-code/hooks

## Files

- `classify_message.py` — the classifier (offline heuristics by default)
- `classifier-prompt.txt` — the LLM classifier prompt (optional LLM mode)
- `settings.json` — the Claude Code hook configuration

## Install (project-local)

```
mkdir -p .claude/hooks
cp classify_message.py .claude/hooks/classify_message.py
cp classifier-prompt.txt .claude/hooks/classifier-prompt.txt
chmod +x .claude/hooks/classify_message.py
```

Then merge the `hooks` block from `settings.json` into your project's
`.claude/settings.json` (or `~/.claude/settings.json` for all projects). You can
also use `/hooks` inside Claude Code to review what is wired up.

## Test it

The hook command in `settings.json` blocks `fraud,political` by default and
uses `$CLAUDE_PROJECT_DIR`, so it works from any working directory and in the
IDE/desktop apps, which don't inherit `export`s from your shell. To change what
is blocked, edit the `NETSI_BLOCK` default in the command (or set the variable
in the environment Claude Code is launched from). Hook changes apply from a new
session, or after reviewing them in `/hooks`.

Submit each message as a prompt in Claude Code:

| Test message | Expected category | With NETSI_BLOCK=fraud,political |
|---|---|---|
| "Are we still on for lunch at 12?" | neutral | allowed |
| "Vote YES on Tuesday — re-elect the mayor!" | political | blocked |
| "Your bank account is locked. Verify now: http://bit.ly/x" | fraud | blocked |
| "Skal vi spise frokost kl. 12?" | neutral | allowed |
| "Stem JA på torsdag så borgmesteren bliver genvalgt" | political | blocked |
| "Din konto er spærret. Bekræft din identitet med MitID her" | fraud | blocked |

The heuristics cover English and Danish. Inside a hook the script prints
nothing when a message is allowed (on `exit 0`, STDOUT would be added to the
agent's context); it only writes the block reason to STDERR. Run by hand, it
prints the category as shown below.

Run it directly too:

```
echo '{"prompt":"Your account is locked, verify now http://bit.ly/x"}' | python3 classify_message.py
# -> fraud   (exit code 2 if fraud is in NETSI_BLOCK)
```

## Optional: use a real LLM classifier

```
pip install openai
export NETSI_USE_OPENAI=1
export OPENAI_API_KEY=sk-...
```

## Important boundary — PROPOSED ARCHITECTURE

A `UserPromptSubmit` hook fires on the prompt **you** submit inside a Claude Code
session. It does not read your phone's messages. A real SMS/iMessage filter needs
a separate, permissioned OS/messaging integration that forwards incoming messages
into this classifier. That integration is not provided here.

## Sources

- Matthew Berman's LinkedIn question: https://lnkd.in/p/e3Qggkjy
- Claude Code hooks (official): https://docs.claude.com/en/docs/claude-code/hooks
