ai-llm-pentest-skill 0.1.0 → 0.2.0
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/README.md +12 -19
- package/bin/cli.mjs +11 -2
- package/installer/banner.txt +9 -0
- package/installer/commands.mjs +18 -0
- package/installer/interactive.mjs +63 -0
- package/installer/ui.mjs +28 -0
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -20,10 +20,10 @@ A Claude Code plugin that drives an **authorized** AI/LLM security assessment en
|
|
|
20
20
|
## ⚡ Quick start
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
npx ai-llm-pentest-skill install #
|
|
23
|
+
npx ai-llm-pentest-skill install # interactive picker — choose your assistants (auto-detected)
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
The engine runs on demand via `uvx` (needs [uv](https://docs.astral.sh/uv/)) — no clone required. Then open your assistant and prompt:
|
|
26
|
+
Run bare, it shows a multi-select of assistants (Claude Code, Cursor, Windsurf, Copilot, Codex/AGENTS.md, Gemini) with "(not detected)" hints; pass `--assistant`, `--all`, or `--project` to skip the prompts (and for CI). The engine runs on demand via `uvx` (needs [uv](https://docs.astral.sh/uv/)) — no clone required. Then open your assistant and prompt:
|
|
27
27
|
|
|
28
28
|
> **Run an AI/LLM pentest** (or `/llm-pentest` in Claude Code)
|
|
29
29
|
|
|
@@ -227,15 +227,17 @@ Development is test-driven; see [`docs/DESIGN.md`](docs/DESIGN.md) for architect
|
|
|
227
227
|
|
|
228
228
|
## 🏷️ Releasing (maintainers)
|
|
229
229
|
|
|
230
|
-
|
|
230
|
+
Publishing is triggered by a **version tag** — no pull requests, no tokens:
|
|
231
231
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
232
|
+
```bash
|
|
233
|
+
npm version minor # bumps package.json + CHANGELOG date, creates tag vX.Y.Z (use patch/major as needed)
|
|
234
|
+
git push --follow-tags # pushing the tag triggers .github/workflows/release.yml
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
`release.yml` (on `v*` tags) tests in a credential-free job, then publishes to
|
|
238
|
+
npm via **Trusted Publishing (OIDC)** with provenance in a minimal job that runs
|
|
239
|
+
no tests and `--ignore-scripts` — so no `NPM_TOKEN` is stored and a dependency
|
|
240
|
+
lifecycle script can't reach the OIDC credential.
|
|
239
241
|
|
|
240
242
|
**One-time setup** on [npmjs.com](https://www.npmjs.com): open the
|
|
241
243
|
`ai-llm-pentest-skill` package → **Settings → Trusted Publisher → Add**:
|
|
@@ -243,15 +245,6 @@ Releases are fully automated from [Conventional Commits](https://www.conventiona
|
|
|
243
245
|
- Provider: **GitHub Actions**, Repository: `mstfknn/ai-llm-pentest-skill`,
|
|
244
246
|
Workflow: `release.yml`
|
|
245
247
|
|
|
246
|
-
> Hardening: the publish job holds `id-token: write` but runs no tests and
|
|
247
|
-
> `--ignore-scripts`, so a dependency lifecycle script can't reach the OIDC
|
|
248
|
-
> credential. For an extra gate, attach the publish job to a GitHub
|
|
249
|
-
> **Environment** with required reviewers.
|
|
250
|
-
>
|
|
251
|
-
> First `0.1.0`: publish once manually (`npm publish --otp=<code>`) to create the
|
|
252
|
-
> package, then configure the Trusted Publisher — every release after that is
|
|
253
|
-
> hands-off.
|
|
254
|
-
|
|
255
248
|
## 📄 License
|
|
256
249
|
|
|
257
250
|
[MIT](LICENSE), with an explicit authorized-use notice.
|
package/bin/cli.mjs
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import os from "node:os";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
import { main } from "../installer/commands.mjs";
|
|
5
|
+
import { interactiveInstall } from "../installer/interactive.mjs";
|
|
6
|
+
|
|
7
|
+
process.exit(
|
|
8
|
+
await main(process.argv.slice(2), {
|
|
9
|
+
isTTY: process.stdin.isTTY,
|
|
10
|
+
ctx: { home: os.homedir(), cwd: process.cwd() },
|
|
11
|
+
interactive: interactiveInstall,
|
|
12
|
+
}),
|
|
13
|
+
);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
█████╗ ██╗ ██╗ ██╗ ███╗ ███╗
|
|
2
|
+
██╔══██╗██║ ██║ ██║ ████╗ ████║
|
|
3
|
+
███████║██║█████╗██║ ██║ ██╔████╔██║
|
|
4
|
+
██╔══██║██║╚════╝██║ ██║ ██║╚██╔╝██║
|
|
5
|
+
██║ ██║██║ ███████╗███████╗██║ ╚═╝ ██║
|
|
6
|
+
╚═╝ ╚═╝╚═╝ ╚══════╝╚══════╝╚═╝ ╚═╝
|
|
7
|
+
authorized AI/LLM pentest orchestration · v__VERSION__
|
|
8
|
+
black · gray · white-box → OWASP · ATLAS · NIST
|
|
9
|
+
github.com/mstfknn/ai-llm-pentest-skill
|
package/installer/commands.mjs
CHANGED
|
@@ -136,6 +136,24 @@ function selectAssistants(rest, ctx) {
|
|
|
136
136
|
return detected.length > 0 ? detected : ["claude-code"];
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
/** True when `install` was invoked bare on a TTY (→ launch the guided flow). */
|
|
140
|
+
export function shouldPrompt(argv, isTTY) {
|
|
141
|
+
return (
|
|
142
|
+
argv[0] === "install" &&
|
|
143
|
+
!argv.includes("--assistant") &&
|
|
144
|
+
!argv.includes("--all") &&
|
|
145
|
+
Boolean(isTTY)
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Top-level entry: launch the guided flow when appropriate, else run a command. */
|
|
150
|
+
export async function main(argv, opts = {}) {
|
|
151
|
+
if (shouldPrompt(argv, opts.isTTY)) {
|
|
152
|
+
return opts.interactive(opts.ctx);
|
|
153
|
+
}
|
|
154
|
+
return run(argv, opts);
|
|
155
|
+
}
|
|
156
|
+
|
|
139
157
|
/** Parse argv and run a subcommand. Returns a process exit code. */
|
|
140
158
|
export function run(argv, opts = {}) {
|
|
141
159
|
const ctx = opts.ctx ?? { home: os.homedir(), cwd: process.cwd() };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Interactive (@clack/prompts) install flow. Thin I/O layer over tested helpers.
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
import * as p from "@clack/prompts";
|
|
7
|
+
|
|
8
|
+
import { installAssistant } from "./commands.mjs";
|
|
9
|
+
import { PKG_ROOT } from "./config.mjs";
|
|
10
|
+
import { assistantChoices, expandSelection, renderBanner } from "./ui.mjs";
|
|
11
|
+
|
|
12
|
+
function packageVersion() {
|
|
13
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, "package.json"), "utf8"));
|
|
14
|
+
return pkg.version;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Run the guided installer. Returns a process exit code. */
|
|
18
|
+
export async function interactiveInstall(ctx) {
|
|
19
|
+
// eslint-disable-next-line no-console
|
|
20
|
+
console.log(renderBanner(packageVersion()));
|
|
21
|
+
p.intro("ai-llm-pentest-skill installer");
|
|
22
|
+
|
|
23
|
+
const choices = assistantChoices(ctx);
|
|
24
|
+
const selected = await p.multiselect({
|
|
25
|
+
message: "Which assistants should this install for?",
|
|
26
|
+
options: [
|
|
27
|
+
...choices.map((c) => ({
|
|
28
|
+
value: c.value,
|
|
29
|
+
label: c.detected ? c.label : `${c.label} (not detected)`,
|
|
30
|
+
})),
|
|
31
|
+
{ value: "__all__", label: "✨ All of the above" },
|
|
32
|
+
],
|
|
33
|
+
required: true,
|
|
34
|
+
});
|
|
35
|
+
if (p.isCancel(selected)) {
|
|
36
|
+
p.cancel("Cancelled.");
|
|
37
|
+
return 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const scope = await p.select({
|
|
41
|
+
message: "Install where?",
|
|
42
|
+
options: [
|
|
43
|
+
{ value: "global", label: "Home directory (all projects)" },
|
|
44
|
+
{ value: "project", label: "This project only" },
|
|
45
|
+
],
|
|
46
|
+
initialValue: "global",
|
|
47
|
+
});
|
|
48
|
+
if (p.isCancel(scope)) {
|
|
49
|
+
p.cancel("Cancelled.");
|
|
50
|
+
return 1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const keys = expandSelection(selected, choices.map((c) => c.value));
|
|
54
|
+
const spin = p.spinner();
|
|
55
|
+
for (const key of keys) {
|
|
56
|
+
spin.start(`Installing for ${key}`);
|
|
57
|
+
installAssistant(key, scope, ctx);
|
|
58
|
+
spin.stop(`Installed for ${key}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
p.outro('Done. Prompt your assistant: "Run an AI/LLM pentest" (or /llm-pentest).');
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
package/installer/ui.mjs
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Presentation helpers for the interactive installer.
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
import { ASSISTANTS, detectInstalledAssistants } from "./assistants.mjs";
|
|
7
|
+
import { PKG_ROOT } from "./config.mjs";
|
|
8
|
+
|
|
9
|
+
/** The ASCII banner with the version filled in. */
|
|
10
|
+
export function renderBanner(version) {
|
|
11
|
+
const banner = fs.readFileSync(path.join(PKG_ROOT, "installer", "banner.txt"), "utf8");
|
|
12
|
+
return banner.split("__VERSION__").join(version);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Build multiselect choices for every assistant, flagged by on-disk detection. */
|
|
16
|
+
export function assistantChoices(ctx) {
|
|
17
|
+
const detected = new Set(detectInstalledAssistants(ctx));
|
|
18
|
+
return Object.entries(ASSISTANTS).map(([value, assistant]) => ({
|
|
19
|
+
value,
|
|
20
|
+
label: assistant.label,
|
|
21
|
+
detected: detected.has(value),
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Resolve a multiselect result: the __all__ sentinel expands to every key. */
|
|
26
|
+
export function expandSelection(values, allKeys) {
|
|
27
|
+
return values.includes("__all__") ? [...allKeys] : values;
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-llm-pentest-skill",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Installer for the ai-llm-pentest agent skill — drive an authorized AI/LLM penetration test (OWASP/ATLAS/NIST-mapped) from Claude Code, Codex, Cursor, Gemini, Copilot, and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -45,5 +45,8 @@
|
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"tdd-guard-vitest": "^0.2.0",
|
|
47
47
|
"vitest": "^5.0.0"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@clack/prompts": "^1.8.1"
|
|
48
51
|
}
|
|
49
52
|
}
|