Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions scripts/lib/gap-candidate-ledger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -924,8 +924,6 @@ function writeExclusiveLedger(
writeAll(fd, raw);
fsyncSync(fd);
temporaryStatus = fstatSync(fd);
closeSync(fd);
fd = undefined;
beforeReplace?.();
assertLeaseActive(lease);
assertSameRegularInode(
Expand All @@ -948,6 +946,8 @@ function writeExclusiveLedger(
"Gap candidate ledger readback did not match initial bytes",
);
}
closeSync(fd);
fd = undefined;
} catch (error) {
if (errorCode(error) === "EEXIST") {
throw new GapCandidateLedgerError(
Expand Down Expand Up @@ -985,8 +985,6 @@ function replaceLedger(
writeAll(fd, nextRaw);
fsyncSync(fd);
temporaryStatus = fstatSync(fd);
closeSync(fd);
fd = undefined;

beforeReplace?.();
assertLeaseActive(lease);
Expand Down Expand Up @@ -1018,6 +1016,8 @@ function replaceLedger(
"Gap candidate ledger readback did not match replacement bytes",
);
}
closeSync(fd);
fd = undefined;
} finally {
if (fd !== undefined) closeSync(fd);
try {
Expand Down
29 changes: 21 additions & 8 deletions scripts/lib/git-principle-publication-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,21 @@ function matchingEntry(parent: string, segment: string): string | undefined {
return matches[0];
}

function canonicalChildDirectory(
parent: string,
segment: string,
label: string,
): string {
const existing = matchingEntry(parent, segment);
if (existing === undefined) {
throw new Error(`${label} is absent: ${join(parent, segment)}`);
}
if (existing !== segment) {
throw new Error(`${label} path is case-colliding: ${join(parent, existing)}`);
}
return realDirectory(join(parent, existing), label);
}

function safeSegments(path: string, label: string): string[] {
const segments = path.split("/");
if (
Expand Down Expand Up @@ -603,14 +618,11 @@ function authoritativeCorpusEntries(
repoRoot: string,
excludedRepoPaths: ReadonlySet<string>,
): Array<{ path: string; bytes: Buffer }> {
const knowledgeBaseRoot = realDirectory(
join(repoRoot, "knowledge-base"),
const knowledgeBaseRoot = canonicalChildDirectory(
repoRoot,
"knowledge-base",
"Principle publication knowledge base",
);
const knowledgeBaseEntry = matchingEntry(repoRoot, "knowledge-base");
if (knowledgeBaseEntry !== "knowledge-base") {
throw new Error("Principle publication knowledge-base path is case-colliding");
}
const entries: Array<{ path: string; bytes: Buffer }> = [];

const walk = (directory: string, relativePath: string): void => {
Expand Down Expand Up @@ -796,8 +808,9 @@ function assertRequestIdentity(
throw new Error("Git adapter attempt nonce must be a SHA-256 value");
}
assertReviewedPrinciplePlanSourceRun(
realDirectory(
join(repoRoot, "knowledge-base"),
canonicalChildDirectory(
repoRoot,
"knowledge-base",
"Publication knowledge base",
),
intent.reviewed_plan,
Expand Down
13 changes: 13 additions & 0 deletions scripts/lib/ingest/safe-fetch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,34 @@ describe("safe remote fetch boundary", () => {
"172.16.0.1",
"192.168.1.1",
"192.0.2.1",
"198.18.0.1",
"224.0.0.1",
"::1",
"::ffff:7f00:1",
"fc00::1",
"fe80::1",
"fec0::1",
"64:ff9b:1::a00:1",
"100::1",
"100:0:0:1::1",
"2001:2::1",
"2001:5::1",
"2001:10::1",
"::ffff:0:7f00:1",
"2001:db8::1",
"3fff::1",
"5f00::1",
"ff02::1",
]) {
expect(isPublicIpAddress(address), address).toBe(false);
}
expect(isPublicIpAddress("8.8.8.8")).toBe(true);
expect(isPublicIpAddress("2606:4700:4700::1111")).toBe(true);
expect(isPublicIpAddress("2001:1::1")).toBe(true);
expect(isPublicIpAddress("2001:3::1")).toBe(true);
expect(isPublicIpAddress("2001:4:112::1")).toBe(true);
expect(isPublicIpAddress("2001:20::1")).toBe(true);
expect(isPublicIpAddress("2001:30::1")).toBe(true);
});

it("rejects unsafe schemes, credentials, and local hostnames before fetch", async () => {
Expand Down
38 changes: 37 additions & 1 deletion scripts/lib/ingest/safe-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,27 @@ import {
import { parseCredentialFreeHttpUrl } from "./url-policy";

const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
const ADDITIONAL_NON_PUBLIC_IPV4_RANGES = [
ipaddr.IPv4.parseCIDR("198.18.0.0/15"),
];
const GLOBALLY_REACHABLE_IPV6_SPECIAL_RANGES = [
ipaddr.IPv6.parseCIDR("2001:1::1/128"),
ipaddr.IPv6.parseCIDR("2001:1::2/128"),
ipaddr.IPv6.parseCIDR("2001:1::3/128"),
ipaddr.IPv6.parseCIDR("2001:3::/32"),
ipaddr.IPv6.parseCIDR("2001:4:112::/48"),
ipaddr.IPv6.parseCIDR("2001:20::/28"),
ipaddr.IPv6.parseCIDR("2001:30::/28"),
];
const ADDITIONAL_NON_PUBLIC_IPV6_RANGES = [
ipaddr.IPv6.parseCIDR("fec0::/10"),
ipaddr.IPv6.parseCIDR("64:ff9b:1::/48"),
ipaddr.IPv6.parseCIDR("100::/64"),
ipaddr.IPv6.parseCIDR("100:0:0:1::/64"),
ipaddr.IPv6.parseCIDR("2001::/23"),
ipaddr.IPv6.parseCIDR("3fff::/20"),
ipaddr.IPv6.parseCIDR("5f00::/16"),
];

export interface SafeFetchDependencies {
assertSafeUrl(input: string | URL): Promise<URL>;
Expand Down Expand Up @@ -91,7 +112,22 @@ const safeDispatcher = new Agent({

export function isPublicIpAddress(address: string): boolean {
if (!ipaddr.isValid(address)) return false;
return ipaddr.process(address).range() === "unicast";
const parsed = ipaddr.process(address);
if (
parsed instanceof ipaddr.IPv6 &&
GLOBALLY_REACHABLE_IPV6_SPECIAL_RANGES.some((range) => parsed.match(range))
) {
return true;
}
if (parsed.range() !== "unicast") return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Move IPv6 exceptions before the range guard

For every address in the new global exception list, ipaddr.js 2.4.0 classifies the address as a named special range rather than unicast (for example 2001:3::1 is amt, and 2001:1::1 falls under reserved; see the locked package source at https://raw.githubusercontent.com/whitequark/ipaddr.js/v2.4.0/lib/ipaddr.js). This guard returns before GLOBALLY_REACHABLE_IPV6_SPECIAL_RANGES is checked, so isPublicIpAddress("2001:3::1") and the other new positive test cases still return false, causing safe fetches to those globally reachable exceptions to be rejected.

Useful? React with 👍 / 👎.

if (parsed instanceof ipaddr.IPv4) {
return !ADDITIONAL_NON_PUBLIC_IPV4_RANGES.some((range) =>
parsed.match(range),
);
}
return !ADDITIONAL_NON_PUBLIC_IPV6_RANGES.some((range) =>
parsed.match(range),
);
}

function isLocalHostname(hostname: string): boolean {
Expand Down
Loading