@produtype/core 0.22.0 → 0.23.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.
|
@@ -30,12 +30,22 @@ async function detectUploads(ctx) {
|
|
|
30
30
|
const uploadDeps = (0, detectContext_1.hasAnyDep)(ctx, ['multer', 'formidable', '@aws-sdk/client-s3', 'aws-sdk']);
|
|
31
31
|
const pyUploadDeps = (0, detectContext_1.hasAnyPyDep)(ctx, ['boto3']);
|
|
32
32
|
const avDeps = (0, detectContext_1.hasAnyDep)(ctx, ['clamav', 'clamscan']);
|
|
33
|
+
/**
|
|
34
|
+
* Each line tagged with the claim it supports.
|
|
35
|
+
*
|
|
36
|
+
* Everything this detector finds used to land in one array, so a report that said "no
|
|
37
|
+
* upload handling was found in this repository" cited six snippets under it — five of
|
|
38
|
+
* them `LoginRequiredMixin` and `@login_required`, one a PDF content type — and was
|
|
39
|
+
* stamped "confidence: high, evidence quality: strong" for them. That a project has
|
|
40
|
+
* authentication is why an upload check would matter; it is not evidence about
|
|
41
|
+
* uploads, and the rate-limit rule already carries that sentence for the same reason.
|
|
42
|
+
*/
|
|
33
43
|
for (const d of uploadDeps)
|
|
34
|
-
evidence.push({ type: 'dependency', value: d });
|
|
44
|
+
evidence.push({ type: 'dependency', value: d, claim: 'uploads' });
|
|
35
45
|
for (const d of pyUploadDeps)
|
|
36
|
-
evidence.push({ type: 'dependency', value: d });
|
|
46
|
+
evidence.push({ type: 'dependency', value: d, claim: 'uploads' });
|
|
37
47
|
for (const d of avDeps)
|
|
38
|
-
evidence.push({ type: 'dependency', value: d });
|
|
48
|
+
evidence.push({ type: 'dependency', value: d, claim: 'antivirus' });
|
|
39
49
|
const routeSignals = [];
|
|
40
50
|
for (const file of ctx.files.source) {
|
|
41
51
|
const text = await (0, readTextFileSafe_1.readTextFileSafe)(ctx.root, file);
|
|
@@ -74,18 +84,18 @@ async function detectUploads(ctx) {
|
|
|
74
84
|
/@permission_required/,
|
|
75
85
|
], 15);
|
|
76
86
|
for (const m of djangoProtectionSignals) {
|
|
77
|
-
evidence.push({ type: 'snippet', value: m.snippet, file: m.file, line: m.line });
|
|
87
|
+
evidence.push({ type: 'snippet', value: m.snippet, file: m.file, line: m.line, claim: 'protection' });
|
|
78
88
|
}
|
|
79
89
|
const validationSignals = await (0, textSearch_1.searchInFiles)(ctx.root, source, [/file-type/i, /mime/i, /content-type/i], 15);
|
|
80
90
|
const protectedRoutes = routeSignals.filter((r) => r.protected);
|
|
81
91
|
const unprotectedRoutes = routeSignals.filter((r) => !r.protected);
|
|
82
92
|
for (const r of routeSignals) {
|
|
83
|
-
evidence.push({ type: 'snippet', value: r.snippet, file: r.file, line: r.line });
|
|
93
|
+
evidence.push({ type: 'snippet', value: r.snippet, file: r.file, line: r.line, claim: 'uploads' });
|
|
84
94
|
}
|
|
85
95
|
for (const m of djangoPublicSignals)
|
|
86
|
-
evidence.push({ type: 'snippet', value: m.snippet, file: m.file, line: m.line });
|
|
96
|
+
evidence.push({ type: 'snippet', value: m.snippet, file: m.file, line: m.line, claim: 'uploads' });
|
|
87
97
|
for (const m of validationSignals)
|
|
88
|
-
evidence.push({ type: 'snippet', value: m.snippet, file: m.file, line: m.line });
|
|
98
|
+
evidence.push({ type: 'snippet', value: m.snippet, file: m.file, line: m.line, claim: 'validation' });
|
|
89
99
|
const publicExposure = unprotectedRoutes.length > 0 || djangoPublicSignals.length > 0;
|
|
90
100
|
const protectedSomehow = protectedRoutes.length > 0 || djangoProtectionSignals.length > 0;
|
|
91
101
|
return {
|
|
@@ -86,7 +86,22 @@ function buildCategoryScores(findings) {
|
|
|
86
86
|
* critical and nine passing checks is not 90% healthy". The cap keeps that;
|
|
87
87
|
* the proportion does the ranking underneath it.
|
|
88
88
|
*/
|
|
89
|
-
|
|
89
|
+
/**
|
|
90
|
+
* The ceiling follows what the report says is wrong, not what the area is worth.
|
|
91
|
+
*
|
|
92
|
+
* These are two different numbers on purpose: `stakes` is what a capability is worth
|
|
93
|
+
* before confidence is folded in, and `severity` is what the report is willing to
|
|
94
|
+
* claim about it. Using stakes here capped a category at the critical ceiling while
|
|
95
|
+
* the row beside it read "0 critical" — a Django project with four of five security
|
|
96
|
+
* checks verified and one high finding open scored 20 of 100, below an area with one
|
|
97
|
+
* of three verified.
|
|
98
|
+
*
|
|
99
|
+
* The proportion below still uses stakes, which is right: how much of what is at
|
|
100
|
+
* stake is failing does not change because the report hedged. What changes is the
|
|
101
|
+
* cap, and a cap that says "nothing looks healthy while something severe is open"
|
|
102
|
+
* has to mean the severity the reader was shown.
|
|
103
|
+
*/
|
|
104
|
+
const worstOpen = actionable.reduce((worst, finding) => (SEVERITY_WEIGHT[finding.severity] > SEVERITY_WEIGHT[worst] ? finding.severity : worst), 'info');
|
|
90
105
|
const ceiling = SEVERITY_CEILING[worstOpen];
|
|
91
106
|
return {
|
|
92
107
|
category,
|
package/dist/rules/rules.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.rules = void 0;
|
|
4
4
|
const inferProductProfile_1 = require("../expectations/inferProductProfile");
|
|
5
|
+
const absenceEvidence_1 = require("../analyzer/absenceEvidence");
|
|
5
6
|
function statusFromFlags(present, complete) {
|
|
6
7
|
if (!present)
|
|
7
8
|
return 'missing';
|
|
@@ -484,7 +485,20 @@ exports.rules = [
|
|
|
484
485
|
? 'Upload handling was found, and none of it is publicly exposed.'
|
|
485
486
|
: 'No upload handling was found in this repository.',
|
|
486
487
|
recommendation: 'Protect upload routes with authz, validate MIME/type, and prefer private object storage.',
|
|
487
|
-
|
|
488
|
+
/**
|
|
489
|
+
* The evidence follows the claim.
|
|
490
|
+
*
|
|
491
|
+
* "No upload handling was found in this repository" used to arrive with six
|
|
492
|
+
* snippets under it, five of them Django auth decorators, and a confidence of
|
|
493
|
+
* high earned from them. Where nothing was found, the honest citation is the
|
|
494
|
+
* terms that were looked for.
|
|
495
|
+
*/
|
|
496
|
+
evidence: status === 'unknown'
|
|
497
|
+
? (0, absenceEvidence_1.searchedFor)('upload handling', ['multer', 'formidable', 'django-storages', 'FileField', '/uploads', 'express.static'])
|
|
498
|
+
// The routes and the public-serving lines, which is what this claim is about.
|
|
499
|
+
// Validation and antivirus signals are tagged separately and belong to other
|
|
500
|
+
// questions.
|
|
501
|
+
: evidenceForClaim(up?.evidence, 'uploads'),
|
|
488
502
|
});
|
|
489
503
|
},
|
|
490
504
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@produtype/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Deterministic CLI and library that analyzes a web application repository and reports how far it is from production-ready for the kind of product it is meant to be.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|