@produtype/core 0.22.1 → 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.
- package/dist/analyzer/detectUploads.js +17 -7
- package/dist/rules/rules.js +15 -1
- package/package.json +1 -1
|
@@ -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 {
|
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": {
|