@clear-capabilities/agentic-security-scanner 0.133.0 → 0.134.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/CHANGELOG.md +103 -0
- package/bin/agentic-security.js +83 -0
- package/dist/113.index.js +2 -2
- package/dist/178.index.js +1 -1
- package/dist/384.index.js +1 -1
- package/dist/499.index.js +86 -0
- package/dist/526.index.js +2 -2
- package/dist/609.index.js +741 -0
- package/dist/637.index.js +1 -1
- package/dist/agentic-security.mjs +56 -56
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +7 -3
- package/src/discovery/CLAUDE.md +38 -0
- package/src/discovery/confirm.js +47 -0
- package/src/discovery/disprove.js +79 -0
- package/src/discovery/hunter.js +116 -0
- package/src/discovery/index.js +159 -0
- package/src/discovery/judge.js +97 -0
- package/src/discovery/lenses.js +69 -0
- package/src/discovery/llm-invoke.js +31 -0
- package/src/discovery/partition.js +92 -0
- package/src/engine.js +120 -1
- package/src/llm-validator/index.js +29 -39
- package/src/llm-validator/providers.js +227 -0
- package/src/posture/CLAUDE.md +76 -0
- package/src/posture/autopilot.js +225 -0
- package/src/posture/comparison.js +181 -0
- package/src/posture/execution-proof.js +25 -1
- package/src/posture/fleet.js +0 -0
- package/src/posture/logic-claims.js +266 -0
- package/src/posture/poc-inprocess.js +404 -2
- package/src/posture/proof-artifact.js +101 -0
- package/src/posture/prove-findings.js +28 -4
- package/src/report/index.js +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,108 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.134.0 — the loop closes, and the logic tier learns how to be wrong
|
|
4
|
+
|
|
5
|
+
The remaining PRD epics. Two of them are new capability; the other two are the
|
|
6
|
+
same idea applied twice — a claim nobody can disagree with is the weakest thing
|
|
7
|
+
this engine emits, so both new tiers ship with the machinery to refute
|
|
8
|
+
themselves.
|
|
9
|
+
|
|
10
|
+
### Two more proof classes, and neither needed a running application
|
|
11
|
+
|
|
12
|
+
`sql-injection` and `path-traversal` join the three existing classes. The PRD
|
|
13
|
+
assumed both would wait on a running-app harness; they did not, because the
|
|
14
|
+
harness was never where the proof lived.
|
|
15
|
+
|
|
16
|
+
- **SQL injection is settled at the driver boundary.** Either the payload
|
|
17
|
+
arrives inside the query TEXT or it arrives as a bound parameter — the first
|
|
18
|
+
is the vulnerability by definition and the second is the fix by definition,
|
|
19
|
+
and no schema, rows or live server are needed to tell them apart. The PoC
|
|
20
|
+
stubs the driver with a recorder and writes the marker only when the payload
|
|
21
|
+
shows up inside something recognisably SQL. A parameterised query reaches
|
|
22
|
+
`proof-failed` by **execution**, not by reading the source.
|
|
23
|
+
- **Path traversal is settled by what comes back.** A sentinel is planted
|
|
24
|
+
outside the served directory and the marker is written only if its content —
|
|
25
|
+
or, for `sendFile`, its resolved path — comes back out of the handler. A
|
|
26
|
+
`basename` guard is refuted by running it.
|
|
27
|
+
|
|
28
|
+
Both directions are pinned by tests that execute in the real sandbox. Classes
|
|
29
|
+
still absent (IDOR, SSRF, XSS) are now documented as decisions with reasons
|
|
30
|
+
rather than gaps: a PoC built on invented application state proves something
|
|
31
|
+
about the invention.
|
|
32
|
+
|
|
33
|
+
Fixed along the way: the webhook PoC wrote its marker after a top-level `await`
|
|
34
|
+
guarded by an **unref'd** timer, so a handler that never replied let Node exit
|
|
35
|
+
with the promise pending and the marker check never ran. The test asserting
|
|
36
|
+
"no decision writes no marker" had been passing without reaching the line it
|
|
37
|
+
was testing. Timer is now ref'd and cleared, with a positive control asserting
|
|
38
|
+
the process reaches exit 0.
|
|
39
|
+
|
|
40
|
+
### The autonomous loop, wired to real stages
|
|
41
|
+
|
|
42
|
+
`scripts/autopilot.mjs` connects the loop to a real scan, a real sandboxed
|
|
43
|
+
exploit, a deterministic-then-model fix, and the real gate. End-to-end tests run
|
|
44
|
+
the whole thing against a live HTTP endpoint, including the one that matters: a
|
|
45
|
+
patch that changes the file, reads like a fix, and would satisfy any "did the
|
|
46
|
+
scanner go quiet?" check is **refused and never written**, because the exploit
|
|
47
|
+
still fires against it.
|
|
48
|
+
|
|
49
|
+
The CLI refuses to start without a confinement backend (the verdict requires
|
|
50
|
+
executing something) and refuses a dirty git tree by default (the test leg
|
|
51
|
+
writes the candidate patch to disk and restores it in a `finally`; a clean tree
|
|
52
|
+
is what makes a crash recoverable). A verified fix reached with no test runner
|
|
53
|
+
detected is counted and reported separately — the exploit stopped firing, but
|
|
54
|
+
nothing checked the application still works.
|
|
55
|
+
|
|
56
|
+
### The business-logic tier learns how to be wrong
|
|
57
|
+
|
|
58
|
+
The deterministic half already existed. The reviewing agent's half was prose:
|
|
59
|
+
it asserted that a handler lets one user act on another's resource, and nothing
|
|
60
|
+
in the finding gave a second party anything to disagree with. It was the only
|
|
61
|
+
tier in this engine with no way to be wrong.
|
|
62
|
+
|
|
63
|
+
`posture/logic-claims.js` adds three offline lenses that can refute one —
|
|
64
|
+
citation (the file exists and the line is inside it), quotation (the quoted
|
|
65
|
+
snippet is at the cited line), corroboration (a "no authentication" claim
|
|
66
|
+
against a handler that plainly authenticates). Verdicts go through the existing
|
|
67
|
+
producer/verifier separation, so a lens can never vote on a claim it produced;
|
|
68
|
+
that is why the lenses are deterministic code and not another prompt. Refuted
|
|
69
|
+
claims are quarantined, never deleted and never severity-touched.
|
|
70
|
+
|
|
71
|
+
### A comparison harness that ships no opinion about who the competition is
|
|
72
|
+
|
|
73
|
+
`posture/comparison.js` + `scripts/comparison.mjs` score this engine
|
|
74
|
+
head-to-head against participants **the operator supplies**. The repository
|
|
75
|
+
ships the harness and the answer key and names no tool — a test enforces that.
|
|
76
|
+
|
|
77
|
+
Two properties are the entire module. Every rate is computed over the
|
|
78
|
+
**intersection** of corpus entries *all* participants completed, because a tool
|
|
79
|
+
that crashed on the forty hardest entries and was scored over the remaining
|
|
80
|
+
hundred and seventy looks like it beat one that completed everything, and the
|
|
81
|
+
difference is invisible in the output. And an entry a participant could not run
|
|
82
|
+
is **unscored**, never counted as a miss: counting a crash as a false negative
|
|
83
|
+
penalises a tool for a harness problem, counting it as a pass rewards it for
|
|
84
|
+
one. Matching is CWE-only so nobody is scored on this engine's vocabulary.
|
|
85
|
+
|
|
86
|
+
No comparison figures are published in this repository. Running other vendors'
|
|
87
|
+
tools and publishing the numbers is the operator's call, not the harness's.
|
|
88
|
+
|
|
89
|
+
### The suppression pragma never worked
|
|
90
|
+
|
|
91
|
+
Found while suppressing a false positive in this release's own new code.
|
|
92
|
+
`// agentic-security-ignore: <rule-id>` is documented in `CLAUDE.md` and
|
|
93
|
+
`pr-comment.js` tells every reviewer to use it — and **nothing implemented it**.
|
|
94
|
+
It has been advertised and inert. A dead suppression mechanism is worse than an
|
|
95
|
+
absent one: the developer writes the pragma, sees the finding again, and
|
|
96
|
+
concludes the scanner is noisy rather than that the pragma is dead.
|
|
97
|
+
|
|
98
|
+
Now implemented in `engine.js`, applied after dedupe and after every cross-file
|
|
99
|
+
pass so it covers a finding whichever analysis produced it. Line-scoped, matched
|
|
100
|
+
against the finding's id / vuln / CWE / family, and **logged** to the same
|
|
101
|
+
ledger custom rules use so `--include-suppressed` can show it. Every test
|
|
102
|
+
carries a positive control — the same file without the pragma must still
|
|
103
|
+
produce the finding, or "0 findings" would prove the suppression works and
|
|
104
|
+
equally prove the detector stopped firing.
|
|
105
|
+
|
|
3
106
|
## 0.133.0 — two ways findings could be silently deleted, both closed
|
|
4
107
|
|
|
5
108
|
Four rounds of adversarial premortem against this repository's own artifacts.
|
package/bin/agentic-security.js
CHANGED
|
@@ -1485,6 +1485,88 @@ async function cmdReset(args) {
|
|
|
1485
1485
|
// FR-LEARN-6: read triage-feedback.json, group repeated FP verdicts by
|
|
1486
1486
|
// (family, dir prefix), and propose a suppression YAML when ≥ threshold
|
|
1487
1487
|
// (default 5) verdicts cluster. Writes to .agentic-security/rules-proposed/.
|
|
1488
|
+
// Languages the Layer-1 IR parses. Anything else cannot be partitioned into a
|
|
1489
|
+
// call-graph focus area, so feeding it to a hunter would spend tokens on files
|
|
1490
|
+
// the confirmation gate can never corroborate.
|
|
1491
|
+
const HUNT_EXTS = /\.(?:js|jsx|mjs|cjs|ts|tsx|py|java|cs|kt|go|php|rb)$/i;
|
|
1492
|
+
const HUNT_IGNORE = ['node_modules/**', '.git/**', 'dist/**', 'build/**', 'vendor/**', '**/.agentic-security/**'];
|
|
1493
|
+
const HUNT_MAX_FILES = 2000;
|
|
1494
|
+
|
|
1495
|
+
async function cmdHunt(args) {
|
|
1496
|
+
const scanRoot = path.resolve(args.flags.root || args._[0] || '.');
|
|
1497
|
+
const { listFiles } = await import('../src/util/glob.js');
|
|
1498
|
+
const { buildProjectIR } = await import('../src/ir/index.js');
|
|
1499
|
+
const { runDiscovery } = await import('../src/discovery/index.js');
|
|
1500
|
+
const { LENSES } = await import('../src/discovery/lenses.js');
|
|
1501
|
+
|
|
1502
|
+
const rels = (await listFiles(scanRoot, { ignore: HUNT_IGNORE })).filter(f => HUNT_EXTS.test(f));
|
|
1503
|
+
if (rels.length > HUNT_MAX_FILES) {
|
|
1504
|
+
console.error(`agentic-security: ${rels.length} source files exceeds the ${HUNT_MAX_FILES}-file hunt cap.`);
|
|
1505
|
+
console.error('Narrow the scope with --root <subdir>. Discovery is token-expensive and');
|
|
1506
|
+
console.error('unbounded fan-out on a large repository is the wrong default.');
|
|
1507
|
+
return 2;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
const fileContents = {};
|
|
1511
|
+
for (const rel of rels) {
|
|
1512
|
+
try { fileContents[rel] = fs.readFileSync(path.join(scanRoot, rel), 'utf8'); } catch { /* unreadable — skip */ }
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
const { perFile, callGraph } = buildProjectIR(fileContents);
|
|
1516
|
+
|
|
1517
|
+
// Prior scan and triage verdicts feed the judge so a hunt does not re-report
|
|
1518
|
+
// what the rule engine already found or what a human already dismissed.
|
|
1519
|
+
let priorScan = null, triageFeedback = null;
|
|
1520
|
+
try { priorScan = JSON.parse(fs.readFileSync(path.join(scanRoot, '.agentic-security', 'last-scan.json'), 'utf8')); } catch {}
|
|
1521
|
+
try { triageFeedback = JSON.parse(fs.readFileSync(path.join(scanRoot, '.agentic-security', 'triage-feedback.json'), 'utf8')); } catch {}
|
|
1522
|
+
|
|
1523
|
+
const lenses = args.flags.lens ? String(args.flags.lens).split(',').map(s => s.trim()).filter(Boolean) : undefined;
|
|
1524
|
+
const report = await runDiscovery(
|
|
1525
|
+
{ perFileIR: perFile, callGraph, fileContents, priorScan, triageFeedback },
|
|
1526
|
+
{ lenses, maxAreas: args.flags['max-areas'] ? parseInt(args.flags['max-areas'], 10) : undefined },
|
|
1527
|
+
);
|
|
1528
|
+
|
|
1529
|
+
const c = report.coverage;
|
|
1530
|
+
console.log('');
|
|
1531
|
+
console.log(`Discovery — ${rels.length} file(s), ${c.areasPlanned} focus area(s), ${c.lensesPerArea} lens(es) each`);
|
|
1532
|
+
console.log(` areas hunted: ${c.areasHunted}/${c.areasPlanned} (fully: ${c.areasFullyHunted}) degraded runs: ${c.degradedRuns}/${report.runs.length}`);
|
|
1533
|
+
console.log(` confirmation: ${JSON.stringify(c.confirmedByTier)} panels: ${c.panelsRun} (undecided ${c.undecidedPanels})`);
|
|
1534
|
+
console.log('');
|
|
1535
|
+
|
|
1536
|
+
if (report.fresh.length === 0) {
|
|
1537
|
+
console.log('No new candidates survived confirmation and refutation.');
|
|
1538
|
+
} else {
|
|
1539
|
+
console.log(`${report.fresh.length} new candidate finding(s):`);
|
|
1540
|
+
for (const f of report.fresh) {
|
|
1541
|
+
console.log(` [${f.severity}] ${f.file}:${f.line} ${f.vuln}`);
|
|
1542
|
+
console.log(` lens=${f.discovery.lens} confirmation=${f.discovery.confirmation?.tier || 'unknown'}`);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
if (report.duplicates.length || report.suppressed.length || report.refutedCandidates.length) {
|
|
1546
|
+
console.log('');
|
|
1547
|
+
console.log(` (${report.duplicates.length} already known, ${report.suppressed.length} previously marked false positive, ` +
|
|
1548
|
+
`${report.refutedCandidates.length} refuted by the panel)`);
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
// Degradation is part of the output. A half-failed hunt must never read as a
|
|
1552
|
+
// clean one, so every reason is printed rather than summarised away.
|
|
1553
|
+
if (c.reasons.length) {
|
|
1554
|
+
console.log('');
|
|
1555
|
+
console.log('Coverage gaps:');
|
|
1556
|
+
for (const r of c.reasons) console.log(` · ${r}`);
|
|
1557
|
+
}
|
|
1558
|
+
if (report.runs.length && c.degradedRuns === report.runs.length) {
|
|
1559
|
+
console.log('');
|
|
1560
|
+
console.log('EVERY hunter run degraded — this result says nothing about the code.');
|
|
1561
|
+
console.log('Set AGENTIC_SECURITY_LLM_ENDPOINT to enable discovery.');
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
// Advisory by design: discovery never gates. Exit 0 unless the scope was bad.
|
|
1565
|
+
console.log('');
|
|
1566
|
+
console.log(`Lenses available: ${LENSES.map(l => l.key).join(', ')} (--lens a,b to narrow)`);
|
|
1567
|
+
return 0;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1488
1570
|
async function cmdRuleSynth(args) {
|
|
1489
1571
|
const scanRoot = path.resolve(args.flags.root || '.');
|
|
1490
1572
|
const { synthesizeRules } = await import('../src/posture/rule-synthesis.js');
|
|
@@ -1777,6 +1859,7 @@ async function main() {
|
|
|
1777
1859
|
case 'validator-cache': process.exit(await cmdValidatorCache(args));
|
|
1778
1860
|
case 'verify': process.exit(await cmdVerify(args));
|
|
1779
1861
|
case 'reset': process.exit(await cmdReset(args));
|
|
1862
|
+
case 'hunt': process.exit(await cmdHunt(args));
|
|
1780
1863
|
case 'rule-synth': process.exit(await cmdRuleSynth(args));
|
|
1781
1864
|
case 'digest': process.exit(await cmdDigest(args));
|
|
1782
1865
|
case 'setup': process.exit(await cmdSetup(args));
|
package/dist/113.index.js
CHANGED
|
@@ -406,8 +406,8 @@ var external_node_child_process_ = __webpack_require__(1421);
|
|
|
406
406
|
var external_node_fs_ = __webpack_require__(3024);
|
|
407
407
|
// EXTERNAL MODULE: external "node:path"
|
|
408
408
|
var external_node_path_ = __webpack_require__(6760);
|
|
409
|
-
// EXTERNAL MODULE: ./src/engine.js +
|
|
410
|
-
var engine = __webpack_require__(
|
|
409
|
+
// EXTERNAL MODULE: ./src/engine.js + 501 modules
|
|
410
|
+
var engine = __webpack_require__(5695);
|
|
411
411
|
;// CONCATENATED MODULE: ./src/posture/fix-honesty-gate.js
|
|
412
412
|
// Deterministic honesty gates on fix / finding output (#7).
|
|
413
413
|
//
|
package/dist/178.index.js
CHANGED
|
@@ -13,7 +13,7 @@ export const modules = {
|
|
|
13
13
|
/* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1421);
|
|
14
14
|
/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3024);
|
|
15
15
|
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6760);
|
|
16
|
-
/* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(
|
|
16
|
+
/* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5695);
|
|
17
17
|
// Time-travel + counterfactual scanning (v0.68).
|
|
18
18
|
//
|
|
19
19
|
// Two new modes that exploit the pure-input shape of runFullScan:
|
package/dist/384.index.js
CHANGED
|
@@ -8,7 +8,7 @@ export const modules = {
|
|
|
8
8
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
9
9
|
/* harmony export */ scanCredentials: () => (/* reexport safe */ _engine_js__WEBPACK_IMPORTED_MODULE_0__.Sv)
|
|
10
10
|
/* harmony export */ });
|
|
11
|
-
/* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
|
|
11
|
+
/* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5695);
|
|
12
12
|
// Secrets submodule view of the engine — credential + entropy + TODO scanning.
|
|
13
13
|
|
|
14
14
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export const id = 499;
|
|
2
|
+
export const ids = [499];
|
|
3
|
+
export const modules = {
|
|
4
|
+
|
|
5
|
+
/***/ 3499:
|
|
6
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
7
|
+
|
|
8
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
9
|
+
/* harmony export */ H: () => (/* binding */ lensByKey),
|
|
10
|
+
/* harmony export */ LENSES: () => (/* binding */ LENSES),
|
|
11
|
+
/* harmony export */ j: () => (/* binding */ buildHunterPrompt)
|
|
12
|
+
/* harmony export */ });
|
|
13
|
+
//
|
|
14
|
+
// The seven hunting lenses. Each hunter run is one (focus area × lens) pair.
|
|
15
|
+
//
|
|
16
|
+
// WHY DIVERSE LENSES RATHER THAN N IDENTICAL HUNTERS: redundancy raises
|
|
17
|
+
// confidence in what was already found and adds nothing to coverage. A lens
|
|
18
|
+
// that is told to look only at authorization asks different questions of the
|
|
19
|
+
// same code than one told to look at crypto, so the union covers failure modes
|
|
20
|
+
// no single prompt reaches. `wildcard` exists because a fixed taxonomy is a
|
|
21
|
+
// ceiling, and the classes worth finding are the ones not on the list.
|
|
22
|
+
const LENSES = Object.freeze([
|
|
23
|
+
{ key: 'injection', title: 'Injection', family: 'injection', cwe: 'CWE-74',
|
|
24
|
+
brief: 'Untrusted input reaching an interpreter: SQL, shell, template, XPath, LDAP, or deserialization. Follow the value, not the function name.' },
|
|
25
|
+
{ key: 'authz', title: 'Authorization', family: 'access-control', cwe: 'CWE-285',
|
|
26
|
+
brief: 'Missing, partial, or bypassable authorization: object references not scoped to the caller, tier checks applied on one path but not another, checks performed after the effect.' },
|
|
27
|
+
{ key: 'crypto', title: 'Cryptography', family: 'crypto', cwe: 'CWE-327',
|
|
28
|
+
brief: 'Misuse rather than choice of primitive: reused nonces, unauthenticated ciphertext, comparisons that are not constant time, keys derived from guessable material.' },
|
|
29
|
+
{ key: 'business-logic', title: 'Business logic', family: 'business-logic', cwe: 'CWE-840',
|
|
30
|
+
brief: 'The code does what it says and what it says is wrong: state machines that accept out-of-order transitions, quantities that may be negative, refunds that exceed charges, limits enforced client side.' },
|
|
31
|
+
{ key: 'feature-abuse', title: 'Feature abuse', family: 'abuse', cwe: 'CWE-799',
|
|
32
|
+
brief: 'A working feature used as a weapon: unbounded fan-out, expensive endpoints with no cost to the caller, invitations or exports that leak across tenants.' },
|
|
33
|
+
{ key: 'chained', title: 'Chained', family: 'attack-chain', cwe: 'CWE-1173',
|
|
34
|
+
brief: 'Two behaviours that are each acceptable alone and unacceptable together. State the chain as an ordered sequence of steps with the attacker capability required at each.' },
|
|
35
|
+
{ key: 'wildcard', title: 'Wildcard', family: 'other', cwe: 'CWE-710',
|
|
36
|
+
brief: 'Anything the other lenses do not cover. Prefer the surprising and specific over the generic; report nothing rather than something already obvious.' },
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
function lensByKey(key) {
|
|
40
|
+
if (typeof key !== 'string') return null;
|
|
41
|
+
return LENSES.find(l => l.key === key) || null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const DEFAULT_MAX_CHARS = 60_000;
|
|
45
|
+
|
|
46
|
+
function buildHunterPrompt(focusArea, lens, ctx = {}) {
|
|
47
|
+
const maxChars = Number.isInteger(ctx.maxChars) && ctx.maxChars > 0 ? ctx.maxChars : DEFAULT_MAX_CHARS;
|
|
48
|
+
const contents = ctx.fileContents || {};
|
|
49
|
+
const files = (focusArea?.files || []).filter(f => typeof contents[f] === 'string');
|
|
50
|
+
|
|
51
|
+
let budget = maxChars;
|
|
52
|
+
const blocks = [];
|
|
53
|
+
for (const f of files) {
|
|
54
|
+
const src = contents[f];
|
|
55
|
+
const slice = src.length > budget ? src.slice(0, Math.max(0, budget)) : src;
|
|
56
|
+
const truncated = slice.length < src.length;
|
|
57
|
+
blocks.push(`--- ${f}${truncated ? ' (truncated)' : ''} ---\n${slice}`);
|
|
58
|
+
budget -= slice.length;
|
|
59
|
+
if (budget <= 0) break;
|
|
60
|
+
}
|
|
61
|
+
const omitted = files.length - blocks.length;
|
|
62
|
+
|
|
63
|
+
return [
|
|
64
|
+
`You are hunting for security vulnerabilities in one area of a codebase.`,
|
|
65
|
+
`Area: ${focusArea?.label ?? 'unknown'} (${files.length} files)`,
|
|
66
|
+
``,
|
|
67
|
+
`Your lens is ${lens.title}. ${lens.brief}`,
|
|
68
|
+
`Report ONLY through this lens. Another hunter covers the others.`,
|
|
69
|
+
``,
|
|
70
|
+
`Rules:`,
|
|
71
|
+
`- Report a candidate only if you can name the entry point an attacker controls and the effect they achieve.`,
|
|
72
|
+
`- Do not report defence-in-depth gaps, style, or "could be hardened". Those are not candidates.`,
|
|
73
|
+
`- Cite a real file and line from the source below. A candidate with no location is discarded.`,
|
|
74
|
+
``,
|
|
75
|
+
`Return JSON: {"candidates":[{"title","file","line","rationale","entryPoint","sink"}]}`,
|
|
76
|
+
`Return {"candidates":[]} if you find nothing. An empty result is a valid and useful answer.`,
|
|
77
|
+
``,
|
|
78
|
+
omitted > 0 ? `NOTE: ${omitted} file(s) omitted, prompt budget exhausted (truncated context).\n` : ``,
|
|
79
|
+
...blocks,
|
|
80
|
+
].join('\n');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
/***/ })
|
|
85
|
+
|
|
86
|
+
};
|
package/dist/526.index.js
CHANGED
|
@@ -234,8 +234,8 @@ var external_node_child_process_ = __webpack_require__(1421);
|
|
|
234
234
|
var external_node_fs_ = __webpack_require__(3024);
|
|
235
235
|
// EXTERNAL MODULE: external "node:path"
|
|
236
236
|
var external_node_path_ = __webpack_require__(6760);
|
|
237
|
-
// EXTERNAL MODULE: ./src/engine.js +
|
|
238
|
-
var engine = __webpack_require__(
|
|
237
|
+
// EXTERNAL MODULE: ./src/engine.js + 501 modules
|
|
238
|
+
var engine = __webpack_require__(5695);
|
|
239
239
|
;// CONCATENATED MODULE: ./src/posture/fix-honesty-gate.js
|
|
240
240
|
// Deterministic honesty gates on fix / finding output (#7).
|
|
241
241
|
//
|