progmune-runtime 3.7.7 → 3.7.9
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 +39 -0
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/dist/frameworks/django-detector.js +133 -0
- package/dist/frameworks/django-detector.test.js +136 -0
- package/dist/frameworks/fastapi-detector.js +94 -0
- package/dist/frameworks/fastapi-detector.test.js +144 -0
- package/dist/frameworks/fastify-detector.js +137 -0
- package/dist/frameworks/fastify-detector.test.js +62 -0
- package/dist/frameworks/flask-detector.js +61 -0
- package/dist/frameworks/flask-detector.test.js +71 -0
- package/dist/frameworks/index.js +15 -3
- package/dist/frameworks/nextjs-detector.js +172 -0
- package/dist/frameworks/nextjs-detector.test.js +113 -0
- package/dist/sdk.js +1 -1
- package/dist/trust/engine.js +279 -0
- package/package.json +1 -1
package/dist/trust/engine.js
CHANGED
|
@@ -62,6 +62,11 @@ const api_semantic_mapper_1 = require("./api-semantic-mapper");
|
|
|
62
62
|
const protocol_domain_validator_1 = require("./protocol-domain-validator");
|
|
63
63
|
const annotation_suggest_1 = require("../annotation-suggest");
|
|
64
64
|
const call_graph_propagator_1 = require("./call-graph-propagator");
|
|
65
|
+
const fastapi_detector_1 = require("../frameworks/fastapi-detector");
|
|
66
|
+
const django_detector_1 = require("../frameworks/django-detector");
|
|
67
|
+
const flask_detector_1 = require("../frameworks/flask-detector");
|
|
68
|
+
const fastify_detector_1 = require("../frameworks/fastify-detector");
|
|
69
|
+
const nextjs_detector_1 = require("../frameworks/nextjs-detector");
|
|
65
70
|
const ssg_bridge_1 = require("./ssg-bridge");
|
|
66
71
|
const call_sequence_1 = require("../call-sequence");
|
|
67
72
|
const extract_ir_1 = require("../extract-ir");
|
|
@@ -92,6 +97,11 @@ async function evaluateTrust(ctx) {
|
|
|
92
97
|
const expressResult = collectExpressViolations(ctx);
|
|
93
98
|
const nestjsResult = collectNestJSViolations(ctx);
|
|
94
99
|
const trpcResult = collectTRPCViolations(ctx);
|
|
100
|
+
const fastapiResult = collectFastapiViolations(ctx);
|
|
101
|
+
const djangoResult = collectDjangoViolations(ctx);
|
|
102
|
+
const flaskResult = collectFlaskViolations(ctx);
|
|
103
|
+
const fastifyResult = collectFastifyViolations(ctx);
|
|
104
|
+
const nextjsResult = collectNextjsViolations(ctx);
|
|
95
105
|
const coverageData = collectVerificationCoverage(ctx);
|
|
96
106
|
const governanceDefects = collectGovernanceDefects(ctx);
|
|
97
107
|
// ═══════════════════════════════════════
|
|
@@ -109,6 +119,11 @@ async function evaluateTrust(ctx) {
|
|
|
109
119
|
...expressViolations,
|
|
110
120
|
...nestjsResult.violations,
|
|
111
121
|
...trpcResult.violations,
|
|
122
|
+
...fastapiResult.violations,
|
|
123
|
+
...djangoResult.violations,
|
|
124
|
+
...flaskResult.violations,
|
|
125
|
+
...fastifyResult.violations,
|
|
126
|
+
...nextjsResult.violations,
|
|
112
127
|
];
|
|
113
128
|
// ═══════════════════════════════════════
|
|
114
129
|
// PHASE 3: SCORE
|
|
@@ -228,6 +243,51 @@ async function evaluateTrust(ctx) {
|
|
|
228
243
|
issuesFound: nestjsResult.violations.length,
|
|
229
244
|
}
|
|
230
245
|
: undefined,
|
|
246
|
+
/** FastAPI framework adapter coverage — route/auth-dependency analysis */
|
|
247
|
+
fastapiCoverage: fastapiResult.coverage.routes > 0
|
|
248
|
+
? {
|
|
249
|
+
appsDetected: fastapiResult.coverage.apps,
|
|
250
|
+
totalRoutes: fastapiResult.coverage.routes,
|
|
251
|
+
filesScanned: fastapiResult.coverage.filesScanned,
|
|
252
|
+
issuesFound: fastapiResult.violations.length,
|
|
253
|
+
}
|
|
254
|
+
: undefined,
|
|
255
|
+
/** Django framework adapter coverage — urlconf/view/permission analysis */
|
|
256
|
+
djangoCoverage: djangoResult.coverage.routes > 0
|
|
257
|
+
? {
|
|
258
|
+
appsDetected: djangoResult.coverage.apps,
|
|
259
|
+
totalRoutes: djangoResult.coverage.routes,
|
|
260
|
+
filesScanned: djangoResult.coverage.filesScanned,
|
|
261
|
+
issuesFound: djangoResult.violations.length,
|
|
262
|
+
}
|
|
263
|
+
: undefined,
|
|
264
|
+
/** Flask framework adapter coverage — route/auth-guard analysis */
|
|
265
|
+
flaskCoverage: flaskResult.coverage.routes > 0
|
|
266
|
+
? {
|
|
267
|
+
appsDetected: flaskResult.coverage.apps,
|
|
268
|
+
totalRoutes: flaskResult.coverage.routes,
|
|
269
|
+
filesScanned: flaskResult.coverage.filesScanned,
|
|
270
|
+
issuesFound: flaskResult.violations.length,
|
|
271
|
+
}
|
|
272
|
+
: undefined,
|
|
273
|
+
/** Fastify framework adapter coverage — route/auth-hook analysis */
|
|
274
|
+
fastifyCoverage: fastifyResult.coverage.routes > 0
|
|
275
|
+
? {
|
|
276
|
+
appsDetected: fastifyResult.coverage.apps,
|
|
277
|
+
totalRoutes: fastifyResult.coverage.routes,
|
|
278
|
+
filesScanned: fastifyResult.coverage.filesScanned,
|
|
279
|
+
issuesFound: fastifyResult.violations.length,
|
|
280
|
+
}
|
|
281
|
+
: undefined,
|
|
282
|
+
/** Next.js framework adapter coverage — App Router route handler analysis */
|
|
283
|
+
nextjsCoverage: nextjsResult.coverage.routes > 0
|
|
284
|
+
? {
|
|
285
|
+
appsDetected: nextjsResult.coverage.apps,
|
|
286
|
+
totalRoutes: nextjsResult.coverage.routes,
|
|
287
|
+
filesScanned: nextjsResult.coverage.filesScanned,
|
|
288
|
+
issuesFound: nextjsResult.violations.length,
|
|
289
|
+
}
|
|
290
|
+
: undefined,
|
|
231
291
|
},
|
|
232
292
|
dimensions: {
|
|
233
293
|
policyCompliance: {
|
|
@@ -366,6 +426,225 @@ function mapPolicyViolation(rv, filePath, _enterprisePolicy) {
|
|
|
366
426
|
* Collect Express-specific security violations from the framework detector.
|
|
367
427
|
* Maps ExpressSecurityIssue[] → TrustViolation[].
|
|
368
428
|
*/
|
|
429
|
+
function collectNextjsViolations(ctx) {
|
|
430
|
+
const violations = [];
|
|
431
|
+
const coverage = { apps: 0, routes: 0, filesScanned: 0 };
|
|
432
|
+
try {
|
|
433
|
+
const middlewareCode = (0, nextjs_detector_1.readNextMiddleware)(ctx.projectPath);
|
|
434
|
+
const analysis = (0, nextjs_detector_1.analyzeNextApp)(ctx.projectPath, middlewareCode);
|
|
435
|
+
if (!analysis.hasNext)
|
|
436
|
+
return { violations, coverage };
|
|
437
|
+
coverage.apps = 1;
|
|
438
|
+
coverage.routes = analysis.routeFiles.length;
|
|
439
|
+
coverage.filesScanned = analysis.routeFiles.length;
|
|
440
|
+
for (const issue of analysis.issues) {
|
|
441
|
+
violations.push({
|
|
442
|
+
severity: issue.severity === "low" ? "low" : issue.severity,
|
|
443
|
+
rule_id: issue.rule,
|
|
444
|
+
file: issue.file || "",
|
|
445
|
+
function: "unknown",
|
|
446
|
+
message: issue.message,
|
|
447
|
+
evidence: issue.route || "",
|
|
448
|
+
why: `Framework structural analysis: ${issue.message}`,
|
|
449
|
+
fix: `Add an auth check inside the route handler (e.g. getServerSession) or protect the app with auth middleware.`,
|
|
450
|
+
policy_ref: "framework-safety.nextjs",
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
catch { /* best-effort */ }
|
|
455
|
+
return { violations, coverage };
|
|
456
|
+
}
|
|
457
|
+
function collectFastifyViolations(ctx) {
|
|
458
|
+
const violations = [];
|
|
459
|
+
const coverage = { apps: 0, routes: 0, filesScanned: 0 };
|
|
460
|
+
try {
|
|
461
|
+
const fs = require("fs");
|
|
462
|
+
const candidateDirs = ["src", "server", "app", "api", "routes", "lib"];
|
|
463
|
+
const extensions = languageToExtensions(ctx.language);
|
|
464
|
+
for (const dir of candidateDirs) {
|
|
465
|
+
const dirPath = path.join(ctx.projectPath, dir);
|
|
466
|
+
if (!fs.existsSync(dirPath))
|
|
467
|
+
continue;
|
|
468
|
+
let files;
|
|
469
|
+
try {
|
|
470
|
+
files = walkDir(dirPath, extensions, 100);
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
for (const file of files) {
|
|
476
|
+
if (/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(file))
|
|
477
|
+
continue;
|
|
478
|
+
coverage.filesScanned++;
|
|
479
|
+
try {
|
|
480
|
+
const analysis = (0, fastify_detector_1.analyzeFastifyFile)(file);
|
|
481
|
+
if (!analysis || !analysis.hasFastify)
|
|
482
|
+
continue;
|
|
483
|
+
coverage.apps++;
|
|
484
|
+
coverage.routes += analysis.routes.length;
|
|
485
|
+
for (const issue of analysis.issues) {
|
|
486
|
+
violations.push({
|
|
487
|
+
severity: issue.severity === "low" ? "low" : issue.severity,
|
|
488
|
+
rule_id: issue.rule,
|
|
489
|
+
file: path.relative(ctx.projectPath, file),
|
|
490
|
+
function: "unknown",
|
|
491
|
+
message: issue.message,
|
|
492
|
+
evidence: issue.route || "",
|
|
493
|
+
why: `Framework structural analysis: ${issue.message}`,
|
|
494
|
+
fix: `Add preHandler/preValidation auth to the route options, or register an auth addHook.`,
|
|
495
|
+
policy_ref: "framework-safety.fastify",
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
catch { /* skip unreadable files */ }
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
catch { /* best-effort */ }
|
|
504
|
+
return { violations, coverage };
|
|
505
|
+
}
|
|
506
|
+
function collectFlaskViolations(ctx) {
|
|
507
|
+
const violations = [];
|
|
508
|
+
const coverage = { apps: 0, routes: 0, filesScanned: 0 };
|
|
509
|
+
// 仅 Python 项目跑框架结构扫描
|
|
510
|
+
const lang = ctx.language || "typescript";
|
|
511
|
+
if (lang !== "python")
|
|
512
|
+
return { violations, coverage };
|
|
513
|
+
try {
|
|
514
|
+
const { execSync } = require("child_process");
|
|
515
|
+
const fs = require("fs");
|
|
516
|
+
const os = require("os");
|
|
517
|
+
// engine.js 位于 dist/trust/ → 仓库根 tools/ 需要两级向上
|
|
518
|
+
const scriptPath = path.resolve(__dirname, "..", "..", "tools", "extract_framework_flask.py");
|
|
519
|
+
const outPath = path.join(os.tmpdir(), `progmune-fwfl-${process.pid}-${Date.now()}.json`);
|
|
520
|
+
execSync(`python3 "${scriptPath}" "${ctx.projectPath}" "${outPath}"`, {
|
|
521
|
+
encoding: "utf-8",
|
|
522
|
+
stdio: "pipe",
|
|
523
|
+
timeout: 60000,
|
|
524
|
+
});
|
|
525
|
+
if (!fs.existsSync(outPath))
|
|
526
|
+
return { violations, coverage };
|
|
527
|
+
const structure = JSON.parse(fs.readFileSync(outPath, "utf-8"));
|
|
528
|
+
fs.unlinkSync(outPath);
|
|
529
|
+
const analysis = (0, flask_detector_1.analyzeFlaskStructure)(structure);
|
|
530
|
+
if (!analysis.hasFlask)
|
|
531
|
+
return { violations, coverage };
|
|
532
|
+
coverage.apps = (structure.apps || []).length + (structure.blueprints || []).length;
|
|
533
|
+
coverage.routes = (structure.routes || []).length;
|
|
534
|
+
coverage.filesScanned = structure.filesScanned || 0;
|
|
535
|
+
for (const issue of analysis.issues) {
|
|
536
|
+
violations.push({
|
|
537
|
+
severity: issue.severity === "low" ? "low" : issue.severity,
|
|
538
|
+
rule_id: issue.rule,
|
|
539
|
+
file: issue.file || "",
|
|
540
|
+
function: issue.handler || "unknown",
|
|
541
|
+
message: issue.message,
|
|
542
|
+
evidence: issue.route || "",
|
|
543
|
+
why: `Framework structural analysis: ${issue.message}`,
|
|
544
|
+
fix: `Add an auth decorator (@login_required or custom) to the route handler, or register an auth before_request guard.`,
|
|
545
|
+
policy_ref: "framework-safety.flask",
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
catch { /* best-effort — framework analysis must never break evaluation */ }
|
|
550
|
+
return { violations, coverage };
|
|
551
|
+
}
|
|
552
|
+
function collectDjangoViolations(ctx) {
|
|
553
|
+
const violations = [];
|
|
554
|
+
const coverage = { apps: 0, routes: 0, filesScanned: 0 };
|
|
555
|
+
// 仅 Python 项目跑框架结构扫描
|
|
556
|
+
const lang = ctx.language || "typescript";
|
|
557
|
+
if (lang !== "python")
|
|
558
|
+
return { violations, coverage };
|
|
559
|
+
try {
|
|
560
|
+
const { execSync } = require("child_process");
|
|
561
|
+
const fs = require("fs");
|
|
562
|
+
const os = require("os");
|
|
563
|
+
// engine.js 位于 dist/trust/ → 仓库根 tools/ 需要两级向上
|
|
564
|
+
const scriptPath = path.resolve(__dirname, "..", "..", "tools", "extract_framework_django.py");
|
|
565
|
+
const outPath = path.join(os.tmpdir(), `progmune-fwdj-${process.pid}-${Date.now()}.json`);
|
|
566
|
+
execSync(`python3 "${scriptPath}" "${ctx.projectPath}" "${outPath}"`, {
|
|
567
|
+
encoding: "utf-8",
|
|
568
|
+
stdio: "pipe",
|
|
569
|
+
timeout: 60000,
|
|
570
|
+
});
|
|
571
|
+
if (!fs.existsSync(outPath))
|
|
572
|
+
return { violations, coverage };
|
|
573
|
+
const structure = JSON.parse(fs.readFileSync(outPath, "utf-8"));
|
|
574
|
+
fs.unlinkSync(outPath);
|
|
575
|
+
const analysis = (0, django_detector_1.analyzeDjangoStructure)(structure);
|
|
576
|
+
if (!analysis.hasDjango)
|
|
577
|
+
return { violations, coverage };
|
|
578
|
+
coverage.apps = 1;
|
|
579
|
+
coverage.routes = (structure.routes || []).length;
|
|
580
|
+
coverage.filesScanned = structure.filesScanned || 0;
|
|
581
|
+
for (const issue of analysis.issues) {
|
|
582
|
+
violations.push({
|
|
583
|
+
severity: issue.severity === "low" ? "low" : issue.severity,
|
|
584
|
+
rule_id: issue.rule,
|
|
585
|
+
file: issue.file || "",
|
|
586
|
+
function: issue.handler || "unknown",
|
|
587
|
+
message: issue.message,
|
|
588
|
+
evidence: issue.route || "",
|
|
589
|
+
why: `Framework structural analysis: ${issue.message}`,
|
|
590
|
+
fix: issue.rule === "DRF_PERMISSION_BYPASS"
|
|
591
|
+
? `Require authenticated permissions on write methods (e.g. permission_classes = (IsAuthenticated,)).`
|
|
592
|
+
: `Add a login decorator (@login_required) or an auth mixin (LoginRequiredMixin) to the view.`,
|
|
593
|
+
policy_ref: "framework-safety.django",
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
catch { /* best-effort — framework analysis must never break evaluation */ }
|
|
598
|
+
return { violations, coverage };
|
|
599
|
+
}
|
|
600
|
+
function collectFastapiViolations(ctx) {
|
|
601
|
+
const violations = [];
|
|
602
|
+
const coverage = { apps: 0, routes: 0, filesScanned: 0 };
|
|
603
|
+
// 仅 Python 项目跑框架结构扫描(避免 TS 项目无谓的 python3 子进程)
|
|
604
|
+
const lang = ctx.language || "typescript";
|
|
605
|
+
if (lang !== "python")
|
|
606
|
+
return { violations, coverage };
|
|
607
|
+
try {
|
|
608
|
+
const { execSync } = require("child_process");
|
|
609
|
+
const fs = require("fs");
|
|
610
|
+
const os = require("os");
|
|
611
|
+
// engine.js 位于 dist/trust/ → 仓库根 tools/ 需要两级向上
|
|
612
|
+
const scriptPath = path.resolve(__dirname, "..", "..", "tools", "extract_framework_py.py");
|
|
613
|
+
const outPath = path.join(os.tmpdir(), `progmune-fw-${process.pid}-${Date.now()}.json`);
|
|
614
|
+
execSync(`python3 "${scriptPath}" "${ctx.projectPath}" "${outPath}"`, {
|
|
615
|
+
encoding: "utf-8",
|
|
616
|
+
stdio: "pipe",
|
|
617
|
+
timeout: 60000,
|
|
618
|
+
});
|
|
619
|
+
if (!fs.existsSync(outPath))
|
|
620
|
+
return { violations, coverage };
|
|
621
|
+
const structure = JSON.parse(fs.readFileSync(outPath, "utf-8"));
|
|
622
|
+
fs.unlinkSync(outPath);
|
|
623
|
+
const analysis = (0, fastapi_detector_1.analyzeFastapiStructure)(structure);
|
|
624
|
+
if (!analysis.hasFastAPI)
|
|
625
|
+
return { violations, coverage };
|
|
626
|
+
coverage.apps = (structure.apps || []).length + (structure.routers || []).length;
|
|
627
|
+
coverage.routes = (structure.routes || []).length;
|
|
628
|
+
coverage.filesScanned = structure.filesScanned || 0;
|
|
629
|
+
for (const issue of analysis.issues) {
|
|
630
|
+
violations.push({
|
|
631
|
+
severity: issue.severity === "low" ? "low" : issue.severity,
|
|
632
|
+
rule_id: issue.rule,
|
|
633
|
+
file: issue.file || "",
|
|
634
|
+
function: issue.handler || "unknown",
|
|
635
|
+
message: issue.message,
|
|
636
|
+
evidence: issue.route || "",
|
|
637
|
+
why: `Framework structural analysis: ${issue.message}`,
|
|
638
|
+
fix: issue.rule === "FASTAPI_ROUTE_NO_AUTH"
|
|
639
|
+
? `Add an auth dependency to the route (Depends/Security of an authenticator) or move it behind authenticated middleware.`
|
|
640
|
+
: `Reference the scheme from a route dependency or remove the unused scheme.`,
|
|
641
|
+
policy_ref: "framework-safety.fastapi",
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
catch { /* best-effort — framework analysis must never break evaluation */ }
|
|
646
|
+
return { violations, coverage };
|
|
647
|
+
}
|
|
369
648
|
function collectExpressViolations(ctx) {
|
|
370
649
|
const violations = [];
|
|
371
650
|
let expressApps = 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "progmune-runtime",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.9",
|
|
4
4
|
"description": "Progmune — AI Trust Decision Engine. Verify AI-generated code before it reaches production. Outputs APPROVED / NEEDS_REVIEW / BLOCKED with evidence.",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist/",
|