@akasecurity/ai-tc-claude-code 0.9.3 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aka",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "description": "AI Traffic Control — inspect and govern AI prompts in Claude Code. Detection runs locally; events are recorded to a local SQLite store on your machine.",
5
5
  "homepage": "https://github.com/akasecurity/ai-tc",
6
6
  "author": { "name": "AKA Security" }
package/README.md CHANGED
@@ -9,7 +9,9 @@ The Claude Code plugin for **[AI Traffic Control](https://github.com/akasecurity
9
9
 
10
10
  Detection runs entirely on your machine. There's no account and no backend — nothing is sent anywhere to be scanned.[^egress]
11
11
 
12
- [^egress]: Live detection and enforcement run locally. The one exception is the **opt-in** `/aka:setup` calibration below: to rate what an initial history scan finds, its judge step sends those findings to the model API through the `claude` CLI — the same provider your Claude session already uses. For each finding that means the raw (unmasked) value including any secret, plus roughly 120 characters of the surrounding transcript text on either side (any other secrets detected in that window are masked). It runs only after you explicitly opt in, and it keeps those values out of your local Claude transcript but a copy of them does leave the machine. Withdrawing the grant later stops future scans; it cannot recall what was already sent.
12
+ [^egress]: Live detection and enforcement run locally. The one exception is the **opt-in** `/aka:setup` calibration below: to rate what an initial history scan finds, its judge step sends those findings to the model API through the `claude` CLI — the same provider your Claude session already uses, reached with your own credentials. For each finding that means the raw (unmasked) value including any secret, roughly 120 characters of the surrounding transcript text on either side (re-scanned before it goes, so every secret the rules detect in that window is masked, including the finding's own value where it appears there), the finding's rule, category, severity, masked value and confidence, and a sequential counter the model echoes back so its verdict can be matched to the finding. The source file's path is not sent. It takes **two** separate opt-ins — one to read your history at all, and a second, distinct grant to send what was found — and without the second the judge does not run. It keeps those values out of your local Claude transcript, but a copy of them does leave the machine. Withdrawing either grant stops future scans; it cannot recall what was already sent.
13
+
14
+ The local store keeps your prompts and tool calls verbatim apart from the spans a rule masks, and file permissions — not encryption — are all that protect it. See [Data at rest](https://github.com/akasecurity/ai-tc/blob/main/SECURITY.md#data-at-rest) for which files it spans and what holds on Windows.
13
15
 
14
16
  ## Install
15
17
 
@@ -50,6 +52,8 @@ The plugin installs Claude Code hooks that run locally with no `node_modules`, a
50
52
 
51
53
  It also adds slash commands for reports and setup (`/aka:health`, `/aka:findings`, `/aka:dashboard`, and more).
52
54
 
55
+ The plugin works standalone, and governs what a session carries: rule packs, findings, policy, and an audit trail. To harden the harness itself — safe-default permissions, structural command guards, and credential deny rules — pair it with [claude-tools](https://github.com/akasecurity/claude-tools). They compose: claude-tools hardens the harness, `ai-tc` governs the traffic.
56
+
53
57
  ## Docs
54
58
 
55
59
  Full documentation and the built-in detection catalog live at **[akasecurity.github.io/ai-tc-docs](https://akasecurity.github.io/ai-tc-docs/)**.
package/commands/setup.md CHANGED
@@ -17,16 +17,26 @@ falls back to a conservative severity-derived floor instead of guessing.
17
17
 
18
18
  The false-positive/severity judgment needs the raw (unmasked) findings to rate
19
19
  them accurately, so it **sends them to the model API** through separate `claude`
20
- CLI subprocesses (a large history is judged in several batches). Two things
21
- cross for each finding: its **raw value**, and about **120 characters of the
22
- surrounding transcript text** on either side of it (any other secrets detected in
23
- that window are masked). Those subprocesses ask the `claude` CLI to write
24
- no transcript, so the raw values do not enter this conversation or your scannable
25
- history but a copy of them **does leave the machine**, sent to the model
26
- provider like any other Claude prompt. You act only on the raw-free plan the
27
- subprocesses print back. The scan that produces those values is offered for
28
- explicit consent in step 1, and that consent must state the model-API egress
29
- plainly before it is given.
20
+ CLI subprocesses (a large history is judged in several batches). Per finding it
21
+ sends the **raw value**; about **120 characters of the surrounding transcript
22
+ text** on either side of it, re-scanned first so every secret AKA's rules detect
23
+ in that window is masked including the finding's own value where it appears
24
+ there, which leaves the raw value itself as the only raw text that crosses; and
25
+ the finding's non-sensitive labels (rule id, category, severity, the masked
26
+ value, the confidence score, and a counter the model echoes back). The source
27
+ transcript's **path never crosses**. Those subprocesses ask the `claude` CLI to
28
+ write no transcript, so the raw values do not enter this conversation or your
29
+ scannable history but a copy of them **does leave the machine**, sent to the
30
+ model provider like any other Claude prompt. You act only on the raw-free plan
31
+ the subprocesses print back.
32
+
33
+ That send takes **two separate consents**, collected at two different points and
34
+ neither implying the other: the **historical-read** grant in step 1 (may AKA
35
+ read past transcripts at all) and the **model-judge** grant in step 3 (may what
36
+ it found be sent to the model API). Each is stated plainly before its own
37
+ picker, and the judge refuses to run without the step-3 grant. The vault grant
38
+ in step 1b is a third, separate question about **local** custody — it authorizes
39
+ no send, and nothing about it changes what leaves the machine.
30
40
 
31
41
  Follow the steps below **in order**. Nothing is written to the policy store
32
42
  until step 5 (or a floor fallback in step 3 if the calibration can't complete).
@@ -132,22 +142,30 @@ interactive picker. The plugin can't draw its own selectable UI (it can't
132
142
  capture keystrokes), so do **not** print a fake option list or ask the user to
133
143
  "reply with a number"; let the picker collect the answer.
134
144
 
135
- **Disclose the model-API egress plainly before you show the pickerthis is what
136
- the user is consenting to, so it must be visible before they choose.** State it in
145
+ **This picker grants the historical read, not the model-API egress disclose the
146
+ egress here anyway, before you show it.** The read exists to feed the judgment, so
147
+ the user has to see the whole shape before granting any part of it. State it in
137
148
  your own words, without softening it: if they say yes, AKA scans the last 30 days
138
- of Claude history, and to rate what it finds it **sends the raw, unmasked values
149
+ of Claude history, and once they also give the separate model-judge grant asked
150
+ in step 3 — to rate what it finds it **sends the raw, unmasked values —
139
151
  including any secrets — to the model API through the `claude` CLI**. Name what
140
152
  travels with each one: **its raw value, and about 120 characters of the
141
- surrounding transcript text on either side** (any other secrets detected in that
142
- window are masked). That is real network egress to the model provider (the same one your
153
+ surrounding transcript text on either side** (that window is re-scanned first, so
154
+ every secret AKA's rules detect in it is masked), plus the finding's non-sensitive
155
+ labels — rule, category, severity, masked value, confidence, and a counter the
156
+ model echoes back. The source file's path is **not** sent. That is real network
157
+ egress to the model provider (the same one your
143
158
  Claude session already uses), not a purely local review. **A copy of each value
144
159
  leaves the machine.** The transcripts those values came from stay on disk
145
160
  untouched — nothing here removes them; that is the separate redaction step in
146
161
  step 6. The values are kept out of your local Claude transcript, and the findings
147
- are also recorded, masked, to the local store. The grant is revocable from the
148
- dashboard's **Settings Historical access**, which stops future scans it
149
- cannot recall anything already sent. Do not present the picker until you have
150
- said this.
162
+ are also recorded, masked, to the local store. Say plainly that answering yes here
163
+ sends nothing on its own the send is a second, separate question in step 3, and
164
+ declining it there stops the send while leaving this read grant in place. Both
165
+ grants are revocable from the dashboard — the read under **Settings → Historical
166
+ access**, the send under **Settings → Model-judge consent** — and revoking either
167
+ stops future runs, but it cannot recall anything already sent. Do not present the
168
+ picker until you have said this.
151
169
 
152
170
  **Want me to look over what Claude's been up to?** — "I'll scan Claude's recent work — transcripts, temp files, agent memory — and send what I find to the model to rate it, so I can tune what I bring you next."
153
171
 
@@ -157,17 +175,80 @@ Offer exactly two options:
157
175
  - **Not now** — "start light and I'll learn as we go"
158
176
 
159
177
  Choosing **Yes, take a look** records the same historical-review consent the wizard has
160
- always recorded — the identical scope (which includes the model-API judgment disclosed
161
- above), the one-time grant, and the same revocation semantics so the simpler question
162
- broadens nothing about what AKA may access. Those granular scope and revocation details
163
- stay inspectable on request and in the dashboard, under **Settings Historical access**,
164
- whose own copy repeats the model-API disclosure.
178
+ always recorded — the identical scope, the one-time grant, and the same revocation
179
+ semantics so the simpler question broadens nothing about what AKA may access. That
180
+ scope is **reading** local surfaces only; it does not authorize sending anything to the
181
+ model API, which is the distinct grant step 3 collects. Those granular scope and
182
+ revocation details stay inspectable on request and in the dashboard, under
183
+ **Settings → Historical access**, whose own copy points at the separate
184
+ **Model-judge consent** control for the egress.
185
+
186
+ ## 1b. Offer the reversible vault
187
+
188
+ Ask this right after the scan question, on **both** answers to it — vaulting
189
+ governs the live session either way, and asking before the backfill means a
190
+ "yes" here lets that same backfill rewrite scanned history into recoverable
191
+ pointers in one pass instead of leaving raw values sitting in transcripts.
192
+ Use **AskUserQuestion**, as always — never a printed option list.
193
+
194
+ **Disclose what changes before you show the picker — this is a custody change,
195
+ and the user must see it before choosing.** State it plainly, in your own words,
196
+ covering every point below; do not soften or skip any of them:
197
+
198
+ - **Today, a detected secret is destroyed.** When a redact policy fires, the
199
+ value is replaced with `[REDACTED:…]` and is gone. With the vault, it is
200
+ replaced with a pointer like `[[aka:secret:…]]` instead, and an **encrypted,
201
+ recoverable copy is kept on this machine** — AES-256-GCM ciphertext in
202
+ `~/.aka/data`, key material in `~/.aka/keys` (keep that directory out of
203
+ backup and sync tools; an OS-keychain option exists in settings). Nothing is
204
+ sent anywhere — the vault is entirely local.
205
+ - **The model only ever sees pointers.** It can keep working with them —
206
+ writing them into files, referring to them — without ever reading the value.
207
+ You see the real values in the dashboard's **Vault** page or with
208
+ `aka vault show <pointer>`; in the terminal, pointers in Claude's replies
209
+ render as masked badges by default (a full inline-reveal mode exists in
210
+ Settings, with its own risk disclosure there).
211
+ - **Pointers are correlatable, and that is visible.** The same value always
212
+ gets the same pointer, so pointers written into files, commits, and
213
+ transcripts reveal **where** a secret is used and **which places share the
214
+ same secret** to anyone who can read those artifacts. The Vault page shows
215
+ you that same map (every sighting of every pointer) so the correlation is
216
+ never something only an outsider can see.
217
+ - **What it unlocks:** a blocked prompt comes back with a paste-ready rewrite
218
+ (pointers in place of the secret) instead of a dead end; scanned history is
219
+ rewritten to recoverable pointers instead of staying raw on disk; and when
220
+ the agent genuinely needs a real value, you can grant it per-value with
221
+ `aka exception approve … --reveal-to-model` — audited every time.
222
+ - **Revoking stops future vaulting; it erases nothing.** Values already stored
223
+ stay recoverable to you until you **purge** the vault (Vault page — the purge
224
+ is the eraser, and it makes every pointer everywhere permanently
225
+ unresolvable). Both controls live in the dashboard.
226
+
227
+ **Keep detected secrets recoverable?** — "Replace detected secrets with pointers and keep an encrypted copy on this machine, or keep destroying them?"
228
+
229
+ Offer exactly two options:
230
+
231
+ - **Vault them** — "reversible: pointers for the model, real values for me, everything local"
232
+ - **No — keep destroying them** — "irreversible redaction, exactly as before"
233
+
234
+ Neither answer is nudged; the honest trade is recoverability against holding a
235
+ recoverable copy at all.
165
236
 
166
237
  ## 2. Save the answer, then branch
167
238
 
168
- Branch on the answer from step 1. On the **Yes, take a look** path the onboarding
169
- writer runs, and it must run **before** the backfill (step 3), because the
170
- backfill script reads `historicalAccess` from the saved settings to decide
239
+ **First, record the vault answer from step 1b on both branches.** A
240
+ **Vault them** answer runs the writer below; a **No** answer records nothing
241
+ (absence IS the un-consented state there is no "declined" marker to write).
242
+ This must also run **before** the backfill, so a granted vault lets the same
243
+ sweep rewrite scanned history into recoverable pointers:
244
+
245
+ ```bash
246
+ node "${CLAUDE_PLUGIN_ROOT}/scripts/onboard.js" --vault-consent grant
247
+ ```
248
+
249
+ Then branch on the answer from step 1. On the **Yes, take a look** path the
250
+ onboarding writer runs, and it must run **before** the backfill (step 3), because
251
+ the backfill script reads `historicalAccess` from the saved settings to decide
171
252
  whether it's allowed to run. Omitting `--policy` is deliberate — the old global
172
253
  redact/warn toggle no longer drives enforcement (posture is per-category now);
173
254
  its field is kept for backward compatibility but this wizard doesn't ask about
@@ -258,12 +339,18 @@ leaves the machine, then use **AskUserQuestion** — the built-in picker (never
258
339
  printed numbered list) — to collect the answer.
259
340
 
260
341
  Say plainly, before the picker: to sort real leaks from routine noise, AKA sends
261
- each finding's `rawMatch` (the raw detected value) plus the surrounding context
262
- window to the model API via `claude`. The **`filePath` is not sent**, and any
263
- secrets detected in that context window are **masked** before it goes — so the
264
- finding's own value is the only raw value that leaves. Nothing else about the
265
- finding or the file crosses. Note that masking covers the secrets AKA's rules
266
- detect; ordinary text in that window travels as-is.
342
+ each finding's `rawMatch` (the raw detected value) plus about 120 characters of
343
+ the surrounding transcript text on either side of it to the model API via `claude`.
344
+ The **`filePath` is not sent**, and any secrets detected in that context window are **masked**
345
+ before it goes including the finding's own value where it appears there so
346
+ `rawMatch` is the only raw value that leaves. Riding alongside it are the
347
+ finding's non-sensitive labels: rule id, category, severity, the masked value, the
348
+ confidence score, and a sequential counter the model echoes back so a verdict can
349
+ be matched to the finding it belongs to. Nothing else about the finding or the
350
+ source file crosses — no path, and no fingerprint of the value. The subprocess
351
+ signs in as you, exactly like any other Claude call, so the request is attributed
352
+ to your own account. Note that masking covers the secrets AKA's rules detect;
353
+ ordinary text in that window travels as-is.
267
354
 
268
355
  **Send findings to the model to sort real leaks from noise?** — "I'll send each
269
356
  detected value, plus a bit of surrounding context with any secrets in it masked,
@@ -312,8 +399,9 @@ The backfill sweeps prior Claude Code transcripts (last 30 days, all projects)
312
399
  and streams one masked-plus-raw triage hit per line; masked findings are
313
400
  recorded to the local store as a side effect. The adapter runs the
314
401
  false-positive/severity **judgment in separate `claude` subprocesses that send the
315
- raw hits — each one's value and its surrounding transcript text (other secrets in
316
- that window masked) to the model API** (a large history is split into several batches; each asks
402
+ raw hits — each one's value, its surrounding transcript text (every secret AKA
403
+ detects in that window masked), and the finding's non-sensitive labels to the
404
+ model API** (a large history is split into several batches; each asks
317
405
  the CLI to write no local transcript), then prints back a **raw-free plan** you
318
406
  can safely show the user:
319
407
  the calibrated-result card (the real-count headline and the recommended posture),
package/hooks/hooks.json CHANGED
@@ -46,6 +46,17 @@
46
46
  ]
47
47
  }
48
48
  ],
49
+ "MessageDisplay": [
50
+ {
51
+ "hooks": [
52
+ {
53
+ "type": "command",
54
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/message-display.js\"",
55
+ "timeout": 10
56
+ }
57
+ ]
58
+ }
59
+ ],
49
60
  "Stop": [
50
61
  {
51
62
  "hooks": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akasecurity/ai-tc-claude-code",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "description": "AI Traffic Control — inspect and govern AI prompts in Claude Code",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -27,17 +27,18 @@
27
27
  "typescript": "^5.8.0",
28
28
  "vitest": "^4.1.10",
29
29
  "zod": "^4.0.0",
30
- "@akasecurity/eslint-config": "0.8.0",
31
30
  "@akasecurity/persistence": "0.8.0",
32
- "@akasecurity/scanner": "0.8.0",
31
+ "@akasecurity/eslint-config": "0.8.0",
33
32
  "@akasecurity/plugin-sdk": "0.8.0",
34
- "@akasecurity/schema": "0.8.0",
35
- "@akasecurity/plugin-runtime": "0.8.0"
33
+ "@akasecurity/scanner": "0.8.0",
34
+ "@akasecurity/plugin-runtime": "0.8.0",
35
+ "@akasecurity/setup-wizard": "0.8.0",
36
+ "@akasecurity/schema": "0.8.0"
36
37
  },
37
38
  "scripts": {
38
39
  "build": "tsup",
39
40
  "dev": "tsup --watch",
40
- "lint": "eslint src test eval",
41
+ "lint": "eslint src test eval *.config.*",
41
42
  "test": "vitest run",
42
43
  "typecheck": "tsc --noEmit"
43
44
  }