@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
|
+
}
|
package/dist/utils/textSearch.js
CHANGED
|
@@ -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.
|
|
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": {
|