@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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +5 -1
- package/commands/setup.md +123 -35
- package/hooks/hooks.json +11 -0
- package/package.json +7 -6
- package/scripts/apply-suppressions.js +2178 -593
- package/scripts/backfill.js +3815 -429
- package/scripts/filescan.js +2379 -336
- package/scripts/firstrun.js +1748 -181
- package/scripts/intro.js +520 -48
- package/scripts/message-display.js +30054 -0
- package/scripts/onboard.js +2007 -208
- package/scripts/post-tool-use.js +3721 -388
- package/scripts/pre-tool-use.js +3900 -414
- package/scripts/query.js +1752 -181
- package/scripts/reconcile.js +3321 -372
- package/scripts/remediate.js +3925 -582
- package/scripts/scan-worker.js +18006 -0
- package/scripts/session-start.js +1879 -273
- package/scripts/start-light.js +540 -68
- package/scripts/statusline.js +1747 -180
- package/scripts/stop.js +504 -75
- package/scripts/user-prompt-submit.js +3695 -408
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aka",
|
|
3
|
-
"version": "0.9.
|
|
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,
|
|
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).
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
that window
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
**
|
|
136
|
-
|
|
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
|
|
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** (
|
|
142
|
-
|
|
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.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
|
262
|
-
|
|
263
|
-
secrets detected in that context window are **masked**
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
|
316
|
-
that window masked)
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akasecurity/ai-tc-claude-code",
|
|
3
|
-
"version": "0.9.
|
|
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/
|
|
31
|
+
"@akasecurity/eslint-config": "0.8.0",
|
|
33
32
|
"@akasecurity/plugin-sdk": "0.8.0",
|
|
34
|
-
"@akasecurity/
|
|
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
|
}
|