@produtype/core 0.48.0 → 0.49.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.
@@ -13,6 +13,15 @@ const ENV_FALLBACK_RE = /(process\.env\.(JWT_SECRET|SECRET_KEY|SESSION_SECRET)\s
13
13
  */
14
14
  const SECRET_ASSIGNMENT_CONTEXT_RE = /(JWT_SECRET|SECRET_KEY|SESSION_SECRET|API_KEY|jwt_secret|secret_key|session_secret|api_key)\s*[:=]|process\.env\.[A-Z0-9_]*(SECRET|API_KEY)/;
15
15
  const GENERIC_SECRET_ASSIGNMENT_RE = /(JWT_SECRET|SECRET_KEY|SESSION_SECRET|API_KEY)\s*[:=]\s*['"][^'"]+['"]/i;
16
+ /**
17
+ * A value that exists to hide a secret, not to be one.
18
+ *
19
+ * `config.global_settings.jwt_secret = "***"` is windmill's own redaction, in the
20
+ * command that prints an instance's settings, and it was reported as a hardcoded
21
+ * secret. The weak-value test runs on the whole line, and the line says "secret"
22
+ * because the identifier does — so the value itself was never looked at.
23
+ */
24
+ const REDACTED_VALUE_RE = /[:=]\s*['"`](\*{2,}|x{3,}|<?\[?redacted\]?>?|hidden|\.{3,})['"`]/i;
16
25
  function classifySecretFallback(snippet) {
17
26
  if (/JWT_SECRET/i.test(snippet))
18
27
  return 'jwt';
@@ -119,6 +128,7 @@ async function detectEnv(ctx) {
119
128
  && SECRET_ASSIGNMENT_CONTEXT_RE.test(m.snippet)
120
129
  && !namesItself(m.snippet)
121
130
  && !valueIsAnIdentifier(m.snippet)
131
+ && !REDACTED_VALUE_RE.test(m.snippet)
122
132
  && !isTableEntry(m));
123
133
  for (const m of weakHits) {
124
134
  const hitEvidence = { type: 'snippet', value: m.snippet, file: m.file, line: m.line };
@@ -1 +1,21 @@
1
1
  export declare function isDevelopmentOnlyFile(file: string): boolean;
2
+ /**
3
+ * The lines of a Rust file that only exist to test it.
4
+ *
5
+ * windmill was reported with a `critical` hardcoded secret:
6
+ * `secret_key: "wrong_secret".to_string()`, in `http_trigger_auth.rs`. It is the
7
+ * fixture of `test_github_authenticate_wrong_secret` — a test asserting that the
8
+ * wrong secret is rejected — and the whole block sits under `#[cfg(test)]`, four
9
+ * hundred lines below the code it tests.
10
+ *
11
+ * The path filter cannot help here and never could: Rust keeps its unit tests inside
12
+ * the file they test, so there is no `tests/` directory to exclude. The language says
13
+ * plainly which lines are test-only, in an attribute written for exactly this purpose,
14
+ * and nothing was reading it.
15
+ *
16
+ * Brace depth rather than a parser: `#[cfg(test)]` is followed by a module, and a
17
+ * module ends where its braces balance. Strings containing braces would confuse it,
18
+ * and the cost of being confused is excluding a few more lines from a security scan
19
+ * of test code — which is where it was heading anyway.
20
+ */
21
+ export declare function testOnlyLines(file: string, text: string): Set<number>;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isDevelopmentOnlyFile = isDevelopmentOnlyFile;
4
+ exports.testOnlyLines = testOnlyLines;
4
5
  /**
5
6
  * A file the product does not run in production.
6
7
  *
@@ -21,3 +22,48 @@ const DEVELOPMENT_ONLY_PATHS = [
21
22
  function isDevelopmentOnlyFile(file) {
22
23
  return DEVELOPMENT_ONLY_PATHS.some((pattern) => pattern.test(file));
23
24
  }
25
+ /**
26
+ * The lines of a Rust file that only exist to test it.
27
+ *
28
+ * windmill was reported with a `critical` hardcoded secret:
29
+ * `secret_key: "wrong_secret".to_string()`, in `http_trigger_auth.rs`. It is the
30
+ * fixture of `test_github_authenticate_wrong_secret` — a test asserting that the
31
+ * wrong secret is rejected — and the whole block sits under `#[cfg(test)]`, four
32
+ * hundred lines below the code it tests.
33
+ *
34
+ * The path filter cannot help here and never could: Rust keeps its unit tests inside
35
+ * the file they test, so there is no `tests/` directory to exclude. The language says
36
+ * plainly which lines are test-only, in an attribute written for exactly this purpose,
37
+ * and nothing was reading it.
38
+ *
39
+ * Brace depth rather than a parser: `#[cfg(test)]` is followed by a module, and a
40
+ * module ends where its braces balance. Strings containing braces would confuse it,
41
+ * and the cost of being confused is excluding a few more lines from a security scan
42
+ * of test code — which is where it was heading anyway.
43
+ */
44
+ function testOnlyLines(file, text) {
45
+ const testOnly = new Set();
46
+ if (!/\.rs$/i.test(file))
47
+ return testOnly;
48
+ const lines = text.split(/\r?\n/);
49
+ for (let i = 0; i < lines.length; i++) {
50
+ if (!/^\s*#\[cfg\(test\)\]/.test(lines[i]))
51
+ continue;
52
+ let depth = 0;
53
+ let opened = false;
54
+ for (let j = i + 1; j < lines.length; j++) {
55
+ testOnly.add(j + 1);
56
+ for (const character of lines[j]) {
57
+ if (character === '{') {
58
+ depth++;
59
+ opened = true;
60
+ }
61
+ else if (character === '}')
62
+ depth--;
63
+ }
64
+ if (opened && depth <= 0)
65
+ break;
66
+ }
67
+ }
68
+ return testOnly;
69
+ }
@@ -5,6 +5,7 @@ exports.matchLines = matchLines;
5
5
  exports.searchInFiles = searchInFiles;
6
6
  exports.anyIncludes = anyIncludes;
7
7
  const readTextFileSafe_1 = require("./readTextFileSafe");
8
+ const developmentOnly_1 = require("../analyzer/developmentOnly");
8
9
  /**
9
10
  * A line that declares a pattern rather than doing anything.
10
11
  *
@@ -96,8 +97,16 @@ const MAX_CITABLE_LINE = 500;
96
97
  function matchLines(text, needles, file = '') {
97
98
  const matches = [];
98
99
  const lines = text.split(/\r?\n/);
100
+ /**
101
+ * Rust keeps its unit tests in the file they test, so no path filter can exclude
102
+ * them. windmill was reported with a `critical` hardcoded secret that was the
103
+ * fixture of a test asserting the wrong secret is rejected.
104
+ */
105
+ const testOnly = (0, developmentOnly_1.testOnlyLines)(file, text);
99
106
  for (let i = 0; i < lines.length; i++) {
100
107
  const line = lines[i];
108
+ if (testOnly.has(i + 1))
109
+ continue;
101
110
  if (line.length > MAX_CITABLE_LINE)
102
111
  continue;
103
112
  if (declaresRatherThanDoes(line))
@@ -125,8 +134,11 @@ async function searchInFiles(root, files, needles, limit = 25) {
125
134
  if (!text)
126
135
  continue;
127
136
  const lines = text.split(/\r?\n/);
137
+ const testOnly = (0, developmentOnly_1.testOnlyLines)(file, text);
128
138
  for (let i = 0; i < lines.length; i++) {
129
139
  const line = lines[i];
140
+ if (testOnly.has(i + 1))
141
+ continue;
130
142
  if (line.length > MAX_CITABLE_LINE)
131
143
  continue;
132
144
  if (declaresRatherThanDoes(line))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@produtype/core",
3
- "version": "0.48.0",
3
+ "version": "0.49.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": {