pwnkit-cli 0.3.0 → 0.3.1
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 +53 -53
- package/dist/index.js +241 -189
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
|
|
2
|
+
<img src="assets/pwnkit-icon.gif" alt="pwnkit" width="80" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<h1 align="center">pwnkit</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
<strong>General-purpose autonomous pentesting framework</strong><br/>
|
|
9
|
+
<em>Scan LLM endpoints. Audit npm packages. Review source code. Pentest web apps. Re-exploit to kill false positives.</em>
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
<a href="https://www.npmjs.com/package/pwnkit-cli"><img src="https://img.shields.io/npm/v/pwnkit-cli?color=crimson&style=flat-square" alt="npm version" /></a>
|
|
14
|
+
<a href="https://github.com/peaktwilight/pwnkit/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue?style=flat-square" alt="license" /></a>
|
|
15
|
+
<a href="https://github.com/peaktwilight/pwnkit/actions"><img src="https://img.shields.io/github/actions/workflow/status/peaktwilight/pwnkit/ci.yml?style=flat-square" alt="CI" /></a>
|
|
16
|
+
<a href="https://github.com/peaktwilight/pwnkit/stargazers"><img src="https://img.shields.io/github/stars/peaktwilight/pwnkit?style=flat-square&color=gold" alt="stars" /></a>
|
|
17
|
+
<a href="https://pwnkit.com"><img src="https://pwnkit.com/badge/peaktwilight/pwnkit" alt="pwnkit verified" /></a>
|
|
18
18
|
</p>
|
|
19
19
|
|
|
20
20
|
<p align="center">
|
|
21
|
-
|
|
21
|
+
<img src="assets/demo.gif" alt="pwnkit Demo" width="700" />
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
24
|
<p align="center">
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
<a href="#quick-start">Quick Start</a> ·
|
|
26
|
+
<a href="#commands">Commands</a> ·
|
|
27
|
+
<a href="#how-it-works">How It Works</a> ·
|
|
28
|
+
<a href="#what-pwnkit-scans">What It Scans</a> ·
|
|
29
|
+
<a href="#how-it-compares">Comparison</a> ·
|
|
30
|
+
<a href="#github-action">CI/CD</a> ·
|
|
31
|
+
<a href="#built-by">About</a>
|
|
32
32
|
</p>
|
|
33
33
|
|
|
34
34
|
---
|
|
@@ -50,10 +50,10 @@ npx pwnkit-cli audit lodash
|
|
|
50
50
|
npx pwnkit-cli review ./my-ai-app
|
|
51
51
|
|
|
52
52
|
# Or just point pwnkit at a target — it auto-detects what to do
|
|
53
|
-
npx pwnkit-cli express
|
|
54
|
-
npx pwnkit-cli ./my-repo
|
|
55
|
-
npx pwnkit-cli https://github.com/user/repo
|
|
56
|
-
npx pwnkit-cli https://example.com
|
|
53
|
+
npx pwnkit-cli express # audits npm package
|
|
54
|
+
npx pwnkit-cli ./my-repo # reviews source code
|
|
55
|
+
npx pwnkit-cli https://github.com/user/repo # clones and reviews
|
|
56
|
+
npx pwnkit-cli https://example.com # scans web endpoint
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
That's it. pwnkit discovers your attack surface, launches targeted attacks, verifies findings, and generates a report — all in under 5 minutes.
|
|
@@ -90,16 +90,16 @@ pwnkit ships five commands — from quick API probes to deep source-level audits
|
|
|
90
90
|
pwnkit runs autonomous AI agents in a research-then-verify pipeline. Each agent uses tools (`read_file`, `run_command`, `send_prompt`, `save_finding`) and makes multi-turn decisions — adapting its strategy based on what it learns:
|
|
91
91
|
|
|
92
92
|
```
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
93
|
+
+-----------+ +------------------+ +-----------+
|
|
94
|
+
| RESEARCH | --> | BLIND VERIFY | --> | REPORT |
|
|
95
|
+
| (Discover | | (PoC + path only | | (Output) |
|
|
96
|
+
| + Attack | | no reasoning) | | |
|
|
97
|
+
| + PoC) | +------------------+ +-----------+
|
|
98
|
+
+-----------+ Runs in parallel Only confirmed
|
|
99
|
+
Single agent per finding — findings in SARIF,
|
|
100
|
+
session: recon, independently Markdown, and JSON
|
|
101
|
+
payloads, and reproduces or with severity +
|
|
102
|
+
proof-of-concept kills finding remediation
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
| Agent | Role | What It Does |
|
|
@@ -178,7 +178,7 @@ Bring your own agent CLI — pwnkit orchestrates it:
|
|
|
178
178
|
| `claude` | `--runtime claude` | Attack generation, deep analysis — spawns Claude Code CLI |
|
|
179
179
|
| `codex` | `--runtime codex` | Verification, source analysis — spawns Codex CLI |
|
|
180
180
|
| `gemini` | `--runtime gemini` | Large context source analysis — spawns Gemini CLI |
|
|
181
|
-
|
|
|
181
|
+
| `` | `--runtime ` | Multi-provider flexibility — spawns CLI |
|
|
182
182
|
| `auto` | `--runtime auto` | Best overall — auto-detects installed runtimes, picks best per stage |
|
|
183
183
|
|
|
184
184
|
Combined with scan modes:
|
|
@@ -190,7 +190,7 @@ Combined with scan modes:
|
|
|
190
190
|
| `mcp` | `--mode mcp` | Connect to MCP server, enumerate tools, test each for security issues |
|
|
191
191
|
| `web` | `--mode web` | Full web pentesting — SQLi, XSS, SSRF, auth bypass, IDOR |
|
|
192
192
|
|
|
193
|
-
> `deep`, `mcp`, and `web` modes require a process runtime (`claude`, `codex`, `gemini`,
|
|
193
|
+
> `deep`, `mcp`, and `web` modes require a process runtime (`claude`, `codex`, `gemini`, ``, or `auto`).
|
|
194
194
|
|
|
195
195
|
## How It Compares
|
|
196
196
|
|
|
@@ -220,28 +220,28 @@ name: AI Security Scan
|
|
|
220
220
|
on: [push, pull_request]
|
|
221
221
|
|
|
222
222
|
permissions:
|
|
223
|
-
|
|
224
|
-
|
|
223
|
+
contents: read
|
|
224
|
+
security-events: write
|
|
225
225
|
|
|
226
226
|
jobs:
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
227
|
+
pwnkit:
|
|
228
|
+
runs-on: ubuntu-latest
|
|
229
|
+
steps:
|
|
230
|
+
- uses: actions/checkout@v4
|
|
231
|
+
|
|
232
|
+
- name: Run pwnkit
|
|
233
|
+
uses: peaktwilight/pwnkit/action@v1
|
|
234
|
+
with:
|
|
235
|
+
target: ${{ secrets.STAGING_API_URL }}
|
|
236
|
+
depth: default # quick | default | deep
|
|
237
|
+
fail-on-severity: high # critical | high | medium | low | info | none
|
|
238
|
+
env:
|
|
239
|
+
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
240
|
+
|
|
241
|
+
- name: Upload SARIF
|
|
242
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
243
|
+
with:
|
|
244
|
+
sarif_file: pwnkit-report/report.sarif
|
|
245
245
|
```
|
|
246
246
|
|
|
247
247
|
> **API Key Priority:** pwnkit checks for `OPENROUTER_API_KEY` first, then `ANTHROPIC_API_KEY`, then `OPENAI_API_KEY`. OpenRouter gives you access to many models (including free ones) through a single key at [openrouter.ai](https://openrouter.ai).
|
|
@@ -291,7 +291,7 @@ Finding lifecycle: `discovered → verified → confirmed → scored → reporte
|
|
|
291
291
|
- [x] MCP server scanning
|
|
292
292
|
- [x] npm package auditing
|
|
293
293
|
- [x] Source code review (local + GitHub)
|
|
294
|
-
- [x] Multi-runtime support (Claude, Codex, Gemini
|
|
294
|
+
- [x] Multi-runtime support (Claude, Codex, Gemini)
|
|
295
295
|
- [x] Multi-turn agentic attacks (agents adapt payloads based on responses)
|
|
296
296
|
- [x] Web pentesting mode (SQLi, XSS, SSRF, auth bypass, IDOR)
|
|
297
297
|
- [ ] RAG pipeline security (poisoning, extraction)
|
package/dist/index.js
CHANGED
|
@@ -3561,7 +3561,7 @@ var VERSION, DEPTH_CONFIG;
|
|
|
3561
3561
|
var init_constants = __esm({
|
|
3562
3562
|
"packages/shared/dist/constants.js"() {
|
|
3563
3563
|
"use strict";
|
|
3564
|
-
VERSION = "0.3.
|
|
3564
|
+
VERSION = "0.3.1";
|
|
3565
3565
|
DEPTH_CONFIG = {
|
|
3566
3566
|
quick: { maxTemplates: 5, maxPayloadsPerTemplate: 1, multiTurn: false },
|
|
3567
3567
|
default: { maxTemplates: 20, maxPayloadsPerTemplate: 3, multiTurn: false },
|
|
@@ -10924,8 +10924,7 @@ var init_process = __esm({
|
|
|
10924
10924
|
RUNTIME_COMMANDS = {
|
|
10925
10925
|
claude: "claude",
|
|
10926
10926
|
codex: "codex",
|
|
10927
|
-
gemini: "gemini"
|
|
10928
|
-
opencode: "opencode"
|
|
10927
|
+
gemini: "gemini"
|
|
10929
10928
|
};
|
|
10930
10929
|
ProcessRuntime = class {
|
|
10931
10930
|
type;
|
|
@@ -11064,8 +11063,6 @@ var init_process = __esm({
|
|
|
11064
11063
|
const args = ["-p", prompt, "--output-format", "stream-json"];
|
|
11065
11064
|
return args;
|
|
11066
11065
|
}
|
|
11067
|
-
case "opencode":
|
|
11068
|
-
return ["-p", prompt, "--output", "text"];
|
|
11069
11066
|
default:
|
|
11070
11067
|
return ["-p", prompt];
|
|
11071
11068
|
}
|
|
@@ -11089,6 +11086,75 @@ var init_process = __esm({
|
|
|
11089
11086
|
}
|
|
11090
11087
|
});
|
|
11091
11088
|
|
|
11089
|
+
// packages/core/dist/runtime/registry.js
|
|
11090
|
+
var registry_exports = {};
|
|
11091
|
+
__export(registry_exports, {
|
|
11092
|
+
RUNTIME_REGISTRY: () => RUNTIME_REGISTRY,
|
|
11093
|
+
detectAvailableRuntimes: () => detectAvailableRuntimes,
|
|
11094
|
+
getRuntimeInfo: () => getRuntimeInfo,
|
|
11095
|
+
pickRuntimeForStage: () => pickRuntimeForStage
|
|
11096
|
+
});
|
|
11097
|
+
function pickRuntimeForStage(stage, availableRuntimes) {
|
|
11098
|
+
const prefs = STAGE_PREFERENCES[stage];
|
|
11099
|
+
for (const rt2 of prefs) {
|
|
11100
|
+
if (availableRuntimes.has(rt2))
|
|
11101
|
+
return rt2;
|
|
11102
|
+
}
|
|
11103
|
+
const first = availableRuntimes.values().next();
|
|
11104
|
+
return first.done ? "claude" : first.value;
|
|
11105
|
+
}
|
|
11106
|
+
async function detectAvailableRuntimes() {
|
|
11107
|
+
const { ProcessRuntime: ProcessRuntime2 } = await Promise.resolve().then(() => (init_process(), process_exports));
|
|
11108
|
+
const available = /* @__PURE__ */ new Set();
|
|
11109
|
+
const checks = RUNTIME_REGISTRY.map(async (info) => {
|
|
11110
|
+
const rt2 = new ProcessRuntime2({ type: info.type, timeout: 5e3 });
|
|
11111
|
+
if (await rt2.isAvailable()) {
|
|
11112
|
+
available.add(info.type);
|
|
11113
|
+
}
|
|
11114
|
+
});
|
|
11115
|
+
await Promise.all(checks);
|
|
11116
|
+
return available;
|
|
11117
|
+
}
|
|
11118
|
+
function getRuntimeInfo(type) {
|
|
11119
|
+
return RUNTIME_REGISTRY.find((r) => r.type === type);
|
|
11120
|
+
}
|
|
11121
|
+
var RUNTIME_REGISTRY, STAGE_PREFERENCES;
|
|
11122
|
+
var init_registry = __esm({
|
|
11123
|
+
"packages/core/dist/runtime/registry.js"() {
|
|
11124
|
+
"use strict";
|
|
11125
|
+
RUNTIME_REGISTRY = [
|
|
11126
|
+
{
|
|
11127
|
+
type: "claude",
|
|
11128
|
+
command: "claude",
|
|
11129
|
+
description: "Claude Code CLI \u2014 best for creative attack generation and deep analysis",
|
|
11130
|
+
strengths: ["attack", "source-analysis", "report"],
|
|
11131
|
+
supportsSystemPrompt: true
|
|
11132
|
+
},
|
|
11133
|
+
{
|
|
11134
|
+
type: "codex",
|
|
11135
|
+
command: "codex",
|
|
11136
|
+
description: "Codex CLI \u2014 strong at code review, pattern matching, and verification",
|
|
11137
|
+
strengths: ["verify", "source-analysis", "discovery"],
|
|
11138
|
+
supportsSystemPrompt: false
|
|
11139
|
+
},
|
|
11140
|
+
{
|
|
11141
|
+
type: "gemini",
|
|
11142
|
+
command: "gemini",
|
|
11143
|
+
description: "Gemini CLI \u2014 large context window, good for source analysis",
|
|
11144
|
+
strengths: ["source-analysis", "report", "discovery"],
|
|
11145
|
+
supportsSystemPrompt: false
|
|
11146
|
+
}
|
|
11147
|
+
];
|
|
11148
|
+
STAGE_PREFERENCES = {
|
|
11149
|
+
"discovery": ["claude", "codex", "gemini"],
|
|
11150
|
+
"source-analysis": ["claude", "gemini", "codex"],
|
|
11151
|
+
"attack": ["claude", "codex", "gemini"],
|
|
11152
|
+
"verify": ["codex", "claude", "gemini"],
|
|
11153
|
+
"report": ["claude", "gemini", "codex"]
|
|
11154
|
+
};
|
|
11155
|
+
}
|
|
11156
|
+
});
|
|
11157
|
+
|
|
11092
11158
|
// packages/db/dist/schema.js
|
|
11093
11159
|
var schema_exports = {};
|
|
11094
11160
|
__export(schema_exports, {
|
|
@@ -44109,14 +44175,22 @@ var init_devtools_window_polyfill = __esm({
|
|
|
44109
44175
|
}
|
|
44110
44176
|
});
|
|
44111
44177
|
|
|
44178
|
+
// stub:react-devtools-core
|
|
44179
|
+
var react_devtools_core_default;
|
|
44180
|
+
var init_react_devtools_core = __esm({
|
|
44181
|
+
"stub:react-devtools-core"() {
|
|
44182
|
+
react_devtools_core_default = {};
|
|
44183
|
+
}
|
|
44184
|
+
});
|
|
44185
|
+
|
|
44112
44186
|
// node_modules/.pnpm/ink@6.8.0_@types+react@19.2.14_react@19.2.4/node_modules/ink/build/devtools.js
|
|
44113
44187
|
var devtools_exports = {};
|
|
44114
|
-
import devtools from "react-devtools-core";
|
|
44115
44188
|
var init_devtools = __esm({
|
|
44116
44189
|
"node_modules/.pnpm/ink@6.8.0_@types+react@19.2.14_react@19.2.4/node_modules/ink/build/devtools.js"() {
|
|
44117
44190
|
init_devtools_window_polyfill();
|
|
44118
|
-
|
|
44119
|
-
|
|
44191
|
+
init_react_devtools_core();
|
|
44192
|
+
react_devtools_core_default.initialize();
|
|
44193
|
+
react_devtools_core_default.connectToDevTools();
|
|
44120
44194
|
}
|
|
44121
44195
|
});
|
|
44122
44196
|
|
|
@@ -52419,12 +52493,20 @@ function formatDuration2(ms) {
|
|
|
52419
52493
|
}
|
|
52420
52494
|
function StageRow({ stage }) {
|
|
52421
52495
|
const icon = stage.status === "done" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: GREEN, children: "\u2713" }) : stage.status === "running" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: CRIMSON, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(build_default, { type: "dots" }) }) : stage.status === "error" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: CRIMSON, children: "\u2717" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: GRAY, children: "\u25CC" });
|
|
52496
|
+
let verifyCount = "";
|
|
52497
|
+
if (stage.id === "verify" && stage.status === "done" && stage.actions.length > 0) {
|
|
52498
|
+
const confirmed = stage.actions.filter((a) => a.startsWith("\u2713")).length;
|
|
52499
|
+
const total = stage.actions.filter((a) => a.startsWith("\u2713") || a.startsWith("\u2717")).length;
|
|
52500
|
+
if (total > 0) {
|
|
52501
|
+
verifyCount = `${confirmed}/${total} confirmed`;
|
|
52502
|
+
}
|
|
52503
|
+
}
|
|
52422
52504
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { flexDirection: "column", children: [
|
|
52423
52505
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { gap: 1, children: [
|
|
52424
52506
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: " " }),
|
|
52425
52507
|
icon,
|
|
52426
52508
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { bold: true, color: stage.status === "pending" ? GRAY : void 0, children: stage.label.padEnd(12) }),
|
|
52427
|
-
stage.detail
|
|
52509
|
+
verifyCount ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: GREEN, children: verifyCount }) : stage.detail ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: stage.status === "done" ? GRAY : void 0, dimColor: stage.status === "done", children: stage.detail }) : null,
|
|
52428
52510
|
stage.duration !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { color: GRAY, children: [
|
|
52429
52511
|
" ",
|
|
52430
52512
|
formatDuration2(stage.duration)
|
|
@@ -52434,7 +52516,19 @@ function StageRow({ stage }) {
|
|
|
52434
52516
|
if (stage.id === "verify") {
|
|
52435
52517
|
const isConfirmed = action.startsWith("\u2713");
|
|
52436
52518
|
const isRejected = action.startsWith("\u2717");
|
|
52437
|
-
|
|
52519
|
+
if (isConfirmed) {
|
|
52520
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { color: GREEN, bold: true, children: [
|
|
52521
|
+
"\u2192 ",
|
|
52522
|
+
action
|
|
52523
|
+
] }, i);
|
|
52524
|
+
}
|
|
52525
|
+
if (isRejected) {
|
|
52526
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { color: CRIMSON, dimColor: true, strikethrough: true, children: [
|
|
52527
|
+
"\u2192 ",
|
|
52528
|
+
action
|
|
52529
|
+
] }, i);
|
|
52530
|
+
}
|
|
52531
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { color: CYAN, children: [
|
|
52438
52532
|
"\u2192 ",
|
|
52439
52533
|
action
|
|
52440
52534
|
] }, i);
|
|
@@ -52627,10 +52721,12 @@ function renderScanUI(opts) {
|
|
|
52627
52721
|
if (event.type === "verify:result") {
|
|
52628
52722
|
const data = event.data;
|
|
52629
52723
|
const confirmed = data?.confirmed;
|
|
52630
|
-
const
|
|
52724
|
+
const title = data?.title ?? event.message;
|
|
52725
|
+
const reason = data?.reason;
|
|
52726
|
+
const label = confirmed ? `\u2713 ${title}` : `\u2717 ${title}${reason ? ` \u2014 ${reason}` : ""}`;
|
|
52631
52727
|
updateStage("verify", (s) => ({
|
|
52632
52728
|
...s,
|
|
52633
|
-
actions: [...s.actions,
|
|
52729
|
+
actions: [...s.actions, label]
|
|
52634
52730
|
}));
|
|
52635
52731
|
return;
|
|
52636
52732
|
}
|
|
@@ -54859,68 +54955,7 @@ var LlmApiRuntime = class {
|
|
|
54859
54955
|
|
|
54860
54956
|
// packages/core/dist/runtime/index.js
|
|
54861
54957
|
init_process();
|
|
54862
|
-
|
|
54863
|
-
// packages/core/dist/runtime/registry.js
|
|
54864
|
-
var RUNTIME_REGISTRY = [
|
|
54865
|
-
{
|
|
54866
|
-
type: "claude",
|
|
54867
|
-
command: "claude",
|
|
54868
|
-
description: "Claude Code CLI \u2014 best for creative attack generation and deep analysis",
|
|
54869
|
-
strengths: ["attack", "source-analysis", "report"],
|
|
54870
|
-
supportsSystemPrompt: true
|
|
54871
|
-
},
|
|
54872
|
-
{
|
|
54873
|
-
type: "codex",
|
|
54874
|
-
command: "codex",
|
|
54875
|
-
description: "Codex CLI \u2014 strong at code review, pattern matching, and verification",
|
|
54876
|
-
strengths: ["verify", "source-analysis", "discovery"],
|
|
54877
|
-
supportsSystemPrompt: false
|
|
54878
|
-
},
|
|
54879
|
-
{
|
|
54880
|
-
type: "gemini",
|
|
54881
|
-
command: "gemini",
|
|
54882
|
-
description: "Gemini CLI \u2014 large context window, good for source analysis",
|
|
54883
|
-
strengths: ["source-analysis", "report", "discovery"],
|
|
54884
|
-
supportsSystemPrompt: false
|
|
54885
|
-
},
|
|
54886
|
-
{
|
|
54887
|
-
type: "opencode",
|
|
54888
|
-
command: "opencode",
|
|
54889
|
-
description: "OpenCode CLI \u2014 multi-provider runtime with flexible model selection",
|
|
54890
|
-
strengths: ["source-analysis", "verify", "attack"],
|
|
54891
|
-
supportsSystemPrompt: false
|
|
54892
|
-
}
|
|
54893
|
-
];
|
|
54894
|
-
var STAGE_PREFERENCES = {
|
|
54895
|
-
"discovery": ["claude", "codex", "gemini", "opencode"],
|
|
54896
|
-
"source-analysis": ["claude", "gemini", "codex", "opencode"],
|
|
54897
|
-
"attack": ["claude", "codex", "gemini", "opencode"],
|
|
54898
|
-
"verify": ["codex", "claude", "gemini", "opencode"],
|
|
54899
|
-
"report": ["claude", "gemini", "codex", "opencode"]
|
|
54900
|
-
};
|
|
54901
|
-
function pickRuntimeForStage(stage, availableRuntimes) {
|
|
54902
|
-
const prefs = STAGE_PREFERENCES[stage];
|
|
54903
|
-
for (const rt2 of prefs) {
|
|
54904
|
-
if (availableRuntimes.has(rt2))
|
|
54905
|
-
return rt2;
|
|
54906
|
-
}
|
|
54907
|
-
const first = availableRuntimes.values().next();
|
|
54908
|
-
return first.done ? "claude" : first.value;
|
|
54909
|
-
}
|
|
54910
|
-
async function detectAvailableRuntimes() {
|
|
54911
|
-
const { ProcessRuntime: ProcessRuntime2 } = await Promise.resolve().then(() => (init_process(), process_exports));
|
|
54912
|
-
const available = /* @__PURE__ */ new Set();
|
|
54913
|
-
const checks = RUNTIME_REGISTRY.map(async (info) => {
|
|
54914
|
-
const rt2 = new ProcessRuntime2({ type: info.type, timeout: 5e3 });
|
|
54915
|
-
if (await rt2.isAvailable()) {
|
|
54916
|
-
available.add(info.type);
|
|
54917
|
-
}
|
|
54918
|
-
});
|
|
54919
|
-
await Promise.all(checks);
|
|
54920
|
-
return available;
|
|
54921
|
-
}
|
|
54922
|
-
|
|
54923
|
-
// packages/core/dist/runtime/index.js
|
|
54958
|
+
init_registry();
|
|
54924
54959
|
init_process();
|
|
54925
54960
|
function createRuntime(config) {
|
|
54926
54961
|
switch (config.type) {
|
|
@@ -54929,11 +54964,13 @@ function createRuntime(config) {
|
|
|
54929
54964
|
case "claude":
|
|
54930
54965
|
case "codex":
|
|
54931
54966
|
case "gemini":
|
|
54932
|
-
case "opencode":
|
|
54933
54967
|
return new ProcessRuntime(config);
|
|
54934
54968
|
}
|
|
54935
54969
|
}
|
|
54936
54970
|
|
|
54971
|
+
// packages/core/dist/scanner.js
|
|
54972
|
+
init_registry();
|
|
54973
|
+
|
|
54937
54974
|
// packages/core/dist/http.js
|
|
54938
54975
|
async function sendPrompt(target, prompt, options) {
|
|
54939
54976
|
const start = Date.now();
|
|
@@ -56706,7 +56743,7 @@ async function scan(config, onEvent, dbPath) {
|
|
|
56706
56743
|
if (isAuto) {
|
|
56707
56744
|
availableRuntimes = await detectAvailableRuntimes();
|
|
56708
56745
|
if (availableRuntimes.size === 0) {
|
|
56709
|
-
throw new Error("--runtime auto: no CLI runtimes (claude, codex, gemini
|
|
56746
|
+
throw new Error("--runtime auto: no CLI runtimes (claude, codex, gemini) detected. Install at least one or use --runtime api.");
|
|
56710
56747
|
}
|
|
56711
56748
|
}
|
|
56712
56749
|
function getRuntimeForStage(stage) {
|
|
@@ -57559,9 +57596,12 @@ Rate based on REAL exploitability:
|
|
|
57559
57596
|
- Call done when you've thoroughly reviewed the codebase`;
|
|
57560
57597
|
}
|
|
57561
57598
|
|
|
57599
|
+
// packages/core/dist/agent-runner.js
|
|
57600
|
+
init_registry();
|
|
57601
|
+
|
|
57562
57602
|
// packages/core/dist/shared-analysis.js
|
|
57563
57603
|
import { execFileSync } from "node:child_process";
|
|
57564
|
-
var CLI_RUNTIME_TYPES = /* @__PURE__ */ new Set(["claude", "codex", "gemini"
|
|
57604
|
+
var CLI_RUNTIME_TYPES = /* @__PURE__ */ new Set(["claude", "codex", "gemini"]);
|
|
57565
57605
|
function bufferToString(value) {
|
|
57566
57606
|
if (!value) {
|
|
57567
57607
|
return "";
|
|
@@ -58700,77 +58740,87 @@ async function runPipeline(opts) {
|
|
|
58700
58740
|
stage: "analyze",
|
|
58701
58741
|
message: `Analysis complete: ${semgrepFindings.length} semgrep findings, ${npmAuditFindings.length} npm advisories`
|
|
58702
58742
|
});
|
|
58703
|
-
|
|
58704
|
-
const
|
|
58705
|
-
|
|
58706
|
-
|
|
58707
|
-
|
|
58708
|
-
|
|
58709
|
-
}
|
|
58710
|
-
|
|
58743
|
+
const hasApiKey = !!(opts.apiKey || process.env.OPENROUTER_API_KEY || process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY);
|
|
58744
|
+
const { detectAvailableRuntimes: detectAvailableRuntimes2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
|
|
58745
|
+
const availableRuntimes = await detectAvailableRuntimes2();
|
|
58746
|
+
const hasCliRuntime = availableRuntimes.size > 0;
|
|
58747
|
+
if (!hasApiKey && !hasCliRuntime) {
|
|
58748
|
+
warnings.push({ stage: "research", message: "No API key or CLI runtime available. AI analysis skipped. Set OPENROUTER_API_KEY, ANTHROPIC_API_KEY, or OPENAI_API_KEY." });
|
|
58749
|
+
emit({ type: "stage:end", stage: "research", message: "Skipped \u2014 no API key or CLI runtime" });
|
|
58750
|
+
emit({ type: "stage:end", stage: "verify", message: "Skipped" });
|
|
58751
|
+
}
|
|
58711
58752
|
let findings2 = [];
|
|
58712
|
-
if (
|
|
58713
|
-
|
|
58714
|
-
const
|
|
58715
|
-
|
|
58716
|
-
|
|
58717
|
-
|
|
58718
|
-
|
|
58719
|
-
|
|
58720
|
-
|
|
58721
|
-
|
|
58722
|
-
|
|
58723
|
-
|
|
58724
|
-
|
|
58725
|
-
|
|
58726
|
-
|
|
58727
|
-
|
|
58728
|
-
|
|
58729
|
-
|
|
58730
|
-
|
|
58731
|
-
|
|
58732
|
-
|
|
58733
|
-
|
|
58734
|
-
|
|
58753
|
+
if (hasApiKey || hasCliRuntime) {
|
|
58754
|
+
emit({ type: "stage:start", stage: "research", message: "Researching vulnerabilities..." });
|
|
58755
|
+
const researchEmit = (event) => {
|
|
58756
|
+
if (event.type === "stage:start") {
|
|
58757
|
+
emit({ type: "stage:start", stage: "research", message: event.message });
|
|
58758
|
+
} else if (event.type === "finding") {
|
|
58759
|
+
emit(event);
|
|
58760
|
+
}
|
|
58761
|
+
};
|
|
58762
|
+
if (prepared.resolvedType === "npm-package" || prepared.resolvedType === "source-code") {
|
|
58763
|
+
const targetLabel = prepared.resolvedType === "npm-package" ? `npm package ${prepared.packageName}@${prepared.packageVersion}` : "repository";
|
|
58764
|
+
const agentSystemPrompt = researchPrompt(prepared.scopePath, semgrepFindings.map((f) => ({ ruleId: f.ruleId, message: f.message, path: f.path, startLine: f.startLine })), npmAuditFindings.map((f) => ({ name: f.name, severity: f.severity, title: f.title })), targetLabel);
|
|
58765
|
+
findings2 = await runAnalysisAgent({
|
|
58766
|
+
role: prepared.resolvedType === "npm-package" ? "audit" : "review",
|
|
58767
|
+
scopePath: prepared.scopePath,
|
|
58768
|
+
target: prepared.resolvedTarget,
|
|
58769
|
+
scanId,
|
|
58770
|
+
config: {
|
|
58771
|
+
runtime: opts.runtime,
|
|
58772
|
+
timeout: opts.timeout,
|
|
58773
|
+
depth: opts.depth,
|
|
58774
|
+
apiKey: opts.apiKey,
|
|
58775
|
+
model: opts.model
|
|
58776
|
+
},
|
|
58777
|
+
db,
|
|
58778
|
+
emit: researchEmit,
|
|
58779
|
+
cliPrompt: buildCliPrompt(prepared.scopePath, semgrepFindings, npmAuditFindings, targetLabel),
|
|
58780
|
+
agentSystemPrompt,
|
|
58781
|
+
cliSystemPrompt: "You are a security researcher performing an authorized source code audit. For EACH vulnerability you find, output it using the exact ---FINDING--- / ---END--- format specified in the prompt. Do NOT write prose analysis \u2014 only output structured finding blocks. If you find no vulnerabilities, say 'No vulnerabilities found.' and nothing else."
|
|
58782
|
+
});
|
|
58783
|
+
} else {
|
|
58784
|
+
warnings.push({
|
|
58785
|
+
stage: "research",
|
|
58786
|
+
message: `Target type "${prepared.resolvedType}" is not yet supported in the unified pipeline. Use 'pwnkit scan' for URL/web-app targets.`
|
|
58787
|
+
});
|
|
58788
|
+
}
|
|
58789
|
+
emit({
|
|
58790
|
+
type: "stage:end",
|
|
58735
58791
|
stage: "research",
|
|
58736
|
-
message:
|
|
58792
|
+
message: `${findings2.length} findings discovered`
|
|
58737
58793
|
});
|
|
58738
|
-
|
|
58739
|
-
|
|
58740
|
-
|
|
58741
|
-
|
|
58742
|
-
|
|
58743
|
-
|
|
58744
|
-
|
|
58745
|
-
|
|
58746
|
-
|
|
58747
|
-
|
|
58748
|
-
|
|
58749
|
-
|
|
58750
|
-
|
|
58751
|
-
|
|
58752
|
-
|
|
58753
|
-
|
|
58754
|
-
|
|
58755
|
-
|
|
58756
|
-
|
|
58757
|
-
|
|
58758
|
-
|
|
58759
|
-
|
|
58760
|
-
|
|
58761
|
-
|
|
58762
|
-
|
|
58763
|
-
|
|
58764
|
-
|
|
58765
|
-
|
|
58766
|
-
|
|
58767
|
-
|
|
58768
|
-
apiKey: opts.apiKey,
|
|
58769
|
-
model: opts.model
|
|
58770
|
-
},
|
|
58771
|
-
db,
|
|
58772
|
-
emit: verifyEmit,
|
|
58773
|
-
cliPrompt: `Verify this vulnerability in ${filePath}:
|
|
58794
|
+
if (findings2.length > 0 && (prepared.resolvedType === "source-code" || prepared.resolvedType === "npm-package")) {
|
|
58795
|
+
emit({ type: "stage:start", stage: "verify", message: `Blind-verifying ${findings2.length} findings...` });
|
|
58796
|
+
try {
|
|
58797
|
+
const verifyResults = await Promise.all(findings2.map(async (finding) => {
|
|
58798
|
+
const filePath = finding.evidence.request || "";
|
|
58799
|
+
const poc = finding.evidence.response || finding.evidence.analysis || "";
|
|
58800
|
+
const claimedSeverity = finding.severity;
|
|
58801
|
+
const verifySystemPrompt = blindVerifyPrompt(filePath, poc, claimedSeverity, prepared.scopePath);
|
|
58802
|
+
const verifyEmit = (event) => {
|
|
58803
|
+
if (event.type === "finding") {
|
|
58804
|
+
emit({ type: "verify:result", message: `Confirmed: ${finding.title}`, data: { confirmed: true, finding } });
|
|
58805
|
+
}
|
|
58806
|
+
};
|
|
58807
|
+
try {
|
|
58808
|
+
const verifiedFindings = await runAnalysisAgent({
|
|
58809
|
+
role: "review",
|
|
58810
|
+
scopePath: prepared.scopePath,
|
|
58811
|
+
target: prepared.resolvedTarget,
|
|
58812
|
+
scanId,
|
|
58813
|
+
config: {
|
|
58814
|
+
runtime: "api",
|
|
58815
|
+
// API runtime: cheaper and faster for focused verification
|
|
58816
|
+
timeout: Math.min(opts.timeout ?? 12e4, 12e4),
|
|
58817
|
+
depth: "quick",
|
|
58818
|
+
apiKey: opts.apiKey,
|
|
58819
|
+
model: opts.model
|
|
58820
|
+
},
|
|
58821
|
+
db,
|
|
58822
|
+
emit: verifyEmit,
|
|
58823
|
+
cliPrompt: `Verify this vulnerability in ${filePath}:
|
|
58774
58824
|
|
|
58775
58825
|
PoC:
|
|
58776
58826
|
${poc}
|
|
@@ -58778,44 +58828,46 @@ ${poc}
|
|
|
58778
58828
|
Claimed severity: ${claimedSeverity}
|
|
58779
58829
|
|
|
58780
58830
|
Read the file, trace data flow, confirm or reject.`,
|
|
58781
|
-
|
|
58782
|
-
|
|
58783
|
-
|
|
58784
|
-
|
|
58785
|
-
|
|
58786
|
-
|
|
58787
|
-
|
|
58788
|
-
|
|
58789
|
-
|
|
58790
|
-
|
|
58791
|
-
|
|
58792
|
-
|
|
58793
|
-
|
|
58794
|
-
|
|
58795
|
-
|
|
58796
|
-
|
|
58797
|
-
|
|
58798
|
-
|
|
58799
|
-
|
|
58800
|
-
|
|
58801
|
-
|
|
58802
|
-
|
|
58803
|
-
|
|
58804
|
-
|
|
58805
|
-
|
|
58806
|
-
|
|
58807
|
-
|
|
58808
|
-
|
|
58809
|
-
|
|
58810
|
-
|
|
58811
|
-
|
|
58812
|
-
|
|
58813
|
-
|
|
58814
|
-
|
|
58815
|
-
|
|
58816
|
-
|
|
58817
|
-
|
|
58818
|
-
|
|
58831
|
+
agentSystemPrompt: verifySystemPrompt,
|
|
58832
|
+
cliSystemPrompt: "You are a blind verification agent. Read the file, trace the PoC, confirm or reject the vulnerability."
|
|
58833
|
+
});
|
|
58834
|
+
const confirmed = verifiedFindings.length > 0;
|
|
58835
|
+
const rejectionReason = confirmed ? void 0 : "Could not independently reproduce";
|
|
58836
|
+
return { finding, confirmed, verifiedFinding: verifiedFindings[0] ?? null, rejectionReason };
|
|
58837
|
+
} catch (err) {
|
|
58838
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
58839
|
+
warnings.push({ stage: "verify", message: `Verification failed for "${finding.title}": ${msg}` });
|
|
58840
|
+
return { finding, confirmed: true, verifiedFinding: null };
|
|
58841
|
+
}
|
|
58842
|
+
}));
|
|
58843
|
+
let confirmedCount = 0;
|
|
58844
|
+
let rejectedCount = 0;
|
|
58845
|
+
findings2 = verifyResults.map(({ finding, confirmed, verifiedFinding, rejectionReason }) => {
|
|
58846
|
+
if (confirmed) {
|
|
58847
|
+
confirmedCount++;
|
|
58848
|
+
emit({ type: "verify:result", message: `Confirmed: ${finding.title}`, data: { confirmed: true, title: finding.title } });
|
|
58849
|
+
return {
|
|
58850
|
+
...finding,
|
|
58851
|
+
status: "verified",
|
|
58852
|
+
confidence: verifiedFinding?.confidence ?? finding.confidence,
|
|
58853
|
+
severity: verifiedFinding?.severity ?? finding.severity
|
|
58854
|
+
};
|
|
58855
|
+
} else {
|
|
58856
|
+
rejectedCount++;
|
|
58857
|
+
emit({ type: "verify:result", message: `Rejected: ${finding.title}`, data: { confirmed: false, title: finding.title, reason: rejectionReason ?? "Could not independently reproduce" } });
|
|
58858
|
+
return { ...finding, status: "false-positive" };
|
|
58859
|
+
}
|
|
58860
|
+
});
|
|
58861
|
+
emit({
|
|
58862
|
+
type: "stage:end",
|
|
58863
|
+
stage: "verify",
|
|
58864
|
+
message: `Verification complete: ${confirmedCount} confirmed, ${rejectedCount} rejected`
|
|
58865
|
+
});
|
|
58866
|
+
} catch (err) {
|
|
58867
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
58868
|
+
warnings.push({ stage: "verify", message: `Verification failed: ${msg}` });
|
|
58869
|
+
emit({ type: "stage:end", stage: "verify", message: `Verification failed: ${msg}` });
|
|
58870
|
+
}
|
|
58819
58871
|
}
|
|
58820
58872
|
}
|
|
58821
58873
|
const confirmedFindings = findings2.filter((f) => f.status !== "false-positive");
|
|
@@ -59577,7 +59629,7 @@ function createEventHandler(opts) {
|
|
|
59577
59629
|
// packages/cli/src/commands/scan.ts
|
|
59578
59630
|
init_utils();
|
|
59579
59631
|
function registerScanCommand(program3) {
|
|
59580
|
-
program3.command("scan").description("Run security scan against an LLM endpoint").requiredOption("--target <url>", "Target API endpoint URL").option("--depth <depth>", "Scan depth: quick, default, deep", "default").option("--format <format>", "Output format: terminal, json, md", "terminal").option("--runtime <runtime>", "Runtime: api, claude, codex, gemini,
|
|
59632
|
+
program3.command("scan").description("Run security scan against an LLM endpoint").requiredOption("--target <url>", "Target API endpoint URL").option("--depth <depth>", "Scan depth: quick, default, deep", "default").option("--format <format>", "Output format: terminal, json, md", "terminal").option("--runtime <runtime>", "Runtime: api, claude, codex, gemini, auto", "api").option("--mode <mode>", "Scan mode: probe, deep, mcp, web", "probe").option("--repo <path>", "Path to target repo for deep scan source analysis").option("--timeout <ms>", "Request timeout in milliseconds", "30000").option("--agentic", "Use multi-turn agentic scan with tool use and SQLite persistence", false).option("--db-path <path>", "Path to SQLite database (default: ~/.pwnkit/pwnkit.db)").option("--api-key <key>", "API key for LLM provider (or set OPENROUTER_API_KEY / ANTHROPIC_API_KEY / OPENAI_API_KEY)").option("--model <model>", "LLM model to use (or set PWNKIT_MODEL)").option("--verbose", "Show detailed output with live attack replay", false).option("--replay", "Replay the last scan's results as an animated attack chain", false).action(async (opts) => {
|
|
59581
59633
|
const depth = opts.depth;
|
|
59582
59634
|
const format = opts.format === "md" ? "markdown" : opts.format;
|
|
59583
59635
|
const runtime = opts.runtime;
|
|
@@ -59635,7 +59687,7 @@ function registerScanCommand(program3) {
|
|
|
59635
59687
|
process.exit(2);
|
|
59636
59688
|
}
|
|
59637
59689
|
}
|
|
59638
|
-
const validRuntimes = ["api", "claude", "codex", "gemini", "
|
|
59690
|
+
const validRuntimes = ["api", "claude", "codex", "gemini", "auto"];
|
|
59639
59691
|
if (!validRuntimes.includes(runtime)) {
|
|
59640
59692
|
console.error(
|
|
59641
59693
|
source_default.red(`Unknown runtime '${runtime}'. Valid: ${validRuntimes.join(", ")}`)
|
|
@@ -59644,7 +59696,7 @@ function registerScanCommand(program3) {
|
|
|
59644
59696
|
}
|
|
59645
59697
|
if (mode !== "probe" && mode !== "web" && runtime === "api") {
|
|
59646
59698
|
console.error(
|
|
59647
|
-
source_default.red(`Mode '${mode}' requires a process runtime (claude, codex, gemini,
|
|
59699
|
+
source_default.red(`Mode '${mode}' requires a process runtime (claude, codex, gemini, or auto)`)
|
|
59648
59700
|
);
|
|
59649
59701
|
process.exit(2);
|
|
59650
59702
|
}
|
|
@@ -59957,7 +60009,7 @@ init_source();
|
|
|
59957
60009
|
init_dist();
|
|
59958
60010
|
init_utils();
|
|
59959
60011
|
function registerReviewCommand(program3) {
|
|
59960
|
-
program3.command("review").description("Deep source code security review of a repository").argument("<repo>", "Local path or git URL to review").option("--depth <depth>", "Review depth: quick, default, deep", "default").option("--format <format>", "Output format: terminal, json, md", "terminal").option("--runtime <runtime>", "Runtime: auto, claude, codex, gemini,
|
|
60012
|
+
program3.command("review").description("Deep source code security review of a repository").argument("<repo>", "Local path or git URL to review").option("--depth <depth>", "Review depth: quick, default, deep", "default").option("--format <format>", "Output format: terminal, json, md", "terminal").option("--runtime <runtime>", "Runtime: auto, claude, codex, gemini, api", "auto").option("--db-path <path>", "Path to SQLite database").option("--api-key <key>", "API key for LLM provider (or set OPENROUTER_API_KEY / ANTHROPIC_API_KEY / OPENAI_API_KEY)").option("--model <model>", "LLM model to use (or set PWNKIT_MODEL)").option("--verbose", "Show detailed output", false).option("--timeout <ms>", "AI agent timeout in milliseconds", "600000").action(async (repo, opts) => {
|
|
59961
60013
|
const depth = opts.depth ?? "default";
|
|
59962
60014
|
const format = opts.format === "md" ? "markdown" : opts.format;
|
|
59963
60015
|
const runtime = opts.runtime;
|
|
@@ -60026,12 +60078,12 @@ init_source();
|
|
|
60026
60078
|
init_dist();
|
|
60027
60079
|
init_utils();
|
|
60028
60080
|
function registerAuditCommand(program3) {
|
|
60029
|
-
program3.command("audit").description("Audit an npm package for security vulnerabilities").argument("<package>", "npm package name (e.g. lodash, express)").option("--version <version>", "Specific version to audit (default: latest)").option("--depth <depth>", "Audit depth: quick, default, deep", "default").option("--format <format>", "Output format: terminal, json, md", "terminal").option("--runtime <runtime>", "Runtime: auto, claude, codex, gemini,
|
|
60081
|
+
program3.command("audit").description("Audit an npm package for security vulnerabilities").argument("<package>", "npm package name (e.g. lodash, express)").option("--version <version>", "Specific version to audit (default: latest)").option("--depth <depth>", "Audit depth: quick, default, deep", "default").option("--format <format>", "Output format: terminal, json, md", "terminal").option("--runtime <runtime>", "Runtime: auto, claude, codex, gemini, api", "auto").option("--db-path <path>", "Path to SQLite database").option("--api-key <key>", "API key for LLM provider (or set OPENROUTER_API_KEY / ANTHROPIC_API_KEY / OPENAI_API_KEY)").option("--model <model>", "LLM model to use (or set PWNKIT_MODEL)").option("--verbose", "Show detailed output", false).option("--timeout <ms>", "AI agent timeout in milliseconds", "600000").action(async (packageName, opts) => {
|
|
60030
60082
|
const depth = opts.depth ?? "default";
|
|
60031
60083
|
const format = opts.format === "md" ? "markdown" : opts.format;
|
|
60032
60084
|
const runtime = opts.runtime;
|
|
60033
60085
|
const verbose = opts.verbose;
|
|
60034
|
-
const validRuntimes = ["api", "claude", "codex", "gemini", "
|
|
60086
|
+
const validRuntimes = ["api", "claude", "codex", "gemini", "auto"];
|
|
60035
60087
|
if (!validRuntimes.includes(runtime)) {
|
|
60036
60088
|
console.error(
|
|
60037
60089
|
source_default.red(`Unknown runtime '${runtime}'. Valid: ${validRuntimes.join(", ")}`)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pwnkit-cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.1",
|
|
5
5
|
"description": "AI-powered agentic security scanner. Scan endpoints, audit packages, review source code. Autonomous agents discover, attack, verify, and report.",
|
|
6
6
|
"bin": {
|
|
7
7
|
"pwnkit": "dist/index.js"
|