Skip to content

Add GeoIP region filtering and address blocklist#1337

Merged
spacebear21 merged 8 commits intopayjoin:masterfrom
DanGould:access-control-v2
Feb 20, 2026
Merged

Add GeoIP region filtering and address blocklist#1337
spacebear21 merged 8 commits intopayjoin:masterfrom
DanGould:access-control-v2

Conversation

@DanGould
Copy link
Copy Markdown
Contributor

@DanGould DanGould commented Feb 18, 2026

Summary

  • Add access-control feature to payjoin-mailroom with GeoIP-based region filtering via maxminddb
  • Auto-fetch DB-IP Lite database when no local MMDB path is configured
  • Screen V1 PSBT payloads against a configurable Bitcoin address blocklist
  • Parse blocklist addresses into script pubkeys at load time for canonical comparison, avoiding bech32 case-handling complexity
  • Address lists loadable from local file and/or periodically-refreshed remote URL
  • GeoIP middleware fails open on lookup errors

This PR was developed with AI assistance (Claude)

@coveralls
Copy link
Copy Markdown
Collaborator

coveralls commented Feb 18, 2026

Pull Request Test Coverage Report for Build 22214985999

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 269 of 459 (58.61%) changed or added relevant lines in 7 files are covered.
  • 21 unchanged lines in 3 files lost coverage.
  • Overall coverage decreased (-0.9%) to 82.444%

Changes Missing Coverage Covered Lines Changed/Added Lines %
payjoin-directory/src/main.rs 0 2 0.0%
payjoin-mailroom/src/config.rs 4 9 44.44%
payjoin-mailroom/src/middleware.rs 6 18 33.33%
payjoin-directory/src/lib.rs 134 162 82.72%
payjoin-mailroom/src/lib.rs 40 111 36.04%
payjoin-mailroom/src/access_control.rs 83 155 53.55%
Files with Coverage Reduction New Missed Lines %
payjoin-mailroom/src/config.rs 1 28.36%
payjoin-mailroom/src/lib.rs 1 62.61%
payjoin-directory/src/lib.rs 19 67.86%
Totals Coverage Status
Change from base Build 22191369989: -0.9%
Covered Lines: 10594
Relevant Lines: 12850

💛 - Coveralls

@DanGould DanGould force-pushed the access-control-v2 branch 10 times, most recently from 1c3d998 to b2cc26a Compare February 19, 2026 13:33
@DanGould DanGould requested a review from spacebear21 February 19, 2026 14:55
DanGould and others added 3 commits February 19, 2026 13:41
Add access-control feature to payjoin-mailroom providing IP-based
region filtering via axum middleware. Requests from blocked ISO
country codes are rejected at the network layer. A free DB-IP Lite
database is fetched automatically when no explicit path is configured.
Screen V1 payjoin payloads against a configurable address blocklist.
Addresses are parsed into script pubkeys at load time for canonical
comparison, avoiding encoding round-trips and bech32 case issues.
Supports loading from a local file, a remote URL with periodic
background refresh, and local cache fallback.
Since address screening is only relevant when V1 is enabled, it doesn't
make much sense to expose the blocked_* config otherwise. This replaces
the `enable_v1` bool with a new `v1` config section. The presence /
absence of that config section indicates whether to enable v1 or not,
and blocked address settings can be configured within that section if
the access-control feature is enabled.
@spacebear21 spacebear21 force-pushed the access-control-v2 branch 2 times, most recently from 3157858 to 9f366c2 Compare February 19, 2026 20:40
Since the receiver's proposal contains new outputs and inputs
contributed by the receiver, that PSBT should also be screened.
}
ScreenResult::Clean => {}
ScreenResult::ParseError(e) => {
warn!("Could not parse V1 PSBT: {e}");
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.

I'm not sure whether this should just warn and pass a bad PSBT through for the counterparty to handle, or should it respond with an error on their behalf? I guess passing through has the benefit that the other party can immediately know the PSBT is bad and abort the payjoin without waiting for expiration.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think the UX of immediate abort is the best.

Comment thread payjoin-directory/src/lib.rs Outdated
Comment on lines +441 to +443
return Err(HandlerError::Forbidden(anyhow::anyhow!(
"blocked address in V1 PSBT"
)));
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.

Is there a risk of leaking information to a malicious actor with a Forbidden response here? Maybe a 400 response with original-psbt-rejected well-known error code is appropriate?

Copy link
Copy Markdown
Contributor Author

@DanGould DanGould Feb 20, 2026

Choose a reason for hiding this comment

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

A bit, I used your error code suggestion here in the most recent commit.

Allow operators to block specific IP addresses or CIDR ranges
independently of geographic region.

Bare IPs ("192.0.2.1") and CIDR notation ("192.0.2.0/24") are
both accepted in the config.
Comment thread payjoin-mailroom/src/access_control.rs Outdated
Comment on lines +128 to +129
let url =
format!("https://download.db-ip.com/free/dbip-country-lite-{}-{}.mmdb.gz", now.0, now.1);
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.

I did a bit of research on this and found two things:

  • https://github.com/wp-statistics/GeoLite2-Country?tab=readme-ov-file provides a community-maintained CDN download link that is updated automatically without needing to specify the year/month.
  • Both db-ip and the wp-statistics alternative are licensed under CC BY-SA 4.0 which requires attribution. I'm not sure if just including a download link in the code applies, but to be thorough I think just a mention in the README would do?

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.

Applied these changes in the last two commits.

spacebear21 and others added 3 commits February 19, 2026 19:20
Use the automatically-updated CDN download link from
https://github.com/wp-statistics/GeoLite2-Country?tab=readme-ov-file to
avoid manual date shenanigans.
Use the well-known original-psbt-rejected error code instead of
403 Forbidden so V1 senders get a standard BIP78 response that
does not reveal screening details.
Copy link
Copy Markdown
Contributor Author

@DanGould DanGould left a comment

Choose a reason for hiding this comment

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

ACK d2195a0

I will follow up with the fail-fast errors but I think this is ok to merge at this point. Can't approve my own PR.

@DanGould
Copy link
Copy Markdown
Contributor Author

I thoroughly considered failing fast for v2 receiver for whom a bad, filtered v1. I think we shouldn't do it because it's more complicated for very little upside of a very small slice of v1 usage and our time is better spent elsewhere. Weee'd need a new Rejected mailbox state to serve to the v2 receiver on subsequent poll. Just letting the receiver time out is probably fine since this whole backwards-compatibility story is exactly that, it's not a first-class citizen.


let app = build_app(services);
#[cfg(feature = "access-control")]
let app = app.into_make_service_with_connect_info::<middleware::MaybePeerIp>();
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.

First time seeing this method. I guess this middleware pattern is because this check is at the TCP/stream level and not HTTP.

Copy link
Copy Markdown
Collaborator

@spacebear21 spacebear21 left a comment

Choose a reason for hiding this comment

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

ACK a761768

@spacebear21 spacebear21 merged commit c0ddf7c into payjoin:master Feb 20, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants