Skip to content

fix(node): close three host-class gaps in the public-URL gate - #346

Open
beardthelion wants to merge 1 commit into
mainfrom
fix/host-gate-bare-labels
Open

fix(node): close three host-class gaps in the public-URL gate#346
beardthelion wants to merge 1 commit into
mainfrom
fix/host-gate-bare-labels

Conversation

@beardthelion

@beardthelion beardthelion commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Closes #339.

is_public_http_url is the shared host-class gate for every operator-supplied outbound URL: peer announce, webhook creation, and the boot-time peer prune. Three host forms got past it that the checks around them plainly meant to exclude.

Dotless names. .local and .internal are suffix tests, so a single label matches neither, and http://internal/, http://wpad/, http://metadata/ were accepted and left to the resolver to complete from its search domain. POST /api/v1/peers/announce is unauthenticated and a peer row's http_url drives outbound sync-notify fan-out, so the caller choosing that name need not be anyone in particular.

The new rule sits on the branch where the host did not parse as an IP literal. That placement is the whole trick: a bracketed IPv6 literal is also dotless, and the existing 6to4 and NAT64 accept cases pin that it stays accepted.

fec0::/10. The IPv6 arm missed it because the link-local mask does not cover it: 0xfec0 & 0xffc0 is 0xfec0, not 0xfe80. RFC 3879 deprecated the range in 2004, which argues for rejecting it rather than ignoring it.

Repeated root dots. Trailing dots were stripped once, so localhost.. reduced to localhost. and matched neither the equality nor the suffix check. Stripping to fixation closes it. I could not resolve that name locally, so it is a predicate gap rather than a demonstrated reachable target.

Verification

Each of the three is covered by a test that goes red when its own production line is reverted, checked one at a time rather than as a batch:

reverted test that reddens
the dotless branch rejects_dotless_single_label_hosts
the fec0::/10 clause rejects_deprecated_site_local_v6
strip-to-fixation rejects_repeated_trailing_root_dots

The dotted forms stay in the tables as negative controls so the suffix checks remain proven, and the IPv6 accept cases guard against the dotless rule over-rejecting.

cargo test -p gitlawb-node --bin gitlawb-node --locked is 826 passed / 0 failed, with fmt and clippy --workspace --all-targets -- -D warnings clean.

Scope

Predicate only. The larger gap, that nothing validates the address a hostname actually resolves to, is #340 and needs a resolver policy on both outbound clients rather than a change here.

This touches the same span of peers.rs as #333, so whichever lands second wants a rebase. The conflict is adjacent lines, not competing logic: #333 adds .localhost to the suffix list, this adds a dotless branch further down. The node.localhost.. case is deliberately not asserted here, since .localhost is not a rejected suffix until #333 lands.

Summary by CodeRabbit

  • Bug Fixes
    • Improved URL validation by handling hostnames with repeated trailing dots.
    • Rejected deprecated site-local IPv6 addresses.
    • Rejected single-label hostnames that are not valid IP addresses.
    • Added coverage for these validation scenarios.

is_public_http_url is the shared gate for every operator-supplied outbound
URL: peer announce, webhook creation, and the boot-time peer prune. Three
host forms got past it that the checks around them plainly meant to exclude.

A name with no dot never matched the `.local` / `.internal` suffix rules, so
`http://internal/` and `http://wpad/` were accepted and left to the resolver
to complete from its search domain. The dotless rule sits on the branch where
the host did not parse as an IP literal, which keeps bracketed IPv6 (also
dotless) on the accepting path; the existing 6to4 and NAT64 accept cases pin
that.

fec0::/10 fell through the IPv6 arm because the link-local mask does not
cover it: 0xfec0 & 0xffc0 is 0xfec0, not 0xfe80. RFC 3879 deprecated the
range, which argues for rejecting it rather than ignoring it.

Trailing root dots were stripped once, so `localhost..` reduced to
`localhost.` and matched neither the equality nor the suffix check. Stripping
to fixation closes that.

Each of the three is covered by a test that reddens when its own production
line is reverted, and the dotted forms stay as negative controls so the
suffix checks remain proven.
@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f808efc5-7455-43b3-867a-059712ea26bd

📥 Commits

Reviewing files that changed from the base of the PR and between 50d3cbb and ceadf8b.

📒 Files selected for processing (1)
  • crates/gitlawb-node/src/api/peers.rs

📝 Walkthrough

Walkthrough

is_public_http_url now removes repeated trailing hostname dots, rejects deprecated site-local IPv6 addresses, and rejects dotless non-IP hostnames. Tests cover all three validation rules.

Changes

Public HTTP URL validation

Layer / File(s) Summary
Validation rules and test coverage
crates/gitlawb-node/src/api/peers.rs
is_public_http_url now normalizes repeated trailing dots, rejects fec0::/10 IPv6 addresses, and rejects dotless hostnames. Tests cover these cases.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to ceadf

The PR tightens public-URL host validation for dotless names, deprecated IPv6 site-local addresses, and repeated trailing dots; no actionable merge-blocking risk remains after normal checks and review.

Possibly related issues

Suggested reviewers: miketomlin19

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the node fix and the three host-class gaps in the public-URL gate.
Description check ✅ Passed The description explains the motivation, concrete behavior changes, isolated tests, verification results, scope, and related issues.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/host-gate-bare-labels

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

@beardthelion beardthelion added crate:node gitlawb-node — the serving node and REST API kind:bug Defect fix — wrong or unsafe behavior subsystem:peers Peer announce, discovery, and registry labels Aug 15, 2026
@beardthelion
beardthelion requested a review from jatmn August 15, 2026 14:01

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

crate:node gitlawb-node — the serving node and REST API kind:bug Defect fix — wrong or unsafe behavior subsystem:peers Peer announce, discovery, and registry

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Host gate accepts bare single-label names, fec0::/10, and a double trailing dot

2 participants