(url, maxChars = 5000)
| 133 | // ─── Web Fetch (extract readable content) ──────────────────────────────────── |
| 134 | |
| 135 | async function webFetch(url, maxChars = 5000) { |
| 136 | // SSRF guard — refuse URLs targeting metadata, RFC1918, or loopback |
| 137 | // unless the operator explicitly opted in. This blocks an LLM from |
| 138 | // tricking us into reading cloud metadata or local admin panels. |
| 139 | try { |
| 140 | _assertUrlSafe(String(url || '')); |
| 141 | } catch (e) { |
| 142 | return `Refused: ${e.message}`; |
| 143 | } |
| 144 | // Try Playwright for JS-heavy pages |
| 145 | const browser = await getBrowser(); |
| 146 | if (browser) { |
| 147 | return await _fetchWithBrowser(browser, url, maxChars); |
| 148 | } |
| 149 | // Fallback: simple fetch |
| 150 | return await _fetchSimple(url, maxChars); |
| 151 | } |
| 152 | |
| 153 | async function _fetchWithBrowser(browser, url, maxChars) { |
| 154 | const page = await browser.newPage(); |
no test coverage detected