Skip to content

Commit c31d2f2

Browse files
committed
Allow private addresses for explicit URLs in fedify lookup
Since v2.1, `fedify lookup` rejected localhost URLs unless `-p`/`--allow-private-address` was passed, because the CLI began forwarding `allowPrivateAddress=false` to the vocab-runtime document loader, whose `validatePublicUrl` check blocks loopback addresses. Split the document/auth loaders into two: - An "initial" loader that always allows private addresses, used for URLs explicitly provided on the command line (plain lookup, the first fetch in `--traverse`, and the first fetch in `--recurse`). - The existing loader, which continues to honor `--allow-private-address`, used for URLs discovered from remote responses (traversal pages and recursion targets). This preserves SSRF protection against `http://localhost/...` URLs embedded in remote `first`/`next`/`inReplyTo` fields while letting users look up local servers without extra flags. Assisted-by: Claude Code:claude-opus-4-7
1 parent 53bb51a commit c31d2f2

1 file changed

Lines changed: 38 additions & 6 deletions

File tree

packages/cli/src/lookup.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,9 @@ const suppressErrorsOption = bindConfig(
8383

8484
const allowPrivateAddressOption = bindConfig(
8585
flag("-p", "--allow-private-address", {
86-
description:
87-
message`Allow private IP addresses for explicit lookup/traverse requests.`,
86+
description: message`Allow private IP addresses for URLs discovered \
87+
via traversal or recursion. URLs explicitly provided \
88+
on the command line always allow private addresses.`,
8889
}),
8990
{
9091
context: configContext,
@@ -716,6 +717,18 @@ export async function runLookup(
716717
}).start();
717718

718719
let server: TemporaryServer | undefined = undefined;
720+
// URLs explicitly provided by the user always allow private addresses,
721+
// so that local servers can be looked up without -p/--allow-private-address.
722+
// URLs discovered via traversal or recursion follow the option, since they
723+
// originate from remote responses and must be protected against SSRF.
724+
const initialBaseDocumentLoader = await getDocumentLoader({
725+
userAgent: command.userAgent,
726+
allowPrivateAddress: true,
727+
});
728+
const initialDocumentLoader = wrapDocumentLoaderWithTimeout(
729+
initialBaseDocumentLoader,
730+
command.timeout,
731+
);
719732
const baseDocumentLoader = await getDocumentLoader({
720733
userAgent: command.userAgent,
721734
allowPrivateAddress: command.allowPrivateAddress,
@@ -734,6 +747,7 @@ export async function runLookup(
734747
);
735748

736749
let authLoader: DocumentLoader | undefined = undefined;
750+
let initialAuthLoader: DocumentLoader | undefined = undefined;
737751
let authIdentity:
738752
| { keyId: URL; privateKey: CryptoKey }
739753
| undefined = undefined;
@@ -836,6 +850,24 @@ export async function runLookup(
836850
baseAuthLoader,
837851
command.timeout,
838852
);
853+
const initialBaseAuthLoader = getAuthenticatedDocumentLoader(
854+
authIdentity,
855+
{
856+
allowPrivateAddress: true,
857+
userAgent: command.userAgent,
858+
specDeterminer: {
859+
determineSpec() {
860+
return command.firstKnock;
861+
},
862+
rememberSpec() {
863+
},
864+
},
865+
},
866+
);
867+
initialAuthLoader = wrapDocumentLoaderWithTimeout(
868+
initialBaseAuthLoader,
869+
command.timeout,
870+
);
839871
}
840872

841873
spinner.text = `Looking up the ${
@@ -885,8 +917,8 @@ export async function runLookup(
885917
command.timeout,
886918
)
887919
: undefined;
888-
const initialLookupDocumentLoader: DocumentLoader = authLoader ??
889-
documentLoader;
920+
const initialLookupDocumentLoader: DocumentLoader = initialAuthLoader ??
921+
initialDocumentLoader;
890922
const recursiveLookupDocumentLoader: DocumentLoader = recursiveAuthLoader ??
891923
recursiveDocumentLoader;
892924
let totalObjects = 0;
@@ -1109,7 +1141,7 @@ export async function runLookup(
11091141
let collection: APObject | null = null;
11101142
try {
11111143
collection = await effectiveDeps.lookupObject(url, {
1112-
documentLoader: authLoader ?? documentLoader,
1144+
documentLoader: initialAuthLoader ?? initialDocumentLoader,
11131145
contextLoader,
11141146
userAgent: command.userAgent,
11151147
});
@@ -1248,7 +1280,7 @@ export async function runLookup(
12481280
for (const url of command.urls) {
12491281
promises.push(
12501282
effectiveDeps.lookupObject(url, {
1251-
documentLoader: authLoader ?? documentLoader,
1283+
documentLoader: initialAuthLoader ?? initialDocumentLoader,
12521284
contextLoader,
12531285
userAgent: command.userAgent,
12541286
}).catch((error) => {

0 commit comments

Comments
 (0)