@produtype/core 0.67.0 → 0.69.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.
|
@@ -173,7 +173,30 @@ async function detectSecurity(ctx) {
|
|
|
173
173
|
* are what HTTP calls this, and a hand-rolled limiter that writes one of them is
|
|
174
174
|
* really limiting something. The package and its binding cover the rest.
|
|
175
175
|
*/
|
|
176
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Issuing the refusal, not receiving one.
|
|
178
|
+
*
|
|
179
|
+
* `\b429\b` matched both directions. nocodb's webhook invoker handles a 429 coming
|
|
180
|
+
* back from somebody else's server — that is this project being throttled, the
|
|
181
|
+
* opposite of this project throttling — and it was one of the lines behind "rate
|
|
182
|
+
* limiting is in place somewhere". Its login has no throttle at all: the global
|
|
183
|
+
* guard is an id extractor, and the rest of the matches are migrations, a SQL
|
|
184
|
+
* client and mock fixtures.
|
|
185
|
+
*
|
|
186
|
+
* `res.status(429)` and `throw new TooManyRequestsError` are a server refusing a
|
|
187
|
+
* caller. `if (response.status === 429)` is a client being refused. HTTP names the
|
|
188
|
+
* number; the direction is in the shape around it.
|
|
189
|
+
*/
|
|
190
|
+
const rateLimitSignals = await (0, textSearch_1.searchInFiles)(ctx.root, source, [
|
|
191
|
+
/\.status\(\s*429/,
|
|
192
|
+
/status(?:Code)?\s*[:=]\s*429/,
|
|
193
|
+
/sendStatus\(\s*429/,
|
|
194
|
+
/throw new \w*TooManyRequests/,
|
|
195
|
+
/abort\(\s*429/,
|
|
196
|
+
/HTTPException\(\s*429/,
|
|
197
|
+
/['"`]Retry-After['"`]\s*[,:]/i,
|
|
198
|
+
/setHeader\(\s*['"`]Retry-After/i,
|
|
199
|
+
], 20);
|
|
177
200
|
const rateLimit = rateLimitDep || rateLimitSignals.length > 0;
|
|
178
201
|
/**
|
|
179
202
|
* Rate limiting where the brute force happens.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lines of a Python file that are a docstring rather than code.
|
|
3
|
+
*
|
|
4
|
+
* This analyzer has skipped comments since the day it read its own prose about Stripe
|
|
5
|
+
* as evidence that it takes payments. The rule looks for a line marker — `#`, `//`,
|
|
6
|
+
* `/*`, `<!--` — and Python's main way of writing prose has none: a docstring is a
|
|
7
|
+
* string, and its interior lines begin with whatever the author was saying.
|
|
8
|
+
*
|
|
9
|
+
* Measured across the corpus: 118 of 1587 citations from Python files land inside
|
|
10
|
+
* one. `/api/auth/... registrazione + JWT` was cited as evidence of an
|
|
11
|
+
* authentication route; `Chi arriva col link (?k=...) si vede posare un cookie` as
|
|
12
|
+
* evidence about cookies. Sentences describing the code, offered as the code.
|
|
13
|
+
*
|
|
14
|
+
* Only a block that stands on its own. `QUERY = """SELECT ..."""` is data assigned to
|
|
15
|
+
* a name and a hardcoded secret could live in one, so a triple-quoted string on the
|
|
16
|
+
* right of an assignment stays readable. A docstring is an expression statement and
|
|
17
|
+
* has nothing before it but indentation.
|
|
18
|
+
*/
|
|
19
|
+
export declare function proseLines(file: string, text: string): Set<number>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.proseLines = proseLines;
|
|
4
|
+
/**
|
|
5
|
+
* The lines of a Python file that are a docstring rather than code.
|
|
6
|
+
*
|
|
7
|
+
* This analyzer has skipped comments since the day it read its own prose about Stripe
|
|
8
|
+
* as evidence that it takes payments. The rule looks for a line marker — `#`, `//`,
|
|
9
|
+
* `/*`, `<!--` — and Python's main way of writing prose has none: a docstring is a
|
|
10
|
+
* string, and its interior lines begin with whatever the author was saying.
|
|
11
|
+
*
|
|
12
|
+
* Measured across the corpus: 118 of 1587 citations from Python files land inside
|
|
13
|
+
* one. `/api/auth/... registrazione + JWT` was cited as evidence of an
|
|
14
|
+
* authentication route; `Chi arriva col link (?k=...) si vede posare un cookie` as
|
|
15
|
+
* evidence about cookies. Sentences describing the code, offered as the code.
|
|
16
|
+
*
|
|
17
|
+
* Only a block that stands on its own. `QUERY = """SELECT ..."""` is data assigned to
|
|
18
|
+
* a name and a hardcoded secret could live in one, so a triple-quoted string on the
|
|
19
|
+
* right of an assignment stays readable. A docstring is an expression statement and
|
|
20
|
+
* has nothing before it but indentation.
|
|
21
|
+
*/
|
|
22
|
+
function proseLines(file, text) {
|
|
23
|
+
const prose = new Set();
|
|
24
|
+
if (!/\.py$/i.test(file))
|
|
25
|
+
return prose;
|
|
26
|
+
const lines = text.split(/\r?\n/);
|
|
27
|
+
let openQuote = null;
|
|
28
|
+
let openIsProse = false;
|
|
29
|
+
for (let i = 0; i < lines.length; i++) {
|
|
30
|
+
const line = lines[i];
|
|
31
|
+
if (openQuote) {
|
|
32
|
+
if (openIsProse)
|
|
33
|
+
prose.add(i + 1);
|
|
34
|
+
if (line.includes(openQuote)) {
|
|
35
|
+
openQuote = null;
|
|
36
|
+
openIsProse = false;
|
|
37
|
+
}
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const match = /("""|''')/.exec(line);
|
|
41
|
+
if (!match)
|
|
42
|
+
continue;
|
|
43
|
+
const before = line.slice(0, match.index);
|
|
44
|
+
/** An expression statement: nothing before the quotes but whitespace. */
|
|
45
|
+
const standsAlone = /^\s*[rRbBuUfF]*$/.test(before);
|
|
46
|
+
const rest = line.slice(match.index + 3);
|
|
47
|
+
const closesOnThisLine = rest.includes(match[1]);
|
|
48
|
+
if (standsAlone)
|
|
49
|
+
prose.add(i + 1);
|
|
50
|
+
if (closesOnThisLine)
|
|
51
|
+
continue;
|
|
52
|
+
openQuote = match[1];
|
|
53
|
+
openIsProse = standsAlone;
|
|
54
|
+
}
|
|
55
|
+
return prose;
|
|
56
|
+
}
|
package/dist/utils/textSearch.js
CHANGED
|
@@ -6,6 +6,7 @@ exports.searchInFiles = searchInFiles;
|
|
|
6
6
|
exports.anyIncludes = anyIncludes;
|
|
7
7
|
const readTextFileSafe_1 = require("./readTextFileSafe");
|
|
8
8
|
const developmentOnly_1 = require("../analyzer/developmentOnly");
|
|
9
|
+
const proseLines_1 = require("../analyzer/proseLines");
|
|
9
10
|
/**
|
|
10
11
|
* A line that declares a pattern rather than doing anything.
|
|
11
12
|
*
|
|
@@ -103,9 +104,11 @@ function matchLines(text, needles, file = '') {
|
|
|
103
104
|
* fixture of a test asserting the wrong secret is rejected.
|
|
104
105
|
*/
|
|
105
106
|
const testOnly = (0, developmentOnly_1.testOnlyLines)(file, text);
|
|
107
|
+
/** A Python docstring is prose with no line marker to recognise it by. */
|
|
108
|
+
const prose = (0, proseLines_1.proseLines)(file, text);
|
|
106
109
|
for (let i = 0; i < lines.length; i++) {
|
|
107
110
|
const line = lines[i];
|
|
108
|
-
if (testOnly.has(i + 1))
|
|
111
|
+
if (testOnly.has(i + 1) || prose.has(i + 1))
|
|
109
112
|
continue;
|
|
110
113
|
if (line.length > MAX_CITABLE_LINE)
|
|
111
114
|
continue;
|
|
@@ -135,9 +138,10 @@ async function searchInFiles(root, files, needles, limit = 25) {
|
|
|
135
138
|
continue;
|
|
136
139
|
const lines = text.split(/\r?\n/);
|
|
137
140
|
const testOnly = (0, developmentOnly_1.testOnlyLines)(file, text);
|
|
141
|
+
const prose = (0, proseLines_1.proseLines)(file, text);
|
|
138
142
|
for (let i = 0; i < lines.length; i++) {
|
|
139
143
|
const line = lines[i];
|
|
140
|
-
if (testOnly.has(i + 1))
|
|
144
|
+
if (testOnly.has(i + 1) || prose.has(i + 1))
|
|
141
145
|
continue;
|
|
142
146
|
if (line.length > MAX_CITABLE_LINE)
|
|
143
147
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@produtype/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.69.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": {
|