Skip to content

Commit e0c7688

Browse files
committed
Tighten lookup loader safety and document public helpers
Apply follow-up fixes from review for config validation, stream output\nbehavior, and API documentation.\n\nThe document loader now defaults to deny private addresses, lookup\nconfig schema now enforces traverse/recurse mutual exclusion and\nrecurseDepth dependency, and recursive helper exports now include\nJSDoc. Image rendering now only runs when writing to stdout.\n\nhttps://github.com//pull/608#discussion_r2901510813\nhttps://github.com//pull/608#discussion_r2901510814\nhttps://github.com//pull/608#discussion_r2901510815\nhttps://github.com//pull/608#discussion_r2901510816\nhttps://github.com//pull/608#discussion_r2901510817\nhttps://github.com//pull/608#discussion_r2901515752\nhttps://github.com//pull/608#discussion_r2901515762\n\nCo-Authored-By: Codex <codex@openai.com>
1 parent 2ab28b8 commit e0c7688

3 files changed

Lines changed: 53 additions & 22 deletions

File tree

packages/cli/src/config.ts

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { parse as parseToml } from "smol-toml";
66
import {
77
array,
88
boolean,
9+
check,
10+
forward,
911
type InferOutput,
1012
integer,
1113
minValue,
@@ -28,28 +30,44 @@ const webfingerSchema = object({
2830
/**
2931
* Schema for the lookup command configuration.
3032
*/
31-
const lookupSchema = object({
32-
authorizedFetch: optional(boolean()),
33-
firstKnock: optional(
34-
picklist(["draft-cavage-http-signatures-12", "rfc9421"]),
33+
const lookupSchema = pipe(
34+
object({
35+
authorizedFetch: optional(boolean()),
36+
firstKnock: optional(
37+
picklist(["draft-cavage-http-signatures-12", "rfc9421"]),
38+
),
39+
traverse: optional(boolean()),
40+
recurse: optional(
41+
picklist([
42+
"replyTarget",
43+
"quoteUrl",
44+
"https://www.w3.org/ns/activitystreams#inReplyTo",
45+
"https://www.w3.org/ns/activitystreams#quoteUrl",
46+
"https://misskey-hub.net/ns#_misskey_quote",
47+
"http://fedibird.com/ns#quoteUri",
48+
]),
49+
),
50+
recurseDepth: optional(pipe(number(), integer(), minValue(1))),
51+
suppressErrors: optional(boolean()),
52+
defaultFormat: optional(picklist(["default", "raw", "compact", "expand"])),
53+
separator: optional(string()),
54+
timeout: optional(number()),
55+
}),
56+
forward(
57+
check(
58+
(input) => !(input.traverse === true && input.recurse != null),
59+
"lookup.traverse and lookup.recurse cannot be used together.",
60+
),
61+
["recurse"],
3562
),
36-
traverse: optional(boolean()),
37-
recurse: optional(
38-
picklist([
39-
"replyTarget",
40-
"quoteUrl",
41-
"https://www.w3.org/ns/activitystreams#inReplyTo",
42-
"https://www.w3.org/ns/activitystreams#quoteUrl",
43-
"https://misskey-hub.net/ns#_misskey_quote",
44-
"http://fedibird.com/ns#quoteUri",
45-
]),
63+
forward(
64+
check(
65+
(input) => input.recurse != null || input.recurseDepth == null,
66+
"lookup.recurseDepth requires lookup.recurse.",
67+
),
68+
["recurseDepth"],
4669
),
47-
recurseDepth: optional(pipe(number(), integer(), minValue(1))),
48-
suppressErrors: optional(boolean()),
49-
defaultFormat: optional(picklist(["default", "raw", "compact", "expand"])),
50-
separator: optional(string()),
51-
timeout: optional(number()),
52-
});
70+
);
5371

5472
/**
5573
* Schema for the inbox command configuration.

packages/cli/src/docloader.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ export interface DocumentLoaderOptions {
1212
allowPrivateAddress?: boolean;
1313
}
1414

15+
/**
16+
* Returns a cache prefix that separates document-loader entries by user agent
17+
* and private-address policy.
18+
*/
1519
export function getDocumentLoaderCachePrefix(
1620
userAgent: string | undefined,
1721
allowPrivateAddress: boolean,
@@ -26,7 +30,7 @@ export function getDocumentLoaderCachePrefix(
2630
}
2731

2832
export async function getDocumentLoader(
29-
{ userAgent, allowPrivateAddress = true }: DocumentLoaderOptions = {},
33+
{ userAgent, allowPrivateAddress = false }: DocumentLoaderOptions = {},
3034
): Promise<DocumentLoader> {
3135
const cacheKey = `${userAgent ?? ""}:${allowPrivateAddress}`;
3236
if (documentLoaders[cacheKey]) return documentLoaders[cacheKey];

packages/cli/src/lookup.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,9 @@ export class TimeoutError extends Error {
290290
}
291291
}
292292

293+
/**
294+
* Error thrown when a recursive lookup target cannot be fetched.
295+
*/
293296
export class RecursiveLookupError extends Error {
294297
target: string;
295298
constructor(target: string) {
@@ -405,7 +408,7 @@ export async function writeObjectToStream(
405408
if (object instanceof APObject) {
406409
imageUrls = await findAllImages(object);
407410
}
408-
if (!outputPath && imageUrls.length > 0) {
411+
if (localStream === process.stdout && imageUrls.length > 0) {
409412
await renderImages(imageUrls);
410413
}
411414
}
@@ -475,6 +478,9 @@ function handleTimeoutError(
475478
);
476479
}
477480

481+
/**
482+
* Gets the next recursion target URL from an ActivityPub object.
483+
*/
478484
export function getRecursiveTargetId(
479485
object: APObject,
480486
recurseProperty: RecurseProperty,
@@ -488,6 +494,9 @@ export function getRecursiveTargetId(
488494
return quoteUrl instanceof URL ? quoteUrl : null;
489495
}
490496

497+
/**
498+
* Collects recursively linked objects up to a depth limit.
499+
*/
491500
export async function collectRecursiveObjects(
492501
initialObject: APObject,
493502
recurseProperty: RecurseProperty,

0 commit comments

Comments
 (0)