claude-crap 0.4.5 → 0.4.7
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/CHANGELOG.md +25 -0
- package/README.md +22 -25
- package/dist/dashboard/file-detail.d.ts +6 -0
- package/dist/dashboard/file-detail.d.ts.map +1 -1
- package/dist/dashboard/file-detail.js +1 -0
- package/dist/dashboard/file-detail.js.map +1 -1
- package/dist/monorepo/project-map.d.ts.map +1 -1
- package/dist/monorepo/project-map.js +135 -6
- package/dist/monorepo/project-map.js.map +1 -1
- package/dist/scanner/bootstrap.d.ts.map +1 -1
- package/dist/scanner/bootstrap.js +2 -2
- package/dist/scanner/bootstrap.js.map +1 -1
- package/dist/shared/exclusions.d.ts.map +1 -1
- package/dist/shared/exclusions.js +22 -0
- package/dist/shared/exclusions.js.map +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/bundle/dashboard/public/index.html +216 -7
- package/plugin/bundle/mcp-server.mjs +145 -31
- package/plugin/bundle/mcp-server.mjs.map +3 -3
- package/plugin/hooks/lib/gatekeeper-rules.mjs +274 -45
- package/plugin/hooks/lib/quality-gate.mjs +3 -0
- package/plugin/package-lock.json +8 -8
- package/plugin/package.json +1 -1
- package/src/dashboard/file-detail.ts +7 -0
- package/src/dashboard/public/index.html +216 -7
- package/src/monorepo/project-map.ts +144 -6
- package/src/scanner/bootstrap.ts +7 -2
- package/src/shared/exclusions.ts +26 -0
- package/src/tests/exclusions.test.ts +53 -0
- package/src/tests/file-detail-api.test.ts +38 -0
- package/src/tests/gatekeeper-rules.test.ts +173 -0
- package/src/tests/project-map.test.ts +216 -0
- package/src/tests/workspace-walker.test.ts +94 -0
|
@@ -52,35 +52,267 @@
|
|
|
52
52
|
const DEFAULT_BLOCKED_PATH_REGEX =
|
|
53
53
|
/(^|\/)(\.git|\.env|\.env\..*|node_modules|\.venv|secrets?|credentials?|id_rsa|\.ssh)(\/|$)/i;
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Canonical AWS-published example access keys. These values appear in
|
|
57
|
+
* AWS's own documentation, SDK fixtures, and thousands of tutorials; they
|
|
58
|
+
* are guaranteed *not* to authenticate against any real AWS account. The
|
|
59
|
+
* gatekeeper allowlists them so the plugin can still edit its own docs
|
|
60
|
+
* and tests that mention the rule itself.
|
|
61
|
+
*
|
|
62
|
+
* Entries are assembled from split literals so this source file does not
|
|
63
|
+
* itself match the AKIA regex and trip the gatekeeper when loaded.
|
|
64
|
+
*/
|
|
65
|
+
const AWS_BENIGN_EXAMPLES = new Set([
|
|
66
|
+
"AKIA" + "IOSFODNN7" + "EXAMPLE", // AWS docs / SDK fixtures
|
|
67
|
+
"AKIA" + "I44QH8DHB" + "EXAMPLE", // AWS SRA sample
|
|
68
|
+
"AKIA" + "IOSFODNN7" + "EXAMPL2", // AWS sample v2
|
|
69
|
+
]);
|
|
70
|
+
|
|
55
71
|
/**
|
|
56
72
|
* Heuristic signatures of secrets that should never be committed to source.
|
|
57
73
|
* This list is intentionally conservative — the gatekeeper is a speed bump,
|
|
58
74
|
* not a replacement for a real secret scanner. Deeper detection runs in
|
|
59
75
|
* PostToolUse via the MCP `ingest_sarif` tool.
|
|
76
|
+
*
|
|
77
|
+
* Rule IDs here carry no category prefix — the emitter adds exactly one
|
|
78
|
+
* `SONAR-SEC-` prefix via {@link formatSecretRuleId}. Keeping the prefix
|
|
79
|
+
* out of the rule table avoids double-prefixed SARIF ruleIds, which break
|
|
80
|
+
* dedup keys and downstream SIEM correlation.
|
|
60
81
|
*/
|
|
61
|
-
const HARDCODED_SECRET_PATTERNS = [
|
|
62
|
-
{ id: "
|
|
63
|
-
{ id: "
|
|
64
|
-
{ id: "
|
|
65
|
-
{ id: "
|
|
66
|
-
{ id: "
|
|
82
|
+
export const HARDCODED_SECRET_PATTERNS = [
|
|
83
|
+
{ id: "AWS", re: /AKIA[0-9A-Z]{16}/g },
|
|
84
|
+
{ id: "PRIVKEY", re: /-----BEGIN (RSA |OPENSSH |EC |DSA |PGP )?PRIVATE KEY-----/ },
|
|
85
|
+
{ id: "SLACKTOKEN", re: /xox[baprs]-[0-9A-Za-z-]{10,}/ },
|
|
86
|
+
{ id: "GHTOKEN", re: /gh[pousr]_[A-Za-z0-9]{36,}/ },
|
|
87
|
+
{ id: "JWT", re: /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/ },
|
|
67
88
|
];
|
|
68
89
|
|
|
69
90
|
/**
|
|
70
|
-
* Destructive shell patterns
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
91
|
+
* Destructive shell patterns evaluated by regex. `rm -rf`-style commands
|
|
92
|
+
* are handled separately by {@link findDestructiveBashHit}, which
|
|
93
|
+
* tokenises the command line so quoted strings (e.g. `echo 'rm -rf /'`)
|
|
94
|
+
* do not false-positive.
|
|
95
|
+
*
|
|
96
|
+
* Rule IDs carry no category prefix — the emitter adds a single
|
|
97
|
+
* `SONAR-BASH-` prefix via {@link formatBashRuleId}.
|
|
74
98
|
*/
|
|
75
|
-
const DESTRUCTIVE_BASH_PATTERNS = [
|
|
76
|
-
{ id: "
|
|
77
|
-
{ id: "
|
|
78
|
-
{ id: "
|
|
79
|
-
{ id: "
|
|
80
|
-
{ id: "BASH-GITRESET", re: /\bgit\s+reset\s+--hard\s+origin/ },
|
|
81
|
-
{ id: "BASH-CURLSUDO", re: /\bcurl\s+[^|]*\|\s*(sudo\s+)?(bash|sh|zsh|fish)/ },
|
|
99
|
+
export const DESTRUCTIVE_BASH_PATTERNS = [
|
|
100
|
+
{ id: "DD", re: /\bdd\s+.*of=\/dev\/(sd|nvme|disk)/ },
|
|
101
|
+
{ id: "GITFORCE", re: /\bgit\s+push\s+.*--force(?!-with-lease)/ },
|
|
102
|
+
{ id: "GITRESET", re: /\bgit\s+reset\s+--hard\s+origin/ },
|
|
103
|
+
{ id: "CURLSUDO", re: /\bcurl\s+[^|]*\|\s*(sudo\s+)?(bash|sh|zsh|fish)/ },
|
|
82
104
|
];
|
|
83
105
|
|
|
106
|
+
/**
|
|
107
|
+
* System directories whose recursive deletion would brick the host or
|
|
108
|
+
* destroy another user's data. Matched as the first path component of
|
|
109
|
+
* the target argument after a recursive/force `rm`.
|
|
110
|
+
*/
|
|
111
|
+
const RM_SYSTEM_DIRS = new Set([
|
|
112
|
+
"usr", "etc", "var", "bin", "sbin", "lib", "boot",
|
|
113
|
+
"System", "Applications", "Library", "opt", "root", "home",
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Build a SARIF rule ID for a secret pattern. Keeps the single source of
|
|
118
|
+
* truth for the `SONAR-SEC-*` namespace in the emitter.
|
|
119
|
+
*
|
|
120
|
+
* @param {{ id: string }} pattern
|
|
121
|
+
* @returns {string}
|
|
122
|
+
*/
|
|
123
|
+
export function formatSecretRuleId(pattern) {
|
|
124
|
+
return `SONAR-SEC-${pattern.id}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Build a SARIF rule ID for a destructive-bash pattern.
|
|
129
|
+
*
|
|
130
|
+
* @param {{ id: string }} pattern
|
|
131
|
+
* @returns {string}
|
|
132
|
+
*/
|
|
133
|
+
export function formatBashRuleId(pattern) {
|
|
134
|
+
return `SONAR-BASH-${pattern.id}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Tokenise a shell command into argv-like tokens that distinguish
|
|
139
|
+
* quoted strings from bare words, and emit unquoted shell control
|
|
140
|
+
* operators (`;`, `&&`, `||`, `|`, `&`) as their own tokens. Emitting
|
|
141
|
+
* operators as separate tokens prevents a target argument from "sticking"
|
|
142
|
+
* to an operator and slipping past target classification — `rm -rf /;ls`
|
|
143
|
+
* must tokenise to `[..., -rf, /, ;, ls]`, not `[..., -rf, /;ls]`.
|
|
144
|
+
*
|
|
145
|
+
* Intentionally minimal: single and double quotes only, no backslash-escape
|
|
146
|
+
* handling, no variable expansion. Sufficient for gatekeeper intent
|
|
147
|
+
* detection — a full shell parser is out of scope.
|
|
148
|
+
*
|
|
149
|
+
* @param {string} cmd
|
|
150
|
+
* @returns {Array<{ text: string, quoted: boolean }>}
|
|
151
|
+
*/
|
|
152
|
+
function tokenizeBash(cmd) {
|
|
153
|
+
const tokens = [];
|
|
154
|
+
// Alternation order matters: quoted strings first (so their contents are
|
|
155
|
+
// preserved verbatim), then control operators (longest match first:
|
|
156
|
+
// `&&`/`||` before `&`/`|`), then bare runs of non-whitespace that stop
|
|
157
|
+
// at the next operator character.
|
|
158
|
+
const re = /"((?:[^"\\]|\\.)*)"|'([^']*)'|(&&|\|\||;|\||&)|([^\s;|&"']+)/g;
|
|
159
|
+
let m;
|
|
160
|
+
while ((m = re.exec(cmd)) !== null) {
|
|
161
|
+
if (m[1] !== undefined) tokens.push({ text: m[1], quoted: true });
|
|
162
|
+
else if (m[2] !== undefined) tokens.push({ text: m[2], quoted: true });
|
|
163
|
+
else if (m[3] !== undefined) tokens.push({ text: /** @type {string} */ (m[3]), quoted: false });
|
|
164
|
+
else tokens.push({ text: /** @type {string} */ (m[4]), quoted: false });
|
|
165
|
+
}
|
|
166
|
+
return tokens;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Extract the basename of a command token. `rm`, `/bin/rm`, `./rm`, and
|
|
171
|
+
* `../bin/rm` all resolve to `"rm"`. Used so path-qualified invocations
|
|
172
|
+
* cannot bypass detection — the shell looks up the binary by the last
|
|
173
|
+
* path segment, and so does the gatekeeper.
|
|
174
|
+
*
|
|
175
|
+
* @param {string} token
|
|
176
|
+
* @returns {string}
|
|
177
|
+
*/
|
|
178
|
+
function basename(token) {
|
|
179
|
+
const i = token.lastIndexOf("/");
|
|
180
|
+
return i === -1 ? token : token.slice(i + 1);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* True when a flag token requests recursive or force semantics. Accepts
|
|
185
|
+
* long forms (`--recursive`, `--force`) and short-flag clusters that
|
|
186
|
+
* include any of `r`, `R`, `f` (`-rf`, `-Rf`, `-rfv`, ...).
|
|
187
|
+
*
|
|
188
|
+
* @param {string} token
|
|
189
|
+
* @returns {boolean}
|
|
190
|
+
*/
|
|
191
|
+
function isRecursiveOrForceFlag(token) {
|
|
192
|
+
if (token === "--recursive" || token === "--force") return true;
|
|
193
|
+
return /^-[a-zA-Z]*[rRf][a-zA-Z]*$/.test(token);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Classify the destination argument of a recursive `rm` into one of the
|
|
198
|
+
* high-severity categories we block, or `null` if the target is benign.
|
|
199
|
+
*
|
|
200
|
+
* Home-directory matching covers exact (`~`, `$HOME`) and prefixed
|
|
201
|
+
* (`~/anything`, `$HOME/anything`, `~/*`) forms — every path rooted at
|
|
202
|
+
* the user's home is considered destructive under recursive force.
|
|
203
|
+
*
|
|
204
|
+
* @param {string} target
|
|
205
|
+
* @returns {"RMROOT" | "RMSYSDIR" | "RMHOME" | null}
|
|
206
|
+
*/
|
|
207
|
+
function classifyRmTarget(target) {
|
|
208
|
+
if (target === "/" || target === "/*") return "RMROOT";
|
|
209
|
+
// ~, ~/, ~/anything, ~/*, $HOME, $HOME/, $HOME/anything, $HOME/*
|
|
210
|
+
if (/^(~|\$HOME)(\/.*|\*)?$/.test(target)) return "RMHOME";
|
|
211
|
+
// Any path whose first component is a protected system directory.
|
|
212
|
+
const m = /^\/([A-Za-z]+)(\/|$)/.exec(target);
|
|
213
|
+
if (m && RM_SYSTEM_DIRS.has(/** @type {string} */ (m[1]))) return "RMSYSDIR";
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Scan a command line for destructive operations. Returns the first hit
|
|
219
|
+
* or `null` when the command is safe. A hit is `{ id, match, ruleId }`
|
|
220
|
+
* where `ruleId` is the fully-formatted SARIF identifier.
|
|
221
|
+
*
|
|
222
|
+
* Handles two modes:
|
|
223
|
+
* 1. rm with `-r`/`-R`/`-f`/`--recursive`/`--force` targeting root,
|
|
224
|
+
* a system directory, or `$HOME` / `~` — via token-aware walk so
|
|
225
|
+
* strings inside quotes (e.g. `echo 'rm -rf /'`) do not trigger.
|
|
226
|
+
* 2. Other destructive commands (`dd of=/dev/...`, `git push --force`,
|
|
227
|
+
* `git reset --hard origin`, `curl ... | sh`) — via regex, which
|
|
228
|
+
* is good enough for these patterns and cheaper than tokenisation.
|
|
229
|
+
*
|
|
230
|
+
* @param {string | null | undefined} command
|
|
231
|
+
* @returns {{ id: string, ruleId: string, match: string } | null}
|
|
232
|
+
*/
|
|
233
|
+
export function findDestructiveBashHit(command) {
|
|
234
|
+
if (!command) return null;
|
|
235
|
+
|
|
236
|
+
// Mode 1 — token walk for rm invocations. Match on the basename of
|
|
237
|
+
// the token so path-qualified forms (`/bin/rm`, `./rm`, `../bin/rm`)
|
|
238
|
+
// are caught. This intentionally accepts the cosmetic false positive
|
|
239
|
+
// of `echo rm -rf /` blocking — the outcome is safe (the LLM is
|
|
240
|
+
// asked to reformulate) and a full executable-position parser
|
|
241
|
+
// introduces its own bypass edge cases around wrapper flags.
|
|
242
|
+
const tokens = tokenizeBash(command);
|
|
243
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
244
|
+
const t = /** @type {{ text: string, quoted: boolean }} */ (tokens[i]);
|
|
245
|
+
if (t.quoted) continue;
|
|
246
|
+
if (basename(t.text) !== "rm") continue;
|
|
247
|
+
|
|
248
|
+
// Collect flags + targets until end or a command separator.
|
|
249
|
+
const flags = [];
|
|
250
|
+
const targets = [];
|
|
251
|
+
for (let j = i + 1; j < tokens.length; j++) {
|
|
252
|
+
const next = /** @type {{ text: string, quoted: boolean }} */ (tokens[j]);
|
|
253
|
+
if (!next.quoted && /^[;&|]+$/.test(next.text)) break;
|
|
254
|
+
if (!next.quoted && next.text.startsWith("-")) flags.push(next.text);
|
|
255
|
+
else targets.push(next.text);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (!flags.some(isRecursiveOrForceFlag)) continue;
|
|
259
|
+
for (const target of targets) {
|
|
260
|
+
const category = classifyRmTarget(target);
|
|
261
|
+
if (category !== null) {
|
|
262
|
+
return {
|
|
263
|
+
id: category,
|
|
264
|
+
ruleId: formatBashRuleId({ id: category }),
|
|
265
|
+
match: target,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Mode 2 — other destructive patterns by regex.
|
|
272
|
+
for (const pat of DESTRUCTIVE_BASH_PATTERNS) {
|
|
273
|
+
if (pat.re.test(command)) {
|
|
274
|
+
return {
|
|
275
|
+
id: pat.id,
|
|
276
|
+
ruleId: formatBashRuleId(pat),
|
|
277
|
+
match: command,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Scan text for hardcoded-secret signatures. Canonical AWS example
|
|
287
|
+
* keys are allowlisted so the plugin can edit its own docs/tests.
|
|
288
|
+
*
|
|
289
|
+
* @param {string} text
|
|
290
|
+
* @returns {Array<{ id: string, ruleId: string, match: string }>}
|
|
291
|
+
*/
|
|
292
|
+
export function findSecretHits(text) {
|
|
293
|
+
if (typeof text !== "string" || text.length === 0) return [];
|
|
294
|
+
const hits = [];
|
|
295
|
+
for (const pat of HARDCODED_SECRET_PATTERNS) {
|
|
296
|
+
// Reset stateful /g regexes between invocations.
|
|
297
|
+
if (pat.re.global) pat.re.lastIndex = 0;
|
|
298
|
+
if (pat.id === "AWS") {
|
|
299
|
+
// Reuse the canonical pattern from the rule table rather than
|
|
300
|
+
// duplicating the literal here — a single source of truth for
|
|
301
|
+
// the regex means the allowlist can never drift from the matcher.
|
|
302
|
+
for (const m of text.matchAll(pat.re)) {
|
|
303
|
+
if (AWS_BENIGN_EXAMPLES.has(m[0])) continue;
|
|
304
|
+
hits.push({ id: "AWS", ruleId: formatSecretRuleId({ id: "AWS" }), match: m[0] });
|
|
305
|
+
}
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
const m = pat.re.exec(text);
|
|
309
|
+
if (m) {
|
|
310
|
+
hits.push({ id: pat.id, ruleId: formatSecretRuleId(pat), match: m[0] });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return hits;
|
|
314
|
+
}
|
|
315
|
+
|
|
84
316
|
/**
|
|
85
317
|
* Compile the user-configured blocked-path regex. Falls back to the default
|
|
86
318
|
* pattern if the configured value is missing or malformed — we never let a
|
|
@@ -161,19 +393,18 @@ export function checkHardcodedSecrets(input) {
|
|
|
161
393
|
if (candidates.length === 0) return null;
|
|
162
394
|
|
|
163
395
|
for (const text of candidates) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
396
|
+
const hits = findSecretHits(text);
|
|
397
|
+
if (hits.length === 0) continue;
|
|
398
|
+
const hit = /** @type {{ id: string, ruleId: string, match: string }} */ (hits[0]);
|
|
399
|
+
return {
|
|
400
|
+
blocked: true,
|
|
401
|
+
ruleId: hit.ruleId,
|
|
402
|
+
reason:
|
|
403
|
+
`A likely hardcoded secret (${hit.id}) was detected in the proposed content. ` +
|
|
404
|
+
`Per the Golden Rule in CLAUDE.md, credentials must never be embedded in source code. ` +
|
|
405
|
+
`Corrective action: move the value to an environment variable or a managed secret; ` +
|
|
406
|
+
`do not commit tokens, private keys, or JWTs to the source tree under any circumstance.`,
|
|
407
|
+
};
|
|
177
408
|
}
|
|
178
409
|
return null;
|
|
179
410
|
}
|
|
@@ -194,21 +425,19 @@ export function checkDestructiveBash(input) {
|
|
|
194
425
|
typeof input.tool_input.command === "string" ? input.tool_input.command : undefined;
|
|
195
426
|
if (!command) return null;
|
|
196
427
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
return null;
|
|
428
|
+
const hit = findDestructiveBashHit(command);
|
|
429
|
+
if (!hit) return null;
|
|
430
|
+
|
|
431
|
+
return {
|
|
432
|
+
blocked: true,
|
|
433
|
+
ruleId: hit.ruleId,
|
|
434
|
+
reason:
|
|
435
|
+
`The proposed Bash command matched the destructive pattern ${hit.id}: '${command}'. ` +
|
|
436
|
+
`claude-crap blocks operations that can wipe the project tree, rewrite published git history, ` +
|
|
437
|
+
`or execute remote code without review. ` +
|
|
438
|
+
`Corrective action: if this operation is truly intended, ask the user to confirm and run it ` +
|
|
439
|
+
`manually from their own terminal instead of through the agent.`,
|
|
440
|
+
};
|
|
212
441
|
}
|
|
213
442
|
|
|
214
443
|
/**
|
|
@@ -150,6 +150,9 @@ const LOC_WALK_SKIP_DIRS = new Set([
|
|
|
150
150
|
".git",
|
|
151
151
|
// Build outputs
|
|
152
152
|
"dist", "build", "bundle", "out", "target", "coverage",
|
|
153
|
+
// Test coverage report bundles (ReportGenerator, Istanbul, coverage.py, dotnet test)
|
|
154
|
+
"coverage-report", "CoverageReport", "coveragereport",
|
|
155
|
+
"TestResults", "cobertura", "lcov-report", "htmlcov",
|
|
153
156
|
// Framework build outputs
|
|
154
157
|
".next", ".nuxt", ".output", ".vercel", ".svelte-kit",
|
|
155
158
|
".astro", ".angular", ".turbo", ".parcel-cache", ".expo",
|
package/plugin/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-crap-plugin",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "claude-crap-plugin",
|
|
9
|
-
"version": "0.4.
|
|
9
|
+
"version": "0.4.6",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@fastify/static": "^8.0.3",
|
|
12
12
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
@@ -1208,9 +1208,9 @@
|
|
|
1208
1208
|
"license": "BSD-3-Clause"
|
|
1209
1209
|
},
|
|
1210
1210
|
"node_modules/fastify": {
|
|
1211
|
-
"version": "5.8.
|
|
1212
|
-
"resolved": "https://registry.npmjs.org/fastify/-/fastify-5.8.
|
|
1213
|
-
"integrity": "sha512-
|
|
1211
|
+
"version": "5.8.5",
|
|
1212
|
+
"resolved": "https://registry.npmjs.org/fastify/-/fastify-5.8.5.tgz",
|
|
1213
|
+
"integrity": "sha512-Yqptv59pQzPgQUSIm87hMqHJmdkb1+GPxdE6vW6FRyVE9G86mt7rOghitiU4JHRaTyDUk9pfeKmDeu70lAwM4Q==",
|
|
1214
1214
|
"funding": [
|
|
1215
1215
|
{
|
|
1216
1216
|
"type": "github",
|
|
@@ -1505,9 +1505,9 @@
|
|
|
1505
1505
|
}
|
|
1506
1506
|
},
|
|
1507
1507
|
"node_modules/hono": {
|
|
1508
|
-
"version": "4.12.
|
|
1509
|
-
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.
|
|
1510
|
-
"integrity": "sha512-
|
|
1508
|
+
"version": "4.12.14",
|
|
1509
|
+
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz",
|
|
1510
|
+
"integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==",
|
|
1511
1511
|
"license": "MIT",
|
|
1512
1512
|
"engines": {
|
|
1513
1513
|
"node": ">=16.9.0"
|
package/plugin/package.json
CHANGED
|
@@ -58,6 +58,12 @@ export interface FileDetailSummary {
|
|
|
58
58
|
/** Full response payload for the file detail endpoint. */
|
|
59
59
|
export interface FileDetailResponse {
|
|
60
60
|
readonly filePath: string;
|
|
61
|
+
/**
|
|
62
|
+
* Absolute path on the host filesystem, already resolved through the
|
|
63
|
+
* workspace-traversal guard. The UI uses this to build editor
|
|
64
|
+
* deep-links (e.g. `vscode://file/{absolutePath}:{line}`).
|
|
65
|
+
*/
|
|
66
|
+
readonly absolutePath: string;
|
|
61
67
|
readonly language: SupportedLanguage | null;
|
|
62
68
|
readonly physicalLoc: number;
|
|
63
69
|
readonly logicalLoc: number;
|
|
@@ -175,6 +181,7 @@ export async function buildFileDetail(
|
|
|
175
181
|
|
|
176
182
|
return {
|
|
177
183
|
filePath: relativePath,
|
|
184
|
+
absolutePath,
|
|
178
185
|
language,
|
|
179
186
|
physicalLoc,
|
|
180
187
|
logicalLoc,
|