instar 1.3.1141 → 1.3.1143
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-unfunneled-credential-write.js +90 -19
- package/scripts/lint-telegram-egress-boundary.mjs +68 -11
- 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.1142.md +32 -0
- package/upgrades/1.3.1143.md +28 -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.1143",
|
|
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": "c72f4bfcc850a6eac1406c3592cf3eb5d8bfbdad13c090be45803c4b2277ed15",
|
|
3
3
|
"registrySha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
|
|
4
|
-
"packageVersion": "1.3.
|
|
4
|
+
"packageVersion": "1.3.1143"
|
|
5
5
|
}
|
package/package.json
CHANGED
|
@@ -86,6 +86,83 @@ const PATTERNS = [
|
|
|
86
86
|
// services) are never false-positived.
|
|
87
87
|
const RAW_KEYCHAIN_WRITE = /add-generic-password/;
|
|
88
88
|
|
|
89
|
+
// ── Evasion-resistant detection (2026-08-14) ─────────────────────────────
|
|
90
|
+
// A peer-agent audit classified this check DEFEATABLE by ordinary renaming,
|
|
91
|
+
// and three bypasses were then reproduced against it:
|
|
92
|
+
//
|
|
93
|
+
// const SVC = 'Claude Code' + '-credentials'; // concatenated service
|
|
94
|
+
// execFileSync('security', ['add-generic-password', '-s', SVC]);
|
|
95
|
+
// const store = defaultCredentialStore; store.write(p); // re-bound receiver
|
|
96
|
+
// provider['writeCredentials'](p); // computed access
|
|
97
|
+
//
|
|
98
|
+
// The first is the sharpest: the raw-keychain rule is GATED on the literal
|
|
99
|
+
// service string appearing in the file, so splitting it across a concatenation
|
|
100
|
+
// switches the whole rule off. That gate exists to avoid false-positiving the
|
|
101
|
+
// other, distinct-service vaults, so it is narrowed rather than removed.
|
|
102
|
+
|
|
103
|
+
/** Collapse simple adjacent string concatenation so `'A' + 'B'` reads as `AB`. */
|
|
104
|
+
export function collapseConcatenation(content) {
|
|
105
|
+
// `'A' + 'B'` / "A" + "B" / mixed quotes → `AB`. Repeated to fold chains.
|
|
106
|
+
let out = content;
|
|
107
|
+
for (let i = 0; i < 5; i++) {
|
|
108
|
+
const next = out.replace(/(['"])\s*\+\s*(['"])/g, '');
|
|
109
|
+
if (next === out) break;
|
|
110
|
+
out = next;
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Local names bound to `defaultCredentialStore`, resolved to a fixpoint. */
|
|
116
|
+
export function credentialStoreBindings(content) {
|
|
117
|
+
const names = new Set(['defaultCredentialStore']);
|
|
118
|
+
for (let pass = 0; pass < 10; pass++) {
|
|
119
|
+
const before = names.size;
|
|
120
|
+
for (const known of [...names]) {
|
|
121
|
+
const re = new RegExp(`\\b(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)\\s*=\\s*${known}\\s*[;,\\n]`, 'g');
|
|
122
|
+
for (const m of content.matchAll(re)) names.add(m[1]);
|
|
123
|
+
}
|
|
124
|
+
if (names.size === before) break;
|
|
125
|
+
}
|
|
126
|
+
return [...names];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Violations in `content`, as { line, msg }. Exported so the rules can be
|
|
131
|
+
* driven with fixtures rather than only end-to-end over the tree.
|
|
132
|
+
*/
|
|
133
|
+
export function findCredentialWriteViolations(content) {
|
|
134
|
+
const hits = [];
|
|
135
|
+
// Concatenation-tolerant: a split service literal no longer disarms the rule.
|
|
136
|
+
const targetsGuardedService = collapseConcatenation(content).includes(GUARDED_SERVICE);
|
|
137
|
+
const storeNames = credentialStoreBindings(content);
|
|
138
|
+
const lines = content.split('\n');
|
|
139
|
+
for (let i = 0; i < lines.length; i++) {
|
|
140
|
+
const line = lines[i];
|
|
141
|
+
if (isCommentLine(line)) continue;
|
|
142
|
+
// Re-bound receiver + computed access on the store write.
|
|
143
|
+
for (const n of storeNames) {
|
|
144
|
+
if (new RegExp(`\\b${n}\\s*\\.\\s*write\\s*\\(`).test(line)
|
|
145
|
+
|| new RegExp(`\\b${n}\\s*\\[\\s*['"\`]write['"\`]\\s*\\]\\s*\\(`).test(line)) {
|
|
146
|
+
hits.push({ line: i + 1, msg: PATTERNS[0].msg });
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// `.writeCredentials(` and its computed form.
|
|
151
|
+
if (/\.writeCredentials\s*\(/.test(line)
|
|
152
|
+
|| /\[\s*['"`]writeCredentials['"`]\s*\]\s*\(/.test(line)) {
|
|
153
|
+
hits.push({ line: i + 1, msg: PATTERNS[1].msg });
|
|
154
|
+
}
|
|
155
|
+
if (targetsGuardedService && RAW_KEYCHAIN_WRITE.test(line)) {
|
|
156
|
+
hits.push({
|
|
157
|
+
line: i + 1,
|
|
158
|
+
msg: `raw 'add-generic-password' to the ${GUARDED_SERVICE} service outside the funnel. `
|
|
159
|
+
+ `Route the write through CredentialWriteFunnel.withSlotLock, or add an allowlist entry here with a justification.`,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return hits;
|
|
164
|
+
}
|
|
165
|
+
|
|
89
166
|
function isCommentLine(line) {
|
|
90
167
|
const t = line.trim();
|
|
91
168
|
return t.startsWith('//') || t.startsWith('*') || t.startsWith('/*') || t.startsWith('#');
|
|
@@ -118,6 +195,15 @@ function listFiles() {
|
|
|
118
195
|
return files;
|
|
119
196
|
}
|
|
120
197
|
|
|
198
|
+
// ── CLI body ─────────────────────────────────────────────────────────────
|
|
199
|
+
// Guarded so the exported detector can be imported by tests WITHOUT running the
|
|
200
|
+
// scan: this module calls process.exit(1) on a violation, so an unguarded
|
|
201
|
+
// import would kill any test run the moment the repo had one. Same pattern as
|
|
202
|
+
// scripts/eli16-pr-description-check.mjs and lint-no-unbounded-llm-spawn.js.
|
|
203
|
+
const invokedDirectly =
|
|
204
|
+
process.argv[1] && import.meta.url === new URL(`file://${process.argv[1]}`).href;
|
|
205
|
+
|
|
206
|
+
if (invokedDirectly) {
|
|
121
207
|
let violations = 0;
|
|
122
208
|
for (const rel of listFiles()) {
|
|
123
209
|
const normalized = rel.split(path.sep).join('/');
|
|
@@ -130,25 +216,9 @@ for (const rel of listFiles()) {
|
|
|
130
216
|
} catch {
|
|
131
217
|
continue;
|
|
132
218
|
}
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const line = lines[i];
|
|
137
|
-
if (isCommentLine(line)) continue;
|
|
138
|
-
for (const { re, msg } of PATTERNS) {
|
|
139
|
-
if (re.test(line)) {
|
|
140
|
-
console.error(`${normalized}:${i + 1} — ${msg}`);
|
|
141
|
-
violations++;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (fileTargetsGuardedService && RAW_KEYCHAIN_WRITE.test(line)) {
|
|
145
|
-
console.error(
|
|
146
|
-
`${normalized}:${i + 1} — raw 'add-generic-password' to the ${GUARDED_SERVICE} service outside ` +
|
|
147
|
-
`the funnel. Route the write through CredentialWriteFunnel.withSlotLock, or add an allowlist entry ` +
|
|
148
|
-
`here with a justification.`,
|
|
149
|
-
);
|
|
150
|
-
violations++;
|
|
151
|
-
}
|
|
219
|
+
for (const hit of findCredentialWriteViolations(content)) {
|
|
220
|
+
console.error(`${normalized}:${hit.line} — ${hit.msg}`);
|
|
221
|
+
violations++;
|
|
152
222
|
}
|
|
153
223
|
}
|
|
154
224
|
|
|
@@ -160,3 +230,4 @@ if (violations > 0) {
|
|
|
160
230
|
process.exit(1);
|
|
161
231
|
}
|
|
162
232
|
console.log('lint-no-unfunneled-credential-write: clean');
|
|
233
|
+
}
|
|
@@ -23,8 +23,10 @@
|
|
|
23
23
|
* WHAT IT DOES NOT PROVE, stated because the previous version's claims outran its analysis:
|
|
24
24
|
* - It resolves a URL through a LOCAL declaration or a local helper only. A Bot API URL assembled
|
|
25
25
|
* in another module and imported would not be recognised.
|
|
26
|
-
* - It
|
|
27
|
-
*
|
|
26
|
+
* - It follows a `fetch` stored in a LOCAL variable declaration (added 2026-08-14), but NOT one
|
|
27
|
+
* re-assigned after declaration, arriving as a function parameter, or imported as a wrapper from
|
|
28
|
+
* another module.
|
|
29
|
+
* All are narrower than the gaps they replace, and all are named here rather than discovered.
|
|
28
30
|
*
|
|
29
31
|
* WHERE THIS ENDS AND THE TESTS BEGIN — established by sabotage, not by assumption. Breaking the
|
|
30
32
|
* door's OWN url-to-method recogniser (so it silently skips the check on every send) leaves this lint
|
|
@@ -109,18 +111,25 @@ function denotesBotApiUrl(node, sf) {
|
|
|
109
111
|
* invisible to a lint whose headline claim is "exactly one file". A property access whose final name
|
|
110
112
|
* is `fetch` counts too.
|
|
111
113
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
114
|
+
* Alias resolution ADDED 2026-08-14 (a peer-agent audit ranked this check #2 of 25 defeatable by
|
|
115
|
+
* renaming, and this header had honestly named its own gap): a fetch bound to a DIFFERENT name
|
|
116
|
+
* (`const send = fetch; send(url)`) is now caught, resolved on the AST via collectFetchAliases and
|
|
117
|
+
* followed to a fixpoint so `const a = fetch; const b = a;` closes too. A computed member on a
|
|
118
|
+
* string literal (`x['fetch']`) was already covered.
|
|
119
|
+
*
|
|
120
|
+
* Still NOT covered, said plainly rather than left for the next reading to find: re-assignment after
|
|
121
|
+
* declaration, a fetch arriving as a function PARAMETER, and a wrapper imported from another module.
|
|
122
|
+
* Those need flow and cross-module analysis this does not do. The claim below is written to match
|
|
123
|
+
* this scope.
|
|
115
124
|
*/
|
|
116
|
-
function isFetchCall(n) {
|
|
125
|
+
function isFetchCall(n, aliases = EMPTY_ALIASES) {
|
|
117
126
|
if (!ts.isCallExpression(n)) return false;
|
|
118
127
|
const e = n.expression;
|
|
119
|
-
if (ts.isIdentifier(e)) return e.text === 'fetch';
|
|
128
|
+
if (ts.isIdentifier(e)) return e.text === 'fetch' || aliases.has(e.text);
|
|
120
129
|
if (ts.isPropertyAccessExpression(e)) {
|
|
121
130
|
// `fetch.call(...)` / `fetch.apply(...)` are direct invocations of fetch, and `x['fetch']` is a
|
|
122
131
|
// property access spelled differently (pass 40 F3).
|
|
123
|
-
if (e.name.text === 'call' || e.name.text === 'apply') return isFetchTarget(e.expression);
|
|
132
|
+
if (e.name.text === 'call' || e.name.text === 'apply') return isFetchTarget(e.expression, aliases);
|
|
124
133
|
return e.name.text === 'fetch';
|
|
125
134
|
}
|
|
126
135
|
if (ts.isElementAccessExpression(e)) {
|
|
@@ -131,12 +140,57 @@ function isFetchCall(n) {
|
|
|
131
140
|
}
|
|
132
141
|
|
|
133
142
|
/** Is this expression the `fetch` function itself (for `.call`/`.apply` forms)? */
|
|
134
|
-
function isFetchTarget(e) {
|
|
135
|
-
if (ts.isIdentifier(e)) return e.text === 'fetch';
|
|
143
|
+
function isFetchTarget(e, aliases = EMPTY_ALIASES) {
|
|
144
|
+
if (ts.isIdentifier(e)) return e.text === 'fetch' || aliases.has(e.text);
|
|
136
145
|
if (ts.isPropertyAccessExpression(e)) return e.name.text === 'fetch';
|
|
137
146
|
return false;
|
|
138
147
|
}
|
|
139
148
|
|
|
149
|
+
const EMPTY_ALIASES = new Set();
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Local names bound to `fetch` in this file, resolved to a fixpoint so a chain
|
|
153
|
+
* (`const a = fetch; const b = a;`) closes too.
|
|
154
|
+
*
|
|
155
|
+
* This closes the gap the header above named plainly and left open: a fetch
|
|
156
|
+
* bound to a DIFFERENT name. Done on the AST rather than by text, because the
|
|
157
|
+
* file is already parsed — a variable declaration whose initialiser IS the
|
|
158
|
+
* fetch function is unambiguous, where a regex over `= fetch` would also match
|
|
159
|
+
* a property called fetch on an unrelated object.
|
|
160
|
+
*
|
|
161
|
+
* Deliberately NOT resolved: re-assignment after declaration, parameters, and
|
|
162
|
+
* imports of a wrapper from another module. Those need flow/cross-module
|
|
163
|
+
* analysis; the claim stays scoped to what is actually checked.
|
|
164
|
+
*/
|
|
165
|
+
export function collectFetchAliases(sf) {
|
|
166
|
+
const names = new Set();
|
|
167
|
+
const decls = [];
|
|
168
|
+
const walkNode = (n) => {
|
|
169
|
+
if (ts.isVariableDeclaration(n) && n.initializer && ts.isIdentifier(n.name)) {
|
|
170
|
+
decls.push([n.name.text, n.initializer]);
|
|
171
|
+
}
|
|
172
|
+
ts.forEachChild(n, walkNode);
|
|
173
|
+
};
|
|
174
|
+
walkNode(sf);
|
|
175
|
+
for (let pass = 0; pass < 10; pass++) {
|
|
176
|
+
const before = names.size;
|
|
177
|
+
for (const [name, init] of decls) {
|
|
178
|
+
if (isFetchTarget(init, names)) names.add(name);
|
|
179
|
+
}
|
|
180
|
+
if (names.size === before) break;
|
|
181
|
+
}
|
|
182
|
+
return names;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ── CLI body ─────────────────────────────────────────────────────────────
|
|
186
|
+
// Guarded so collectFetchAliases can be imported by tests WITHOUT running the
|
|
187
|
+
// scan: this module has four process.exit(1) paths, so an unguarded import
|
|
188
|
+
// would kill any test run the moment the repo had a violation. Same pattern as
|
|
189
|
+
// scripts/eli16-pr-description-check.mjs.
|
|
190
|
+
const invokedDirectly =
|
|
191
|
+
process.argv[1] && import.meta.url === new URL(`file://${process.argv[1]}`).href;
|
|
192
|
+
|
|
193
|
+
if (invokedDirectly) {
|
|
140
194
|
const violations = [];
|
|
141
195
|
let doorSeen = false;
|
|
142
196
|
|
|
@@ -147,9 +201,10 @@ for (const file of walk(SRC)) {
|
|
|
147
201
|
if (!text.toLowerCase().includes('api.telegram.org')) continue;
|
|
148
202
|
const sf = parse(file, text);
|
|
149
203
|
const isDoor = path.resolve(file) === DOOR;
|
|
204
|
+
const aliases = collectFetchAliases(sf);
|
|
150
205
|
|
|
151
206
|
const visit = (n) => {
|
|
152
|
-
if (isFetchCall(n) && ts.isCallExpression(n)) {
|
|
207
|
+
if (isFetchCall(n, aliases) && ts.isCallExpression(n)) {
|
|
153
208
|
if (denotesBotApiUrl(n.arguments[0], sf)) {
|
|
154
209
|
if (isDoor) doorSeen = true;
|
|
155
210
|
else {
|
|
@@ -254,3 +309,5 @@ console.log(
|
|
|
254
309
|
'lint-telegram-egress-boundary: clean — Telegram Bot API egress is confined to '
|
|
255
310
|
+ 'src/messaging/telegram-egress.ts, which checks the serialised body before sending.',
|
|
256
311
|
);
|
|
312
|
+
|
|
313
|
+
}
|
|
@@ -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-14T20:31:20.547Z",
|
|
5
|
+
"instarVersion": "1.3.1143",
|
|
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.1143",
|
|
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": "c72f4bfcc850a6eac1406c3592cf3eb5d8bfbdad13c090be45803c4b2277ed15",
|
|
3
3
|
"registrySha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
|
|
4
|
-
"packageVersion": "1.3.
|
|
4
|
+
"packageVersion": "1.3.1143"
|
|
5
5
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
The rule that stops secrets being written outside the approved path could be switched off by writing one name in two halves.
|
|
9
|
+
|
|
10
|
+
- **The raw-keychain part of the check only arms itself when it sees a particular service name written out in full.** That narrowing is deliberate — it keeps the other, unrelated keychain stores from being flagged — but it means splitting the name across a join turned the entire rule off.
|
|
11
|
+
- **Two smaller ways past existed too**: assign the credential store to a variable and write through the variable, or reach the write method with bracket notation instead of a dot.
|
|
12
|
+
- **All three were confirmed getting past the check before this change**, with a known-caught example passing in the same run to prove the probe worked.
|
|
13
|
+
- **All three are now closed.** Split strings are joined before the check decides whether to arm, variables holding the store are followed to a fixed point, and bracket access is recognised.
|
|
14
|
+
- **Nothing new is forbidden.** The same writes are disallowed as before; more of them are visible, and the codebase passes cleanly before and after.
|
|
15
|
+
|
|
16
|
+
## What to Tell Your User
|
|
17
|
+
|
|
18
|
+
Every write of the stored Claude credential is meant to go through a single controlled path, and a check enforces it. To avoid complaining about unrelated keychain entries, part of that check only switched on when it recognised a specific name spelled out in full — so spelling it in two pieces made the check ignore the file completely.
|
|
19
|
+
|
|
20
|
+
Nothing was actually being written unsafely; the hole was in the guard. It is closed, along with two smaller ones.
|
|
21
|
+
|
|
22
|
+
The care here went into the opposite risk. Widening a rule that blocks work can flood people with false complaints about correct code, and the quickest way to silence a noisy check is to switch it off. So five deliberate tests confirm the things that must *not* be flagged still are not, and the whole codebase passes cleanly before and after.
|
|
23
|
+
|
|
24
|
+
## Summary of New Capabilities
|
|
25
|
+
|
|
26
|
+
None. This widens what an existing check can see. No new command, route, setting, or rule.
|
|
27
|
+
|
|
28
|
+
## Evidence
|
|
29
|
+
|
|
30
|
+
The three bypasses were reproduced against the shipped check before any change, alongside a known-caught example in the same run to prove the probe could detect anything at all. Proven in both directions: restored to the old behaviour, seven tests fail and seven pass — and the seven that pass are exactly the five opposite-direction controls plus the plain cases, which is what makes them guards rather than echoes. Those controls check that a different keychain service is not flagged, that a raw keychain write with no reference to the guarded service is not flagged, that an unrelated store's write is not flagged, that an unrelated variable assignment is not absorbed, and that comments are not violations. The real codebase passes cleanly before and after, and the source was restored byte-identical after the check.
|
|
31
|
+
|
|
32
|
+
Remaining gaps are named rather than implied: a service name built through a template with a variable in it, or imported as a constant from another file, still disarms the rule — both need value resolution this check does not do.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
The check that proves only one file can talk to Telegram had already admitted, in its own header, that renaming the network call would defeat it. That gap is now closed.
|
|
9
|
+
|
|
10
|
+
- **Assigning the network call to a variable and calling the variable** was not recognised. The check's own documentation said so plainly and scoped its stated claim to match — honest, and still a hole in a door that carries the credential for the whole messaging channel.
|
|
11
|
+
- **It is now recognised**, followed through chains of assignment to a fixed point.
|
|
12
|
+
- **Done on the parsed structure of the file, not by matching text.** The file is already parsed, and a declaration whose value *is* the network call is unambiguous — searching for the text would also match a property with that name on an unrelated object.
|
|
13
|
+
- **The header's statement of limits is corrected.** It named this gap as open; three narrower ones are named in its place. Leaving the old wording would have left a false statement in the one place a reader looks to learn what the check is worth.
|
|
14
|
+
- **Nothing new is forbidden**, and the codebase passes cleanly before and after.
|
|
15
|
+
|
|
16
|
+
## What to Tell Your User
|
|
17
|
+
|
|
18
|
+
Everything sent to Telegram has to go through one file, which looks at the message before it goes out. A check proves nothing else can reach the network on a Telegram address.
|
|
19
|
+
|
|
20
|
+
It recognised that call written several ways, but not the simplest disguise: put it in a variable first. The check said so about itself, which is better than pretending otherwise, but it was still a way around a door that holds the messaging credential. It is closed now, and the three narrower ways still open are written down where the next reader will find them.
|
|
21
|
+
|
|
22
|
+
## Summary of New Capabilities
|
|
23
|
+
|
|
24
|
+
None. This widens what an existing check can see and corrects its written description of its own limits. No new command, route, setting, or rule.
|
|
25
|
+
|
|
26
|
+
## Evidence
|
|
27
|
+
|
|
28
|
+
The gap was not discovered by anyone — the check declared it, in two separate places, and both are corrected here rather than left stating something now false. Proven in both directions: with the resolution removed, three tests fail and six pass, and those six are exactly the four deliberate opposite-direction controls plus two tests that pin the remaining gaps as still open. Those controls matter more than usual because this check blocks work: an unrelated assignment is not absorbed, a property whose name merely differs is not absorbed, a file with no reference yields nothing, and the word itself as a piece of text is not a binding. The real codebase passes cleanly before and after, source restored byte-identical after the check, and the module is now safe to import — it previously had four exit paths and no guard, so importing it in a test would have stopped the test run the moment the codebase had a violation.
|