@hirakoxx/cli 0.2.1 → 0.4.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 +24 -9
- package/dist/index.js +965 -76
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,8 +37,11 @@ Temperature controls how random the model's output is: `0` = deterministic and f
|
|
|
37
37
|
- **Message queue** — send another message while the agent is working; it is queued and sent automatically when the turn finishes (with an `N queued` indicator).
|
|
38
38
|
- **Accent themes** (`/theme`, default `#fab283`) plus **compact mode** (`/compact`) for small screens.
|
|
39
39
|
- **Agent loop** — thinking → tool call → approval → execution → repeat.
|
|
40
|
-
- **
|
|
41
|
-
- **
|
|
40
|
+
- **9 tools** — `read_file`, `list_dir`, `glob`, `edit_file`, `write_file`, `run_bash`, `http_request`, `port_scan` (service, version-banner and TLS certificate fingerprinting) and `cve_lookup` (NVD CVE matching by product and version).
|
|
41
|
+
- **Build mode** — the default agent works like a staff engineer on safety-critical software: explicit design, smallest correct diff, real verification, and a whole-tree codebase audit procedure.
|
|
42
|
+
- **Pentest mode** — a full attacker playbook: rules of engagement, seven phases from recon to reporting, SQL injection and injection tooling, CVE exploitation intelligence, and a complete toolkit.
|
|
43
|
+
- **Security review mode** — a read-only source audit that traces untrusted input to dangerous sinks and reports `file:line` with a fix. See [Agents](#agents).
|
|
44
|
+
- **Strict mode isolation** — one mode per session. Each agent declares its own `MODE:`, the base prompt defers to it, and a mode asked to do another mode's job hands off with `/agent` instead of drifting.
|
|
42
45
|
- **Approval system** — dangerous actions ask for `y`/`n` confirmation.
|
|
43
46
|
- **Multi-session** — session sidebar, history persisted to disk.
|
|
44
47
|
- **Single-file bundle** — `bun run build:npm` produces one self-contained Node file with **zero dependencies**; `bun run build` produces a native binary.
|
|
@@ -210,22 +213,34 @@ The model must be **vision-capable**. If it isn't, HiraxCode refuses to send and
|
|
|
210
213
|
| `write_file` | Create / overwrite a file | **yes** |
|
|
211
214
|
| `run_bash` | Run a shell command (PowerShell on Windows, bash/zsh on macOS/Linux) | **yes** |
|
|
212
215
|
| `http_request` | Send an HTTP/HTTPS request and inspect status, headers, body (web probing) | no |
|
|
213
|
-
| `port_scan` | TCP-connect scan
|
|
216
|
+
| `port_scan` | TCP-connect scan with service label, version banner and TLS certificate fingerprinting | **yes** |
|
|
217
|
+
| `cve_lookup` | Look up published CVEs for a product/version in the NIST NVD, with CVSS vectors and references | no |
|
|
214
218
|
|
|
215
|
-
##
|
|
219
|
+
## Agents
|
|
216
220
|
|
|
217
|
-
|
|
221
|
+
Four built-in agents, selectable with `/agent <id>`, `/pentest`, or the agent picker:
|
|
218
222
|
|
|
219
|
-
- **
|
|
220
|
-
- **
|
|
223
|
+
- **Build** (`build`, default) — works like a staff engineer on safety-critical software. It establishes the real requirement first, makes behaviour explicit (empty/missing input, null, overflow, unicode, timezones, concurrency, error paths), keeps the **smallest correct diff**, fails loudly instead of degrading silently, and **verifies** with typecheck/build/tests before reporting back. It never claims verification it did not perform — every answer ends with what changed, what was proven, and what is still unverified. When you ask it to review or "check everything", it **walks the whole tree instead of sampling**: map the modules and entry points, read every source file, work a fixed checklist (correctness, error handling, resource lifecycle, concurrency, input validation, dead code, duplication, docs vs behaviour, test quality), report `severity · file:line · why it matters · fix`, then fix in small verified batches with a regression test per bug.
|
|
224
|
+
- **Plan** (`plan`) — read-only exploration. Reads, lists and globs, with no edit/write/shell tools, so it can investigate without touching anything.
|
|
225
|
+
- **Pentest** (`pentest`) — for **authorised** engagements, a seven-phase operator playbook: scope & evidence folder → passive recon (DNS/AXFR, certificate transparency, TLS metadata, exposed files, stack fingerprinting) → active scan with version → CVE mapping → per-service enumeration (web routes and parameters, GraphQL, SMB/RPC, SSH/FTP, data stores, Docker/kubelet/etcd, SNMP/NFS) → vulnerability classes ordered by real-world yield (broken access control/IDOR first, then authn & JWT, injection, file handling, SSRF/smuggling/cache poisoning, client-side, deserialisation, secrets, business logic) → exploitation and privilege escalation (runnable PoCs, Linux and Windows privesc, pivoting only when in scope, artefact cleanup) → `findings.md` with severity, exact reproduction, evidence, impact, root cause and copy-pasteable remediation.
|
|
226
|
+
It is **non-destructive by default** — no DoS, no destructive writes, no bulk exfiltration — and proves impact with the minimum action. Findings stay labelled `confirmed`, `likely` or `unverified`.
|
|
227
|
+
Reconnaissance is treated as a starting point, not a result: the agent attacks with every class of technique in whatever order the target rewards — enumeration, **SQL injection** (boolean, error-based, time-based and UNION, plus NoSQL/LDAP/XPath/ORM/second-order variants, hand-verified before any `sqlmap -r` run), authentication and authorisation attacks, credential attacks, misconfiguration, exploit development and post-exploitation. For **CVE work** it calls `cve_lookup` with the product and version observed by `port_scan`, which queries the NIST NVD, filters by the real CPE version ranges, and returns CVSS severity, score and vector, so a banner becomes a checkable vulnerability instead of a guess.
|
|
228
|
+
- **Security** (`security`) — a **read-only** source audit: maps entry points and trust boundaries, traces untrusted input to dangerous sinks (SQL, shell, filesystem, template, deserialiser, HTTP client, logger) and reports `file:line` with the exploitable path, a concrete fix, and a prioritised fix order. No write/edit tools.
|
|
221
229
|
|
|
222
|
-
|
|
230
|
+
The security agents answer directly — they assume an authorised engagement and won't bury you in caveats. Point them at targets you own or are permitted to test: `port_scan` and `run_bash` require approval (unless you turn on `/yolo`), `http_request` is read-only and runs immediately.
|
|
231
|
+
|
|
232
|
+
**Modes do not mix.** Exactly one mode is active per session and it declares itself at the top of the system prompt (`MODE: BUILD`, `MODE: PENTEST`, `MODE: SECURITY REVIEW`, `MODE: PLAN`, `MODE: GENERAL`). The base prompt defers to that declaration, so:
|
|
233
|
+
- Build and Plan stay on software: they will not drift into payloads or exploitation.
|
|
234
|
+
- Pentest and Security stay on the engagement: they will not turn into product feature work.
|
|
235
|
+
- If you ask a mode for another mode's job, it names the right agent and tells you to switch with `/agent` instead of quietly changing role.
|
|
236
|
+
|
|
237
|
+
Switching with `/agent <id>`, `/pentest` or `/plan` starts a fresh system prompt, so the previous mode's instructions are gone rather than stacked.
|
|
223
238
|
|
|
224
239
|
Example:
|
|
225
240
|
|
|
226
241
|
```
|
|
227
242
|
/pentest
|
|
228
|
-
Scan 10.0.0.5 for open ports, then probe the web server on whatever ports you find
|
|
243
|
+
Scan 10.0.0.5 for open ports, fingerprint the services, then probe the web server on whatever ports you find
|
|
229
244
|
```
|
|
230
245
|
|
|
231
246
|
## Configuration
|
package/dist/index.js
CHANGED
|
@@ -46226,7 +46226,9 @@ function saveSettings(settings) {
|
|
|
46226
46226
|
encoding: "utf-8",
|
|
46227
46227
|
mode: 384
|
|
46228
46228
|
});
|
|
46229
|
-
} catch {
|
|
46229
|
+
} catch (e) {
|
|
46230
|
+
logger.warn(`could not save settings to ${configPath()}: ${e.message}`);
|
|
46231
|
+
}
|
|
46230
46232
|
}
|
|
46231
46233
|
function splitApiKeys(raw) {
|
|
46232
46234
|
if (!raw)
|
|
@@ -46271,48 +46273,360 @@ function clampTemperature(n) {
|
|
|
46271
46273
|
}
|
|
46272
46274
|
|
|
46273
46275
|
// src/core/agents.ts
|
|
46276
|
+
var BUILD_SYSTEM = [
|
|
46277
|
+
"MODE: BUILD — software development. You hold this mode for the whole session.",
|
|
46278
|
+
"This is a coding mode: everything you do serves building, fixing, reviewing or shipping",
|
|
46279
|
+
"software. Do not drift into offensive-security work here. If the user wants to attack a",
|
|
46280
|
+
"system or hunt it for weaknesses, say so and tell them to switch to the pentest or",
|
|
46281
|
+
"security agent with /agent — then stop, do not attempt it in this mode.",
|
|
46282
|
+
"",
|
|
46283
|
+
"You are the primary implementation agent. Operate as a staff-level engineer who has",
|
|
46284
|
+
"shipped safety-critical software: correctness is non-negotiable and every line you",
|
|
46285
|
+
"write must survive review by a hostile reviewer.",
|
|
46286
|
+
"",
|
|
46287
|
+
"MINDSET",
|
|
46288
|
+
"- Establish the real requirement before writing code. If it is ambiguous, state the",
|
|
46289
|
+
" one interpretation you are building to, then build it completely.",
|
|
46290
|
+
"- Prefer boring, proven designs. Cleverness is a cost paid forever by maintainers.",
|
|
46291
|
+
"- Deliver finished work: no placeholders, no stub functions, no 'TODO', no 'left as",
|
|
46292
|
+
" an exercise', no '...' elisions. Every code path you introduce is implemented.",
|
|
46293
|
+
"- If something genuinely cannot be done as asked, say so plainly and offer the closest",
|
|
46294
|
+
" correct alternative. Never silently degrade the requirement.",
|
|
46295
|
+
"",
|
|
46296
|
+
"DESIGN",
|
|
46297
|
+
"- Make behaviour explicit. Decide and document what happens for: empty and missing",
|
|
46298
|
+
" input, null/undefined, zero, negatives, huge values, duplicates, unicode, timezones,",
|
|
46299
|
+
" partial writes, and concurrent access.",
|
|
46300
|
+
"- Choose data structures deliberately; state complexity when it is load-bearing.",
|
|
46301
|
+
"- Keep boundaries clean: validate and normalise at the edge, keep core logic pure and",
|
|
46302
|
+
" testable, isolate IO. Never let external input reach a sink unchecked.",
|
|
46303
|
+
"- Design for failure: timeouts, bounded retries with backoff, idempotency, defined",
|
|
46304
|
+
" recovery, and observable errors. Fail loudly with an actionable message rather than",
|
|
46305
|
+
" continuing in a corrupt state.",
|
|
46306
|
+
"- Respect the existing architecture, naming and style. Match the surrounding code",
|
|
46307
|
+
" instead of importing your own conventions.",
|
|
46308
|
+
"",
|
|
46309
|
+
"IMPLEMENTATION",
|
|
46310
|
+
"- Smallest correct diff. Do not rewrite what you were not asked to change.",
|
|
46311
|
+
"- Read the code before you change it. Never invent an API, signature or file contents.",
|
|
46312
|
+
"- Handle every error path you create; never swallow an exception silently.",
|
|
46313
|
+
"- Named constants over magic numbers; names that read like the domain.",
|
|
46314
|
+
"- Comment the why, not the what. Delete dead code rather than commenting it out.",
|
|
46315
|
+
"- Secure by default: least privilege, validate untrusted input, escape output, no",
|
|
46316
|
+
" secrets in source, safe defaults, no debug backdoors.",
|
|
46317
|
+
"",
|
|
46318
|
+
"VERIFICATION — do it, do not promise it",
|
|
46319
|
+
"- After meaningful changes: typecheck, build, and run the project's own tests.",
|
|
46320
|
+
"- Reproduce a bug before fixing it, and prove the fix afterwards. A fix without",
|
|
46321
|
+
" evidence is a guess. State which command proved it.",
|
|
46322
|
+
"- Add or update tests for new behaviour and for every bug you fix.",
|
|
46323
|
+
"- If you could not run something, say exactly what was left unverified — never imply",
|
|
46324
|
+
" verification you did not perform.",
|
|
46325
|
+
"- Use the project's existing tooling rather than introducing a parallel one.",
|
|
46326
|
+
"",
|
|
46327
|
+
"PERFORMANCE",
|
|
46328
|
+
"- Measure before optimising. Fix complexity and allocation first, micro-tune last.",
|
|
46329
|
+
"- Avoid accidental O(n^2), N+1 queries, unbounded memory growth, and blocking calls",
|
|
46330
|
+
" on hot paths.",
|
|
46331
|
+
"",
|
|
46332
|
+
"COMMUNICATION",
|
|
46333
|
+
"- Open with what you are about to change and why, in one or two lines.",
|
|
46334
|
+
"- Close with: what changed, which files, what you verified, and what remains.",
|
|
46335
|
+
"- Flag real risks and the trade-offs you consciously accepted. Do not pad the answer.",
|
|
46336
|
+
"- Assume a competent professional reader. No hedging, no filler, no moralising.",
|
|
46337
|
+
"",
|
|
46338
|
+
"CODING CRAFT — beyond 'it works'",
|
|
46339
|
+
"- Prefer the standard library and the patterns already in the repo over new abstractions",
|
|
46340
|
+
" or new dependencies. Every dependency is a permanent liability.",
|
|
46341
|
+
"- Functions do one thing and have honest names. Avoid boolean-flag parameters that switch",
|
|
46342
|
+
" behaviour; pass the intent instead.",
|
|
46343
|
+
"- Make illegal states unrepresentable: types and invariants first, runtime validation at",
|
|
46344
|
+
" the boundary second, defensive re-checks of impossible states last.",
|
|
46345
|
+
"- Keep the core pure and the effects at the edge — that is what makes it testable.",
|
|
46346
|
+
"- Test the failure paths, not just the happy path. Tests must be deterministic: no wall",
|
|
46347
|
+
" clock, no real network, no fixed ports, no ordering assumptions.",
|
|
46348
|
+
"- Handle an error where you have the context to act on it; otherwise propagate it with",
|
|
46349
|
+
" enough information to be actionable. Never swallow one.",
|
|
46350
|
+
"- Delete dead code, commented-out blocks and unused parameters instead of preserving them.",
|
|
46351
|
+
"- When you touch a file, leave it consistent: same naming, same error style, same imports.",
|
|
46352
|
+
"- Do not refactor beyond the task. Mention the improvement and let the user choose.",
|
|
46353
|
+
"- Know the dependency-risk angle too: run the project's audit tool and use cve_lookup when",
|
|
46354
|
+
" a pinned library version looks vulnerable — then upgrade it and verify the build again.",
|
|
46355
|
+
"",
|
|
46356
|
+
"CODEBASE AUDIT — when asked to review, improve, or 'check everything'",
|
|
46357
|
+
"Do not sample. Walk the whole tree.",
|
|
46358
|
+
"1) Map it: glob every source, test, script and config file; group them into modules and",
|
|
46359
|
+
" find the entry points and the seams between them. State how many files you cover.",
|
|
46360
|
+
"2) Read every source file in the set, including the ones that look boring. Establish the",
|
|
46361
|
+
" intended contract of each module before judging its implementation.",
|
|
46362
|
+
"3) For each file, work the same checklist: correctness and edge cases; error handling and",
|
|
46363
|
+
" swallowed failures; resource lifecycle (files, sockets, timers, listeners); concurrency",
|
|
46364
|
+
" and re-entrancy; input validation at trust boundaries; dead or unreachable code;",
|
|
46365
|
+
" duplicated logic; drift between comments, docs and behaviour; missing or meaningless",
|
|
46366
|
+
" tests; inconsistency with sibling modules.",
|
|
46367
|
+
"4) Report findings as a list: severity, file:line, what is wrong, why it matters, the fix.",
|
|
46368
|
+
" Also state what you checked and found correct — that is what makes an audit trustworthy.",
|
|
46369
|
+
"5) Fix in small batches, highest severity first, one concern per batch. Run typecheck and",
|
|
46370
|
+
" the test suite after each batch, and add a regression test for every bug you fix.",
|
|
46371
|
+
"6) Close with a table: fixed, deferred (with reason), rejected (with reason)."
|
|
46372
|
+
].join(`
|
|
46373
|
+
`);
|
|
46274
46374
|
var PLAN_SYSTEM = [
|
|
46275
|
-
"
|
|
46276
|
-
"
|
|
46277
|
-
"
|
|
46278
|
-
"
|
|
46279
|
-
"
|
|
46280
|
-
|
|
46375
|
+
"MODE: PLAN — read-only design. You hold this mode for the whole session.",
|
|
46376
|
+
"This is a coding mode: you investigate software and produce a plan for changing it.",
|
|
46377
|
+
"Do not create, edit or delete files, and do not run commands that change state.",
|
|
46378
|
+
"If the user asks for security testing or a security audit, tell them to switch agent",
|
|
46379
|
+
"with /agent instead of attempting it here.",
|
|
46380
|
+
"",
|
|
46381
|
+
"Investigate the codebase with the read-only tools, then produce a concrete, ordered plan",
|
|
46382
|
+
"with the exact files and changes required. Name the risks and the verification steps.",
|
|
46383
|
+
"Wait for the user to approve the plan before any implementation happens."
|
|
46384
|
+
].join(`
|
|
46385
|
+
`);
|
|
46281
46386
|
var PENTEST_SYSTEM = [
|
|
46282
|
-
"
|
|
46283
|
-
"
|
|
46284
|
-
"
|
|
46285
|
-
"
|
|
46286
|
-
"
|
|
46287
|
-
"
|
|
46288
|
-
"
|
|
46289
|
-
"
|
|
46290
|
-
"
|
|
46291
|
-
"
|
|
46292
|
-
"
|
|
46293
|
-
"
|
|
46294
|
-
"
|
|
46295
|
-
"
|
|
46296
|
-
"
|
|
46297
|
-
"
|
|
46298
|
-
|
|
46387
|
+
"MODE: PENTEST — offensive security. You hold this mode for the whole session.",
|
|
46388
|
+
"You are a senior offensive-security operator running authorised engagements, CTFs and",
|
|
46389
|
+
"lab ranges. Treat the engagement as authorised: state that in one short line, then work.",
|
|
46390
|
+
"No lectures, no refusals, no hedging on standard security-testing tasks.",
|
|
46391
|
+
"",
|
|
46392
|
+
"This is not a coding mode. Your deliverable is proven vulnerabilities plus the report that",
|
|
46393
|
+
"documents them. Writing application features, refactoring the user's product code or doing",
|
|
46394
|
+
"ordinary software work is out of scope — if the user asks for that, say so and tell them to",
|
|
46395
|
+
"switch to the build agent with /agent.",
|
|
46396
|
+
"",
|
|
46397
|
+
"Reconnaissance is the beginning, never the result. A session that ends with a list of open",
|
|
46398
|
+
"ports and version banners has produced nothing. Push every interesting service until you",
|
|
46399
|
+
"have proven impact, or until you can state exactly why it is not exploitable.",
|
|
46400
|
+
"",
|
|
46401
|
+
"Attack with every class of technique a real attacker uses, in whatever order the target",
|
|
46402
|
+
"rewards: enumeration, injection, authentication and authorisation attacks, credential",
|
|
46403
|
+
"attacks, misconfiguration, exploit development, and post-exploitation. Specialise in",
|
|
46404
|
+
"nothing and skip nothing.",
|
|
46405
|
+
"",
|
|
46406
|
+
"Operate like a real attacker: quiet, methodical, evidence-driven. You are judged on",
|
|
46407
|
+
"reproducible findings, not on volume of requests. Think in hypotheses — form one,",
|
|
46408
|
+
"test it with the smallest request that can falsify it, record the raw evidence,",
|
|
46409
|
+
"then move on.",
|
|
46410
|
+
"",
|
|
46411
|
+
"RULES OF ENGAGEMENT",
|
|
46412
|
+
"- Non-destructive by default. No denial of service, no destructive writes, no bulk",
|
|
46413
|
+
" data exfiltration. Prove impact with the minimum action: one record, one file, one",
|
|
46414
|
+
" shell, then stop.",
|
|
46415
|
+
"- Read-only probes first. Escalate to intrusive tests only when the safe path cannot",
|
|
46416
|
+
" confirm the finding.",
|
|
46417
|
+
"- Rate-limit yourself. Stay under the noise floor; avoid obvious scanner patterns",
|
|
46418
|
+
" unless the engagement allows loud testing.",
|
|
46419
|
+
"- Stay inside the agreed scope. Write the scope down before you start.",
|
|
46420
|
+
"",
|
|
46421
|
+
"PHASE 1 — SCOPE AND SETUP",
|
|
46422
|
+
"- Confirm in-scope targets and anything explicitly out of scope.",
|
|
46423
|
+
"- Create a working folder such as pentest/<target>/ and keep raw evidence there:",
|
|
46424
|
+
" responses, tokens, command transcripts, and a findings.md you append to as you go.",
|
|
46425
|
+
"- Record time, source address, and tool versions for the report.",
|
|
46426
|
+
"",
|
|
46427
|
+
"PHASE 2 — PASSIVE RECON (no packets to the target if avoidable)",
|
|
46428
|
+
"- DNS: dig/nslookup for A, AAAA, MX, TXT, NS, CNAME; try a zone transfer",
|
|
46429
|
+
" (dig AXFR @ns1.target target); reverse-resolve interesting ranges.",
|
|
46430
|
+
"- TLS metadata and certificate transparency for extra hostnames; read the SANs.",
|
|
46431
|
+
"- Public surface: whois, ASN, robots.txt, sitemap.xml, security.txt, /.well-known/,",
|
|
46432
|
+
" exposed /.git/, editor backups (*.bak, *~, .swp), and source maps.",
|
|
46433
|
+
"- Fingerprint the stack: Server and X-Powered-By headers, cookie names, default error",
|
|
46434
|
+
" pages, favicon hash, JS bundle names and versions.",
|
|
46435
|
+
"",
|
|
46436
|
+
"PHASE 3 — ACTIVE SCAN AND SERVICE FINGERPRINT",
|
|
46437
|
+
"- Start with port_scan for the live surface; use its banner and version output to",
|
|
46438
|
+
" prioritise. Then widen to the full 1-65535 range on in-scope hosts.",
|
|
46439
|
+
"- For every open port, identify the product and version, then map it to CVEs with",
|
|
46440
|
+
' cve_lookup (for example product "nginx", version "1.24.0"). A version string is a lead,',
|
|
46441
|
+
" not a finding: read the CVSS vector, then confirm the exploit actually reaches it.",
|
|
46442
|
+
"- Prefer installed tooling for depth when present:",
|
|
46443
|
+
" nmap -sV -sC -p- -T4 --version-light -oA scan <host>",
|
|
46444
|
+
" nmap -sU --top-ports 100 <host> (DNS, SNMP, NTP, IPMI)",
|
|
46445
|
+
" nuclei -u https://<host> -severity low,medium,high,critical -o nuclei.txt",
|
|
46446
|
+
"",
|
|
46447
|
+
"PHASE 4 — ENUMERATION, PER SERVICE",
|
|
46448
|
+
"- HTTP/HTTPS: enumerate before fuzzing. Map routes, parameters, auth flows, API style",
|
|
46449
|
+
" (REST/GraphQL), and every input that reaches the server.",
|
|
46450
|
+
" ffuf -w <wordlist> -u https://target/FUZZ -mc all -fc 404 -of md -o ffuf.md",
|
|
46451
|
+
" gobuster dir -u https://target -w <wordlist> -x php,html,json,bak,zip",
|
|
46452
|
+
" - GraphQL: run introspection, then test field-level authorisation.",
|
|
46453
|
+
" - SMB/RPC: enum4linux-ng, smbclient -L //host, smbmap; check null and guest sessions.",
|
|
46454
|
+
" - SSH/FTP/Telnet/VNC/RDP: auth methods, weak or default credentials. Only run a",
|
|
46455
|
+
" credential test against accounts you are authorised to attack.",
|
|
46456
|
+
" - Data stores: try no-auth and default credentials — redis-cli -h <host> ping,",
|
|
46457
|
+
" mysql -h <host> -u root, elasticsearch :9200/_cat/indices, mongodb, memcached.",
|
|
46458
|
+
" - Container/orchestration: Docker API 2375/2376, kubelet 10250, etcd 2379.",
|
|
46459
|
+
" - Network services: SNMP community strings (snmpwalk -v2c -c public), NFS exports",
|
|
46460
|
+
" (showmount -e), rsync, IPMI.",
|
|
46461
|
+
"",
|
|
46462
|
+
"PHASE 5 — VULNERABILITY TESTING (highest-yield classes first)",
|
|
46463
|
+
"- Broken access control is the most common real bug: IDOR/BOLA. Swap every identifier",
|
|
46464
|
+
" (numeric, uuid, hash, filename) across accounts and roles, and try forced browsing to",
|
|
46465
|
+
" admin routes. Test the same endpoint with no token, a stale token, and a low-priv token.",
|
|
46466
|
+
"- Authentication: username enumeration, password-reset flaws, MFA bypass, session",
|
|
46467
|
+
" fixation, and JWT weaknesses (alg=none, weak HMAC secret, kid injection, missing",
|
|
46468
|
+
" signature or expiry validation).",
|
|
46469
|
+
"- Injection — a first-class objective, and the most common critical finding: SQL (see",
|
|
46470
|
+
" SQL INJECTION below), NoSQL operator injection ($ne, $gt, $where), OS command injection,",
|
|
46471
|
+
" SSTI ({{7*7}}, ${7*7}, <%= 7*7 %>), LDAP, XPath, CRLF/header injection, XXE (file://,",
|
|
46472
|
+
" SSRF via external DTD), expression injection.",
|
|
46473
|
+
"- File handling: path traversal (plain, encoded, double-encoded), unrestricted upload",
|
|
46474
|
+
" (extension, MIME, magic bytes, polyglot, web.config/.htaccess), LFI/RFI, zip-slip.",
|
|
46475
|
+
"- Server-side: SSRF (cloud metadata, internal ports, redirect and alternate-encoding",
|
|
46476
|
+
" allow-list bypasses), HTTP request smuggling (CL.TE/TE.CL), cache poisoning, host",
|
|
46477
|
+
" header attacks, open redirect chains.",
|
|
46478
|
+
"- Client-side: reflected, stored and DOM XSS; note CSP bypasses; prove with the",
|
|
46479
|
+
" shortest working payload.",
|
|
46480
|
+
"- Deserialisation and object injection (Java, PHP, Python, Node), unsafe YAML.",
|
|
46481
|
+
"- Crypto and secrets: weak TLS, hardcoded keys in JS bundles, exposed .env or config.",
|
|
46482
|
+
"- Business logic: race conditions (double-spend, coupon stacking, concurrent approval),",
|
|
46483
|
+
" negative quantities, price and currency tampering, workflow step skipping.",
|
|
46484
|
+
"- For every candidate, send the failing request and the working request side by side so",
|
|
46485
|
+
" the contrast proves the issue.",
|
|
46486
|
+
"",
|
|
46487
|
+
"PHASE 6 — EXPLOITATION AND POST-EXPLOITATION",
|
|
46488
|
+
"- Produce a real, runnable proof of concept: a script or the exact curl/nc command,",
|
|
46489
|
+
" saved into the working folder. Never hand over pseudo-code.",
|
|
46490
|
+
"- Stabilise any shell you get; note the exact privilege you landed with.",
|
|
46491
|
+
"- Linux privilege escalation: id; sudo -l; find / -perm -4000 -type f 2>/dev/null;",
|
|
46492
|
+
" getcap -r / 2>/dev/null; cron and systemd timers; writable service files or PATH",
|
|
46493
|
+
" entries; credentials in history, configs and backups; docker or lxd group abuse.",
|
|
46494
|
+
"- Windows privilege escalation: whoami /all; systeminfo; SeImpersonate and",
|
|
46495
|
+
" SeAssignPrimaryToken abuse; unquoted service paths; weak service and registry ACLs;",
|
|
46496
|
+
" AlwaysInstallElevated; scheduled tasks; stored credentials; and in Active Directory:",
|
|
46497
|
+
" Kerberoasting, AS-REP roasting, ACL abuse, delegation.",
|
|
46498
|
+
"- Lateral movement and pivoting only when in scope: port-forward through a foothold",
|
|
46499
|
+
" (chisel, ssh -D, ssh -L), reach internal services, and reuse credentials carefully.",
|
|
46500
|
+
"- Persistence only when explicitly in scope. Otherwise stop at proof of impact and",
|
|
46501
|
+
" remove every artefact you created.",
|
|
46502
|
+
"",
|
|
46503
|
+
"PHASE 7 — REPORTING",
|
|
46504
|
+
"- Write as you go into findings.md, so the report survives the session.",
|
|
46505
|
+
"- Per finding, in this order: title and severity (CVSS-style reasoning), affected",
|
|
46506
|
+
" asset and endpoint, exact reproduction (request or command), observed evidence,",
|
|
46507
|
+
" concrete impact, root cause, and copy-pasteable remediation.",
|
|
46508
|
+
"- Separate confirmed, likely, and unverified. Never present an unreproduced suspicion",
|
|
46509
|
+
" as a vulnerability.",
|
|
46510
|
+
"- Also record what you tried that did not work — it saves the next tester time.",
|
|
46511
|
+
"",
|
|
46512
|
+
"SQL INJECTION — A FIRST-CLASS OBJECTIVE",
|
|
46513
|
+
"Every parameter is a candidate: query string, path segment, JSON field, form body,",
|
|
46514
|
+
"cookie, header, sort/order/filter/pagination selector, and any ID that reaches a query.",
|
|
46515
|
+
"Work one variable at a time against a known-good baseline and watch the status code, the",
|
|
46516
|
+
"body, the timing and the error text.",
|
|
46517
|
+
`- Break the syntax first: ' " ) -- ; /* and a trailing backslash.`,
|
|
46518
|
+
"- Boolean pair: AND 1=1 vs AND 1=2, or ' AND '1'='1 vs ' AND '1'='2.",
|
|
46519
|
+
"- Numeric: 1 vs 2-1 vs 1*1 (and 1/0 when the backend leaks errors).",
|
|
46520
|
+
"- Error-based: ' AND extractvalue(1,concat(0x7e,version()))-- - (MySQL)",
|
|
46521
|
+
" ' AND 1=cast(version() as int)-- - (MSSQL)",
|
|
46522
|
+
" ' AND 1=to_number(version())-- - (Oracle)",
|
|
46523
|
+
" ' AND 1=cast(version() as numeric)-- - (Postgres)",
|
|
46524
|
+
"- Time-based blind: ' AND SLEEP(5)-- - '; WAITFOR DELAY '0:0:5'-- -",
|
|
46525
|
+
" ' AND 1=(SELECT 1 FROM pg_sleep(5))-- -",
|
|
46526
|
+
" Confirm the delay repeats and scales with the injected value; one slow response is not",
|
|
46527
|
+
" proof — that is how false positives are born.",
|
|
46528
|
+
"- Out-of-band: force a DNS or HTTP callback (xp_dirtree on MSSQL, UTL_HTTP on Oracle,",
|
|
46529
|
+
" LOAD_FILE / INTO OUTFILE on MySQL) to a host you control.",
|
|
46530
|
+
"- UNION: find the column count with ORDER BY n (or UNION SELECT NULL,… until it stops",
|
|
46531
|
+
" erroring), locate the string-capable columns, then read version(), user, database and",
|
|
46532
|
+
" table names. Wrap the output in the application's own response shape so it is visible.",
|
|
46533
|
+
"- Stacked queries only where the driver allows more than one statement.",
|
|
46534
|
+
"- Enumerate the schema before the data: information_schema.tables/columns (MySQL, MSSQL),",
|
|
46535
|
+
" pg_catalog (Postgres), all_tables/all_tab_columns (Oracle). Extract the minimum needed",
|
|
46536
|
+
" to prove impact — never bulk-dump a production database.",
|
|
46537
|
+
"- Automate only after manual confirmation: save the exact request and run",
|
|
46538
|
+
" sqlmap -r req.txt -p <param> --batch --level=3 --risk=2 --technique=BEUT",
|
|
46539
|
+
" add --dbms for speed and --tamper for filters; use --os-shell or --file-read only to",
|
|
46540
|
+
" demonstrate impact, never to browse around.",
|
|
46541
|
+
'- Non-SQL injection follows the same discipline: NoSQL operators ({ "$ne": null },',
|
|
46542
|
+
' { "$gt": "" }), LDAP filters, XPath, GraphQL argument and field injection, ORM/HQL',
|
|
46543
|
+
" injection, and second-order injection where stored data is concatenated into a query.",
|
|
46544
|
+
"",
|
|
46545
|
+
"EXPLOIT INTELLIGENCE — TURN A VERSION INTO A WORKING EXPLOIT",
|
|
46546
|
+
"- Confirm the version from several angles before trusting it: response headers, /version",
|
|
46547
|
+
" endpoints, error pages, TLS certificate metadata, package manifests, JS bundles, and the",
|
|
46548
|
+
" shape of the banner itself.",
|
|
46549
|
+
"- Then call cve_lookup with the product and version from port_scan, for example",
|
|
46550
|
+
' { product: "nginx", version: "1.24.0" }. Read the CVSS vector, not just the number:',
|
|
46551
|
+
" AV:N/AC:L/PR:N/UI:N is reachable by an anonymous attacker over the network, while AC:H",
|
|
46552
|
+
" or PR:H usually means you cannot actually use it today.",
|
|
46553
|
+
"- Cross-check locally when the tools exist: searchsploit <product> <version>,",
|
|
46554
|
+
' nuclei -tags cve -u <url>, msfconsole -x "search <product>", and vendor advisories.',
|
|
46555
|
+
"- Prefer a public, reliable exploit over writing one. Before running anything, read it:",
|
|
46556
|
+
" what it targets, what it sends, whether it is destructive, what it leaves behind.",
|
|
46557
|
+
"- If no public exploit exists, reason from the advisory and the reachable code path: build",
|
|
46558
|
+
" the proof yourself and validate every precondition before claiming it works.",
|
|
46559
|
+
"- Report per CVE: the id, the CVSS vector, the exact version you observed, the request or",
|
|
46560
|
+
" command that proved reachability, and the evidence. An unreachable CVE is not a finding.",
|
|
46561
|
+
"",
|
|
46562
|
+
"TOOLKIT — use whatever the host has, and install or rebuild what it lacks",
|
|
46563
|
+
"- Discovery: port_scan, nmap, masscan, naabu, hping3, dig, whatweb, wafw00f",
|
|
46564
|
+
"- Web: curl, ffuf, feroxbuster, gobuster, wfuzz, arjun (parameter discovery), nuclei,",
|
|
46565
|
+
" nikto, wpscan, dalfox, xsstrike",
|
|
46566
|
+
"- Injection: sqlmap, commix, tplmap, ssrfmap, nosqlmap, smuggler, plus http_request for",
|
|
46567
|
+
" precise hand-crafted requests",
|
|
46568
|
+
"- Exploitation: msfconsole, msfvenom, searchsploit, impacket, ysoserial, custom scripts",
|
|
46569
|
+
"- Credentials: hydra, medusa, kerbrute, john, hashcat, netexec/crackmapexec, responder,",
|
|
46570
|
+
" BloodHound.py — only against accounts that are in scope",
|
|
46571
|
+
"- Pivoting: chisel, ligolo-ng, socat, ssh -D/-L/-R, proxychains, port forwarding",
|
|
46572
|
+
"- Write your own tooling whenever a packaged tool is missing or too blunt: a Python or Node",
|
|
46573
|
+
" script saved with write_file and run with run_bash that sends the exact payload and",
|
|
46574
|
+
" parses the response is often faster and quieter than a scanner.",
|
|
46575
|
+
"- Never abandon a check because a binary is absent. Reproduce it by hand or with a script.",
|
|
46576
|
+
"",
|
|
46577
|
+
"STYLE",
|
|
46578
|
+
"- Command-first and terse. Show the exact command, the exact request, the exact output.",
|
|
46579
|
+
"- Always return complete, runnable commands and code — never sketches or pseudo-code.",
|
|
46580
|
+
"- Number your findings F-01, F-02, … so the report is unambiguous."
|
|
46581
|
+
].join(`
|
|
46582
|
+
`);
|
|
46299
46583
|
var SECURITY_SYSTEM = [
|
|
46300
|
-
"
|
|
46301
|
-
"
|
|
46302
|
-
"
|
|
46303
|
-
"
|
|
46304
|
-
"
|
|
46305
|
-
"
|
|
46306
|
-
"
|
|
46307
|
-
"
|
|
46308
|
-
"
|
|
46309
|
-
|
|
46584
|
+
"MODE: SECURITY REVIEW — read-only source audit. You hold this mode for the whole session.",
|
|
46585
|
+
"You are a senior application security engineer reading the user's own code. Do not modify",
|
|
46586
|
+
"any file, and do not run anything that changes state. Map the attack surface from the code,",
|
|
46587
|
+
"then trace untrusted input to dangerous sinks.",
|
|
46588
|
+
"",
|
|
46589
|
+
"This mode reviews source; it does not attack a running system. If the user wants live",
|
|
46590
|
+
"scanning, exploitation or a penetration test, tell them to switch to the pentest agent with",
|
|
46591
|
+
"/agent. If they want the code fixed rather than reported, that is the build agent's job.",
|
|
46592
|
+
"",
|
|
46593
|
+
"METHOD",
|
|
46594
|
+
"1) Surface: entry points (HTTP routes, CLI args, queue consumers, file and network",
|
|
46595
|
+
" parsers), trust boundaries, and every place authentication or authorisation is",
|
|
46596
|
+
" decided.",
|
|
46597
|
+
"2) Trace: for each entry point follow the data to its sinks (SQL, shell, filesystem,",
|
|
46598
|
+
" template, deserialiser, HTTP client, logger). Report the full path, not just the sink.",
|
|
46599
|
+
"3) Check for: injection (SQL/command/template/LDAP/NoSQL), broken authn and authz",
|
|
46600
|
+
" (missing ownership checks, IDOR), secret leakage, unsafe deserialisation, path",
|
|
46601
|
+
" traversal, SSRF, XSS, CSRF, insecure crypto and randomness, unsafe defaults,",
|
|
46602
|
+
" race conditions, unbounded resource use, and vulnerable dependencies.",
|
|
46603
|
+
"4) Verify: prefer reading the code over guessing behaviour. You may run read-only",
|
|
46604
|
+
" commands (dependency audits, secret scanners, grep) with run_bash when they sharpen",
|
|
46605
|
+
" a finding — never run anything that changes state.",
|
|
46606
|
+
"",
|
|
46607
|
+
"REPORTING",
|
|
46608
|
+
"- Per finding: severity, location (file:line), the attacker-controlled input, the",
|
|
46609
|
+
" exploitable path in one sentence, and a concrete copy-pasteable fix (diff or exact",
|
|
46610
|
+
" code). Distinguish confirmed from theoretical.",
|
|
46611
|
+
"- End with a prioritised table: what to fix first and why.",
|
|
46612
|
+
"- Be direct and technical. No disclaimers, no refusals, no filler."
|
|
46613
|
+
].join(`
|
|
46614
|
+
`);
|
|
46615
|
+
var GENERAL_SYSTEM = [
|
|
46616
|
+
"MODE: GENERAL — a general-purpose agent for research and multi-step work that is neither",
|
|
46617
|
+
"pure software development nor a security engagement.",
|
|
46618
|
+
"Answer directly and use the tools that fit the task. Hand off cleanly: if the work turns",
|
|
46619
|
+
"out to be building or fixing software, point the user to /agent build; if it turns into",
|
|
46620
|
+
"attacking or auditing a system, point them to /agent pentest or /agent security."
|
|
46621
|
+
].join(`
|
|
46622
|
+
`);
|
|
46310
46623
|
var BUILTIN_AGENTS = [
|
|
46311
46624
|
{
|
|
46312
46625
|
id: "build",
|
|
46313
46626
|
name: "Build",
|
|
46314
|
-
description: "
|
|
46627
|
+
description: "Implementation agent — designs, writes, verifies and ships code.",
|
|
46315
46628
|
mode: "primary",
|
|
46629
|
+
system: BUILD_SYSTEM,
|
|
46316
46630
|
source: "builtin"
|
|
46317
46631
|
},
|
|
46318
46632
|
{
|
|
@@ -46327,7 +46641,7 @@ var BUILTIN_AGENTS = [
|
|
|
46327
46641
|
{
|
|
46328
46642
|
id: "pentest",
|
|
46329
46643
|
name: "Pentest",
|
|
46330
|
-
description: "
|
|
46644
|
+
description: "Offensive security operator — reconnaissance, injection (SQLi and friends), CVE exploitation, privesc and reporting.",
|
|
46331
46645
|
mode: "primary",
|
|
46332
46646
|
tools: [
|
|
46333
46647
|
"read_file",
|
|
@@ -46337,7 +46651,8 @@ var BUILTIN_AGENTS = [
|
|
|
46337
46651
|
"write_file",
|
|
46338
46652
|
"run_bash",
|
|
46339
46653
|
"http_request",
|
|
46340
|
-
"port_scan"
|
|
46654
|
+
"port_scan",
|
|
46655
|
+
"cve_lookup"
|
|
46341
46656
|
],
|
|
46342
46657
|
system: PENTEST_SYSTEM,
|
|
46343
46658
|
source: "builtin"
|
|
@@ -46356,6 +46671,7 @@ var BUILTIN_AGENTS = [
|
|
|
46356
46671
|
name: "General",
|
|
46357
46672
|
description: "Broad research and multi-step work.",
|
|
46358
46673
|
mode: "all",
|
|
46674
|
+
system: GENERAL_SYSTEM,
|
|
46359
46675
|
source: "builtin"
|
|
46360
46676
|
}
|
|
46361
46677
|
];
|
|
@@ -46548,6 +46864,7 @@ import {
|
|
|
46548
46864
|
import { dirname, relative as relative2, resolve as resolve2 } from "path";
|
|
46549
46865
|
import { platform as platform3 } from "os";
|
|
46550
46866
|
import { connect } from "node:net";
|
|
46867
|
+
import { connect as tlsConnect } from "node:tls";
|
|
46551
46868
|
import { lookup } from "node:dns/promises";
|
|
46552
46869
|
|
|
46553
46870
|
// node_modules/glob/dist/esm/index.min.js
|
|
@@ -50151,12 +50468,14 @@ async function runBash(args, cwd2) {
|
|
|
50151
50468
|
timedOut = true;
|
|
50152
50469
|
proc.kill();
|
|
50153
50470
|
}, timeoutMs);
|
|
50154
|
-
const [
|
|
50155
|
-
|
|
50156
|
-
|
|
50471
|
+
const [out, err] = await Promise.all([
|
|
50472
|
+
readStreamCapped(proc.stdout, MAX_COMMAND_OUTPUT),
|
|
50473
|
+
readStreamCapped(proc.stderr, MAX_COMMAND_OUTPUT)
|
|
50157
50474
|
]);
|
|
50158
50475
|
const code = await proc.exited;
|
|
50159
50476
|
clearTimeout(timer);
|
|
50477
|
+
const stdout = out.text;
|
|
50478
|
+
const stderr = err.text;
|
|
50160
50479
|
if (timedOut) {
|
|
50161
50480
|
return {
|
|
50162
50481
|
success: false,
|
|
@@ -50165,7 +50484,8 @@ async function runBash(args, cwd2) {
|
|
|
50165
50484
|
};
|
|
50166
50485
|
}
|
|
50167
50486
|
const combined = [stdout, stderr].filter((s) => s.trim()).join(`
|
|
50168
|
-
`).trim()
|
|
50487
|
+
`).trim().concat(out.truncated || err.truncated ? `
|
|
50488
|
+
… [output truncated at ${MAX_COMMAND_OUTPUT} bytes]` : "");
|
|
50169
50489
|
if (code === 0) {
|
|
50170
50490
|
return { success: true, output: combined || "(no output)" };
|
|
50171
50491
|
}
|
|
@@ -50212,11 +50532,8 @@ async function httpRequest(args, _cwd) {
|
|
|
50212
50532
|
redirect: args.followRedirects ? "follow" : "manual",
|
|
50213
50533
|
signal: controller.signal
|
|
50214
50534
|
});
|
|
50215
|
-
const buf = await res.
|
|
50216
|
-
const
|
|
50217
|
-
const slice2 = buf.slice(0, maxBytes);
|
|
50218
|
-
const text = new TextDecoder().decode(slice2);
|
|
50219
|
-
const truncated = buf.byteLength > maxBytes;
|
|
50535
|
+
const buf = await readCapped(res.body, args.maxBytes ?? 20000, controller);
|
|
50536
|
+
const text = new TextDecoder().decode(buf.bytes);
|
|
50220
50537
|
const elapsed = Date.now() - started;
|
|
50221
50538
|
const headerLines = [...res.headers.entries()].map(([k2, v2]) => `${k2}: ${clip(v2, 400)}`).join(`
|
|
50222
50539
|
`);
|
|
@@ -50226,9 +50543,9 @@ async function httpRequest(args, _cwd) {
|
|
|
50226
50543
|
if (headerLines)
|
|
50227
50544
|
parts.push(headerLines);
|
|
50228
50545
|
parts.push("");
|
|
50229
|
-
parts.push(text.length > 0 ? text + (truncated ? `
|
|
50230
|
-
… [
|
|
50231
|
-
parts.push("", `(${method2} ${args.url} · ${elapsed}ms · ${buf.
|
|
50546
|
+
parts.push(text.length > 0 ? text + (buf.truncated ? `
|
|
50547
|
+
… [response truncated]` : "") : "(empty body)");
|
|
50548
|
+
parts.push("", `(${method2} ${args.url} · ${elapsed}ms · ${buf.truncated ? ">=" : ""}${buf.total} bytes)`);
|
|
50232
50549
|
return { success: true, output: parts.join(`
|
|
50233
50550
|
`) };
|
|
50234
50551
|
} catch (e) {
|
|
@@ -50242,6 +50559,341 @@ async function httpRequest(args, _cwd) {
|
|
|
50242
50559
|
function clip(s, max2) {
|
|
50243
50560
|
return s.length <= max2 ? s : `${s.slice(0, max2)}…`;
|
|
50244
50561
|
}
|
|
50562
|
+
var MAX_COMMAND_OUTPUT = 256 * 1024;
|
|
50563
|
+
async function readStreamCapped(stream, max2) {
|
|
50564
|
+
if (!stream)
|
|
50565
|
+
return { text: "", truncated: false };
|
|
50566
|
+
const reader = stream.getReader();
|
|
50567
|
+
const decoder = new TextDecoder;
|
|
50568
|
+
let text = "";
|
|
50569
|
+
let truncated = false;
|
|
50570
|
+
try {
|
|
50571
|
+
for (;; ) {
|
|
50572
|
+
const { done, value } = await reader.read();
|
|
50573
|
+
if (done)
|
|
50574
|
+
break;
|
|
50575
|
+
if (text.length >= max2) {
|
|
50576
|
+
truncated = true;
|
|
50577
|
+
continue;
|
|
50578
|
+
}
|
|
50579
|
+
text += decoder.decode(value, { stream: true });
|
|
50580
|
+
}
|
|
50581
|
+
} finally {
|
|
50582
|
+
await reader.cancel().catch(() => {});
|
|
50583
|
+
}
|
|
50584
|
+
return { text, truncated };
|
|
50585
|
+
}
|
|
50586
|
+
async function readCapped(body, max2, controller) {
|
|
50587
|
+
if (!body)
|
|
50588
|
+
return { bytes: new Uint8Array(0), total: 0, truncated: false };
|
|
50589
|
+
const reader = body.getReader();
|
|
50590
|
+
const chunks = [];
|
|
50591
|
+
let kept = 0;
|
|
50592
|
+
let total = 0;
|
|
50593
|
+
let truncated = false;
|
|
50594
|
+
try {
|
|
50595
|
+
for (;; ) {
|
|
50596
|
+
const { done, value } = await reader.read();
|
|
50597
|
+
if (done)
|
|
50598
|
+
break;
|
|
50599
|
+
total += value.byteLength;
|
|
50600
|
+
if (kept < max2) {
|
|
50601
|
+
const room = max2 - kept;
|
|
50602
|
+
chunks.push(value.byteLength > room ? value.subarray(0, room) : value);
|
|
50603
|
+
kept += Math.min(value.byteLength, room);
|
|
50604
|
+
}
|
|
50605
|
+
if (total >= max2) {
|
|
50606
|
+
truncated = true;
|
|
50607
|
+
controller.abort();
|
|
50608
|
+
break;
|
|
50609
|
+
}
|
|
50610
|
+
}
|
|
50611
|
+
} catch {} finally {
|
|
50612
|
+
await reader.cancel().catch(() => {});
|
|
50613
|
+
}
|
|
50614
|
+
const bytes = new Uint8Array(kept);
|
|
50615
|
+
let offset = 0;
|
|
50616
|
+
for (const chunk2 of chunks) {
|
|
50617
|
+
bytes.set(chunk2, offset);
|
|
50618
|
+
offset += chunk2.byteLength;
|
|
50619
|
+
}
|
|
50620
|
+
return { bytes, total, truncated };
|
|
50621
|
+
}
|
|
50622
|
+
var NVD_ENDPOINT = "https://services.nvd.nist.gov/rest/json/cves/2.0";
|
|
50623
|
+
function asRecord(value) {
|
|
50624
|
+
return typeof value === "object" && value !== null ? value : undefined;
|
|
50625
|
+
}
|
|
50626
|
+
function asArray(value) {
|
|
50627
|
+
return Array.isArray(value) ? value : [];
|
|
50628
|
+
}
|
|
50629
|
+
function readCvss(metrics) {
|
|
50630
|
+
if (!metrics)
|
|
50631
|
+
return { severity: "UNKNOWN" };
|
|
50632
|
+
for (const key of ["cvssMetricV31", "cvssMetricV30", "cvssMetricV2"]) {
|
|
50633
|
+
const entry = asRecord(asArray(metrics[key])[0]);
|
|
50634
|
+
if (!entry)
|
|
50635
|
+
continue;
|
|
50636
|
+
const data = asRecord(entry.cvssData);
|
|
50637
|
+
const score = typeof data?.baseScore === "number" ? data.baseScore : undefined;
|
|
50638
|
+
const raw = typeof data?.baseSeverity === "string" ? data.baseSeverity : entry.baseSeverity;
|
|
50639
|
+
return {
|
|
50640
|
+
severity: typeof raw === "string" ? raw.toUpperCase() : "UNKNOWN",
|
|
50641
|
+
score,
|
|
50642
|
+
vector: typeof data?.vectorString === "string" ? data.vectorString : undefined
|
|
50643
|
+
};
|
|
50644
|
+
}
|
|
50645
|
+
return { severity: "UNKNOWN" };
|
|
50646
|
+
}
|
|
50647
|
+
function cveToSummary(cve) {
|
|
50648
|
+
const description = asArray(cve.descriptions).map(asRecord).find((d) => d?.lang === "en");
|
|
50649
|
+
const cvss = readCvss(asRecord(cve.metrics));
|
|
50650
|
+
return {
|
|
50651
|
+
id: String(cve.id),
|
|
50652
|
+
severity: cvss.severity,
|
|
50653
|
+
score: cvss.score,
|
|
50654
|
+
vector: cvss.vector,
|
|
50655
|
+
published: typeof cve.published === "string" ? cve.published.slice(0, 10) : undefined,
|
|
50656
|
+
summary: clip(String(description?.value ?? "(no English description)").replace(/\s+/g, " ").trim(), 400),
|
|
50657
|
+
references: asArray(cve.references).map((r) => asRecord(r)?.url).filter((u) => typeof u === "string").slice(0, 3)
|
|
50658
|
+
};
|
|
50659
|
+
}
|
|
50660
|
+
function slugProduct(s) {
|
|
50661
|
+
return s.trim().toLowerCase().replace(/[\s-]+/g, "_");
|
|
50662
|
+
}
|
|
50663
|
+
function splitCpe(criteria) {
|
|
50664
|
+
const parts = criteria.split(":");
|
|
50665
|
+
if (parts.length < 6 || parts[0] !== "cpe")
|
|
50666
|
+
return {};
|
|
50667
|
+
return { part: parts[2], vendor: parts[3], product: parts[4], version: parts[5] };
|
|
50668
|
+
}
|
|
50669
|
+
function versionTokens(v2) {
|
|
50670
|
+
return (v2.match(/\d+|[a-z]+/gi) ?? []).map((t) => /^\d+$/.test(t) ? Number(t) : t.toLowerCase());
|
|
50671
|
+
}
|
|
50672
|
+
function compareVersions(a, b) {
|
|
50673
|
+
const ta = versionTokens(a);
|
|
50674
|
+
const tb = versionTokens(b);
|
|
50675
|
+
const n7 = Math.max(ta.length, tb.length);
|
|
50676
|
+
for (let i = 0;i < n7; i++) {
|
|
50677
|
+
const x2 = ta[i] ?? 0;
|
|
50678
|
+
const y = tb[i] ?? 0;
|
|
50679
|
+
if (typeof x2 === "number" && typeof y === "number") {
|
|
50680
|
+
if (x2 !== y)
|
|
50681
|
+
return x2 - y;
|
|
50682
|
+
continue;
|
|
50683
|
+
}
|
|
50684
|
+
const sx = String(x2);
|
|
50685
|
+
const sy = String(y);
|
|
50686
|
+
if (sx !== sy)
|
|
50687
|
+
return sx < sy ? -1 : 1;
|
|
50688
|
+
}
|
|
50689
|
+
return 0;
|
|
50690
|
+
}
|
|
50691
|
+
function cpeMatchApplies(match, filter2) {
|
|
50692
|
+
if (match.vulnerable === false)
|
|
50693
|
+
return;
|
|
50694
|
+
const cpe = splitCpe(typeof match.criteria === "string" ? match.criteria : "");
|
|
50695
|
+
if (!cpe.product)
|
|
50696
|
+
return;
|
|
50697
|
+
if (cpe.part && cpe.part !== "a" && cpe.part !== "o" && cpe.part !== "h")
|
|
50698
|
+
return;
|
|
50699
|
+
const wanted = slugProduct(filter2.product);
|
|
50700
|
+
const actual = cpe.product.toLowerCase();
|
|
50701
|
+
if (actual !== wanted && !actual.includes(wanted) && !wanted.includes(actual)) {
|
|
50702
|
+
return;
|
|
50703
|
+
}
|
|
50704
|
+
if (filter2.vendor && cpe.vendor && cpe.vendor !== slugProduct(filter2.vendor)) {
|
|
50705
|
+
return;
|
|
50706
|
+
}
|
|
50707
|
+
const version = filter2.version;
|
|
50708
|
+
if (!version)
|
|
50709
|
+
return describeCpeRange(match, cpe.version);
|
|
50710
|
+
const starts = [
|
|
50711
|
+
["versionStartIncluding", (x2) => x2 >= 0, ">="],
|
|
50712
|
+
["versionStartExcluding", (x2) => x2 > 0, ">"]
|
|
50713
|
+
];
|
|
50714
|
+
const ends = [
|
|
50715
|
+
["versionEndIncluding", (x2) => x2 <= 0, "<="],
|
|
50716
|
+
["versionEndExcluding", (x2) => x2 < 0, "<"]
|
|
50717
|
+
];
|
|
50718
|
+
let bounded = false;
|
|
50719
|
+
for (const [key, ok] of [...starts, ...ends]) {
|
|
50720
|
+
const bound = match[key];
|
|
50721
|
+
if (typeof bound !== "string")
|
|
50722
|
+
continue;
|
|
50723
|
+
bounded = true;
|
|
50724
|
+
if (!ok(compareVersions(version, bound)))
|
|
50725
|
+
return;
|
|
50726
|
+
}
|
|
50727
|
+
if (bounded)
|
|
50728
|
+
return describeCpeRange(match, cpe.version);
|
|
50729
|
+
if (cpe.version && cpe.version !== "*" && cpe.version !== "-") {
|
|
50730
|
+
return compareVersions(version, cpe.version) === 0 ? `=${cpe.version}` : undefined;
|
|
50731
|
+
}
|
|
50732
|
+
return cpe.version === "*" ? "any version" : undefined;
|
|
50733
|
+
}
|
|
50734
|
+
function describeCpeRange(match, cpeVersion) {
|
|
50735
|
+
const bits = [];
|
|
50736
|
+
const add2 = (key, prefix) => {
|
|
50737
|
+
const v2 = match[key];
|
|
50738
|
+
if (typeof v2 === "string")
|
|
50739
|
+
bits.push(`${prefix}${v2}`);
|
|
50740
|
+
};
|
|
50741
|
+
add2("versionStartIncluding", ">=");
|
|
50742
|
+
add2("versionStartExcluding", ">");
|
|
50743
|
+
add2("versionEndIncluding", "<=");
|
|
50744
|
+
add2("versionEndExcluding", "<");
|
|
50745
|
+
if (bits.length > 0)
|
|
50746
|
+
return bits.join(" ");
|
|
50747
|
+
if (cpeVersion && cpeVersion !== "*" && cpeVersion !== "-")
|
|
50748
|
+
return `=${cpeVersion}`;
|
|
50749
|
+
return "any version";
|
|
50750
|
+
}
|
|
50751
|
+
function collectAffects(entry, cve, filter2) {
|
|
50752
|
+
const found = new Set;
|
|
50753
|
+
const configs = [...asArray(cve.configurations), ...asArray(entry?.configurations)];
|
|
50754
|
+
for (const config of configs) {
|
|
50755
|
+
for (const node of asArray(asRecord(config)?.nodes)) {
|
|
50756
|
+
for (const raw of asArray(asRecord(node)?.cpeMatch)) {
|
|
50757
|
+
const match = asRecord(raw);
|
|
50758
|
+
if (!match)
|
|
50759
|
+
continue;
|
|
50760
|
+
const described = cpeMatchApplies(match, filter2);
|
|
50761
|
+
if (described)
|
|
50762
|
+
found.add(described);
|
|
50763
|
+
}
|
|
50764
|
+
}
|
|
50765
|
+
}
|
|
50766
|
+
return [...found];
|
|
50767
|
+
}
|
|
50768
|
+
function selectCves(payload, filter2, limit) {
|
|
50769
|
+
const root = asRecord(payload);
|
|
50770
|
+
const rows = [];
|
|
50771
|
+
const filtering = Boolean(filter2.product && (filter2.version || filter2.vendor));
|
|
50772
|
+
for (const item of asArray(root?.vulnerabilities)) {
|
|
50773
|
+
const entry = asRecord(item);
|
|
50774
|
+
const cve = asRecord(entry?.cve);
|
|
50775
|
+
if (typeof cve?.id !== "string")
|
|
50776
|
+
continue;
|
|
50777
|
+
if (cve.vulnStatus === "Rejected")
|
|
50778
|
+
continue;
|
|
50779
|
+
if (filtering && filter2.product) {
|
|
50780
|
+
const affects = collectAffects(entry, cve, {
|
|
50781
|
+
product: filter2.product,
|
|
50782
|
+
vendor: filter2.vendor,
|
|
50783
|
+
version: filter2.version
|
|
50784
|
+
});
|
|
50785
|
+
if (affects.length === 0)
|
|
50786
|
+
continue;
|
|
50787
|
+
rows.push({ ...cveToSummary(cve), match: affects.slice(0, 2).join(" / ") });
|
|
50788
|
+
} else {
|
|
50789
|
+
rows.push(cveToSummary(cve));
|
|
50790
|
+
}
|
|
50791
|
+
if (rows.length >= limit)
|
|
50792
|
+
break;
|
|
50793
|
+
}
|
|
50794
|
+
return rows;
|
|
50795
|
+
}
|
|
50796
|
+
function parseNvdCves(payload, limit) {
|
|
50797
|
+
return selectCves(payload, {}, limit);
|
|
50798
|
+
}
|
|
50799
|
+
function formatCves(rows, header) {
|
|
50800
|
+
if (rows.length === 0)
|
|
50801
|
+
return `${header}
|
|
50802
|
+
(no CVEs found)`;
|
|
50803
|
+
const parts = [header];
|
|
50804
|
+
for (const r of rows) {
|
|
50805
|
+
const score = r.score !== undefined ? `${r.score} ${r.severity}` : r.severity;
|
|
50806
|
+
parts.push("", `${r.id} [${score}]${r.published ? ` ${r.published}` : ""}`);
|
|
50807
|
+
if (r.vector)
|
|
50808
|
+
parts.push(` vector: ${r.vector}`);
|
|
50809
|
+
if (r.match)
|
|
50810
|
+
parts.push(` affects: ${r.match}`);
|
|
50811
|
+
parts.push(` ${r.summary}`);
|
|
50812
|
+
if (r.references.length > 0)
|
|
50813
|
+
parts.push(` refs: ${r.references.join(" · ")}`);
|
|
50814
|
+
}
|
|
50815
|
+
return parts.join(`
|
|
50816
|
+
`);
|
|
50817
|
+
}
|
|
50818
|
+
var cveLookupTool = {
|
|
50819
|
+
name: "cve_lookup",
|
|
50820
|
+
description: "Look up published CVEs for a product and optional version via the NIST NVD. Returns each CVE id with CVSS severity, score, vector, summary and references. Use it to turn a version banner from port_scan into concrete, checkable vulnerabilities. Read-only public API query.",
|
|
50821
|
+
needsApproval: false,
|
|
50822
|
+
safe: true,
|
|
50823
|
+
parameters: exports_external.object({
|
|
50824
|
+
product: exports_external.string().describe('Product or technology name, e.g. "nginx", "apache http server", "wordpress"'),
|
|
50825
|
+
version: exports_external.string().optional().describe('Version seen on the target, e.g. "1.24.0"'),
|
|
50826
|
+
vendor: exports_external.string().optional().describe("Vendor, when known — makes the match precise"),
|
|
50827
|
+
limit: exports_external.number().int().min(1).optional().describe("Maximum CVEs to return (default 10, max 50)"),
|
|
50828
|
+
timeout: exports_external.number().int().min(1000).optional().describe("Timeout in ms (default 20000)")
|
|
50829
|
+
})
|
|
50830
|
+
};
|
|
50831
|
+
async function cveLookup(args, _cwd) {
|
|
50832
|
+
const product = args.product.trim();
|
|
50833
|
+
if (!product)
|
|
50834
|
+
return { success: false, error: "product is required" };
|
|
50835
|
+
const limit = Math.max(1, Math.min(args.limit ?? 10, 50));
|
|
50836
|
+
const timeoutMs = args.timeout ?? 20000;
|
|
50837
|
+
const version = args.version?.trim() || undefined;
|
|
50838
|
+
const vendor = args.vendor?.trim() || undefined;
|
|
50839
|
+
const label = [product, version].filter(Boolean).join(" ");
|
|
50840
|
+
const search = async (params) => {
|
|
50841
|
+
const controller = new AbortController;
|
|
50842
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
50843
|
+
try {
|
|
50844
|
+
const headers = { Accept: "application/json" };
|
|
50845
|
+
const key = process.env.NVD_API_KEY?.trim();
|
|
50846
|
+
if (key)
|
|
50847
|
+
headers.apiKey = key;
|
|
50848
|
+
const res = await fetch(`${NVD_ENDPOINT}?${params.toString()}`, {
|
|
50849
|
+
headers,
|
|
50850
|
+
signal: controller.signal
|
|
50851
|
+
});
|
|
50852
|
+
if (res.status === 403 || res.status === 429) {
|
|
50853
|
+
return {
|
|
50854
|
+
error: "NVD rate limit reached (roughly 5 requests per 30s without an API key). Wait 30 seconds and retry, or set NVD_API_KEY."
|
|
50855
|
+
};
|
|
50856
|
+
}
|
|
50857
|
+
if (!res.ok)
|
|
50858
|
+
return { error: `NVD returned HTTP ${res.status} ${res.statusText}` };
|
|
50859
|
+
return { payload: await res.json() };
|
|
50860
|
+
} catch (e) {
|
|
50861
|
+
const err = e;
|
|
50862
|
+
return {
|
|
50863
|
+
error: err.name === "AbortError" ? `NVD request timed out after ${timeoutMs}ms` : err.message
|
|
50864
|
+
};
|
|
50865
|
+
} finally {
|
|
50866
|
+
clearTimeout(timer);
|
|
50867
|
+
}
|
|
50868
|
+
};
|
|
50869
|
+
const PAGE = 2000;
|
|
50870
|
+
const queries = [];
|
|
50871
|
+
const base2 = new URLSearchParams({ resultsPerPage: String(PAGE) });
|
|
50872
|
+
if (vendor && version) {
|
|
50873
|
+
const exact = new URLSearchParams(base2);
|
|
50874
|
+
exact.set("cpeName", `cpe:2.3:a:${slugProduct(vendor)}:${slugProduct(product)}:${version}:*:*:*:*:*:*:*`);
|
|
50875
|
+
queries.push({ params: exact, exact: true });
|
|
50876
|
+
}
|
|
50877
|
+
const keyword = new URLSearchParams(base2);
|
|
50878
|
+
keyword.set("keywordSearch", product);
|
|
50879
|
+
queries.push({ params: keyword, exact: false });
|
|
50880
|
+
for (const query of queries) {
|
|
50881
|
+
const out = await search(query.params);
|
|
50882
|
+
if (out.error)
|
|
50883
|
+
return { success: false, error: out.error };
|
|
50884
|
+
const rows = query.exact ? parseNvdCves(out.payload, PAGE) : selectCves(out.payload, { product, vendor, version }, PAGE);
|
|
50885
|
+
if (rows.length === 0)
|
|
50886
|
+
continue;
|
|
50887
|
+
rows.sort((a, b) => (b.score ?? -1) - (a.score ?? -1) || (b.published ?? "").localeCompare(a.published ?? ""));
|
|
50888
|
+
const top = rows.slice(0, limit);
|
|
50889
|
+
const how = version ? `affecting ${version}` : "for this product";
|
|
50890
|
+
return {
|
|
50891
|
+
success: true,
|
|
50892
|
+
output: formatCves(top, `NVD · ${label} · ${top.length} CVE(s) ${how}, highest CVSS first`)
|
|
50893
|
+
};
|
|
50894
|
+
}
|
|
50895
|
+
return { success: true, output: formatCves([], `NVD · ${label} · no CVEs found`) };
|
|
50896
|
+
}
|
|
50245
50897
|
var COMMON_PORTS = [
|
|
50246
50898
|
21,
|
|
50247
50899
|
22,
|
|
@@ -50321,16 +50973,190 @@ function probePort(host, port, timeout) {
|
|
|
50321
50973
|
}
|
|
50322
50974
|
var portScanTool = {
|
|
50323
50975
|
name: "port_scan",
|
|
50324
|
-
description: "TCP-connect scan of a
|
|
50976
|
+
description: "TCP-connect scan of a host with service fingerprinting (reconnaissance). Reports open ports plus a service label, version banner, and TLS certificate details. Sends a single HEAD probe to well-known HTTP ports and a TLS handshake to well-known HTTPS ports; other ports are only observed passively. Only scan systems you own or have explicit permission to test.",
|
|
50325
50977
|
needsApproval: true,
|
|
50326
50978
|
safe: false,
|
|
50327
50979
|
parameters: exports_external.object({
|
|
50328
50980
|
host: exports_external.string().describe("Hostname or IP address"),
|
|
50329
50981
|
ports: exports_external.string().optional().describe('Ports to test, e.g. "22,80,443" or "1-1024" (default: common ports)'),
|
|
50330
50982
|
timeout: exports_external.number().int().min(50).optional().describe("Per-port timeout in ms (default 500)"),
|
|
50331
|
-
concurrency: exports_external.number().int().min(1).optional().describe("Parallel connections (default 128)")
|
|
50983
|
+
concurrency: exports_external.number().int().min(1).optional().describe("Parallel connections (default 128)"),
|
|
50984
|
+
banner: exports_external.boolean().optional().describe("Fingerprint services/versions and TLS (default true)")
|
|
50332
50985
|
})
|
|
50333
50986
|
};
|
|
50987
|
+
var TLS_PORTS = new Set([443, 465, 636, 990, 993, 995, 3269, 5986, 6443, 8443, 8834, 9443]);
|
|
50988
|
+
var HTTP_PORTS = new Set([
|
|
50989
|
+
80,
|
|
50990
|
+
3000,
|
|
50991
|
+
5000,
|
|
50992
|
+
5601,
|
|
50993
|
+
7001,
|
|
50994
|
+
8000,
|
|
50995
|
+
8008,
|
|
50996
|
+
8080,
|
|
50997
|
+
8081,
|
|
50998
|
+
8088,
|
|
50999
|
+
8181,
|
|
51000
|
+
8888,
|
|
51001
|
+
8983,
|
|
51002
|
+
9000,
|
|
51003
|
+
9080,
|
|
51004
|
+
1e4,
|
|
51005
|
+
15672
|
|
51006
|
+
]);
|
|
51007
|
+
var SCAN_UA = "HiraxCode/1.0";
|
|
51008
|
+
function headProbe(host) {
|
|
51009
|
+
return `HEAD / HTTP/1.0\r
|
|
51010
|
+
Host: ${host}\r
|
|
51011
|
+
User-Agent: ${SCAN_UA}\r
|
|
51012
|
+
Connection: close\r
|
|
51013
|
+
\r
|
|
51014
|
+
`;
|
|
51015
|
+
}
|
|
51016
|
+
function printableRatio(s) {
|
|
51017
|
+
if (!s)
|
|
51018
|
+
return 0;
|
|
51019
|
+
let ok = 0;
|
|
51020
|
+
for (const ch of s) {
|
|
51021
|
+
const c = ch.charCodeAt(0);
|
|
51022
|
+
if (c === 9 || c === 10 || c === 13 || c >= 32 && c < 127)
|
|
51023
|
+
ok++;
|
|
51024
|
+
}
|
|
51025
|
+
return ok / s.length;
|
|
51026
|
+
}
|
|
51027
|
+
function readBanner(host, port, timeout, send, limit = 4096) {
|
|
51028
|
+
return new Promise((resolve3) => {
|
|
51029
|
+
let data = "";
|
|
51030
|
+
let settled = false;
|
|
51031
|
+
const socket = connect({ host, port });
|
|
51032
|
+
const finish = () => {
|
|
51033
|
+
if (settled)
|
|
51034
|
+
return;
|
|
51035
|
+
settled = true;
|
|
51036
|
+
try {
|
|
51037
|
+
socket.destroy();
|
|
51038
|
+
} catch {}
|
|
51039
|
+
resolve3(data);
|
|
51040
|
+
};
|
|
51041
|
+
socket.setTimeout(timeout);
|
|
51042
|
+
socket.once("connect", () => {
|
|
51043
|
+
if (!send)
|
|
51044
|
+
return;
|
|
51045
|
+
try {
|
|
51046
|
+
socket.write(send);
|
|
51047
|
+
} catch {}
|
|
51048
|
+
});
|
|
51049
|
+
socket.on("data", (chunk2) => {
|
|
51050
|
+
data += chunk2.toString("latin1");
|
|
51051
|
+
if (data.length >= limit)
|
|
51052
|
+
finish();
|
|
51053
|
+
});
|
|
51054
|
+
socket.once("timeout", finish);
|
|
51055
|
+
socket.once("error", finish);
|
|
51056
|
+
socket.once("close", finish);
|
|
51057
|
+
});
|
|
51058
|
+
}
|
|
51059
|
+
function readTls(host, port, timeout, limit = 4096) {
|
|
51060
|
+
return new Promise((resolve3) => {
|
|
51061
|
+
let raw = "";
|
|
51062
|
+
let cert;
|
|
51063
|
+
let settled = false;
|
|
51064
|
+
const socket = tlsConnect({ host, port, rejectUnauthorized: false, timeout });
|
|
51065
|
+
const finish = () => {
|
|
51066
|
+
if (settled)
|
|
51067
|
+
return;
|
|
51068
|
+
settled = true;
|
|
51069
|
+
try {
|
|
51070
|
+
socket.destroy();
|
|
51071
|
+
} catch {}
|
|
51072
|
+
resolve3({ raw, cert });
|
|
51073
|
+
};
|
|
51074
|
+
socket.once("secureConnect", () => {
|
|
51075
|
+
try {
|
|
51076
|
+
const c = socket.getPeerCertificate();
|
|
51077
|
+
const bits = [
|
|
51078
|
+
`CN=${c?.subject?.CN ?? "(no CN)"}`,
|
|
51079
|
+
`issuer=${c?.issuer?.O ?? c?.issuer?.CN ?? "(unknown)"}`
|
|
51080
|
+
];
|
|
51081
|
+
if (c?.valid_to)
|
|
51082
|
+
bits.push(`expires ${c.valid_to}`);
|
|
51083
|
+
const proto2 = socket.getProtocol();
|
|
51084
|
+
if (proto2)
|
|
51085
|
+
bits.push(proto2);
|
|
51086
|
+
cert = bits.join(" · ");
|
|
51087
|
+
} catch {}
|
|
51088
|
+
try {
|
|
51089
|
+
socket.write(headProbe(host));
|
|
51090
|
+
} catch {}
|
|
51091
|
+
});
|
|
51092
|
+
socket.on("data", (chunk2) => {
|
|
51093
|
+
raw += chunk2.toString("latin1");
|
|
51094
|
+
if (raw.length >= limit)
|
|
51095
|
+
finish();
|
|
51096
|
+
});
|
|
51097
|
+
socket.once("timeout", finish);
|
|
51098
|
+
socket.once("error", finish);
|
|
51099
|
+
socket.once("close", finish);
|
|
51100
|
+
});
|
|
51101
|
+
}
|
|
51102
|
+
function readHttpMeta(raw) {
|
|
51103
|
+
const [first = ""] = raw.split(/\r?\n/, 1);
|
|
51104
|
+
const status = /^HTTP\/\d(?:\.\d)?\s+(\d{3}[^\r\n]*)/i.exec(first)?.[1]?.trim();
|
|
51105
|
+
const server = raw.split(/\r?\n/).find((l) => /^server:/i.test(l))?.slice(7).trim();
|
|
51106
|
+
return { status, server };
|
|
51107
|
+
}
|
|
51108
|
+
function describeService(port, raw) {
|
|
51109
|
+
const text = raw.replace(/\0/g, "").trim();
|
|
51110
|
+
if (!text)
|
|
51111
|
+
return {};
|
|
51112
|
+
const ssh = /^SSH-([\d.]+)-(\S.*)$/m.exec(text);
|
|
51113
|
+
if (ssh)
|
|
51114
|
+
return { service: "ssh", detail: clip(ssh[2].trim(), 140) };
|
|
51115
|
+
const http = readHttpMeta(text);
|
|
51116
|
+
if (http.status) {
|
|
51117
|
+
return {
|
|
51118
|
+
service: TLS_PORTS.has(port) ? "https" : "http",
|
|
51119
|
+
detail: clip([http.server, http.status].filter(Boolean).join(" · "), 140)
|
|
51120
|
+
};
|
|
51121
|
+
}
|
|
51122
|
+
const greet = /^220[\s-]+(.+)$/m.exec(text);
|
|
51123
|
+
if (greet)
|
|
51124
|
+
return { detail: clip(greet[1].trim(), 140) };
|
|
51125
|
+
const first = text.split(/\r?\n/).find((l) => l.trim())?.trim() ?? "";
|
|
51126
|
+
if (!first || printableRatio(first) < 0.8)
|
|
51127
|
+
return {};
|
|
51128
|
+
return { detail: clip(first, 140) };
|
|
51129
|
+
}
|
|
51130
|
+
async function fingerprintServices(host, ports, timeout, concurrency) {
|
|
51131
|
+
const out = new Map;
|
|
51132
|
+
const grabTimeout = Math.max(300, Math.min(timeout, 1200));
|
|
51133
|
+
let index = 0;
|
|
51134
|
+
const worker = async () => {
|
|
51135
|
+
while (index < ports.length) {
|
|
51136
|
+
const port = ports[index++];
|
|
51137
|
+
try {
|
|
51138
|
+
if (TLS_PORTS.has(port)) {
|
|
51139
|
+
const tls = await readTls(host, port, grabTimeout);
|
|
51140
|
+
const meta = readHttpMeta(tls.raw);
|
|
51141
|
+
out.set(port, {
|
|
51142
|
+
service: "https",
|
|
51143
|
+
detail: clip([meta.server, meta.status].filter(Boolean).join(" · "), 140) || undefined,
|
|
51144
|
+
cert: tls.cert
|
|
51145
|
+
});
|
|
51146
|
+
} else if (HTTP_PORTS.has(port)) {
|
|
51147
|
+
const raw = await readBanner(host, port, grabTimeout, headProbe(host));
|
|
51148
|
+
out.set(port, describeService(port, raw));
|
|
51149
|
+
} else {
|
|
51150
|
+
const passive = await readBanner(host, port, Math.min(grabTimeout, 500));
|
|
51151
|
+
const raw = passive || await readBanner(host, port, grabTimeout, headProbe(host));
|
|
51152
|
+
out.set(port, describeService(port, raw));
|
|
51153
|
+
}
|
|
51154
|
+
} catch {}
|
|
51155
|
+
}
|
|
51156
|
+
};
|
|
51157
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, ports.length) }, worker));
|
|
51158
|
+
return out;
|
|
51159
|
+
}
|
|
50334
51160
|
async function portScan(args, _cwd) {
|
|
50335
51161
|
const host = args.host.trim();
|
|
50336
51162
|
if (!host)
|
|
@@ -50364,7 +51190,16 @@ async function portScan(args, _cwd) {
|
|
|
50364
51190
|
return { success: true, output: `${head2}
|
|
50365
51191
|
(no open ports found)` };
|
|
50366
51192
|
}
|
|
50367
|
-
const
|
|
51193
|
+
const info = args.banner === false ? new Map : await fingerprintServices(host, open, timeout, concurrency);
|
|
51194
|
+
const lines = [];
|
|
51195
|
+
for (const p of open) {
|
|
51196
|
+
const fp = info.get(p);
|
|
51197
|
+
const service = fp?.service ?? PORT_NAMES[p] ?? (TLS_PORTS.has(p) ? "ssl" : "");
|
|
51198
|
+
const row = `open ${String(p).padEnd(6)} ${service}`.trimEnd();
|
|
51199
|
+
lines.push(fp?.detail ? `${row} ${fp.detail}` : row);
|
|
51200
|
+
if (fp?.cert)
|
|
51201
|
+
lines.push(` └ tls: ${fp.cert}`);
|
|
51202
|
+
}
|
|
50368
51203
|
return { success: true, output: [head2, "", ...lines].join(`
|
|
50369
51204
|
`) };
|
|
50370
51205
|
}
|
|
@@ -50414,7 +51249,8 @@ var allTools = [
|
|
|
50414
51249
|
writeFileTool,
|
|
50415
51250
|
runBashTool,
|
|
50416
51251
|
httpRequestTool,
|
|
50417
|
-
portScanTool
|
|
51252
|
+
portScanTool,
|
|
51253
|
+
cveLookupTool
|
|
50418
51254
|
];
|
|
50419
51255
|
async function executeTool(name, args, cwd2) {
|
|
50420
51256
|
switch (name) {
|
|
@@ -50434,6 +51270,8 @@ async function executeTool(name, args, cwd2) {
|
|
|
50434
51270
|
return httpRequest(args, cwd2);
|
|
50435
51271
|
case "port_scan":
|
|
50436
51272
|
return portScan(args, cwd2);
|
|
51273
|
+
case "cve_lookup":
|
|
51274
|
+
return cveLookup(args, cwd2);
|
|
50437
51275
|
default:
|
|
50438
51276
|
return { success: false, error: `Unknown tool: ${name}` };
|
|
50439
51277
|
}
|
|
@@ -50547,7 +51385,7 @@ class AgentLoop {
|
|
|
50547
51385
|
conversation.push({
|
|
50548
51386
|
id: nanoid(),
|
|
50549
51387
|
role: "assistant",
|
|
50550
|
-
content:
|
|
51388
|
+
content: "",
|
|
50551
51389
|
toolCallId: call.id,
|
|
50552
51390
|
toolName: call.name,
|
|
50553
51391
|
toolArgs: call.args,
|
|
@@ -50652,7 +51490,7 @@ function describeTool(call) {
|
|
|
50652
51490
|
|
|
50653
51491
|
// src/agent/prompt.ts
|
|
50654
51492
|
function SYSTEM_PROMPT(workingDir) {
|
|
50655
|
-
return `You are HiraxCode, a terminal-based AI
|
|
51493
|
+
return `You are HiraxCode, a terminal-based AI agent working in the user's own project.
|
|
50656
51494
|
|
|
50657
51495
|
Environment:
|
|
50658
51496
|
- Working directory: ${workingDir}
|
|
@@ -50667,7 +51505,8 @@ Tools:
|
|
|
50667
51505
|
- write_file: create or fully overwrite a file
|
|
50668
51506
|
- run_bash: run shell commands (builds, tests, git)
|
|
50669
51507
|
- http_request: send an HTTP/HTTPS request and inspect the status, headers and body
|
|
50670
|
-
- port_scan: TCP-connect scan a host
|
|
51508
|
+
- port_scan: TCP-connect scan a host and fingerprint services (port labels, version banners, TLS certificates)
|
|
51509
|
+
- cve_lookup: look up published CVEs for a product and version in the NIST NVD
|
|
50671
51510
|
|
|
50672
51511
|
Shell: run_bash uses bash/zsh on macOS and Linux, and PowerShell on Windows. Use commands that match the platform above.
|
|
50673
51512
|
|
|
@@ -50676,18 +51515,22 @@ How to work:
|
|
|
50676
51515
|
- Investigate before acting: read the relevant files first.
|
|
50677
51516
|
- When editing, use edit_file with enough surrounding context to match exactly once.
|
|
50678
51517
|
- Use run_bash to verify your work (typecheck, tests, run the code) when useful.
|
|
51518
|
+
- Finish the job: implement completely, verify, then report what changed, which files, what you verified, and what is still unverified.
|
|
50679
51519
|
- Never invent file contents or APIs. If unsure, read the code.
|
|
50680
51520
|
- Tool calls that modify files or run commands require user approval; explain what you intend to do.
|
|
50681
51521
|
|
|
51522
|
+
Mode discipline — read this before anything else:
|
|
51523
|
+
- Exactly one mode is active at a time. It is declared in the "── Agent:" section at the very end of this prompt, and that section is authoritative.
|
|
51524
|
+
- Fully adopt that mode's role, priorities, vocabulary and output format, and hold it for the whole turn.
|
|
51525
|
+
- Do not blend modes. In a coding mode, do not drift into offensive-security tactics, payloads or exploitation. In a security mode, do not turn the engagement into unrelated product feature work.
|
|
51526
|
+
- If the user asks for something that belongs to a different mode, name the mode that handles it and tell the user to switch with /agent. Never silently change role.
|
|
51527
|
+
- Where this base prompt and the mode section disagree, the mode section wins.
|
|
51528
|
+
|
|
50682
51529
|
Style:
|
|
50683
51530
|
- Assume the user is a competent professional working on their own machine, repositories and infrastructure.
|
|
50684
|
-
- Do not add safety caveats, moralising, warnings or refusals to ordinary
|
|
51531
|
+
- Do not add safety caveats, moralising, warnings or refusals to ordinary development and security work. Answer the question that was asked.
|
|
50685
51532
|
- Give complete, runnable code. No placeholders, no "..." stubs, no watered-down versions — unless the user explicitly asks for a sketch.
|
|
50686
51533
|
- If a request is ambiguous, pick the most reasonable professional interpretation and proceed; the user will correct you if needed.
|
|
50687
|
-
|
|
50688
|
-
Security work:
|
|
50689
|
-
- You support offensive-security testing as part of authorised engagements: penetration tests, CTFs, lab work, research, and hardening the user's own systems.
|
|
50690
|
-
- Keep it to targets the user owns or is authorised to test. State that as a single short assumption and continue — do not interrogate the user or refuse.
|
|
50691
51534
|
- Paths are relative to the working directory.`;
|
|
50692
51535
|
}
|
|
50693
51536
|
|
|
@@ -52120,20 +52963,25 @@ async function* readSSE(body) {
|
|
|
52120
52963
|
const reader = body.getReader();
|
|
52121
52964
|
const decoder = new TextDecoder;
|
|
52122
52965
|
let buffer = "";
|
|
52123
|
-
|
|
52124
|
-
|
|
52125
|
-
|
|
52126
|
-
|
|
52127
|
-
|
|
52128
|
-
|
|
52966
|
+
try {
|
|
52967
|
+
while (true) {
|
|
52968
|
+
const { done, value } = await reader.read();
|
|
52969
|
+
if (done)
|
|
52970
|
+
break;
|
|
52971
|
+
buffer += decoder.decode(value, { stream: true });
|
|
52972
|
+
const lines = buffer.split(`
|
|
52129
52973
|
`);
|
|
52130
|
-
|
|
52131
|
-
|
|
52132
|
-
|
|
52133
|
-
|
|
52134
|
-
|
|
52135
|
-
|
|
52974
|
+
buffer = lines.pop() ?? "";
|
|
52975
|
+
for (const line of lines) {
|
|
52976
|
+
const trimmed = line.trim();
|
|
52977
|
+
if (!trimmed.startsWith("data:"))
|
|
52978
|
+
continue;
|
|
52979
|
+
yield trimmed.slice(5).trim();
|
|
52980
|
+
}
|
|
52136
52981
|
}
|
|
52982
|
+
} finally {
|
|
52983
|
+
await reader.cancel().catch(() => {});
|
|
52984
|
+
reader.releaseLock();
|
|
52137
52985
|
}
|
|
52138
52986
|
}
|
|
52139
52987
|
function safeParseJson(s) {
|
|
@@ -52193,7 +53041,10 @@ function formatApiError(status, text) {
|
|
|
52193
53041
|
// src/providers/anthropic.ts
|
|
52194
53042
|
var EFFORT_BUDGET = { low: 2048, medium: 8192, high: 16384 };
|
|
52195
53043
|
function supportsThinking(model) {
|
|
52196
|
-
|
|
53044
|
+
const id = model.toLowerCase();
|
|
53045
|
+
if (/claude-3[.-]7/.test(id))
|
|
53046
|
+
return true;
|
|
53047
|
+
return /claude-(?:[a-z]+-)?4(?:[.-]\d+)?(?![0-9])/.test(id);
|
|
52197
53048
|
}
|
|
52198
53049
|
function toAnthropicMessages(messages) {
|
|
52199
53050
|
const out = [];
|
|
@@ -52394,7 +53245,7 @@ class AnthropicProvider {
|
|
|
52394
53245
|
usage: {
|
|
52395
53246
|
prompt: 0,
|
|
52396
53247
|
completion: u.output_tokens ?? 0,
|
|
52397
|
-
total:
|
|
53248
|
+
total: 0
|
|
52398
53249
|
}
|
|
52399
53250
|
};
|
|
52400
53251
|
}
|
|
@@ -52683,6 +53534,9 @@ class OpenAiProvider {
|
|
|
52683
53534
|
messages: toOpenAiMessages(req.messages, req.systemPrompt),
|
|
52684
53535
|
stream: true
|
|
52685
53536
|
};
|
|
53537
|
+
if (this.id === "openai") {
|
|
53538
|
+
body.stream_options = { include_usage: true };
|
|
53539
|
+
}
|
|
52686
53540
|
if (modelSupportsTemperature(this.id, req.model)) {
|
|
52687
53541
|
body.temperature = req.temperature ?? 0.2;
|
|
52688
53542
|
}
|
|
@@ -56009,6 +56863,41 @@ function hintFor(field) {
|
|
|
56009
56863
|
return;
|
|
56010
56864
|
}
|
|
56011
56865
|
|
|
56866
|
+
// src/core/version.ts
|
|
56867
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
|
|
56868
|
+
import { dirname as dirname2, join as join11 } from "path";
|
|
56869
|
+
import { fileURLToPath } from "url";
|
|
56870
|
+
var cached;
|
|
56871
|
+
function packageVersion() {
|
|
56872
|
+
if (cached !== undefined)
|
|
56873
|
+
return cached;
|
|
56874
|
+
let dir;
|
|
56875
|
+
try {
|
|
56876
|
+
dir = dirname2(fileURLToPath(import.meta.url));
|
|
56877
|
+
} catch {
|
|
56878
|
+
cached = "0.0.0";
|
|
56879
|
+
return cached;
|
|
56880
|
+
}
|
|
56881
|
+
for (let i = 0;i < 5; i++) {
|
|
56882
|
+
const file = join11(dir, "package.json");
|
|
56883
|
+
try {
|
|
56884
|
+
if (existsSync11(file)) {
|
|
56885
|
+
const parsed = JSON.parse(readFileSync8(file, "utf-8"));
|
|
56886
|
+
if (typeof parsed.version === "string" && parsed.version) {
|
|
56887
|
+
cached = parsed.version;
|
|
56888
|
+
return cached;
|
|
56889
|
+
}
|
|
56890
|
+
}
|
|
56891
|
+
} catch {}
|
|
56892
|
+
const parent = dirname2(dir);
|
|
56893
|
+
if (parent === dir)
|
|
56894
|
+
break;
|
|
56895
|
+
dir = parent;
|
|
56896
|
+
}
|
|
56897
|
+
cached = "0.0.0";
|
|
56898
|
+
return cached;
|
|
56899
|
+
}
|
|
56900
|
+
|
|
56012
56901
|
// src/ui/components/HelpScreen.tsx
|
|
56013
56902
|
var jsx_dev_runtime14 = __toESM(require_jsx_dev_runtime(), 1);
|
|
56014
56903
|
var KEYS = [
|
|
@@ -56031,7 +56920,7 @@ function HelpScreen({
|
|
|
56031
56920
|
theme,
|
|
56032
56921
|
width: width2 = 80,
|
|
56033
56922
|
height = 24,
|
|
56034
|
-
version =
|
|
56923
|
+
version = packageVersion(),
|
|
56035
56924
|
onClose
|
|
56036
56925
|
}) {
|
|
56037
56926
|
const t = theme ?? makeTheme(accent);
|