mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
* 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>
52 lines
2.1 KiB
TypeScript
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);
|
|
});
|
|
});
|