@bacnh85/pi-web 0.10.1 → 0.10.2
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/CHANGELOG.md +12 -0
- package/extensions/index.ts +3 -1
- package/extensions/lib/imageapi.ts +6 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.2 (2026-09-13)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- `.gif` downloads inline as `image/gif` (was mislabeled `image/png`).
|
|
8
|
+
- **SSRF guard on gateway-supplied image URLs**: downloads to
|
|
9
|
+
loopback/private/link-local hosts (e.g. cloud metadata 169.254.169.254)
|
|
10
|
+
are refused before any request is made — the URL is surfaced instead.
|
|
11
|
+
- **25 MB download cap**: oversized image downloads are not written to disk;
|
|
12
|
+
the URL is surfaced instead.
|
|
13
|
+
- pi-hub catalog/README description updated to match 0.10.x features.
|
|
14
|
+
|
|
3
15
|
## 0.10.1 (2026-09-13)
|
|
4
16
|
|
|
5
17
|
### Added
|
package/extensions/index.ts
CHANGED
|
@@ -87,13 +87,15 @@ const engineSchema = {
|
|
|
87
87
|
|
|
88
88
|
// Saved image file → inline image block (the 0.6.2 vision-loop lesson: the
|
|
89
89
|
// generating model should see its own output).
|
|
90
|
-
async function toImageBlock(file: string): Promise<{ type: "image"; data: string; mimeType: string }> {
|
|
90
|
+
export async function toImageBlock(file: string): Promise<{ type: "image"; data: string; mimeType: string }> {
|
|
91
91
|
const data = (await fs.promises.readFile(file)).toString("base64");
|
|
92
92
|
const lower = file.toLowerCase();
|
|
93
93
|
const mimeType = lower.endsWith(".jpg") || lower.endsWith(".jpeg")
|
|
94
94
|
? "image/jpeg"
|
|
95
95
|
: lower.endsWith(".webp")
|
|
96
96
|
? "image/webp"
|
|
97
|
+
: lower.endsWith(".gif")
|
|
98
|
+
? "image/gif"
|
|
97
99
|
: "image/png";
|
|
98
100
|
return { type: "image" as const, data, mimeType };
|
|
99
101
|
}
|
|
@@ -6,6 +6,7 @@ import fs from "node:fs";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
8
8
|
import { findEnvValue } from "./config";
|
|
9
|
+
import { isLocalUrl } from "./chrome";
|
|
9
10
|
import {
|
|
10
11
|
describeGeminiError,
|
|
11
12
|
geminiGenerateImage,
|
|
@@ -169,6 +170,9 @@ export interface ApiImageResult {
|
|
|
169
170
|
model?: string;
|
|
170
171
|
}
|
|
171
172
|
|
|
173
|
+
/** Hard cap on downloaded image size — gateways can point at arbitrary URLs. */
|
|
174
|
+
export const MAX_DOWNLOAD_BYTES = 25 * 1024 * 1024;
|
|
175
|
+
|
|
172
176
|
export class ImageApiError extends Error {
|
|
173
177
|
constructor(
|
|
174
178
|
public status: number,
|
|
@@ -231,6 +235,7 @@ export async function apiGenerateImage(opts: {
|
|
|
231
235
|
} else if (typeof item?.url === "string" && item.url) {
|
|
232
236
|
// A failed download must not waste the generation: surface the URL.
|
|
233
237
|
try {
|
|
238
|
+
if (isLocalUrl(item.url)) throw new Error("image host is private/loopback (SSRF-guarded)");
|
|
234
239
|
paths.push(await downloadImage(fetchImpl, item.url, opts.outDir, i, opts.signal, opts.timeoutMs));
|
|
235
240
|
} catch (err) {
|
|
236
241
|
urls.push(item.url);
|
|
@@ -265,6 +270,7 @@ async function downloadImage(
|
|
|
265
270
|
});
|
|
266
271
|
if (!res.ok) throw new ImageApiError(res.status, `image download failed (HTTP ${res.status})`);
|
|
267
272
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
273
|
+
if (buf.length > MAX_DOWNLOAD_BYTES) throw new Error(`image exceeds the ${MAX_DOWNLOAD_BYTES}-byte download cap`);
|
|
268
274
|
const file = path.join(outDir, `pi-web-image-${randomUUID().slice(0, 8)}-${i}${extFor(url)}`);
|
|
269
275
|
fs.writeFileSync(file, buf);
|
|
270
276
|
return file;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bacnh85/pi-web",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "Pi extension for web search, page extraction, Firecrawl scraping/crawling, Crawl4AI headless browser crawling, Gemini web-tier research, free upstream image generation, and one-off gateway chat.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|