torusguard 1.3.6 → 2.0.0-alpha
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/.torusguard/.manifest.json +9 -8
- package/.torusguard/auth.json +5 -0
- package/.torusguard/rules_catalog.json +242 -518
- package/.torusguard/runs/report-latest.html +3529 -364
- package/.torusguard/runs/run-20260918-105940-audit/manifest.json +2 -1
- package/.torusguard/runs/run-20260918-105940-audit/remediation.md +7 -0
- package/.torusguard/runs/run-20260919-145701-audit/findings.json +1 -0
- package/.torusguard/runs/run-20260919-145701-audit/findings.md +8 -0
- package/.torusguard/runs/run-20260919-145701-audit/manifest.json +12 -0
- package/.torusguard/runs/run-20260919-145701-audit/summary.md +5 -0
- package/.torusguard/runs/run-20260919-151729-audit/findings.json +1 -0
- package/.torusguard/runs/run-20260919-151729-audit/findings.md +8 -0
- package/.torusguard/runs/run-20260919-151729-audit/manifest.json +13 -0
- package/.torusguard/runs/run-20260919-151729-audit/remediation.md +7 -0
- package/.torusguard/runs/run-20260919-151729-audit/summary.md +5 -0
- package/.torusguard/runs/run-20260919-152340-audit/findings.json +1 -0
- package/.torusguard/runs/run-20260919-152340-audit/findings.md +8 -0
- package/.torusguard/runs/run-20260919-152340-audit/manifest.json +12 -0
- package/.torusguard/runs/run-20260919-152340-audit/summary.md +5 -0
- package/.torusguard/runs/run-20260919-152345-audit/findings.json +1 -0
- package/.torusguard/runs/run-20260919-152345-audit/findings.md +8 -0
- package/.torusguard/runs/run-20260919-152345-audit/manifest.json +12 -0
- package/.torusguard/runs/run-20260919-152345-audit/summary.md +5 -0
- package/.torusguard/runs/run-20260919-153500-audit/findings.json +1 -0
- package/.torusguard/runs/run-20260919-153500-audit/findings.md +8 -0
- package/.torusguard/runs/run-20260919-153500-audit/manifest.json +12 -0
- package/.torusguard/runs/run-20260919-153500-audit/summary.md +5 -0
- package/.torusguard/scripts/__pycache__/apply_runner.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/audit_runner.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/harden_runner.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/html_reporter.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/manifest_builder.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/recipes_runner.cpython-314.pyc +0 -0
- package/.torusguard/scripts/__pycache__/report_sync.cpython-314.pyc +0 -0
- package/.torusguard/scripts/apply_runner.py +101 -8
- package/.torusguard/scripts/audit_runner.py +116 -11
- package/.torusguard/scripts/harden_runner.py +122 -68
- package/.torusguard/scripts/html_reporter.py +1170 -253
- package/.torusguard/scripts/manifest_builder.py +2 -0
- package/.torusguard/scripts/recheck_runner.py +2 -1
- package/.torusguard/scripts/recipes_runner.py +91 -8
- package/.torusguard/scripts/report_sync.py +134 -7
- package/.torusguard/scripts/status_runner.py +267 -0
- package/.torusguard/snapshots/20260921-182638/go.mod.bak +11 -0
- package/.torusguard/snapshots/20260921-185213/file.go.bak +5 -0
- package/README.md +314 -364
- package/bin/torusguard.js +190 -20
- package/package.json +6 -2
- package/skills/torusguard/__pycache__/bootstrap.cpython-314.pyc +0 -0
- package/skills/torusguard/bootstrap.py +45 -6
- package/skills/torusguard/payload/.manifest.json +29 -14
- package/skills/torusguard/payload/TORUSGUARD.md +145 -145
- package/skills/torusguard/payload/agents/auditor.md +41 -41
- package/skills/torusguard/payload/agents/profiler.md +49 -49
- package/skills/torusguard/payload/agents/remediator.md +41 -41
- package/skills/torusguard/payload/agents/reviewer.md +39 -39
- package/skills/torusguard/payload/agents/validator.md +46 -46
- package/skills/torusguard/payload/config/scope.json +24 -24
- package/skills/torusguard/payload/references/csharp-security.md +41 -41
- package/skills/torusguard/payload/references/go-security.md +41 -41
- package/skills/torusguard/payload/references/java-security.md +40 -40
- package/skills/torusguard/payload/references/polyglot-security-matrix.md +25 -25
- package/skills/torusguard/payload/references/rust-security.md +40 -40
- package/skills/torusguard/payload/rules/custom/.gitkeep +1 -1
- package/skills/torusguard/payload/rules/custom/README.md +30 -30
- package/skills/torusguard/payload/scripts/__pycache__/audit_runner.cpython-314.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/html_reporter.cpython-314.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/report_sync.cpython-314.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/sarif_exporter.cpython-314.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/term_ui.cpython-314.pyc +0 -0
- package/skills/torusguard/payload/scripts/apply_runner.py +101 -8
- package/skills/torusguard/payload/scripts/audit_runner.py +116 -11
- package/skills/torusguard/payload/scripts/harden_runner.py +122 -68
- package/skills/torusguard/payload/scripts/html_reporter.py +1170 -253
- package/skills/torusguard/payload/scripts/manifest_builder.py +2 -0
- package/skills/torusguard/payload/scripts/recheck_runner.py +2 -1
- package/skills/torusguard/payload/scripts/recipes_runner.py +91 -8
- package/skills/torusguard/payload/scripts/report_sync.py +134 -7
- package/skills/torusguard/payload/scripts/rules_sync.py +321 -321
- package/skills/torusguard/payload/scripts/safety_gate.py +64 -64
- package/skills/torusguard/payload/scripts/status_runner.py +267 -0
- package/skills/torusguard/payload/skills/torusguard/references/csharp-security.md +41 -41
- package/skills/torusguard/payload/skills/torusguard/references/go-security.md +41 -41
- package/skills/torusguard/payload/skills/torusguard/references/java-security.md +40 -40
- package/skills/torusguard/payload/skills/torusguard/references/polyglot-security-matrix.md +25 -25
- package/skills/torusguard/payload/skills/torusguard/references/rust-security.md +40 -40
- package/skills/torusguard/payload/templates/audit-report.template.md +54 -54
- package/skills/torusguard/payload/templates/authorization.template.md +34 -34
- package/skills/torusguard/payload/templates/finding-card.template.md +32 -32
- package/skills/torusguard/payload/templates/remediation-bundle.template.md +35 -35
- package/skills/torusguard/payload/workflows/apply.md +9 -1
- package/skills/torusguard/payload/workflows/audit.md +8 -2
- package/skills/torusguard/payload/workflows/harden.md +6 -1
- package/skills/torusguard/payload/workflows/init.md +7 -1
- package/skills/torusguard/payload/workflows/recipes.md +55 -0
- package/skills/torusguard/payload/workflows/report.md +59 -55
- package/skills/torusguard/payload/workflows/status.md +4 -1
- package/skills/torusguard/payload/workflows/torusguard-apply.md +110 -0
- package/skills/torusguard/payload/workflows/torusguard-audit.md +105 -0
- package/skills/torusguard/payload/workflows/torusguard-authorize.md +96 -0
- package/skills/torusguard/payload/workflows/torusguard-exploit-check.md +98 -0
- package/skills/torusguard/payload/workflows/torusguard-harden.md +104 -0
- package/skills/torusguard/payload/workflows/torusguard-init.md +106 -0
- package/skills/torusguard/payload/workflows/torusguard-recheck.md +101 -0
- package/skills/torusguard/payload/workflows/torusguard-recipes.md +55 -0
- package/skills/torusguard/payload/workflows/torusguard-report.md +105 -0
- package/skills/torusguard/payload/workflows/torusguard-status.md +103 -0
- package/skills/torusguard/payload/workflows/torusguard-verify.md +100 -0
- package/skills/torusguard/payload/workflows/torusguard-web-validate.md +96 -0
- package/skills/torusguard/payload/workflows/torusguard.md +19 -0
package/bin/torusguard.js
CHANGED
|
@@ -15,6 +15,51 @@ const command = args[0] || 'init';
|
|
|
15
15
|
const rootDir = path.resolve(__dirname, '..');
|
|
16
16
|
const cwd = process.cwd();
|
|
17
17
|
|
|
18
|
+
// Parse version flag immediately
|
|
19
|
+
if (command === '--version' || command === '-v' || command === 'version') {
|
|
20
|
+
let ver = '1.4.0';
|
|
21
|
+
try {
|
|
22
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(rootDir, 'package.json'), 'utf-8'));
|
|
23
|
+
ver = pkg.version || ver;
|
|
24
|
+
} catch (e) {}
|
|
25
|
+
console.log(`torusguard v${ver}`);
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function parseTargetAndArgs(cliArgs) {
|
|
30
|
+
let target = '.';
|
|
31
|
+
const remaining = [];
|
|
32
|
+
for (let i = 1; i < cliArgs.length; i++) {
|
|
33
|
+
const a = cliArgs[i];
|
|
34
|
+
if (a === '--target' || a === '-t' || a === '--root') {
|
|
35
|
+
if (i + 1 < cliArgs.length) {
|
|
36
|
+
target = cliArgs[i + 1];
|
|
37
|
+
i++;
|
|
38
|
+
}
|
|
39
|
+
} else if (a.startsWith('--target=') || a.startsWith('--root=')) {
|
|
40
|
+
target = a.split('=')[1];
|
|
41
|
+
} else {
|
|
42
|
+
remaining.push(a);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (target === '.' && remaining.length > 0 && !remaining[0].startsWith('-')) {
|
|
46
|
+
target = remaining.shift();
|
|
47
|
+
}
|
|
48
|
+
return { target, remaining };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function compareSemver(a, b) {
|
|
52
|
+
const pa = a.replace(/^v/, '').split('.').map(n => parseInt(n, 10) || 0);
|
|
53
|
+
const pb = b.replace(/^v/, '').split('.').map(n => parseInt(n, 10) || 0);
|
|
54
|
+
for (let i = 0; i < 3; i++) {
|
|
55
|
+
const na = pa[i] || 0;
|
|
56
|
+
const nb = pb[i] || 0;
|
|
57
|
+
if (na > nb) return 1;
|
|
58
|
+
if (na < nb) return -1;
|
|
59
|
+
}
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
18
63
|
// ─── ANSI Color Helpers ───────────────────────────────────────────────────────
|
|
19
64
|
const BOLD = '\x1b[1m';
|
|
20
65
|
const DIM = '\x1b[2m';
|
|
@@ -169,12 +214,13 @@ function cardHeader(title, subtitle = '', version = 'v1.3.6', borderColor = CYAN
|
|
|
169
214
|
|
|
170
215
|
function printHelp() {
|
|
171
216
|
console.log();
|
|
172
|
-
console.log(cardHeader('🛡️ T O R U S G U A R D C L I', 'Autonomous Security Engine for AI-Built Applications', 'v1.
|
|
217
|
+
console.log(cardHeader('🛡️ T O R U S G U A R D C L I', 'Autonomous Security Engine for AI-Built Applications', 'v1.4.0'));
|
|
173
218
|
console.log(`\n ${BOLD}Usage:${RESET} ${GREEN}npx torusguard${RESET} ${WHITE}[command]${RESET} ${GRAY}[options]${RESET}\n`);
|
|
174
219
|
|
|
175
220
|
console.log(cardBorderTop('Commands'));
|
|
176
221
|
console.log(formatBoxLine(`${GREEN}init${RESET} Scaffold ${BOLD}.torusguard/${RESET} workspace + unlock slash commands`));
|
|
177
222
|
console.log(formatBoxLine(`${GREEN}status${RESET} Display active security posture, memory, rules, & stack`));
|
|
223
|
+
console.log(formatBoxLine(`${GREEN}update${RESET} Check npm registry for updates (--install to auto-upgrade)`));
|
|
178
224
|
console.log(formatBoxLine(`${GREEN}rules${RESET} Auto-sync memory to AI IDE rules (.cursorrules, CLAUDE.md)`));
|
|
179
225
|
console.log(formatBoxLine(`${GREEN}memory${RESET} Manage persistent security memory (export, hook, learn)`));
|
|
180
226
|
console.log(formatBoxLine(`${GREEN}audit${RESET} Run static AST security scan on target project`));
|
|
@@ -198,6 +244,7 @@ function printHelp() {
|
|
|
198
244
|
console.log(formatBoxLine(`${GRAY}--target <dir>${RESET} Target directory to analyze or scaffold ${DIM}(default: .)${RESET}`));
|
|
199
245
|
console.log(formatBoxLine(`${GRAY}--force${RESET} Overwrite existing workspace and re-scaffold`));
|
|
200
246
|
console.log(formatBoxLine(`${GRAY}--yes, -y${RESET} Non-interactive auto-approval for patch application`));
|
|
247
|
+
console.log(formatBoxLine(`${GRAY}--version, -v${RESET} Display TorusGuard package version`));
|
|
201
248
|
console.log(cardBorderBottom());
|
|
202
249
|
|
|
203
250
|
console.log(`\n ${BOLD}AI Chat Commands:${RESET}`);
|
|
@@ -219,7 +266,18 @@ if (command === 'help' || command === '--help' || command === '-h') {
|
|
|
219
266
|
}
|
|
220
267
|
|
|
221
268
|
if (command === 'status') {
|
|
222
|
-
const
|
|
269
|
+
const { target, remaining } = parseTargetAndArgs(args);
|
|
270
|
+
const targetDir = path.resolve(cwd, target);
|
|
271
|
+
const statusScript = path.join(targetDir, '.torusguard', 'scripts', 'status_runner.py');
|
|
272
|
+
const fallbackStatusScript = path.join(rootDir, '.torusguard', 'scripts', 'status_runner.py');
|
|
273
|
+
const actualStatusScript = fs.existsSync(statusScript) ? statusScript : fallbackStatusScript;
|
|
274
|
+
|
|
275
|
+
if (fs.existsSync(actualStatusScript)) {
|
|
276
|
+
const proc = spawnSync(pythonCmd, [actualStatusScript, targetDir, ...remaining], { stdio: 'inherit', cwd: targetDir });
|
|
277
|
+
process.exit(proc.status !== null ? proc.status : 0);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const cfgPath = path.join(targetDir, '.torusguard', 'config', 'torusguard.json');
|
|
223
281
|
if (fs.existsSync(cfgPath)) {
|
|
224
282
|
try {
|
|
225
283
|
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
|
|
@@ -441,11 +499,13 @@ if (command === 'rules') {
|
|
|
441
499
|
|
|
442
500
|
// Subcommand: audit
|
|
443
501
|
if (command === 'audit') {
|
|
444
|
-
const
|
|
502
|
+
const { target, remaining } = parseTargetAndArgs(args);
|
|
503
|
+
const targetDir = path.resolve(cwd, target);
|
|
504
|
+
const auditScript = path.join(targetDir, '.torusguard', 'scripts', 'audit_runner.py');
|
|
445
505
|
const fallbackAuditScript = path.join(rootDir, '.torusguard', 'scripts', 'audit_runner.py');
|
|
446
506
|
const actualAuditScript = fs.existsSync(fallbackAuditScript) ? fallbackAuditScript : auditScript;
|
|
447
507
|
|
|
448
|
-
const proc = spawnSync(pythonCmd, [actualAuditScript, ...
|
|
508
|
+
const proc = spawnSync(pythonCmd, [actualAuditScript, targetDir, ...remaining], { stdio: 'inherit', cwd: targetDir });
|
|
449
509
|
process.exit(proc.status !== null ? proc.status : 0);
|
|
450
510
|
}
|
|
451
511
|
|
|
@@ -455,7 +515,7 @@ if (command === 'report') {
|
|
|
455
515
|
const wantSarif = args.includes('--sarif') || (!wantHtml && args[1] !== 'html');
|
|
456
516
|
|
|
457
517
|
// Resolve target directory (supports --target and --root)
|
|
458
|
-
const rootIdx = args.indexOf('--root') !== -1 ? args.indexOf('--root') : args.indexOf('--target');
|
|
518
|
+
const rootIdx = args.indexOf('--root') !== -1 ? args.indexOf('--root') : (args.indexOf('--target') !== -1 ? args.indexOf('--target') : args.indexOf('-t'));
|
|
459
519
|
let targetRoot = cwd;
|
|
460
520
|
if (rootIdx !== -1 && args[rootIdx + 1]) {
|
|
461
521
|
targetRoot = path.resolve(cwd, args[rootIdx + 1]);
|
|
@@ -518,69 +578,179 @@ if (command === 'report') {
|
|
|
518
578
|
|
|
519
579
|
// Subcommand: harden
|
|
520
580
|
if (command === 'harden') {
|
|
521
|
-
const
|
|
581
|
+
const { target, remaining } = parseTargetAndArgs(args);
|
|
582
|
+
const targetDir = path.resolve(cwd, target);
|
|
583
|
+
const scriptPath = path.join(targetDir, '.torusguard', 'scripts', 'harden_runner.py');
|
|
522
584
|
const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'harden_runner.py');
|
|
523
585
|
const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
|
|
524
586
|
|
|
525
|
-
const proc = spawnSync(pythonCmd, [actualScript, ...
|
|
587
|
+
const proc = spawnSync(pythonCmd, [actualScript, targetDir, ...remaining], { stdio: 'inherit', cwd: targetDir });
|
|
526
588
|
process.exit(proc.status !== null ? proc.status : 0);
|
|
527
589
|
}
|
|
528
590
|
|
|
529
591
|
// Subcommand: apply
|
|
530
592
|
if (command === 'apply') {
|
|
531
|
-
const
|
|
593
|
+
const { target, remaining } = parseTargetAndArgs(args);
|
|
594
|
+
const targetDir = path.resolve(cwd, target);
|
|
595
|
+
const scriptPath = path.join(targetDir, '.torusguard', 'scripts', 'apply_runner.py');
|
|
532
596
|
const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'apply_runner.py');
|
|
533
597
|
const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
|
|
534
598
|
|
|
535
|
-
const proc = spawnSync(pythonCmd, [actualScript, ...
|
|
599
|
+
const proc = spawnSync(pythonCmd, [actualScript, targetDir, ...remaining], { stdio: 'inherit', cwd: targetDir });
|
|
536
600
|
process.exit(proc.status !== null ? proc.status : 0);
|
|
537
601
|
}
|
|
538
602
|
|
|
539
603
|
// Subcommand: rollback
|
|
540
604
|
if (command === 'rollback') {
|
|
541
|
-
const
|
|
605
|
+
const { target, remaining } = parseTargetAndArgs(args);
|
|
606
|
+
const targetDir = path.resolve(cwd, target);
|
|
607
|
+
const scriptPath = path.join(targetDir, '.torusguard', 'scripts', 'apply_runner.py');
|
|
542
608
|
const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'apply_runner.py');
|
|
543
609
|
const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
|
|
544
610
|
|
|
545
|
-
const proc = spawnSync(pythonCmd, [actualScript, '--rollback', ...
|
|
611
|
+
const proc = spawnSync(pythonCmd, [actualScript, targetDir, '--rollback', ...remaining], { stdio: 'inherit', cwd: targetDir });
|
|
546
612
|
process.exit(proc.status !== null ? proc.status : 0);
|
|
547
613
|
}
|
|
548
614
|
|
|
549
615
|
// Subcommand: recheck / verify
|
|
550
616
|
if (command === 'recheck' || command === 'verify') {
|
|
551
|
-
const
|
|
617
|
+
const { target, remaining } = parseTargetAndArgs(args);
|
|
618
|
+
const targetDir = path.resolve(cwd, target);
|
|
619
|
+
const scriptPath = path.join(targetDir, '.torusguard', 'scripts', 'recheck_runner.py');
|
|
552
620
|
const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'recheck_runner.py');
|
|
553
621
|
const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
|
|
554
622
|
|
|
555
|
-
const proc = spawnSync(pythonCmd, [actualScript, ...
|
|
623
|
+
const proc = spawnSync(pythonCmd, [actualScript, targetDir, ...remaining], { stdio: 'inherit', cwd: targetDir });
|
|
556
624
|
process.exit(proc.status !== null ? proc.status : 0);
|
|
557
625
|
}
|
|
558
626
|
|
|
559
627
|
// Subcommand: recipes
|
|
560
628
|
if (command === 'recipes') {
|
|
561
|
-
const
|
|
629
|
+
const { target, remaining } = parseTargetAndArgs(args);
|
|
630
|
+
const targetDir = path.resolve(cwd, target);
|
|
631
|
+
const scriptPath = path.join(targetDir, '.torusguard', 'scripts', 'recipes_runner.py');
|
|
562
632
|
const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'recipes_runner.py');
|
|
563
633
|
const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
|
|
564
634
|
|
|
565
|
-
const proc = spawnSync(pythonCmd, [actualScript, ...
|
|
635
|
+
const proc = spawnSync(pythonCmd, [actualScript, targetDir, ...remaining], { stdio: 'inherit', cwd: targetDir });
|
|
566
636
|
process.exit(proc.status !== null ? proc.status : 0);
|
|
567
637
|
}
|
|
568
638
|
|
|
569
639
|
// Subcommand: init (scaffold workspace)
|
|
570
640
|
if (command === 'init') {
|
|
641
|
+
const { target, remaining } = parseTargetAndArgs(args);
|
|
642
|
+
const targetDir = path.resolve(cwd, target);
|
|
571
643
|
const localBootstrap = path.join(rootDir, 'skills', 'torusguard', 'bootstrap.py');
|
|
572
644
|
const localInstall = path.join(rootDir, 'install.py');
|
|
573
645
|
const scriptToRun = fs.existsSync(localBootstrap) ? localBootstrap : localInstall;
|
|
574
646
|
|
|
575
|
-
const scriptArgs = [scriptToRun, '--full-commands', ...
|
|
647
|
+
const scriptArgs = [scriptToRun, '--full-commands', '--target', targetDir, ...remaining];
|
|
576
648
|
const proc = spawnSync(pythonCmd, scriptArgs, {
|
|
577
649
|
stdio: 'inherit',
|
|
578
|
-
cwd:
|
|
650
|
+
cwd: targetDir,
|
|
579
651
|
});
|
|
580
652
|
process.exit(proc.status !== null ? proc.status : 0);
|
|
581
653
|
}
|
|
582
654
|
|
|
655
|
+
// Subcommand: update
|
|
656
|
+
if (command === 'update') {
|
|
657
|
+
const https = require('https');
|
|
658
|
+
const pkgJsonPath = path.join(rootDir, 'package.json');
|
|
659
|
+
let currentVersion = '1.4.0';
|
|
660
|
+
try {
|
|
661
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
662
|
+
currentVersion = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8')).version || currentVersion;
|
|
663
|
+
}
|
|
664
|
+
} catch (e) {}
|
|
665
|
+
|
|
666
|
+
console.log();
|
|
667
|
+
console.log(cardHeader('🛡️ TORUSGUARD UPDATE CHECK', 'Package Distribution & Registry Verifier', `v${currentVersion}`));
|
|
668
|
+
console.log(`\n ${DIM}Checking npm registry for updates...${RESET}`);
|
|
669
|
+
|
|
670
|
+
const autoInstall = args.includes('--install') || args.includes('-i') || args.includes('--yes') || args.includes('-y');
|
|
671
|
+
|
|
672
|
+
function printUpdateOffline(reason) {
|
|
673
|
+
console.log();
|
|
674
|
+
console.log(cardBorderTop('Offline / Registry Unreachable', YELLOW));
|
|
675
|
+
console.log(formatBoxLine(`Current: ${WHITE}v${currentVersion}${RESET}`, 67, '│', YELLOW));
|
|
676
|
+
console.log(formatBoxLine(`Notice: ${DIM}${reason}${RESET}`, 67, '│', YELLOW));
|
|
677
|
+
console.log(formatBoxLine(`Manual: ${BOLD}${CYAN}npm update torusguard${RESET} or ${BOLD}${CYAN}npm i -g torusguard@latest${RESET}`, 67, '│', YELLOW));
|
|
678
|
+
console.log(cardBorderBottom(YELLOW));
|
|
679
|
+
console.log();
|
|
680
|
+
process.exit(0);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const req = https.get('https://registry.npmjs.org/torusguard/latest', {
|
|
684
|
+
headers: { 'User-Agent': `torusguard-cli/${currentVersion}` },
|
|
685
|
+
timeout: 6000
|
|
686
|
+
}, (res) => {
|
|
687
|
+
let raw = '';
|
|
688
|
+
res.on('data', chunk => raw += chunk);
|
|
689
|
+
res.on('end', () => {
|
|
690
|
+
try {
|
|
691
|
+
const data = JSON.parse(raw);
|
|
692
|
+
const latestVersion = data.version;
|
|
693
|
+
if (!latestVersion) {
|
|
694
|
+
printUpdateOffline('No version field returned in npm registry response');
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const isNewer = compareSemver(latestVersion, currentVersion) > 0;
|
|
699
|
+
console.log();
|
|
700
|
+
console.log(cardBorderTop('Version Telemetry', CYAN));
|
|
701
|
+
console.log(formatBoxLine(`Installed Version: ${WHITE}v${currentVersion}${RESET}`));
|
|
702
|
+
console.log(formatBoxLine(`Latest on NPM: ${BOLD}${GREEN}v${latestVersion}${RESET}`));
|
|
703
|
+
console.log(cardBorderBottom(CYAN));
|
|
704
|
+
console.log();
|
|
705
|
+
|
|
706
|
+
if (isNewer) {
|
|
707
|
+
console.log(cardBorderTop('Update Available', YELLOW, true));
|
|
708
|
+
console.log(formatBoxLine(`${YELLOW}A new version of TorusGuard is available!${RESET}`, 67, '║', YELLOW));
|
|
709
|
+
console.log(formatBoxLine(`Global: ${BOLD}${WHITE}npm install -g torusguard@latest${RESET}`, 67, '║', YELLOW));
|
|
710
|
+
console.log(formatBoxLine(`Local: ${BOLD}${WHITE}npm install torusguard@latest${RESET}`, 67, '║', YELLOW));
|
|
711
|
+
console.log(cardBorderBottom(YELLOW, true));
|
|
712
|
+
console.log();
|
|
713
|
+
|
|
714
|
+
if (autoInstall) {
|
|
715
|
+
console.log(` ${CYAN}Auto-installing latest version via npm...${RESET}`);
|
|
716
|
+
const npmProc = spawnSync('npm', ['install', '-g', 'torusguard@latest'], { stdio: 'inherit' });
|
|
717
|
+
if (npmProc.status === 0) {
|
|
718
|
+
console.log(`\n ${GREEN}✔ Successfully upgraded TorusGuard to v${latestVersion}!${RESET}\n`);
|
|
719
|
+
} else {
|
|
720
|
+
console.error(`\n ${RED}✖ Failed to auto-install update. Run: npm install -g torusguard@latest${RESET}\n`);
|
|
721
|
+
}
|
|
722
|
+
} else {
|
|
723
|
+
console.log(` ${DIM}Tip: Run with ${CYAN}npx torusguard update --install${DIM} to auto-upgrade.${RESET}\n`);
|
|
724
|
+
}
|
|
725
|
+
} else {
|
|
726
|
+
console.log(cardBorderTop('Status: Up To Date', GREEN, true));
|
|
727
|
+
console.log(formatBoxLine(`${GREEN}✔ TorusGuard is up to date (v${currentVersion})${RESET}`, 67, '║', GREEN));
|
|
728
|
+
console.log(cardBorderBottom(GREEN, true));
|
|
729
|
+
console.log();
|
|
730
|
+
}
|
|
731
|
+
process.exit(0);
|
|
732
|
+
} catch (err) {
|
|
733
|
+
printUpdateOffline(err.message);
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
req.on('error', (err) => printUpdateOffline(err.message));
|
|
739
|
+
req.on('timeout', () => {
|
|
740
|
+
req.destroy();
|
|
741
|
+
printUpdateOffline('npm registry request timed out (6000ms)');
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
|
|
583
745
|
// Unknown command fallback
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
746
|
+
const KNOWN_COMMANDS = new Set([
|
|
747
|
+
'init', 'status', 'rules', 'memory', 'diff-guard', 'audit',
|
|
748
|
+
'report', 'harden', 'apply', 'rollback', 'recheck', 'verify',
|
|
749
|
+
'recipes', 'update', 'help', '--help', '-h', '--version', '-v', 'version'
|
|
750
|
+
]);
|
|
751
|
+
|
|
752
|
+
if (!KNOWN_COMMANDS.has(command)) {
|
|
753
|
+
console.error(`\n ${RED}✖ Unknown command:${RESET} ${WHITE}${command}${RESET}`);
|
|
754
|
+
console.error(` Run ${GREEN}npx torusguard help${RESET} for available commands.\n`);
|
|
755
|
+
process.exit(1);
|
|
756
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "torusguard",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-alpha",
|
|
4
4
|
"description": "Security guardrails, governed remediation, and authorized runtime validation for AI-built web applications.",
|
|
5
5
|
"main": "bin/torusguard.js",
|
|
6
6
|
"bin": {
|
|
@@ -17,8 +17,12 @@
|
|
|
17
17
|
"scripts": {
|
|
18
18
|
"test": "python harness/runner.py",
|
|
19
19
|
"init": "node bin/torusguard.js init",
|
|
20
|
+
"status": "node bin/torusguard.js status",
|
|
20
21
|
"audit": "node bin/torusguard.js audit",
|
|
21
|
-
"
|
|
22
|
+
"harden": "node bin/torusguard.js harden",
|
|
23
|
+
"apply": "node bin/torusguard.js apply",
|
|
24
|
+
"recipes": "node bin/torusguard.js recipes",
|
|
25
|
+
"update": "node bin/torusguard.js update"
|
|
22
26
|
},
|
|
23
27
|
"keywords": [
|
|
24
28
|
"security",
|
|
Binary file
|
|
@@ -135,7 +135,7 @@ def print_header():
|
|
|
135
135
|
print(f"""
|
|
136
136
|
{CYAN}╭─────────────────────────────────────────────────────────────────────────╮{RESET}
|
|
137
137
|
{CYAN}│{RESET} {CYAN}│{RESET}
|
|
138
|
-
{CYAN}│{RESET} {BOLD}{WHITE}🛡️ T O R U S G U A R D{RESET} {GRAY}v1.
|
|
138
|
+
{CYAN}│{RESET} {BOLD}{WHITE}🛡️ T O R U S G U A R D{RESET} {GRAY}v1.4.0{RESET} {CYAN}│{RESET}
|
|
139
139
|
{CYAN}│{RESET} {DIM}Autonomous Security Engine for AI-Built Applications{RESET} {CYAN}│{RESET}
|
|
140
140
|
{CYAN}│{RESET} {CYAN}│{RESET}
|
|
141
141
|
{CYAN}╰─────────────────────────────────────────────────────────────────────────╯{RESET}
|
|
@@ -220,7 +220,7 @@ def print_already_initialized(target_root, cfg):
|
|
|
220
220
|
|
|
221
221
|
{BOLD}▸ Project Root:{RESET} {GREEN}{target_root}{RESET}
|
|
222
222
|
{BOLD}▸ Workspace:{RESET} {GREEN}.torusguard/{RESET} {DIM}(Already Initialized){RESET}
|
|
223
|
-
{BOLD}▸ Version:{RESET} {CYAN}{cfg.get('version', '1.
|
|
223
|
+
{BOLD}▸ Version:{RESET} {CYAN}{cfg.get('version', '1.4.0')}{RESET}
|
|
224
224
|
{BOLD}▸ Severity Floor:{RESET} {YELLOW}{cfg.get('severity_threshold', 'medium')}{RESET}
|
|
225
225
|
|
|
226
226
|
{DIM}To refresh templates or re-scaffold, run:{RESET}
|
|
@@ -228,7 +228,7 @@ def print_already_initialized(target_root, cfg):
|
|
|
228
228
|
""")
|
|
229
229
|
|
|
230
230
|
|
|
231
|
-
def scaffold_workspace(target_root=None, force=False, full_commands=False):
|
|
231
|
+
def scaffold_workspace(target_root=None, force=False, full_commands=False, template=None, run_audit=False):
|
|
232
232
|
"""Scaffold the .torusguard workspace into the target project root."""
|
|
233
233
|
target_root = Path(target_root or find_project_root()).resolve()
|
|
234
234
|
torusguard_target = target_root / ".torusguard"
|
|
@@ -379,12 +379,26 @@ def scaffold_workspace(target_root=None, force=False, full_commands=False):
|
|
|
379
379
|
except Exception:
|
|
380
380
|
detected_stack = None
|
|
381
381
|
|
|
382
|
+
TEMPLATE_MAP = {
|
|
383
|
+
"golang": {"language": "Go", "framework": "Gin/Fiber", "data_layer": "database/sql"},
|
|
384
|
+
"go": {"language": "Go", "framework": "Gin/Fiber", "data_layer": "database/sql"},
|
|
385
|
+
"nextjs": {"language": "TypeScript", "framework": "Next.js", "data_layer": "Prisma ORM"},
|
|
386
|
+
"fastapi": {"language": "Python", "framework": "FastAPI", "data_layer": "SQLAlchemy"},
|
|
387
|
+
"express": {"language": "JavaScript", "framework": "Express", "data_layer": "Mongoose"},
|
|
388
|
+
"django": {"language": "Python", "framework": "Django", "data_layer": "Django ORM"},
|
|
389
|
+
"react": {"language": "JavaScript/TypeScript", "framework": "React / Vite", "data_layer": "Supabase"}
|
|
390
|
+
}
|
|
391
|
+
if template and template.lower() in TEMPLATE_MAP:
|
|
392
|
+
detected_stack = TEMPLATE_MAP[template.lower()]
|
|
393
|
+
|
|
382
394
|
config_file = torusguard_target / "config" / "torusguard.json"
|
|
383
395
|
if config_file.exists():
|
|
384
396
|
try:
|
|
385
397
|
with open(config_file, "r", encoding="utf-8") as f:
|
|
386
398
|
cfg = json.load(f)
|
|
387
|
-
if
|
|
399
|
+
if template and template.lower() in TEMPLATE_MAP:
|
|
400
|
+
cfg["detected_stack"] = TEMPLATE_MAP[template.lower()]
|
|
401
|
+
elif detected_stack and detected_stack.get("framework") != "None":
|
|
388
402
|
cfg["detected_stack"] = {
|
|
389
403
|
"language": detected_stack.get("language"),
|
|
390
404
|
"framework": detected_stack.get("framework"),
|
|
@@ -516,15 +530,40 @@ Parse the requested action from `$ARGUMENTS` (e.g. `audit`, `verify`, `web-valid
|
|
|
516
530
|
|
|
517
531
|
# ── Success Card ────────────────────────────────────────────────────────
|
|
518
532
|
print_success_card()
|
|
533
|
+
|
|
534
|
+
# ── Optional Immediate Audit ────────────────────────────────────────────
|
|
535
|
+
if run_audit:
|
|
536
|
+
audit_script = torusguard_target / "scripts" / "audit_runner.py"
|
|
537
|
+
if audit_script.is_file():
|
|
538
|
+
print(f"\n {CYAN}▸ Running immediate baseline security audit (--audit)...{RESET}\n")
|
|
539
|
+
try:
|
|
540
|
+
import importlib.util
|
|
541
|
+
spec = importlib.util.spec_from_file_location("audit_runner", str(audit_script))
|
|
542
|
+
if spec and spec.loader:
|
|
543
|
+
ar_mod = importlib.util.module_from_spec(spec)
|
|
544
|
+
spec.loader.exec_module(ar_mod)
|
|
545
|
+
if hasattr(ar_mod, "execute_audit"):
|
|
546
|
+
ar_mod.execute_audit(target_root)
|
|
547
|
+
except Exception as e:
|
|
548
|
+
print(f" {YELLOW}⚠ Immediate audit notice: {e}{RESET}")
|
|
549
|
+
|
|
519
550
|
return True
|
|
520
551
|
|
|
521
552
|
|
|
522
553
|
if __name__ == "__main__":
|
|
523
554
|
parser = argparse.ArgumentParser(description="TorusGuard Autonomous Workspace Bootstrapper")
|
|
524
|
-
parser.add_argument("--target", type=str, help="Target project root directory")
|
|
555
|
+
parser.add_argument("--target", "-t", type=str, help="Target project root directory")
|
|
525
556
|
parser.add_argument("--force", action="store_true", help="Force overwrite existing workspace")
|
|
526
557
|
parser.add_argument("--full-commands", action="store_true", help="Unlock and register all 11 individual slash commands")
|
|
558
|
+
parser.add_argument("--template", choices=["golang", "go", "nextjs", "fastapi", "express", "django", "react"], help="Pre-configure target framework stack template")
|
|
559
|
+
parser.add_argument("--audit", action="store_true", help="Execute immediate baseline audit post-initialization")
|
|
527
560
|
args = parser.parse_args()
|
|
528
561
|
|
|
529
|
-
success = scaffold_workspace(
|
|
562
|
+
success = scaffold_workspace(
|
|
563
|
+
target_root=args.target,
|
|
564
|
+
force=args.force,
|
|
565
|
+
full_commands=args.full_commands,
|
|
566
|
+
template=args.template,
|
|
567
|
+
run_audit=args.audit
|
|
568
|
+
)
|
|
530
569
|
sys.exit(0 if success else 1)
|
|
@@ -142,23 +142,24 @@
|
|
|
142
142
|
"schemas/remediation.schema.json": "544904239f871dec9552973e01e09dc1f40dbb8330f20d98c319f3b24031d630",
|
|
143
143
|
"schemas/retest.schema.json": "97c0407ca4365a524020c430912f686f698cc0de90043194621071e9fc681cc7",
|
|
144
144
|
"schemas/runtime-evidence.schema.json": "929d2b094c1cd1f7f9db07be1eba5994e8002477eab3fd24140e1e973fd18cc1",
|
|
145
|
-
"scripts/apply_runner.py": "
|
|
146
|
-
"scripts/audit_runner.py": "
|
|
145
|
+
"scripts/apply_runner.py": "b145a68a4bc10fcacb853f972aee81fa3607e32d282b2b850c5a63d8e72da890",
|
|
146
|
+
"scripts/audit_runner.py": "bc1e06ea5153ae326c4ad1de5d0cbb9503e618186623bea7cb8bb64d7b7b05ec",
|
|
147
147
|
"scripts/diff_guard.py": "0dc178cdde1ac0ae1a1ea4b2eb2ce1756b2126f6156909cf55d85249f115fcbd",
|
|
148
148
|
"scripts/finding_scorer.py": "a5315be6ee256c01c4539da3d1905ebc8d0840a356b83c55f9f6f2654a9f0c60",
|
|
149
|
-
"scripts/harden_runner.py": "
|
|
150
|
-
"scripts/html_reporter.py": "
|
|
151
|
-
"scripts/manifest_builder.py": "
|
|
149
|
+
"scripts/harden_runner.py": "5d5a40029c088ccb66790a9b0128bdefcd35883ef1dac78088ceb9531392a8f1",
|
|
150
|
+
"scripts/html_reporter.py": "655c57be39e13c177db8842ef471664e5853f3e43b9f2a63e590df40ff9c16cb",
|
|
151
|
+
"scripts/manifest_builder.py": "18db6979b341a87e9bf38fe06525365d12b07d532c0dbfd2ac3ef0f539dc1f6f",
|
|
152
152
|
"scripts/memory_engine.py": "b715d0ca030e95eb06ad6eda99027535df8a7e37d64897c023be6a291cf13dc6",
|
|
153
153
|
"scripts/monorepo_detector.py": "5603674136a5294ebfff89efeca0a546dd4a7deb1d184a2592ff21aff2131f73",
|
|
154
|
-
"scripts/recheck_runner.py": "
|
|
155
|
-
"scripts/recipes_runner.py": "
|
|
156
|
-
"scripts/report_sync.py": "
|
|
154
|
+
"scripts/recheck_runner.py": "89e93fcc6843493d7e5f0bf97065dbdb40106273a00e07f903250665ca25f6fb",
|
|
155
|
+
"scripts/recipes_runner.py": "090c06ef0de3c640a111c5646837233ceeaca494b161bd5edc630cc1371dca0e",
|
|
156
|
+
"scripts/report_sync.py": "5ebef684690200694a53af30db850d9808d03ec710ed21ef75c4de52b5e5217c",
|
|
157
157
|
"scripts/rules_sync.py": "4101b2ca8036ff4f468800b32a8d08898e8640a74d14124af53d454706a62d3a",
|
|
158
158
|
"scripts/run_manager.py": "b738ff70879e46547d32fac6befc61de9599f676411384280fb9819ea3ad7035",
|
|
159
159
|
"scripts/safety_gate.py": "3379b7534b2590cf563717a448983ad8ed7e8f174f0ad65683d33e8a10f51558",
|
|
160
160
|
"scripts/sarif_exporter.py": "86700765212adb3ab590ca72614bf3ad4ce7ce60e3193f7f3bd1a04d34d10f6e",
|
|
161
161
|
"scripts/stack_detect.py": "c05a1ade4c8692025476bf7ea080a8f07c92e5d8e9a04f94c1654386352ffbdc",
|
|
162
|
+
"scripts/status_runner.py": "4c358c91950c7874bcc6a5f23f2d3fda62b0752efcf6c6b658adf02ed77c7752",
|
|
162
163
|
"scripts/term_ui.py": "ca69398f85c86fe89526d6334b3ee866c890273997351113c3cc1765b7bd20c3",
|
|
163
164
|
"skills/torusguard/SKILL.md": "4d5b8e43fa3c09e5e1079cbc3e2184654219a2c47b2272db394da749969318f8",
|
|
164
165
|
"skills/torusguard/bootstrap.py": "3d946f388cb4befa739c9d3b1404255d9a8ea5770ebbb79e5a1b9839589e9bc7",
|
|
@@ -206,16 +207,30 @@
|
|
|
206
207
|
"templates/authorization.template.md": "81ef843d42a19412c369f383b787465ac24173c0b14c1f5f211786eab3d10e8b",
|
|
207
208
|
"templates/finding-card.template.md": "8f756edbd3e4139d3da2fb96f35bd2d2b267b33b754f187b590a89584265c1e6",
|
|
208
209
|
"templates/remediation-bundle.template.md": "4d85a540e3a2e74f9b21fc8a2a1072e27ca03e97cd67ca482f529cfe722acc4d",
|
|
209
|
-
"workflows/apply.md": "
|
|
210
|
-
"workflows/audit.md": "
|
|
210
|
+
"workflows/apply.md": "67e1002fc6e9d68e0c7d871c40b6f3c3b9c3919c8e02106a579882729c0f6b9f",
|
|
211
|
+
"workflows/audit.md": "eac9102ca88d53f71da474cd55717d431fd9c3b08c66d0760da9b53a659cf89a",
|
|
211
212
|
"workflows/authorize.md": "28ca58cfc656e956f7cec8160f2aadc152abcde3eb5ad5588df149007afb0df8",
|
|
212
213
|
"workflows/exploit-check.md": "41e77afe912be9266c5bc1e00fd796baff22a67973eb6e86d196d34f4d7bdfa4",
|
|
213
|
-
"workflows/harden.md": "
|
|
214
|
-
"workflows/init.md": "
|
|
214
|
+
"workflows/harden.md": "7d92132973ff867b99e0425e3aee3c4e91868cb5193ec601069af2e857b6697f",
|
|
215
|
+
"workflows/init.md": "611418577ff9d5719df459f56b3fa53b91eb3717b02c4b4e5e86ed483bed3bfa",
|
|
215
216
|
"workflows/memory.md": "8ad2ec154176e2540c2859a28eff9f7c2dbca0217aa29d0f4aa88b87c43f8bef",
|
|
216
217
|
"workflows/recheck.md": "42eecc3ba194564d48a6a88d8003e86cafe003503e887dc565314d4aa558e8fd",
|
|
217
|
-
"workflows/
|
|
218
|
-
"workflows/
|
|
218
|
+
"workflows/recipes.md": "300c1fed0167e1a299fcd33c07db05630116b18a71ec2513c385862bba0f179b",
|
|
219
|
+
"workflows/report.md": "ecbd67bfa95bde3c0f51e06aac6fad70d5989b0be82245116cd7ba367f378a4c",
|
|
220
|
+
"workflows/status.md": "7f07e726bbb0488e48b718c2f36971634b1422bc2ddfbb381eb3e1ea67c1cfbf",
|
|
221
|
+
"workflows/torusguard-apply.md": "67e1002fc6e9d68e0c7d871c40b6f3c3b9c3919c8e02106a579882729c0f6b9f",
|
|
222
|
+
"workflows/torusguard-audit.md": "eac9102ca88d53f71da474cd55717d431fd9c3b08c66d0760da9b53a659cf89a",
|
|
223
|
+
"workflows/torusguard-authorize.md": "28ca58cfc656e956f7cec8160f2aadc152abcde3eb5ad5588df149007afb0df8",
|
|
224
|
+
"workflows/torusguard-exploit-check.md": "41e77afe912be9266c5bc1e00fd796baff22a67973eb6e86d196d34f4d7bdfa4",
|
|
225
|
+
"workflows/torusguard-harden.md": "7d92132973ff867b99e0425e3aee3c4e91868cb5193ec601069af2e857b6697f",
|
|
226
|
+
"workflows/torusguard-init.md": "611418577ff9d5719df459f56b3fa53b91eb3717b02c4b4e5e86ed483bed3bfa",
|
|
227
|
+
"workflows/torusguard-recheck.md": "42eecc3ba194564d48a6a88d8003e86cafe003503e887dc565314d4aa558e8fd",
|
|
228
|
+
"workflows/torusguard-recipes.md": "300c1fed0167e1a299fcd33c07db05630116b18a71ec2513c385862bba0f179b",
|
|
229
|
+
"workflows/torusguard-report.md": "ecbd67bfa95bde3c0f51e06aac6fad70d5989b0be82245116cd7ba367f378a4c",
|
|
230
|
+
"workflows/torusguard-status.md": "7f07e726bbb0488e48b718c2f36971634b1422bc2ddfbb381eb3e1ea67c1cfbf",
|
|
231
|
+
"workflows/torusguard-verify.md": "6412e123a9346240e5ba898a0da73d3409d9a215d6e8f202ab2eb5719b663731",
|
|
232
|
+
"workflows/torusguard-web-validate.md": "3b2a3d02b69469539b4a48ed3bd355362927cc21470dbcdeccba564a9e68a371",
|
|
233
|
+
"workflows/torusguard.md": "4b5da6d38dae6be277bd52782121cdb181a75f1400068041310ca69deae89589",
|
|
219
234
|
"workflows/verify.md": "6412e123a9346240e5ba898a0da73d3409d9a215d6e8f202ab2eb5719b663731",
|
|
220
235
|
"workflows/web-validate.md": "3b2a3d02b69469539b4a48ed3bd355362927cc21470dbcdeccba564a9e68a371"
|
|
221
236
|
}
|