Files
openclaw/test/scripts/ts-guard-utils.test.ts
Davanum Srinivas 08ae021d1f fix(qqbot): guard image-size probe against SSRF (#63495)
* fix(qqbot): replace raw fetch in image-size probe with SSRF-guarded fetchRemoteMedia

Replace the bare fetch() in getImageSizeFromUrl() with fetchRemoteMedia()
from the plugin SDK, closing the blind SSRF via markdown image dimension
probing (GHSA-2767-2q9v-9326).

fetchRemoteMedia options: maxBytes 65536, maxRedirects 0, generic
public-network-only SSRF policy (no hostname allowlist, blocks
private/reserved/loopback/link-local/metadata IPs after DNS resolution).

Also fixes the repo-root resolution in scripts/lib/ts-guard-utils.mjs
which caused lint:tmp:no-raw-channel-fetch to miss extension files
entirely. The guard now walks up to .git instead of hardcoding two parent
traversals, and the allowlist is refreshed with all pre-existing raw
fetch callsites that became visible.

* fix(qqbot): guard image-size probe against SSRF (#63495) (thanks @dims)

---------

Co-authored-by: sliverp <870080352@qq.com>
2026-04-09 16:48:04 +08:00

52 lines
2.1 KiB
TypeScript

import { existsSync } from "node:fs";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { describe, expect, it } from "vitest";
import { resolveRepoRoot } from "../../scripts/lib/ts-guard-utils.mjs";
/**
* Regression tests for resolveRepoRoot().
*
* The original implementation went up exactly two levels from the caller's
* import.meta.url, which broke for scripts at scripts/*.mjs (one level below
* root) — it overshot to the repo's parent directory.
*/
describe("resolveRepoRoot", () => {
it("resolves correctly from a scripts/lib/*.mjs path (two levels below root)", () => {
const fakeUrl = pathToFileURL(path.resolve("scripts", "lib", "some-guard-utils.mjs")).href;
const root = resolveRepoRoot(fakeUrl);
expect(existsSync(path.join(root, ".git"))).toBe(true);
expect(existsSync(path.join(root, "package.json"))).toBe(true);
});
it("resolves correctly from a scripts/*.mjs path (one level below root)", () => {
const fakeUrl = pathToFileURL(path.resolve("scripts", "check-no-raw-channel-fetch.mjs")).href;
const root = resolveRepoRoot(fakeUrl);
expect(existsSync(path.join(root, ".git"))).toBe(true);
expect(existsSync(path.join(root, "package.json"))).toBe(true);
});
it("resolves correctly from a deeply nested extension path", () => {
const fakeUrl = pathToFileURL(
path.resolve("extensions", "qqbot", "src", "utils", "hypothetical.mjs"),
).href;
const root = resolveRepoRoot(fakeUrl);
expect(existsSync(path.join(root, ".git"))).toBe(true);
expect(existsSync(path.join(root, "package.json"))).toBe(true);
});
it("all caller depths resolve to the same root", () => {
const fromLib = resolveRepoRoot(pathToFileURL(path.resolve("scripts", "lib", "a.mjs")).href);
const fromScripts = resolveRepoRoot(pathToFileURL(path.resolve("scripts", "b.mjs")).href);
const fromExtension = resolveRepoRoot(
pathToFileURL(path.resolve("extensions", "qqbot", "c.mjs")).href,
);
expect(fromLib).toBe(fromScripts);
expect(fromScripts).toBe(fromExtension);
});
});