tewip-ai 0.70.0 → 0.70.1

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.
@@ -169,12 +169,54 @@ export async function scanWeb(url, { timeoutMs = 15_000, probeExposed = true, pa
169
169
  return { findings, requests, note: null };
170
170
  }
171
171
  /** Localhost, a private address, or a name only this machine resolves. */
172
+ /** The four octets of a dotted IPv4 address, or null for anything else - including a name. */
173
+ function ipv4(host) {
174
+ const parts = host.split('.');
175
+ if (parts.length !== 4)
176
+ return null;
177
+ const numbers = parts.map((p) => (/^\d{1,3}$/.test(p) ? Number(p) : -1));
178
+ return numbers.every((n) => n >= 0 && n <= 255) ? numbers : null;
179
+ }
180
+ /**
181
+ * Is this address the person's own machine or network?
182
+ *
183
+ * **This is a consent control, not a convenience.** `secure --url` refuses a non-local address
184
+ * without `--i-am-authorised`, because scanning a system you do not run is not Tewip's decision to
185
+ * make - so anything this returns `true` for is scanned with no questions asked.
186
+ *
187
+ * It was a prefix match, and five hostnames walked straight past it: `localhost.evil.example`,
188
+ * `localhostile.example`, `10.evil.example`, `127.0.0.1.evil.example` and `172.16.evil.example` were
189
+ * all "this machine", because each *starts with* something local-looking. `localhost.<anything>` is
190
+ * one of the oldest allowlist bypasses there is, and a careless paste reaches it as easily as a
191
+ * malicious one.
192
+ *
193
+ * So a name is local only if it **is** `localhost` or ends in a reserved local suffix, and an address
194
+ * is local only if its octets really fall in a private range. Link-local 169.254/16 is deliberately
195
+ * **not** local: `169.254.169.254` is the cloud metadata endpoint, and asking before touching it is
196
+ * exactly the point of this function.
197
+ */
172
198
  export function isLocal(url) {
199
+ let host;
173
200
  try {
174
- const host = new URL(url).hostname;
175
- return /^(?:localhost|127\.|0\.0\.0\.0|\[::1\]|::1|10\.|192\.168\.|172\.(?:1[6-9]|2\d|3[01])\.)/.test(host) || host.endsWith('.local') || host.endsWith('.localhost');
201
+ host = new URL(url).hostname.toLowerCase();
176
202
  }
177
203
  catch {
178
204
  return false;
179
205
  }
206
+ // `new URL('http://[::1]').hostname` is "[::1]" in Node, so both spellings are accepted.
207
+ if (host === 'localhost' || host === '::1' || host === '[::1]')
208
+ return true;
209
+ if (host.endsWith('.localhost') || host.endsWith('.local'))
210
+ return true;
211
+ const octets = ipv4(host);
212
+ if (!octets)
213
+ return false;
214
+ const [a, b] = octets;
215
+ if (a === 0 || a === 127)
216
+ return true; // 0.0.0.0 and the whole loopback range
217
+ if (a === 10)
218
+ return true;
219
+ if (a === 192 && b === 168)
220
+ return true;
221
+ return a === 172 && b >= 16 && b <= 31;
180
222
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tewip-ai",
3
- "version": "0.70.0",
3
+ "version": "0.70.1",
4
4
  "mcpName": "io.github.hmamut39/tewip",
5
5
  "description": "Tewip finds why each failing test failed - real bug, flaky test, environment, test data or changed selector - for any stack, from the command line, CI, or your editor.",
6
6
  "keywords": [