Skip to content

auth: prove and correct NIP-98 URL binding #1

Description

@H4rmon1c

Goal

Prove whether NIP-98 URL comparison can treat meaningfully different request targets as equivalent, and correct the behavior if confirmed.

Research finding

Source inspection at commit 6b1ea9ff161f1c558659d69f91757272019f32a8 found a permissive fallback in urls_match_for_nip98 that strips trailing / characters from the complete URL string after parsed URL equality fails.

The same verifier is used by the session creation path and protected-request authentication path. The suspected mismatch includes meaningful differences inside query values.

Current evidence is source inspection, not executed validation. Native Rust regression and router-level results remain pending. The earlier synthetic-reproduction claim is not supported by an available execution record and is not counted as proof. No production exploit is claimed.

Source:

/// Loose URL compare: normalize trailing slash on path-only roots.
fn urls_match_for_nip98(authorized: &str, expected: &str) -> bool {
fn norm(s: &str) -> String {
let t = s.trim();
// Strip trailing slash except for scheme://host only handled by Url if available.
if t.len() > 1 && t.ends_with('/') {
t.trim_end_matches('/').to_string()
} else {
t.to_string()
}
}
// Prefer Url equality when both parse.
match (
NostrUrl::parse(authorized.trim()),
NostrUrl::parse(expected.trim()),
) {
(Ok(a), Ok(e)) => a == e || norm(authorized) == norm(expected),
_ => norm(authorized) == norm(expected),
}
}

Plan

  1. Add a native regression using the real NIP-98 verifier and synthetic signing material.
  2. Demonstrate the regression against the unmodified implementation.
  3. Establish the intended trailing-slash compatibility behavior.
  4. Implement the smallest sufficient correction without broader URL normalization.
  5. Add positive and negative coverage for relevant path, query, encoding, authority, and port cases.
  6. Exercise the relevant request-handling path and prove rejection occurs before a protected handler acts.
  7. Review the complete diff against current main.
  8. Run applicable repository checks and private-data scans.
  9. Rebase on current main if needed and rerun validation.
  10. Have the human contributor GPG-sign all commits and push the branch.
  11. Create a pull request describing the completed work and exact validation results.

Scope

Keep this change limited to NIP-98 URL comparison and directly relevant authentication tests.

Do not include unrelated HTTP/3 work, session redesign, replay protection, dependency upgrades, or opportunistic cleanup.

Acceptance criteria

  • The original mismatch is demonstrated by a regression.
  • The corrected implementation rejects the mismatched target.
  • Supported valid requests continue to authenticate.
  • Relevant router-level behavior is tested.
  • Applicable repository checks pass.
  • Private-data scans pass.
  • The final diff contains no unrelated work.
  • Every submitted commit is GPG-signed.

Working branch

fix/nip98-url-binding

Follow AGENTS.md and docs/COMPACTION-PIN.md. Agents do not stage, commit, push, or alter signing configuration. Use the approved build path; no laptop Cargo or BUILD_LOCAL override. No production changes are part of this work.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions