@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.
@@ -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 {
@@ -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
- evidence: up?.evidence ?? [],
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.22.1",
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": {