instar 1.3.1139 → 1.3.1141
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/dist/data/standards-guard-index.json +1 -1
- package/dist/data/standards-guard-index.meta.json +2 -2
- package/dist/data/standards-registry.meta.json +1 -1
- package/package.json +1 -1
- package/scripts/lint-no-unbounded-llm-spawn.js +85 -16
- package/src/data/builtin-manifest.json +2 -2
- package/src/data/standards-guard-index.json +1 -1
- package/src/data/standards-guard-index.meta.json +2 -2
- package/src/data/standards-registry.meta.json +1 -1
- package/upgrades/1.3.1140.md +30 -0
- package/upgrades/1.3.1141.md +30 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"generatedFrom": "source-tree",
|
|
4
4
|
"registrySha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
|
|
5
|
-
"packageVersion": "1.3.
|
|
5
|
+
"packageVersion": "1.3.1141",
|
|
6
6
|
"guards": [
|
|
7
7
|
{
|
|
8
8
|
"ref": "docs/audits/phase-b/f10-triage.md",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"sha256": "
|
|
2
|
+
"sha256": "adce98bd928dd093c27e5fc91bcc6e5d9591b5214386ae330a7df922818f24db",
|
|
3
3
|
"registrySha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
|
|
4
|
-
"packageVersion": "1.3.
|
|
4
|
+
"packageVersion": "1.3.1141"
|
|
5
5
|
}
|
package/package.json
CHANGED
|
@@ -66,7 +66,74 @@ const EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.mjs', '.cjs']);
|
|
|
66
66
|
// `new <ProviderClass>(` — the construction. An IMPORT of the class is fine
|
|
67
67
|
// (the factory imports them); only a direct `new …(` outside the funnel is a
|
|
68
68
|
// bypass.
|
|
69
|
-
|
|
69
|
+
//
|
|
70
|
+
// ALIAS + NAMESPACE AWARE (2026-08-14). The original matched the class NAME as
|
|
71
|
+
// literal text, so two ordinary import styles walked straight past a SAFETY
|
|
72
|
+
// floor — the spawn cap added after the 2026-06-20 OOM fork-bomb:
|
|
73
|
+
//
|
|
74
|
+
// import { ClaudeCliIntelligenceProvider as Provider } from '...';
|
|
75
|
+
// new Provider(...) // real uncapped construction, invisible
|
|
76
|
+
//
|
|
77
|
+
// import * as mod from '...';
|
|
78
|
+
// new mod.ClaudeCliIntelligenceProvider(...) // also invisible: `\bnew\s+Cls`
|
|
79
|
+
// // cannot match across `mod.`
|
|
80
|
+
//
|
|
81
|
+
// Found by a peer-agent audit for checks defeatable by renaming. Resolve the
|
|
82
|
+
// local bindings FIRST, then match constructions under any of them.
|
|
83
|
+
|
|
84
|
+
/** Every local name a provider class is bound to in this file, plus namespace forms.
|
|
85
|
+
*
|
|
86
|
+
* Widened 2026-08-14 after a peer-agent SABOTAGE pass against the first version
|
|
87
|
+
* found two SAME-FILE evasions the alias resolution missed:
|
|
88
|
+
* const C = ClaudeCliIntelligenceProvider; new C({}); // plain re-binding
|
|
89
|
+
* new Providers['ClaudeCliIntelligenceProvider']({}); // computed access
|
|
90
|
+
* The first is probably the easiest bypass of all to write. Re-binding is
|
|
91
|
+
* resolved to a FIXPOINT so chains (`const C = Cls; const D = C;`) close too. */
|
|
92
|
+
export function localProviderBindings(content, cls) {
|
|
93
|
+
const names = new Set([cls]);
|
|
94
|
+
// `import { Cls as Alias }` and `const { Cls: Alias } = await import(...)`
|
|
95
|
+
for (const m of content.matchAll(new RegExp(`\\b${cls}\\s+as\\s+([A-Za-z_$][\\w$]*)`, 'g'))) names.add(m[1]);
|
|
96
|
+
for (const m of content.matchAll(new RegExp(`\\b${cls}\\s*:\\s*([A-Za-z_$][\\w$]*)`, 'g'))) names.add(m[1]);
|
|
97
|
+
// `const|let|var Alias = <knownName>;` — plain re-binding, resolved to a
|
|
98
|
+
// fixpoint so a chain of re-bindings cannot walk out of the set.
|
|
99
|
+
for (let pass = 0; pass < 10; pass++) {
|
|
100
|
+
const before = names.size;
|
|
101
|
+
for (const known of [...names]) {
|
|
102
|
+
const re = new RegExp(`\\b(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)\\s*=\\s*${known}\\s*[;,\\n]`, 'g');
|
|
103
|
+
for (const m of content.matchAll(re)) names.add(m[1]);
|
|
104
|
+
}
|
|
105
|
+
if (names.size === before) break;
|
|
106
|
+
}
|
|
107
|
+
return [...names];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Line numbers (1-based) in `content` that construct a spawn-capable provider,
|
|
112
|
+
* under ANY local binding or namespace qualifier. Comment-only lines excluded.
|
|
113
|
+
*/
|
|
114
|
+
export function findProviderConstructions(content, classes = PROVIDER_CLASSES) {
|
|
115
|
+
const hits = [];
|
|
116
|
+
const lines = content.split('\n');
|
|
117
|
+
const perClass = classes.map((cls) => ({
|
|
118
|
+
cls,
|
|
119
|
+
names: localProviderBindings(content, cls),
|
|
120
|
+
}));
|
|
121
|
+
for (let i = 0; i < lines.length; i++) {
|
|
122
|
+
const trimmed = lines[i].trimStart();
|
|
123
|
+
if (/^(\/\/|\*|\/\*|#)/.test(trimmed)) continue;
|
|
124
|
+
for (const { cls, names } of perClass) {
|
|
125
|
+
const bare = names.some((n) => new RegExp(`\\bnew\\s+${n}\\s*\\(`).test(lines[i]));
|
|
126
|
+
// `new <ns>.<Cls>(` — a namespace import the bare form cannot see.
|
|
127
|
+
const viaNs = new RegExp(`\\bnew\\s+[A-Za-z_$][\\w$]*\\.${cls}\\s*\\(`).test(lines[i]);
|
|
128
|
+
// `new <ns>['<Cls>'](` — computed access; found by sabotage 2026-08-14.
|
|
129
|
+
const viaComputed = new RegExp(
|
|
130
|
+
`\\bnew\\s+[A-Za-z_$][\\w$]*\\s*\\[\\s*['"\`]${cls}['"\`]\\s*\\]\\s*\\(`,
|
|
131
|
+
).test(lines[i]);
|
|
132
|
+
if (bare || viaNs || viaComputed) { hits.push({ line: i + 1, cls }); break; }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return hits;
|
|
136
|
+
}
|
|
70
137
|
|
|
71
138
|
function listFiles() {
|
|
72
139
|
const staged = process.argv.includes('--staged');
|
|
@@ -96,6 +163,15 @@ function listFiles() {
|
|
|
96
163
|
return files;
|
|
97
164
|
}
|
|
98
165
|
|
|
166
|
+
// ── CLI body ─────────────────────────────────────────────────────────────
|
|
167
|
+
// Guarded so the exported detector above can be imported by tests WITHOUT
|
|
168
|
+
// running the scan — this module calls process.exit(1) on a violation, so an
|
|
169
|
+
// unguarded import would kill any test run the moment the repo had one.
|
|
170
|
+
// Same pattern as scripts/eli16-pr-description-check.mjs.
|
|
171
|
+
const invokedDirectly =
|
|
172
|
+
process.argv[1] && import.meta.url === new URL(`file://${process.argv[1]}`).href;
|
|
173
|
+
|
|
174
|
+
if (invokedDirectly) {
|
|
99
175
|
let violations = 0;
|
|
100
176
|
for (const rel of listFiles()) {
|
|
101
177
|
const normalized = rel.split(path.sep).join('/');
|
|
@@ -111,21 +187,13 @@ for (const rel of listFiles()) {
|
|
|
111
187
|
} catch {
|
|
112
188
|
continue;
|
|
113
189
|
}
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
console.error(
|
|
122
|
-
`${normalized}:${i + 1} — direct LLM-CLI provider construction outside the spawn-cap funnel. ` +
|
|
123
|
-
`Build it through buildIntelligenceProvider() (which installs the host-wide spawn cap + circuit breaker), ` +
|
|
124
|
-
`or add an allowlist entry here with a spawn-bounding justification.`,
|
|
125
|
-
);
|
|
126
|
-
violations++;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
190
|
+
for (const hit of findProviderConstructions(content)) {
|
|
191
|
+
console.error(
|
|
192
|
+
`${normalized}:${hit.line} — direct LLM-CLI provider construction outside the spawn-cap funnel. ` +
|
|
193
|
+
`Build it through buildIntelligenceProvider() (which installs the host-wide spawn cap + circuit breaker), ` +
|
|
194
|
+
`or add an allowlist entry here with a spawn-bounding justification.`,
|
|
195
|
+
);
|
|
196
|
+
violations++;
|
|
129
197
|
}
|
|
130
198
|
}
|
|
131
199
|
|
|
@@ -135,3 +203,4 @@ if (violations > 0) {
|
|
|
135
203
|
process.exit(1);
|
|
136
204
|
}
|
|
137
205
|
console.log('lint-no-unbounded-llm-spawn: clean');
|
|
206
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-08-
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-08-14T19:38:02.081Z",
|
|
5
|
+
"instarVersion": "1.3.1141",
|
|
6
6
|
"entryCount": 202,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"generatedFrom": "source-tree",
|
|
4
4
|
"registrySha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
|
|
5
|
-
"packageVersion": "1.3.
|
|
5
|
+
"packageVersion": "1.3.1141",
|
|
6
6
|
"guards": [
|
|
7
7
|
{
|
|
8
8
|
"ref": "docs/audits/phase-b/f10-triage.md",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"sha256": "
|
|
2
|
+
"sha256": "adce98bd928dd093c27e5fc91bcc6e5d9591b5214386ae330a7df922818f24db",
|
|
3
3
|
"registrySha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
|
|
4
|
-
"packageVersion": "1.3.
|
|
4
|
+
"packageVersion": "1.3.1141"
|
|
5
5
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
The check that stops an uncapped AI process being created outside its safety limit could be walked past by renaming an import.
|
|
9
|
+
|
|
10
|
+
- **It matched the class by its name as literal text.** Two ordinary import styles defeat that: rename it on import and use the new name, or import the whole module and reach the class through it. Either is a real, uncapped construction that the check could not see.
|
|
11
|
+
- **It now works out every local name the class is bound to** in a file before looking for constructions, and separately recognises the module-qualified form.
|
|
12
|
+
- **The detection is now a separate importable function**, so it can be tested with small fixtures rather than only by running the whole check over the whole codebase.
|
|
13
|
+
- **Importing it no longer runs it.** The command body stops the process when it finds a violation, so importing the detector in a test would have killed the test run the moment the codebase had one.
|
|
14
|
+
- **No new rule and no new work.** The same constructions are forbidden as before; more of them are visible. The codebase passes cleanly both before and after.
|
|
15
|
+
|
|
16
|
+
## What to Tell Your User
|
|
17
|
+
|
|
18
|
+
In June this system created somewhere between two and three hundred AI processes at once and ran the machine out of memory. The fix was a hard ceiling, plus a check that refuses any code creating one of those processes outside the ceilinged path.
|
|
19
|
+
|
|
20
|
+
That check looked for the thing by name, and renaming something on import is completely ordinary — so a real violation could sit there in plain sight and the check would report everything fine. Nothing was actually wrong today; the hole was in the guard, not the code it guards.
|
|
21
|
+
|
|
22
|
+
It now recognises the renamed and module-qualified forms. This was chosen first out of twenty-five checks with the same weakness, because it guards a safety limit rather than a convention, and because the failure it prevents has already happened once.
|
|
23
|
+
|
|
24
|
+
## Summary of New Capabilities
|
|
25
|
+
|
|
26
|
+
None. This widens what an existing check can see. No new command, route, setting, or rule, and no new violations are introduced.
|
|
27
|
+
|
|
28
|
+
## Evidence
|
|
29
|
+
|
|
30
|
+
The blindness was confirmed in the check's own source before any change: it built a pattern from the class name and tested it line by line, which cannot match across a module qualifier and matches nothing at all once the name is replaced by an alias. Proven in both directions — restricted back to name-only matching, five tests fail and six still pass, those six being the plain case and four deliberate opposite-direction controls: an import alone is not a construction, a comment mentioning the class is not, a differently-named class is not flagged, and a plain variable sharing the name is not. Without those, something that flagged everything would pass every test about catching bypasses and be useless on a healthy codebase. The real codebase passes cleanly before and after, so no false positives were introduced. Import-safety verified in both modes. Source restored byte-identical after the check.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
After hardening the runaway-process guard against renamed imports this morning, a second agent was asked to break it — and got past it twice more.
|
|
9
|
+
|
|
10
|
+
- **Assigning the class to a variable and building from the variable** slipped through. That is probably the simplest bypass anyone could write, and it was not anticipated.
|
|
11
|
+
- **Reaching the class out of a module with bracket notation** instead of a dot also slipped through.
|
|
12
|
+
- **Both are now recognised.** Variable assignments are followed to a fixed point, so passing the class along several times cannot walk out of the set.
|
|
13
|
+
- **The written statement of what the guard still misses has been corrected.** It named one kind of gap; there were three. Two of them were inside the ground the earlier fix implied was covered, so leaving that statement unchanged would have left a claim on record narrower than the truth.
|
|
14
|
+
- **No new work.** The same constructions are forbidden as before, the codebase passes cleanly before and after.
|
|
15
|
+
|
|
16
|
+
## What to Tell Your User
|
|
17
|
+
|
|
18
|
+
This morning's fix made the guard understand renamed imports. It was then attacked on purpose, and two more ways past turned up — both of them ordinary code, both inside a single file, which is exactly what that fix claimed to have handled.
|
|
19
|
+
|
|
20
|
+
Those are closed. Three ways past remain, all involving a class passed onward through several modules; closing those needs the check to follow symbols across files, which it does not do. That limit is now written down and pinned by a test, so nobody later mistakes it for an oversight.
|
|
21
|
+
|
|
22
|
+
Worth noting how this was found: not by re-reading the fix, but by asking someone else to break it and telling them plainly that finding a hole would be more useful than a clean report. A review that can only confirm is not a review.
|
|
23
|
+
|
|
24
|
+
## Summary of New Capabilities
|
|
25
|
+
|
|
26
|
+
None. This widens what an existing check can see and corrects a written claim about its limits. No new command, route, setting, or rule.
|
|
27
|
+
|
|
28
|
+
## Evidence
|
|
29
|
+
|
|
30
|
+
The two bypasses are the reviewer's own snippets, reproduced verbatim as tests. Proven in both directions: reverting both additions fails four tests while thirteen still pass — those thirteen including all six deliberate opposite-direction controls and a test that pins the remaining cross-module gap as still open. Among the controls is a new one specifically for this change: assigning an unrelated symbol to a variable and building from it is not flagged, because a rule that treated every variable as suspicious would pass every bypass test and fail on ordinary code. The real codebase passes cleanly before and after; source restored byte-identical after the check.
|