@wrongstack/primitives 0.310.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.
- package/LICENSE +21 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +137 -0
- package/dist/regex-guard.d.ts +43 -0
- package/dist/time.d.ts +11 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ECOSTACK TECHNOLOGY OÜ
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @wrongstack/primitives — dependency-leaf helpers shared by every tier of
|
|
3
|
+
* the workspace graph. This package must stay importable from the lowest
|
|
4
|
+
* packages (kanban, persistence) and therefore declares NO workspace
|
|
5
|
+
* dependencies. Anything that needs another @wrongstack/* package does not
|
|
6
|
+
* belong here.
|
|
7
|
+
*/
|
|
8
|
+
export { type CompileFail, type CompileResult, type CompileUserRegexResult, capSubject, compileUserRegex, MAX_SUBJECT_LEN, } from './regex-guard.js';
|
|
9
|
+
export { nowIso } from './time.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// src/regex-guard.ts
|
|
2
|
+
var MAX_PATTERN_LEN = 256;
|
|
3
|
+
var DANGEROUS_PATTERNS = [
|
|
4
|
+
// (a+)+, (.*)+, etc — nested quantifier on a group with internal quantifier
|
|
5
|
+
/(\([^)]*[+*][^)]*\))[+*]/,
|
|
6
|
+
/(\(\?:[^)]*[+*][^)]*\))[+*]/,
|
|
7
|
+
// Adjacent quantifiers: a++ a*+
|
|
8
|
+
/[+*]{2,}/,
|
|
9
|
+
// Quantifier on alternation with length 2+
|
|
10
|
+
/\([^|)]+\|[^)]+\)[+*][+*]/,
|
|
11
|
+
// Greedy quantifier inside lookahead/lookbehind — (?!.*a+)
|
|
12
|
+
/\(\?<?[!=][^)]*[+*][^)]*\)/
|
|
13
|
+
];
|
|
14
|
+
function hasAmbiguousQuantifiedAlternation(pattern) {
|
|
15
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
16
|
+
if (pattern[i] !== "(") continue;
|
|
17
|
+
if (i > 0 && pattern[i - 1] === "\\") continue;
|
|
18
|
+
let depth = 0;
|
|
19
|
+
let inClass = false;
|
|
20
|
+
let j = i;
|
|
21
|
+
for (; j < pattern.length; j++) {
|
|
22
|
+
const ch = pattern[j];
|
|
23
|
+
if (ch === "\\") {
|
|
24
|
+
j++;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (inClass) {
|
|
28
|
+
if (ch === "]") inClass = false;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (ch === "[") {
|
|
32
|
+
inClass = true;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (ch === "(") depth++;
|
|
36
|
+
else if (ch === ")") {
|
|
37
|
+
depth--;
|
|
38
|
+
if (depth === 0) break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (j >= pattern.length) return false;
|
|
42
|
+
const next = pattern[j + 1];
|
|
43
|
+
if (next !== "+" && next !== "*" && next !== "{") continue;
|
|
44
|
+
let inner = pattern.slice(i + 1, j);
|
|
45
|
+
inner = inner.replace(/^\?(?::|<?[=!])/u, "");
|
|
46
|
+
const branches = [];
|
|
47
|
+
let current = "";
|
|
48
|
+
let d = 0;
|
|
49
|
+
let cls = false;
|
|
50
|
+
for (let k = 0; k < inner.length; k++) {
|
|
51
|
+
const ch = inner[k];
|
|
52
|
+
if (ch === "\\") {
|
|
53
|
+
current += ch + (inner[k + 1] ?? "");
|
|
54
|
+
k++;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (cls) {
|
|
58
|
+
if (ch === "]") cls = false;
|
|
59
|
+
current += ch;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (ch === "[") {
|
|
63
|
+
cls = true;
|
|
64
|
+
current += ch;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (ch === "(") d++;
|
|
68
|
+
if (ch === ")") d--;
|
|
69
|
+
if (ch === "|" && d === 0) {
|
|
70
|
+
branches.push(current);
|
|
71
|
+
current = "";
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
current += ch;
|
|
75
|
+
}
|
|
76
|
+
branches.push(current);
|
|
77
|
+
if (branches.length < 2) continue;
|
|
78
|
+
for (let a = 0; a < branches.length; a++) {
|
|
79
|
+
for (let b = a + 1; b < branches.length; b++) {
|
|
80
|
+
const x = branches[a];
|
|
81
|
+
const y = branches[b];
|
|
82
|
+
if (x === "" || y === "") return true;
|
|
83
|
+
if (x === y || x.startsWith(y) || y.startsWith(x)) return true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
function compileUserRegex(pattern, flags) {
|
|
90
|
+
if (typeof pattern !== "string") {
|
|
91
|
+
return { ok: false, reason: "pattern must be a string" };
|
|
92
|
+
}
|
|
93
|
+
if (pattern.length === 0) {
|
|
94
|
+
return { ok: false, reason: "pattern is empty" };
|
|
95
|
+
}
|
|
96
|
+
if (pattern.length > MAX_PATTERN_LEN) {
|
|
97
|
+
return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
|
|
98
|
+
}
|
|
99
|
+
for (const rx of DANGEROUS_PATTERNS) {
|
|
100
|
+
if (rx.test(pattern)) {
|
|
101
|
+
return {
|
|
102
|
+
ok: false,
|
|
103
|
+
reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (hasAmbiguousQuantifiedAlternation(pattern)) {
|
|
108
|
+
return {
|
|
109
|
+
ok: false,
|
|
110
|
+
reason: "pattern quantifies an alternation with overlapping branches \u2014 rewrite so no two branches can match the same text"
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
return { ok: true, regex: new RegExp(pattern, flags) };
|
|
115
|
+
} catch (err) {
|
|
116
|
+
return {
|
|
117
|
+
ok: false,
|
|
118
|
+
reason: err instanceof Error ? err.message : "invalid regex"
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
var MAX_SUBJECT_LEN = 64 * 1024;
|
|
123
|
+
function capSubject(line) {
|
|
124
|
+
return line.length > MAX_SUBJECT_LEN ? line.slice(0, MAX_SUBJECT_LEN) : line;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/time.ts
|
|
128
|
+
function nowIso() {
|
|
129
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
130
|
+
}
|
|
131
|
+
export {
|
|
132
|
+
MAX_SUBJECT_LEN,
|
|
133
|
+
capSubject,
|
|
134
|
+
compileUserRegex,
|
|
135
|
+
nowIso
|
|
136
|
+
};
|
|
137
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile a user-supplied regex with conservative bounds against ReDoS.
|
|
3
|
+
*
|
|
4
|
+
* Canonical home (card #5 first slice): previously three drifted copies —
|
|
5
|
+
* `core/src/utils/regex-guard.ts`, `tools/src/_regex.ts`, and
|
|
6
|
+
* `kanban/src/verification/safe-regex.ts` — held in sync only by
|
|
7
|
+
* `tools/tests/regex-guard-parity.test.ts` because kanban sits below both
|
|
8
|
+
* packages in the workspace DAG. This package is a dependency leaf (no
|
|
9
|
+
* workspace deps), so all three import tiers can share one implementation
|
|
10
|
+
* without inverting the layer graph.
|
|
11
|
+
*
|
|
12
|
+
* V8's regex engine is backtracking-based and cannot interrupt a
|
|
13
|
+
* synchronous match — a pattern like `(a+)+$` against a sufficiently
|
|
14
|
+
* long line will pin a worker for seconds. Two coarse bounds:
|
|
15
|
+
*
|
|
16
|
+
* 1. Cap pattern length — practically all legitimate user patterns are
|
|
17
|
+
* under 256 characters.
|
|
18
|
+
* 2. Reject patterns containing the most obvious super-linear structures.
|
|
19
|
+
* This is a coarse filter (false-positives are likely; we accept that
|
|
20
|
+
* for hostile-input contexts).
|
|
21
|
+
*
|
|
22
|
+
* Callers should additionally bound the *subject* length via `capSubject`.
|
|
23
|
+
*/
|
|
24
|
+
export interface CompileResult {
|
|
25
|
+
ok: true;
|
|
26
|
+
regex: RegExp;
|
|
27
|
+
}
|
|
28
|
+
export interface CompileFail {
|
|
29
|
+
ok: false;
|
|
30
|
+
reason: string;
|
|
31
|
+
}
|
|
32
|
+
export type CompileUserRegexResult = CompileResult | CompileFail;
|
|
33
|
+
export declare function compileUserRegex(pattern: string, flags: string): CompileResult | CompileFail;
|
|
34
|
+
/**
|
|
35
|
+
* Maximum subject length handed to a user-supplied regex before matching.
|
|
36
|
+
* A linear-time pattern over a multi-megabyte line is still a stall; tools
|
|
37
|
+
* that need exact-line matching against very long lines should use ripgrep
|
|
38
|
+
* externally rather than the native walker.
|
|
39
|
+
*/
|
|
40
|
+
export declare const MAX_SUBJECT_LEN: number;
|
|
41
|
+
/** Truncate a subject line to {@link MAX_SUBJECT_LEN} for synchronous regex eval. */
|
|
42
|
+
export declare function capSubject(line: string): string;
|
|
43
|
+
//# sourceMappingURL=regex-guard.d.ts.map
|
package/dist/time.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wall-clock time helpers.
|
|
3
|
+
*
|
|
4
|
+
* `nowIso` existed as six byte-identical private/public copies across
|
|
5
|
+
* kanban, cli, sage, and plugins (card #5 slice 5e). It lives here as the
|
|
6
|
+
* canonical definition so every tier of the workspace graph — including
|
|
7
|
+
* packages that sit below core — can share one clock read.
|
|
8
|
+
*/
|
|
9
|
+
/** Current wall-clock time as an ISO-8601 UTC timestamp (`2026-08-22T14:50:00.000Z`). */
|
|
10
|
+
export declare function nowIso(): string;
|
|
11
|
+
//# sourceMappingURL=time.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wrongstack/primitives",
|
|
3
|
+
"version": "0.310.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "Dependency-leaf primitives shared across WrongStack packages (regex guard, slugs, timestamps).",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/WrongStack/WrongStack.git",
|
|
9
|
+
"directory": "packages/primitives"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/WrongStack/WrongStack#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/WrongStack/WrongStack/issues"
|
|
14
|
+
},
|
|
15
|
+
"author": "ECOSTACK TECHNOLOGY OÜ",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"!dist/**/*.map",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^26.2.0",
|
|
33
|
+
"typescript": "^7.0.2"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "node ../../scripts/build-package.mjs",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
|
|
42
|
+
}
|
|
43
|
+
}
|