atris 3.79.0 → 3.80.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 +6 -0
- package/atris/atris.md +66 -167
- package/atris/skills/security-audit/SKILL.md +77 -0
- package/atris/team/brainstormer/MEMBER.md +2 -2
- package/atris.md +66 -167
- package/bin/atris.js +6 -0
- package/commands/business.js +44 -1984
- package/commands/member.js +189 -966
- package/commands/mission.js +229 -1409
- package/commands/pack.js +112 -1476
- package/commands/security-review.js +125 -0
- package/commands/task.js +137 -1359
- package/lib/business-core.js +2022 -0
- package/lib/engine-ask.js +1 -0
- package/lib/handoff-wake.js +8 -6
- package/lib/known-commands.js +1 -1
- package/lib/member-autoland.js +21 -2
- package/lib/member-board.js +12 -10
- package/lib/member-core.js +1014 -0
- package/lib/member-up.js +46 -3
- package/lib/mission-core.js +1559 -0
- package/lib/pack-core.js +1588 -0
- package/lib/security-audit-classes.js +300 -0
- package/lib/security-audit.js +1519 -0
- package/lib/task-core.js +1412 -0
- package/lib/task-decision.js +66 -0
- package/lib/task-explanation.js +20 -0
- package/lib/yes-lock.js +1 -1
- package/package.json +1 -1
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
// atris security-review --deep model-review prompt + evidence
|
|
16
16
|
// atris security-review --update-baseline accept current findings
|
|
17
17
|
// atris security-review hook install a pre-commit gate
|
|
18
|
+
// atris security-review audit sentinel audit: engines hunt, a
|
|
19
|
+
// different model verifies (see
|
|
20
|
+
// lib/security-audit.js)
|
|
18
21
|
//
|
|
19
22
|
// Exit code: 0 = clean, 1 = active findings at/over the fail threshold, 2 = bad usage.
|
|
20
23
|
|
|
@@ -123,6 +126,7 @@ function securityReviewCommand(argv = []) {
|
|
|
123
126
|
if (sub === 'help' || argv.includes('-h') || argv.includes('--help')) return printHelp();
|
|
124
127
|
if (sub === 'rules') return printRules();
|
|
125
128
|
if (sub === 'hook' || sub === 'install-hook') return installHook();
|
|
129
|
+
if (sub === 'audit') return auditCommand(argv.slice(1));
|
|
126
130
|
|
|
127
131
|
const root = process.cwd();
|
|
128
132
|
let opts;
|
|
@@ -397,6 +401,125 @@ function installHook() {
|
|
|
397
401
|
} catch (e) { console.error(` ${e.message}`); return 2; }
|
|
398
402
|
}
|
|
399
403
|
|
|
404
|
+
function parseAuditArgs(argv) {
|
|
405
|
+
const opts = { profile: 'standard', scope: [], json: false, planOnly: false, budget: null, out: null, hunter: null, verifier: null, timeoutMs: null };
|
|
406
|
+
const takes = (i, flag) => {
|
|
407
|
+
if (!argv[i + 1] || argv[i + 1].startsWith('--')) throw new Error(`${flag} needs a value`);
|
|
408
|
+
return argv[i + 1];
|
|
409
|
+
};
|
|
410
|
+
for (let i = 0; i < argv.length; i++) {
|
|
411
|
+
const arg = argv[i];
|
|
412
|
+
const [flag, inline] = arg.includes('=') && arg.startsWith('--') ? [arg.slice(0, arg.indexOf('=')), arg.slice(arg.indexOf('=') + 1)] : [arg, null];
|
|
413
|
+
const value = () => (inline !== null ? inline : takes(i++, flag));
|
|
414
|
+
if (flag === '--json') opts.json = true;
|
|
415
|
+
else if (flag === '--plan' || flag === '--dry-run') opts.planOnly = true;
|
|
416
|
+
else if (flag === '--quick' || flag === '--standard' || flag === '--deep') opts.profile = flag.slice(2);
|
|
417
|
+
else if (flag === '--profile') opts.profile = value();
|
|
418
|
+
else if (flag === '--budget') opts.budget = Number(value());
|
|
419
|
+
else if (flag === '--out') opts.out = value();
|
|
420
|
+
else if (flag === '--hunter') opts.hunter = value();
|
|
421
|
+
else if (flag === '--verifier') opts.verifier = value();
|
|
422
|
+
else if (flag === '--timeout') opts.timeoutMs = Number(value()) * 1000;
|
|
423
|
+
else if (arg.startsWith('-')) throw new Error(`unknown flag: ${arg}`);
|
|
424
|
+
else opts.scope.push(arg);
|
|
425
|
+
}
|
|
426
|
+
const { PROFILES } = require('../lib/security-audit');
|
|
427
|
+
if (!PROFILES.includes(opts.profile)) throw new Error(`--profile must be one of ${PROFILES.join(', ')}`);
|
|
428
|
+
if (opts.budget !== null && (!Number.isInteger(opts.budget) || opts.budget < 1 || opts.budget > 500)) throw new Error('--budget must be a whole number from 1 to 500');
|
|
429
|
+
if (opts.timeoutMs !== null && (!Number.isFinite(opts.timeoutMs) || opts.timeoutMs < 10000)) throw new Error('--timeout is in seconds, at least 10');
|
|
430
|
+
return opts;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function tildePath(p) {
|
|
434
|
+
const home = require('os').homedir();
|
|
435
|
+
return p.startsWith(home) ? `~${p.slice(home.length)}` : p;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// The audit landing: what was found, how much was read, what is left.
|
|
439
|
+
function renderAuditLanding({ dir, meta, records }) {
|
|
440
|
+
const confirmed = records.filter((r) => r.verdict === 'confirmed');
|
|
441
|
+
const needs = records.filter((r) => r.verdict === 'needs_validation');
|
|
442
|
+
const rejected = records.filter((r) => r.verdict === 'rejected');
|
|
443
|
+
const sev = meta.counts.severity;
|
|
444
|
+
const cov = meta.coverage;
|
|
445
|
+
const sevText = ['critical', 'high', 'medium', 'low', 'informational'].filter((s) => sev[s]).map((s) => `${sev[s]} ${s}`).join(', ');
|
|
446
|
+
const L = ['', ` ◉ sentinel security audit · ${meta.profile}${meta.scope_paths.length ? ` · ${meta.scope_paths.join(', ')}` : ''}`, ''];
|
|
447
|
+
if (meta.incomplete_reason === 'plan_only') {
|
|
448
|
+
L.push(` plan only: ${cov.in_scope} areas to review, no engines called`);
|
|
449
|
+
L.push(` a ${meta.profile} run with budget ${meta.budget} sends ${meta.plan.hunters} hunters (${meta.plan.units_per_hunter} area${meta.plan.units_per_hunter === 1 ? '' : 's'} each) and keeps ${meta.plan.verifyReserve} checks for verifying`);
|
|
450
|
+
} else {
|
|
451
|
+
L.push(` ${confirmed.length} confirmed${sevText ? ` (${sevText})` : ''} · ${needs.length} need a fact from you · ${rejected.length} disproved`);
|
|
452
|
+
L.push(` read ${cov.fresh} areas this run, ${cov.carried} carried unchanged, ${cov.deferred || 0} left for the next run`);
|
|
453
|
+
L.push(` found by ${meta.engines.hunter}, checked by ${meta.engines.verifier}${meta.engines.hunter === meta.engines.verifier ? ' (same engine)' : ''} · ${meta.invocations.used} engine calls of ${meta.budget}`);
|
|
454
|
+
for (const r of confirmed.slice(0, 6)) {
|
|
455
|
+
const sink = r.trace[r.trace.length - 1];
|
|
456
|
+
L.push(` ${r.severity.overall.padEnd(8)} ${r.title} (${sink.file}:${sink.line})`);
|
|
457
|
+
}
|
|
458
|
+
if (meta.run_status === 'incomplete') L.push(` incomplete: ${String(meta.incomplete_reason).replace(/_/g, ' ')}`);
|
|
459
|
+
}
|
|
460
|
+
L.push(` report: ${tildePath(path.join(dir, 'REPORT.md'))}`, '');
|
|
461
|
+
return L.join('\n');
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
async function auditCommand(argv) {
|
|
465
|
+
if (argv[0] === 'help' || argv.includes('-h') || argv.includes('--help')) return printAuditHelp();
|
|
466
|
+
const audit = require('../lib/security-audit');
|
|
467
|
+
if (argv[0] === 'status' || argv[0] === 'last') {
|
|
468
|
+
const last = audit.latestRun(process.cwd());
|
|
469
|
+
if (!last || !last.meta) { console.log('\n no sentinel audit has run for this repo yet\n'); return 0; }
|
|
470
|
+
if (argv.includes('--json')) { console.log(JSON.stringify({ dir: last.dir, meta: last.meta, findings: last.records }, null, 2)); return 0; }
|
|
471
|
+
console.log(renderAuditLanding({ dir: last.dir, meta: last.meta, records: last.records }));
|
|
472
|
+
return 0;
|
|
473
|
+
}
|
|
474
|
+
let opts;
|
|
475
|
+
try { opts = parseAuditArgs(argv); } catch (e) { console.error(`security-review audit: ${e.message}`); return 2; }
|
|
476
|
+
const log = opts.json ? () => {} : (msg) => console.log(` · ${msg}`);
|
|
477
|
+
let result;
|
|
478
|
+
try {
|
|
479
|
+
result = await audit.runAudit({
|
|
480
|
+
root: process.cwd(), profile: opts.profile, scope: opts.scope, budget: opts.budget ?? undefined, out: opts.out,
|
|
481
|
+
hunter: opts.hunter || undefined, verifier: opts.verifier || undefined, planOnly: opts.planOnly,
|
|
482
|
+
timeoutMs: opts.timeoutMs || undefined, log,
|
|
483
|
+
});
|
|
484
|
+
} catch (e) {
|
|
485
|
+
console.error(`security-review audit: ${e.message}`);
|
|
486
|
+
return 2;
|
|
487
|
+
}
|
|
488
|
+
const fail = audit.shouldFailAudit(result.records);
|
|
489
|
+
if (opts.json) console.log(JSON.stringify({ ok: !fail, dir: result.dir, meta: result.meta, findings: result.records }, null, 2));
|
|
490
|
+
else console.log(renderAuditLanding(result));
|
|
491
|
+
return fail ? 1 : 0;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function printAuditHelp() {
|
|
495
|
+
console.log(`
|
|
496
|
+
atris security-review audit, the sentinel's full security audit
|
|
497
|
+
|
|
498
|
+
atris security-review audit standard audit of the repo
|
|
499
|
+
atris security-review audit src/ audit only these paths
|
|
500
|
+
atris security-review audit --quick one wave, coarse areas, cheap
|
|
501
|
+
atris security-review audit --deep more waves, smaller areas
|
|
502
|
+
atris security-review audit --plan map areas and budget, call no engine
|
|
503
|
+
atris security-review audit --budget 30 most engine calls this run may spend
|
|
504
|
+
atris security-review audit --hunter codex --verifier claude
|
|
505
|
+
which engines hunt and which verify
|
|
506
|
+
atris security-review audit --out DIR write the run here (outside the repo)
|
|
507
|
+
atris security-review audit status the last run's landing
|
|
508
|
+
|
|
509
|
+
How it works: every source file x attack class with a reason to be read
|
|
510
|
+
becomes an area in a coverage ledger. Read-only engine hunters each own a
|
|
511
|
+
few areas. A coverage critic names gaps. Every candidate goes to a different
|
|
512
|
+
model that tries to disprove it, and cited lines must exist before any spend.
|
|
513
|
+
Runs add up: unchanged areas are skipped next time.
|
|
514
|
+
|
|
515
|
+
Output: ~/.atris/security-audit/<repo>/run-<N>/ (REPORT.md,
|
|
516
|
+
FINDINGS-DETAIL.md, NEEDS-VALIDATION.md, findings.json, coverage-ledger.json).
|
|
517
|
+
Nothing is written inside the repo. Exit 1 when a confirmed finding is high
|
|
518
|
+
or critical.
|
|
519
|
+
`);
|
|
520
|
+
return 0;
|
|
521
|
+
}
|
|
522
|
+
|
|
400
523
|
function printHelp() {
|
|
401
524
|
console.log(`
|
|
402
525
|
atris security-review, deterministic secrets / PII / privacy scan (no LLM)
|
|
@@ -416,6 +539,8 @@ function printHelp() {
|
|
|
416
539
|
ignore .security-review.baseline.json
|
|
417
540
|
atris security-review rules list the active detectors
|
|
418
541
|
atris security-review hook install a pre-commit gate
|
|
542
|
+
atris security-review audit sentinel audit: engines hunt, another model
|
|
543
|
+
verifies (atris security-review audit help)
|
|
419
544
|
|
|
420
545
|
Scans for real-looking secrets, API keys, personal data, tracked sensitive
|
|
421
546
|
files, and code-exec review evidence. exit 0 = clean, 1 = found. Wire into
|