Skip to content

Allow private addresses for explicit URLs in fedify lookup#698

Merged
dahlia merged 4 commits intofedify-dev:2.1-maintenancefrom
2chanhaeng:main
Apr 19, 2026
Merged

Allow private addresses for explicit URLs in fedify lookup#698
dahlia merged 4 commits intofedify-dev:2.1-maintenancefrom
2chanhaeng:main

Conversation

@2chanhaeng
Copy link
Copy Markdown
Contributor

@2chanhaeng 2chanhaeng commented Apr 19, 2026

Closes #696.

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

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
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 19, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3256d290-827d-4ad9-91cc-c58111f6c132

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The lookup command now distinguishes between user-provided URLs and discovered URLs during traversal. User-provided URLs always bypass private-address restrictions using dedicated initial loaders, while discovered URLs during recursion/traversal respect the --allow-private-address flag. The help text was updated to reflect this clarification.

Changes

Cohort / File(s) Summary
Lookup Loader Selection Logic
packages/cli/src/lookup.ts
Introduced separate "initial" document loaders configured with allowPrivateAddress: true for first-fetch operations on user-provided URLs. Modified control flow to use initial loaders for explicitly provided URLs while regular loaders govern traversal/recursion of discovered URLs. Updated --allow-private-address help text to clarify scope. Added authentication-specific initial loader support when authorized-fetch is enabled.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: allowing private addresses for explicit URLs in fedify lookup, which directly addresses the core issue in the PR.
Description check ✅ Passed The description clearly explains the problem (localhost URLs rejected since v2.1), the solution (split loaders), and the rationale (preserve SSRF protection while enabling local lookup).
Linked Issues check ✅ Passed The PR addresses all requirements from issue #696: enables fedify lookup to work locally without the -a flag by allowing private addresses for explicitly provided command-line URLs.
Out of Scope Changes check ✅ Passed All changes in lookup.ts are directly scoped to fixing issue #696: updating CLI help text and implementing the dual-loader mechanism for explicit vs. discovered URLs.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@issues-auto-labeler issues-auto-labeler bot added component/cli CLI tools related component/federation Federation object related component/testing Testing utilities (@fedify/testing) labels Apr 19, 2026
@2chanhaeng
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 19, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@2chanhaeng 2chanhaeng requested review from Copilot and dahlia and removed request for Copilot April 19, 2026 11:56
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request updates the lookup command to allow private IP addresses for URLs explicitly provided by the user, while ensuring that URLs discovered via traversal or recursion still respect the --allow-private-address flag. It introduces dedicated initial loaders for the primary lookup phase. Feedback indicates a potential inconsistency where the recursion logic may still hardcode private address restrictions, which would conflict with the updated documentation for the CLI option.

Comment thread packages/cli/src/lookup.ts Outdated
Comment on lines +86 to +88
description: message`Allow private IP addresses for URLs discovered \
via traversal or recursion. URLs explicitly provided \
on the command line always allow private addresses.`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The updated description for the --allow-private-address option states that it applies to URLs discovered via both traversal and recursion. However, while the traversal logic (lines 1185-1186) correctly follows this option, the recursion logic (lines 884-919 in the full file) still hardcodes allowPrivateAddress: false for its document and auth loaders. This creates a precedence issue where the hardcoded value overrides the CLI configuration. To ensure consistency with the description and the PR's stated goal, the recurse block should be updated to use loaders that honor the command.allowPrivateAddress option instead of hardcoding false.

References
  1. When handling CLI options that bind to configuration, ensure the implementation correctly accounts for the precedence between CLI arguments and hardcoded defaults to avoid the CLI flag being ignored.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adjusts the fedify lookup CLI to allow loopback/private addresses for explicit URLs passed on the command line (restoring pre-v2.1 ergonomics) while keeping SSRF protections for URLs discovered during traversal/recursion.

Changes:

  • Introduces “initial” document/auth loaders that always set allowPrivateAddress: true for user-supplied URLs.
  • Keeps the existing loaders (honoring --allow-private-address) for URLs discovered through traversal/recursion.
  • Updates the --allow-private-address flag description to reflect the new behavior.

Comment thread packages/cli/src/lookup.ts Outdated
Comment on lines +87 to +88
via traversal or recursion. URLs explicitly provided \
on the command line always allow private addresses.`,
Comment thread packages/cli/src/lookup.ts Outdated
Comment on lines +722 to +723
// URLs discovered via traversal or recursion follow the option, since they
// originate from remote responses and must be protected against SSRF.
Comment on lines +724 to +731
const initialBaseDocumentLoader = await getDocumentLoader({
userAgent: command.userAgent,
allowPrivateAddress: true,
});
const initialDocumentLoader = wrapDocumentLoaderWithTimeout(
initialBaseDocumentLoader,
command.timeout,
);
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/cli/src/lookup.ts (1)

720-870: 🧹 Nitpick | 🔵 Trivial

Consider extracting helpers to avoid duplicating loader construction.

The "initial" loaders now duplicate the full option bags used by the existing loaders, differing only in allowPrivateAddress. baseAuthLoader (lines 835–848) and initialBaseAuthLoader (lines 853–866) in particular repeat the entire specDeterminer object verbatim, which is easy to get out of sync on a future change (e.g., if firstKnock/userAgent handling changes, you'd have to update two places).

A small helper keyed on allowPrivateAddress would localize the policy choice and make the intent clearer at the call sites:

♻️ Proposed refactor
+  const makeDocumentLoader = async (allowPrivateAddress: boolean) =>
+    wrapDocumentLoaderWithTimeout(
+      await getDocumentLoader({
+        userAgent: command.userAgent,
+        allowPrivateAddress,
+      }),
+      command.timeout,
+    );
+
-  const initialBaseDocumentLoader = await getDocumentLoader({
-    userAgent: command.userAgent,
-    allowPrivateAddress: true,
-  });
-  const initialDocumentLoader = wrapDocumentLoaderWithTimeout(
-    initialBaseDocumentLoader,
-    command.timeout,
-  );
-  const baseDocumentLoader = await getDocumentLoader({
-    userAgent: command.userAgent,
-    allowPrivateAddress: command.allowPrivateAddress,
-  });
-  const documentLoader = wrapDocumentLoaderWithTimeout(
-    baseDocumentLoader,
-    command.timeout,
-  );
+  const initialDocumentLoader = await makeDocumentLoader(true);
+  const documentLoader = await makeDocumentLoader(command.allowPrivateAddress);

And similarly for the authenticated loader:

+  const makeAuthLoader = (allowPrivateAddress: boolean) =>
+    wrapDocumentLoaderWithTimeout(
+      getAuthenticatedDocumentLoader(authIdentity!, {
+        allowPrivateAddress,
+        userAgent: command.userAgent,
+        specDeterminer: {
+          determineSpec: () => command.firstKnock,
+          rememberSpec() {},
+        },
+      }),
+      command.timeout,
+    );
+  authLoader = makeAuthLoader(command.allowPrivateAddress);
+  initialAuthLoader = makeAuthLoader(true);

The same helper can be reused to build recursiveAuthLoader (currently at lines 900–919) with false.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/lookup.ts` around lines 720 - 870, The loader construction
is duplicated between "initial" and regular loaders (e.g.,
initialBaseDocumentLoader/baseDocumentLoader,
initialBaseAuthLoader/baseAuthLoader, initialAuthLoader/authLoader) differing
only by allowPrivateAddress and causing repeated specDeterminer and option bags;
refactor by extracting small factory helpers (e.g.,
makeDocumentLoader(optionsAllowPrivate: boolean) and
makeAuthenticatedDocumentLoader(allowPrivate: boolean)) that call
getDocumentLoader/getContextLoader/getAuthenticatedDocumentLoader with the
common option fields (userAgent, timeout handling, specDeterminer that returns
command.firstKnock, etc.), then replace direct calls to
initialBaseDocumentLoader/baseDocumentLoader and
baseAuthLoader/initialBaseAuthLoader with calls to these helpers (and wrap with
wrapDocumentLoaderWithTimeout where needed) so the only difference at call sites
is the boolean allowPrivateAddress.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cli/src/lookup.ts`:
- Around line 86-88: Update the help text for the -p/--allow-private-address
flag (the description message string) to explicitly state that the flag only
affects URLs discovered via --traverse or --recurse and is effectively a no-op
for plain lookup, which uses initialAuthLoader ?? initialDocumentLoader for all
fetches; reference -p/--allow-private-address, the description message template,
and the plain lookup code path using initialAuthLoader/initialDocumentLoader so
users know the flag only applies to traversal/recursion.

---

Outside diff comments:
In `@packages/cli/src/lookup.ts`:
- Around line 720-870: The loader construction is duplicated between "initial"
and regular loaders (e.g., initialBaseDocumentLoader/baseDocumentLoader,
initialBaseAuthLoader/baseAuthLoader, initialAuthLoader/authLoader) differing
only by allowPrivateAddress and causing repeated specDeterminer and option bags;
refactor by extracting small factory helpers (e.g.,
makeDocumentLoader(optionsAllowPrivate: boolean) and
makeAuthenticatedDocumentLoader(allowPrivate: boolean)) that call
getDocumentLoader/getContextLoader/getAuthenticatedDocumentLoader with the
common option fields (userAgent, timeout handling, specDeterminer that returns
command.firstKnock, etc.), then replace direct calls to
initialBaseDocumentLoader/baseDocumentLoader and
baseAuthLoader/initialBaseAuthLoader with calls to these helpers (and wrap with
wrapDocumentLoaderWithTimeout where needed) so the only difference at call sites
is the boolean allowPrivateAddress.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4f9db069-6a31-47de-ba22-0d21beca161f

📥 Commits

Reviewing files that changed from the base of the PR and between 53bb51a and c31d2f2.

📒 Files selected for processing (1)
  • packages/cli/src/lookup.ts

Comment thread packages/cli/src/lookup.ts Outdated
Comment on lines +86 to +88
description: message`Allow private IP addresses for URLs discovered \
via traversal or recursion. URLs explicitly provided \
on the command line always allow private addresses.`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Help text is accurate, but note -p is effectively a no-op in plain lookup mode.

With the new split, the flag only affects URLs reached via --traverse (next/prev pages) and --recurse (relationship targets). In plain lookup (the non-traverse, non-recurse path at line 1283), every fetch now goes through initialAuthLoader ?? initialDocumentLoader, so -p/--allow-private-address has no observable effect there.

The current wording is technically correct, but a user running fedify lookup <url> -p in plain mode won't get any different behavior than without -p. Consider whether that's acceptable or whether the help text/docs should call it out explicitly (e.g., "only meaningful with --traverse or --recurse").

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/lookup.ts` around lines 86 - 88, Update the help text for
the -p/--allow-private-address flag (the description message string) to
explicitly state that the flag only affects URLs discovered via --traverse or
--recurse and is effectively a no-op for plain lookup, which uses
initialAuthLoader ?? initialDocumentLoader for all fetches; reference
-p/--allow-private-address, the description message template, and the plain
lookup code path using initialAuthLoader/initialDocumentLoader so users know the
flag only applies to traversal/recursion.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 19, 2026

Codecov Report

❌ Patch coverage is 50.00000% with 16 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
packages/cli/src/lookup.ts 50.00% 16 Missing ⚠️
Files with missing lines Coverage Δ
packages/cli/src/lookup.ts 67.79% <50.00%> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The option's help text now notes that it only has an effect when
combined with -t/--traverse or --recurse, since URLs explicitly
provided on the command line always allow private addresses.

fedify-dev#696
fedify-dev#698

Assisted-by: Claude Code:claude-opus-4-7
Update CHANGES.md with the 2.1.6 entry for the regression fix, and
rewrite the -p/--allow-private-address section in docs/cli.md to
reflect that URLs given on the command line always allow private
addresses while the option gates URLs discovered via --traverse or
--recurse.

fedify-dev#696
fedify-dev#698

Assisted-by: Claude Code:claude-opus-4-7
The previous help text, inline comment, changelog entry, and CLI
docs all stated that -p/--allow-private-address affects URLs
discovered via --recurse, but recursive fetches hardcode
`allowPrivateAddress: false` and never honor the option.  Update
the CLI help, the loader-split comment in `runLookup`, the 2.1.6
changelog entry, and the `-p` section of `docs/cli.md` so that they
all describe the same actual behavior: the option only gates URLs
discovered during --traverse, while --recurse always disallows
private addresses.

fedify-dev#696
fedify-dev#698

Assisted-by: Claude Code:claude-opus-4-7
@dahlia dahlia self-assigned this Apr 19, 2026
@dahlia dahlia merged commit 75dabd2 into fedify-dev:2.1-maintenance Apr 19, 2026
17 checks passed
@dahlia dahlia linked an issue Apr 19, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/cli CLI tools related component/federation Federation object related component/testing Testing utilities (@fedify/testing)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fedfiy lookup is not working without -a option in 2.1.x

3 participants