@produtype/core 0.63.0 → 0.64.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.
@@ -44,19 +44,32 @@ function withoutStringLiterals(line) {
44
44
  }
45
45
  /** The Express middleware, brought into the file that configures it. */
46
46
  const IMPORTS_CORS = /(require\(['"]cors['"]\))|(from\s+['"]cors['"])|(import\s+['"]cors['"])/;
47
- function detectCorsConfig(text, file) {
47
+ /**
48
+ * The middleware, under whatever name it was given.
49
+ *
50
+ * `cors(` is the name of the export, and an author is free not to use it:
51
+ * `const apriTutto = require('cors'); app.use(apriTutto());` is a wide-open policy
52
+ * that came back as "CORS configuration not detected" — a real hole reported as the
53
+ * absence of a question. The package is the anchor; the names come from the binding
54
+ * and are recognised here rather than guessed.
55
+ */
56
+ function detectCorsConfig(text, file, boundNames) {
48
57
  const loose = [];
49
58
  const strict = [];
50
59
  const lines = text.split(/\r?\n/);
51
- const corsVarRegex = /\bcors\(\s*([A-Za-z_$][\w$]*)\s*\)/;
60
+ const names = [...new Set(['cors', ...boundNames])].map((name) => name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
61
+ const anyName = names.join('|');
62
+ const callRegex = new RegExp(`\\b(?:${anyName})\\s*\\(`);
63
+ const bareCallRegex = new RegExp(`\\b(?:${anyName})\\(\\s*\\)`);
64
+ const corsVarRegex = new RegExp(`\\b(?:${anyName})\\(\\s*([A-Za-z_$][\\w$]*)\\s*\\)`);
52
65
  for (let i = 0; i < lines.length; i++) {
53
66
  const line = lines[i];
54
67
  if (!(0, textSearch_1.isCitableLine)(line))
55
68
  continue;
56
- if (!/\bcors\s*\(/.test(withoutStringLiterals(line)))
69
+ if (!callRegex.test(withoutStringLiterals(line)))
57
70
  continue;
58
71
  const snippet = line.trim().slice(0, 200);
59
- if (/\bcors\(\s*\)/.test(line)) {
72
+ if (bareCallRegex.test(line)) {
60
73
  loose.push({ file, line: i + 1, snippet });
61
74
  continue;
62
75
  }
@@ -202,6 +215,14 @@ async function detectSecurity(ctx) {
202
215
  evidence.push({ type: 'snippet', value: m.snippet, file: m.file, line: m.line, claim: 'headers' });
203
216
  for (const m of rateLimitSignals)
204
217
  evidence.push({ type: 'snippet', value: m.snippet, file: m.file, line: m.line, claim: 'rate-limit' });
218
+ /**
219
+ * Every identifier the `cors` package reaches, per file.
220
+ *
221
+ * `const apriTutto = require('cors')` binds the middleware to a name no word list
222
+ * will ever hold, and `app.use(apriTutto())` is a wide-open policy that read as no
223
+ * policy at all. The import is the fact; the name is whatever this author typed.
224
+ */
225
+ const corsBindings = await (0, valuesFromPackage_1.readPackageValueUses)(ctx.root, source, ['cors']);
205
226
  const corsLoose = [];
206
227
  const corsStrict = [];
207
228
  for (const file of source) {
@@ -243,9 +264,11 @@ async function detectSecurity(ctx) {
243
264
  }
244
265
  if (!IMPORTS_CORS.test(text))
245
266
  continue;
246
- if (!/\bcors\s*\(/.test(text))
267
+ const boundNames = new Set((corsBindings ?? []).filter((use) => use.file === file).map((use) => use.name));
268
+ // `cors(` under its own name, or under the one the binding gave it.
269
+ if (boundNames.size === 0 && !/\bcors\s*\(/.test(text))
247
270
  continue;
248
- const detected = detectCorsConfig(text, file);
271
+ const detected = detectCorsConfig(text, file, boundNames);
249
272
  corsLoose.push(...detected.loose);
250
273
  corsStrict.push(...detected.strict);
251
274
  }
@@ -1,6 +1,14 @@
1
1
  export interface PackageValueUse {
2
2
  file: string;
3
3
  line: number;
4
+ /**
5
+ * The identifier the value is reached through.
6
+ *
7
+ * A caller that needs to reason about the call — is this `cors()` bare or
8
+ * `cors({ origin })`? — needs the name the author gave it, not to guess the name
9
+ * but to recognise it once the import has already proved what it is.
10
+ */
11
+ name: string;
4
12
  /**
5
13
  * The path it was mounted on, where the use is an argument to `use(path, value)`.
6
14
  *
@@ -155,6 +155,7 @@ async function readPackageValueUses(root, files, packages) {
155
155
  uses.push({
156
156
  file,
157
157
  line: source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1,
158
+ name: node.text,
158
159
  mountPath: mountPathOf(ts, node),
159
160
  });
160
161
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@produtype/core",
3
- "version": "0.63.0",
3
+ "version": "0.64.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": {