Skip to content

Commit 75dabd2

Browse files
authored
Merge pull request #698 from 2chanhaeng/main
Allow private addresses for explicit URLs in fedify lookup
2 parents 53bb51a + cff1c31 commit 75dabd2

3 files changed

Lines changed: 81 additions & 14 deletions

File tree

CHANGES.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,23 @@ Version 2.1.6
88

99
To be released.
1010

11+
### @fedify/cli
12+
13+
- Fixed `fedify lookup` failing to look up URLs on private or localhost
14+
addresses unless `-p`/`--allow-private-address` was passed, which was a
15+
regression introduced in Fedify 2.1.0 when the CLI began forwarding
16+
the `allowPrivateAddress` option to the underlying document loader.
17+
URLs explicitly provided on the command line now always allow private
18+
addresses, while URLs discovered during [`-t`/`--traverse`] honor the
19+
option to mitigate SSRF attacks against private addresses. Recursive
20+
fetches via [`--recurse`] continue to always disallow private
21+
addresses regardless of the option. [[#696], [#698] by Chanhaeng Lee]
22+
23+
[`-t`/`--traverse`]: https://fedify.dev/cli#t-traverse-traverse-the-collection
24+
[`--recurse`]: https://fedify.dev/cli#recurse-recurse-through-object-relationships
25+
[#696]: https://github.com/fedify-dev/fedify/issues/696
26+
[#698]: https://github.com/fedify-dev/fedify/pull/698
27+
1128

1229
Version 2.1.5
1330
-------------

docs/cli.md

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -522,8 +522,10 @@ and `quoteUri` are not accepted as short forms.
522522
> are mutually exclusive.
523523
>
524524
> Recursive fetches always disallow private/localhost addresses for safety.
525-
> `-p`/`--allow-private-address` only applies to explicit lookup/traverse
526-
> targets, not to recursive steps.
525+
> URLs explicitly provided on the command line always allow private
526+
> addresses, while
527+
> [`-p`/`--allow-private-address`](#p-allow-private-address-allow-private-ip-addresses)
528+
> has no effect on recursive steps.
527529
528530
### `--recurse-depth`: Set recursion depth limit
529531

@@ -980,18 +982,29 @@ fedify lookup --user-agent MyApp/1.0 @fedify@hollo.social
980982

981983
### `-p`/`--allow-private-address`: Allow private IP addresses
982984

983-
By default, `fedify lookup` does not fetch private or localhost addresses.
984-
The `-p`/`--allow-private-address` option allows explicit lookup/traverse
985-
requests to private addresses when needed for local development.
985+
URLs explicitly provided on the command line always allow private or
986+
localhost addresses, so local servers can be looked up without any extra
987+
flags:
986988

987989
~~~~ sh
988-
fedify lookup --allow-private-address http://localhost:8000/users/alice
990+
fedify lookup http://localhost:8000/users/alice
991+
~~~~
992+
993+
The `-p`/`--allow-private-address` option additionally allows private
994+
addresses for URLs discovered during traversal. It only has an effect
995+
when used together with
996+
[`-t`/`--traverse`](#t-traverse-traverse-the-collection), since URLs
997+
embedded in remote responses are otherwise rejected to mitigate SSRF
998+
attacks against private addresses.
999+
1000+
~~~~ sh
1001+
fedify lookup --traverse --allow-private-address http://localhost:8000/users/alice/outbox
9891002
~~~~
9901003

9911004
> [!NOTE]
9921005
> Recursive fetches enabled by
993-
> [`--recurse`](#recurse-recurse-through-object-relationships) continue to
994-
> disallow private addresses.
1006+
> [`--recurse`](#recurse-recurse-through-object-relationships) always
1007+
> disallow private addresses regardless of this option.
9951008
9961009
### `-s`/`--separator`: Output separator
9971010

packages/cli/src/lookup.ts

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,12 @@ 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+
during traversal. This option only has an effect when used together \
88+
with ${optionNames(["-t", "--traverse"])}, since URLs explicitly \
89+
provided on the command line always allow private addresses and \
90+
recursive fetches via ${optionNames(["--recurse"])} always disallow \
91+
them.`,
8892
}),
8993
{
9094
context: configContext,
@@ -716,6 +720,20 @@ export async function runLookup(
716720
}).start();
717721

718722
let server: TemporaryServer | undefined = undefined;
723+
// URLs explicitly provided by the user always allow private addresses,
724+
// so that local servers can be looked up without -p/--allow-private-address.
725+
// URLs discovered during traversal follow the option to mitigate SSRF
726+
// against private addresses, while recursive fetches always disallow
727+
// private addresses regardless of the option (see the --recurse branch
728+
// below, which hardcodes `allowPrivateAddress: false`).
729+
const initialBaseDocumentLoader = await getDocumentLoader({
730+
userAgent: command.userAgent,
731+
allowPrivateAddress: true,
732+
});
733+
const initialDocumentLoader = wrapDocumentLoaderWithTimeout(
734+
initialBaseDocumentLoader,
735+
command.timeout,
736+
);
719737
const baseDocumentLoader = await getDocumentLoader({
720738
userAgent: command.userAgent,
721739
allowPrivateAddress: command.allowPrivateAddress,
@@ -734,6 +752,7 @@ export async function runLookup(
734752
);
735753

736754
let authLoader: DocumentLoader | undefined = undefined;
755+
let initialAuthLoader: DocumentLoader | undefined = undefined;
737756
let authIdentity:
738757
| { keyId: URL; privateKey: CryptoKey }
739758
| undefined = undefined;
@@ -836,6 +855,24 @@ export async function runLookup(
836855
baseAuthLoader,
837856
command.timeout,
838857
);
858+
const initialBaseAuthLoader = getAuthenticatedDocumentLoader(
859+
authIdentity,
860+
{
861+
allowPrivateAddress: true,
862+
userAgent: command.userAgent,
863+
specDeterminer: {
864+
determineSpec() {
865+
return command.firstKnock;
866+
},
867+
rememberSpec() {
868+
},
869+
},
870+
},
871+
);
872+
initialAuthLoader = wrapDocumentLoaderWithTimeout(
873+
initialBaseAuthLoader,
874+
command.timeout,
875+
);
839876
}
840877

841878
spinner.text = `Looking up the ${
@@ -885,8 +922,8 @@ export async function runLookup(
885922
command.timeout,
886923
)
887924
: undefined;
888-
const initialLookupDocumentLoader: DocumentLoader = authLoader ??
889-
documentLoader;
925+
const initialLookupDocumentLoader: DocumentLoader = initialAuthLoader ??
926+
initialDocumentLoader;
890927
const recursiveLookupDocumentLoader: DocumentLoader = recursiveAuthLoader ??
891928
recursiveDocumentLoader;
892929
let totalObjects = 0;
@@ -1109,7 +1146,7 @@ export async function runLookup(
11091146
let collection: APObject | null = null;
11101147
try {
11111148
collection = await effectiveDeps.lookupObject(url, {
1112-
documentLoader: authLoader ?? documentLoader,
1149+
documentLoader: initialAuthLoader ?? initialDocumentLoader,
11131150
contextLoader,
11141151
userAgent: command.userAgent,
11151152
});
@@ -1248,7 +1285,7 @@ export async function runLookup(
12481285
for (const url of command.urls) {
12491286
promises.push(
12501287
effectiveDeps.lookupObject(url, {
1251-
documentLoader: authLoader ?? documentLoader,
1288+
documentLoader: initialAuthLoader ?? initialDocumentLoader,
12521289
contextLoader,
12531290
userAgent: command.userAgent,
12541291
}).catch((error) => {

0 commit comments

Comments
 (0)