Add HyperFrames skills (from heygen-com/hyperframes, Apache-2.0)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FXVsD1DgdzZLiPuwtKzMSY
This commit is contained in:
300 files changed
+49175
No files matched your search
@@ -0,0 +1,85 @@
|
||||
// Media downloads use public HTTP(S) URLs. Validate every redirect target;
|
||||
// a provider result must meet the same host policy as a direct ingest URL.
|
||||
// Public HTTPS-to-HTTP redirects are allowed, matching direct HTTP support.
|
||||
// This is a literal-host policy, not DNS pinning: DNS resolution remains trusted.
|
||||
|
||||
import { BlockList, isIP } from "node:net";
|
||||
|
||||
const blocked = new BlockList();
|
||||
for (const [network, prefix] of [
|
||||
["0.0.0.0", 8],
|
||||
["10.0.0.0", 8],
|
||||
["100.64.0.0", 10],
|
||||
["127.0.0.0", 8],
|
||||
["169.254.0.0", 16],
|
||||
["172.16.0.0", 12],
|
||||
["192.0.0.0", 24],
|
||||
["192.0.2.0", 24],
|
||||
["192.88.99.0", 24],
|
||||
["192.168.0.0", 16],
|
||||
["198.18.0.0", 15],
|
||||
["198.51.100.0", 24],
|
||||
["203.0.113.0", 24],
|
||||
["224.0.0.0", 4],
|
||||
["240.0.0.0", 4],
|
||||
])
|
||||
blocked.addSubnet(network, prefix, "ipv4");
|
||||
for (const [network, prefix] of [
|
||||
["::", 128],
|
||||
["::1", 128],
|
||||
["fc00::", 7],
|
||||
["fe80::", 10],
|
||||
["fec0::", 10],
|
||||
["ff00::", 8],
|
||||
["2001:db8::", 32],
|
||||
])
|
||||
blocked.addSubnet(network, prefix, "ipv6");
|
||||
|
||||
export function isPublicMediaUrl(value) {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
||||
const host = url.hostname.replace(/\.$/, "");
|
||||
if (
|
||||
host === "localhost" ||
|
||||
host.endsWith(".localhost") ||
|
||||
host.endsWith(".local") ||
|
||||
host.endsWith(".internal")
|
||||
)
|
||||
return false;
|
||||
const address = host.replace(/^\[|\]$/g, "");
|
||||
const family = isIP(address);
|
||||
return family === 0 || !blocked.check(address, family === 4 ? "ipv4" : "ipv6");
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchMedia(url, { method = "GET", signal, fetchImpl = fetch } = {}) {
|
||||
let current = String(url);
|
||||
for (let hop = 0; hop <= 5; hop++) {
|
||||
if (!isPublicMediaUrl(current))
|
||||
throw new Error("Media download blocked: URL is not public HTTP(S)");
|
||||
const response = await fetchImpl(current, { method, signal, redirect: "manual" });
|
||||
if (!(response.status >= 300 && response.status < 400)) return response;
|
||||
const location = response.headers.get("location");
|
||||
if (!location) return response;
|
||||
await response.body?.cancel();
|
||||
current = new URL(location, current).href;
|
||||
}
|
||||
throw new Error("Media download exceeded redirect limit");
|
||||
}
|
||||
|
||||
// Stream and abort once the cap is crossed, so a lying or chunked body can't buffer past it.
|
||||
export async function readCappedBody(res, maxBytes, label) {
|
||||
const declared = Number(res.headers.get("content-length"));
|
||||
if (declared > maxBytes) throw new Error(`${label}: ${declared} bytes exceeds ${maxBytes} cap`);
|
||||
const chunks = [];
|
||||
let total = 0;
|
||||
for await (const chunk of res.body) {
|
||||
total += chunk.length;
|
||||
if (total > maxBytes) throw new Error(`${label}: stream exceeds ${maxBytes} cap`);
|
||||
chunks.push(chunk);
|
||||
}
|
||||
return Buffer.concat(chunks, total);
|
||||
}
|
||||
Reference in new issue
Block a user