Vouch for a funding.json manifest, when this relay is configured to

funding.json is how FLOSS/fund takes applications: a manifest in the repository
rather than a proposal, submitted to their directory by URL. Its provenance rule
is that a manifest served from a code host while claiming a hostname needs that
hostname to confirm it, which is what .well-known/funding-manifest-urls is for.

FN_FUNDING_MANIFESTS holds the URLs, whitespace or comma separated, and anything
not beginning with https:// is dropped at config time. Unset means the route
404s, for the same reason the contact addresses render as nothing when
unconfigured: a relay somebody else runs must never tell a funder that
donations for it reach us. The manifest itself carries a legal name, a contact
address and bank details, so it stays in the repository and out of this binary.

Verified against a local relay: 404 with the variable unset, 200 with it set,
and an http:// entry passed alongside an https:// one was filtered out rather
than served.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Paul Hitt
2026-10-15 16:04:23 -04:00
co-authored by Claude Opus 5
parent 41580da291
commit a3675e49c6
2 changed files with 30 additions and 0 deletions
+15
View File
@@ -22,6 +22,17 @@ struct Config: Sendable {
let reportsTo: String?
/// Who runs this relay, for the pages and security.txt that say so.
var contact: ContactAddresses = ContactAddresses(env: [:], fallback: nil)
/// The funding.json manifests this domain vouches for, from
/// `FN_FUNDING_MANIFESTS`, whitespace or comma separated.
///
/// funding.json carries an operator's legal name, contact address and bank
/// details, so it lives in the repository rather than in this binary, and a
/// manifest hosted somewhere else needs the domain it claims to say it is
/// genuine. That is what `.well-known/funding-manifest-urls` is for. Unset
/// means the route 404s, for the same reason the contact addresses above
/// render as nothing when unconfigured: a self-hosted relay must never
/// advertise somebody else's funding.
let fundingManifests: [String]
let donateProvider: String?
let donateSecret: String?
/// The Stripe connected account donations are taken on, or nil to take them
@@ -274,6 +285,10 @@ struct Config: Sendable {
smtp: SMTPConfig(env: env),
reportsTo: env["FN_REPORTS_TO"].flatMap { $0.isEmpty ? nil : $0 },
contact: ContactAddresses(env: env, fallback: env["FN_REPORTS_TO"]),
fundingManifests: (env["FN_FUNDING_MANIFESTS"] ?? "")
.split(whereSeparator: { $0 == "," || $0.isWhitespace })
.map(String.init)
.filter { $0.hasPrefix("https://") },
donateProvider: env["FN_DONATE_PROVIDER"].flatMap { $0.isEmpty ? nil : $0 },
donateSecret: env["FN_DONATE_SECRET"].flatMap { $0.isEmpty ? nil : $0 },
donateAccount: env["FN_DONATE_ACCOUNT"].flatMap { $0.isEmpty ? nil : $0 },
+15
View File
@@ -1249,6 +1249,21 @@ func register(routes r: Routes, on app: Application) throws {
app.get(".well-known", "assetlinks.json") { _ -> Response in
try AppLinks.json(AppLinks.android)
}
// The manifests this domain vouches for, one URL per line, per the
// funding.json provenance rule: a manifest served from a code host claims
// this hostname, so this hostname has to confirm it.
//
// Unset configuration means 404, like security.txt above. A relay somebody
// else runs must not tell a funder that donations for it reach us.
app.get(".well-known", "funding-manifest-urls") { _ -> Response in
let urls = r.config.fundingManifests
guard !urls.isEmpty else { throw Abort(.notFound) }
return Response(
status: .ok,
headers: ["Content-Type": "text/plain; charset=utf-8"],
body: .init(string: urls.joined(separator: "\n") + "\n"))
}
app.get(".well-known", "security.txt") { req -> Response in
// RFC 9116 requires Expires, and a stale one is worse than none: it
// tells a researcher the file is abandoned.