@produtype/core 0.43.0 → 0.44.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.
@@ -334,7 +334,7 @@ async function detectAuth(ctx) {
334
334
  {
335
335
  key: 'auth.2fa',
336
336
  present: twoFaDeps.length > 0 || twoFaSignals.length > 0,
337
- evidence: [...depEvidence(twoFaDeps), ...snippetEvidence(twoFaSignals)],
337
+ evidence: (0, absenceEvidence_1.evidenceOrSearch)([...depEvidence(twoFaDeps), ...snippetEvidence(twoFaSignals)], 'a second factor', ['otplib', 'speakeasy', 'notp', 'pyotp', 'django-otp', 'totp', 'authenticator app', 'webauthn', '@simplewebauthn']),
338
338
  },
339
339
  {
340
340
  key: 'auth.apiKeys',
@@ -129,25 +129,25 @@ async function detectBilling(ctx) {
129
129
  {
130
130
  key: 'billing.webhook.route',
131
131
  present: webhookRouteHits.length > 0 || webhookPathHits.length > 0,
132
- evidence: [
132
+ evidence: (0, absenceEvidence_1.evidenceOrSearch)([
133
133
  ...toEvidence(webhookRouteHits),
134
134
  ...webhookPathHits.map((file) => ({ type: 'file', value: file, file })),
135
- ],
135
+ ], 'a route the payment provider calls back', ['/webhook', '/webhooks/stripe', 'a file under a webhook directory']),
136
136
  },
137
137
  {
138
138
  key: 'billing.webhook.rawBody',
139
139
  present: rawBodyHits.length > 0,
140
- evidence: toEvidence(rawBodyHits),
140
+ evidence: (0, absenceEvidence_1.evidenceOrSearch)(toEvidence(rawBodyHits), 'the unparsed body a signature is computed over', ['express.raw', 'bodyParser.raw', 'request.text()', 'rawBody', 'await req.arrayBuffer()']),
141
141
  },
142
142
  {
143
143
  key: 'billing.webhook.secret',
144
144
  present: secretHits.length > 0,
145
- evidence: toEvidence(secretHits),
145
+ evidence: (0, absenceEvidence_1.evidenceOrSearch)(toEvidence(secretHits), 'the signing secret, read from configuration', ['STRIPE_WEBHOOK_SECRET', 'WEBHOOK_SIGNING_SECRET', 'whsec_']),
146
146
  },
147
147
  {
148
148
  key: 'billing.webhook.signatureValidation',
149
149
  present: signatureHits.length > 0,
150
- evidence: toEvidence(signatureHits),
150
+ evidence: (0, absenceEvidence_1.evidenceOrSearch)(toEvidence(signatureHits), 'the signature being checked', ['constructEvent(', 'verifyHeader(', 'Webhook.constructEvent', 'stripe-signature']),
151
151
  },
152
152
  ];
153
153
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.detectDocker = detectDocker;
4
4
  const readTextFileSafe_1 = require("../utils/readTextFileSafe");
5
+ const absenceEvidence_1 = require("./absenceEvidence");
5
6
  async function detectDocker(ctx) {
6
7
  const evidence = [];
7
8
  const dockerfile = ctx.files.all.find((f) => /(^|\/)Dockerfile$/.test(f));
@@ -37,7 +38,7 @@ async function detectDocker(ctx) {
37
38
  key: 'infra.docker',
38
39
  present: Boolean(dockerfile || composeFile),
39
40
  complete: hasHealthcheck,
40
- evidence,
41
+ evidence: (0, absenceEvidence_1.evidenceOrSearch)(evidence, 'a container definition', ['Dockerfile', 'Containerfile', 'docker-compose.yml', 'compose.yaml']),
41
42
  details: { dockerfile: Boolean(dockerfile), compose: Boolean(composeFile), hasHealthcheck, hasExpose, services },
42
43
  };
43
44
  }
@@ -4,6 +4,7 @@ exports.detectJobs = detectJobs;
4
4
  const detectContext_1 = require("./detectContext");
5
5
  const textSearch_1 = require("../utils/textSearch");
6
6
  const readTextFileSafe_1 = require("../utils/readTextFileSafe");
7
+ const absenceEvidence_1 = require("./absenceEvidence");
7
8
  /**
8
9
  * Work that happens outside a request.
9
10
  *
@@ -124,7 +125,7 @@ async function detectJobs(ctx) {
124
125
  // A file named `workers/` is only taken as proof alongside something declared in
125
126
  // the code: the name is a convention, and conventions are borrowed.
126
127
  present: declared,
127
- evidence,
128
+ evidence: (0, absenceEvidence_1.evidenceOrSearch)(evidence, 'work that happens outside a request', ['bullmq', 'bull', 'agenda', 'bee-queue', 'celery', 'rq', 'sidekiq', 'resque', 'graphile-worker', 'a cron schedule', 'a worker entrypoint']),
128
129
  details: {
129
130
  nodeQueue: nodeDeps.length > 0,
130
131
  pythonQueue: pyDeps.length > 0,
@@ -367,17 +367,17 @@ async function detectMobile(ctx) {
367
367
  {
368
368
  key: 'mobile.forcedUpdate',
369
369
  present: forcedUpdate.length > 0,
370
- evidence: forcedUpdate.map((match) => ({
370
+ evidence: (0, absenceEvidence_1.evidenceOrSearch)(forcedUpdate.map((match) => ({
371
371
  type: 'snippet',
372
372
  value: match.snippet,
373
373
  file: match.file,
374
374
  line: match.line,
375
- })),
375
+ })), 'a check that the installed version is still supported', ['minimumVersion', 'minSupportedVersion', 'forceUpdate', 'upgrade_required', 'in_app_update', 'AppUpdateManager']),
376
376
  },
377
377
  {
378
378
  key: 'mobile.privacyDeclaration',
379
379
  present: privacyFiles.length > 0,
380
- evidence: privacyFiles.map((file) => ({ type: 'file', value: file, file })),
380
+ evidence: (0, absenceEvidence_1.evidenceOrSearch)(privacyFiles.map((file) => ({ type: 'file', value: file, file })), 'the privacy declaration the stores require', ['PrivacyInfo.xcprivacy', 'a data-safety declaration', 'privacy_policy', 'PRIVACY.md']),
381
381
  },
382
382
  ];
383
383
  }
@@ -253,7 +253,7 @@ async function detectPackaging(ctx) {
253
253
  key: 'quality.ci',
254
254
  present: ciFiles.length > 0,
255
255
  complete: ciFiles.length > 0 && testFiles.length > 0,
256
- evidence: ciFiles.map((file) => ({ type: 'file', value: file, file })),
256
+ evidence: (0, absenceEvidence_1.evidenceOrSearch)(ciFiles.map((file) => ({ type: 'file', value: file, file })), 'anything that runs the tests without being asked', ['.github/workflows/', '.gitlab-ci.yml', '.circleci/config.yml', 'azure-pipelines.yml', 'Jenkinsfile', '.travis.yml']),
257
257
  details: { files: ciFiles.length },
258
258
  },
259
259
  ];
@@ -263,7 +263,18 @@ exports.rules = [
263
263
  ? `Hardcoded fallback secrets detected (${weakTypes || 'unknown'} key context).`
264
264
  : 'No weak fallback secret patterns detected.',
265
265
  recommendation: 'Require strong secrets through environment variables with strict startup validation.',
266
- evidence: weakEvidence,
266
+ /**
267
+ * A check that passes because it found nothing has to say what it looked for.
268
+ *
269
+ * This was the most repeated line in the product: 175 reports said "no weak
270
+ * fallback secret patterns detected" with nothing underneath, which tells the
271
+ * reader neither how hard it looked nor whether their own placeholder would
272
+ * have been caught. A negative result is only worth something when the search
273
+ * behind it is visible.
274
+ */
275
+ evidence: weakEvidence.length > 0
276
+ ? weakEvidence
277
+ : (0, absenceEvidence_1.searchedFor)('hardcoded fallback secrets', ['changeme', 'your-secret', 'dev-secret', 'local-secret', 'change-in-production', 'test123', 'a secret assigned a literal beside jwt_secret, secret_key or session_secret']),
267
278
  });
268
279
  },
269
280
  },
@@ -438,7 +449,11 @@ exports.rules = [
438
449
  ? 'Django settings contain DEBUG=True.'
439
450
  : 'No DEBUG=True signal found.',
440
451
  recommendation: 'Set DEBUG=False for non-local environments and enforce via environment variables.',
441
- evidence: !isDjango ? [{ type: 'note', value: 'Django stack not detected' }] : evidenceForClaim(sec?.evidence, 'django-debug'),
452
+ evidence: !isDjango
453
+ ? [{ type: 'note', value: 'Django stack not detected' }]
454
+ : evidenceForClaim(sec?.evidence, 'django-debug').length > 0
455
+ ? evidenceForClaim(sec?.evidence, 'django-debug')
456
+ : (0, absenceEvidence_1.searchedFor)('DEBUG left on', ['a line reading DEBUG = True in the settings Django actually loads, outside any if settings.DEBUG block']),
442
457
  });
443
458
  },
444
459
  },
@@ -464,7 +479,11 @@ exports.rules = [
464
479
  ? 'Secure cookie settings appear configured.'
465
480
  : 'SESSION/CSRF secure cookie flags are weak.',
466
481
  recommendation: 'Enable SESSION_COOKIE_SECURE and CSRF_COOKIE_SECURE in production.',
467
- evidence: !isDjango ? [{ type: 'note', value: 'Django stack not detected' }] : evidenceForClaim(sec?.evidence, 'django-cookies'),
482
+ evidence: !isDjango
483
+ ? [{ type: 'note', value: 'Django stack not detected' }]
484
+ : evidenceForClaim(sec?.evidence, 'django-cookies').length > 0
485
+ ? evidenceForClaim(sec?.evidence, 'django-cookies')
486
+ : (0, absenceEvidence_1.searchedFor)('cookies left insecure', ['SESSION_COOKIE_SECURE = False', 'CSRF_COOKIE_SECURE = False']),
468
487
  });
469
488
  },
470
489
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@produtype/core",
3
- "version": "0.43.0",
3
+ "version": "0.44.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": {