Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
bafdff9
feat(db): add NIP-FI identity + final-admission schema foundation
wpfleger96 Aug 28, 2026
738655d
fix(buzz-db): drop stale relationship_revision_floor from invalidatio…
Aug 28, 2026
315740d
fix(buzz-db): narrow identity_bindings policy FK and add provenance r…
Aug 28, 2026
a83d29e
test(buzz-db): add absent-policy FK rejection to provenance regression
Aug 28, 2026
a862dd4
chore(buzz-db): renumber NIP-FI migrations 0040/0041 → 0041/0042
Aug 28, 2026
152c507
fix(buzz-db): add NIP-FI Carl r2 guards — policy monotonicity, admiss…
Aug 29, 2026
5ba7d26
fix(schema): address Thufir pass 1 blockers on NIP-FI PR 2
Aug 29, 2026
fd2a79b
fix(schema): address Thufir pass 2 blockers on NIP-FI PR 2
Aug 29, 2026
fc309e6
test(buzz-db): replace barrier race with controlled lock-wait-observa…
Aug 29, 2026
3c2c919
fix(schema): correct NIP-FI authorization shape/cardinality contracts…
Aug 31, 2026
156ced4
fix(schema): enforce zero mapped-success events for denied lifecycle …
Aug 31, 2026
bd851f8
test(buzz-db): isolate event-side trigger and add applied lifecycle f…
Aug 31, 2026
5d7cf01
feat(auth): NIP-FI Phase A PR 3 — production assertion runtime
Aug 31, 2026
e78bafa
fix(auth): harden JWKS boundary, reject current-status, fix generatio…
Aug 31, 2026
e5736b8
fix(buzz-auth): close SSRF/redirect bypass in HttpJwksFetcher; trim i…
Aug 31, 2026
30e68bd
fix(buzz-auth): validate URI + full deadline + IPv6 safe path in fetc…
Aug 31, 2026
122ac43
fix(buzz-auth): complete SSRF policy, cancellation-safe permit, and i…
Aug 31, 2026
a82d476
feat(buzz-relay): NIP-FI Phase A PR 4 — PostgreSQL final authority (D…
Aug 31, 2026
be004c2
Merge remote-tracking branch 'origin/main' into hayt/nip-fi-pg-author…
Aug 31, 2026
120be31
fix(buzz-relay): close authority comparison gaps and strengthen compi…
Aug 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ pub const ALL_KINDS: &[u32] // 80 entries (KIND_AUTH excluded — never stored)
|----------|---------|
| `filters_match(filters, event)` | OR across filters, AND within each filter. Includes NIP-01 prefix matching on event IDs. |
| `verify_event(event)` | Schnorr signature + SHA-256 ID check. CPU-bound — callers use `spawn_blocking`. |
| `is_private_ip(ip)` | SSRF protection: IPv4 unspecified/loopback/private/link-local/CGNAT/benchmarking/broadcast + IPv6 loopback/ULA/link-local/multicast/documentation + IPv4-mapped IPv6. |
| `is_not_global_unicast(ip)` | SSRF protection: starts from IANA deny/exception table — denies ranges whose registry entry is non-global or blank, carves out explicit global exceptions inside denied envelopes, and evaluates embedded IPv4 recursively. Registries last updated 2025-10-09. Compat alias: `is_private_ip`. |

**Does NOT:** store events, make network calls, spawn tasks, or depend on any async runtime.

Expand Down Expand Up @@ -746,12 +746,10 @@ Every security-sensitive operation uses an explicit, verified pattern. No implic

### SSRF Protection

`is_private_ip()` in `buzz-core` covers:
- IPv4: unspecified (0.0.0.0/8), loopback (127.0.0.0/8), private (10/8, 172.16/12, 192.168/16), link-local (169.254/16), CGNAT (100.64/10), benchmarking (198.18/15), broadcast (255.255.255.255)
- IPv6: loopback (::1), ULA (fc00::/7), link-local (fe80::/10), multicast (ff00::/8), documentation (2001:db8::/32)
- IPv4-mapped IPv6 (::ffff:0:0/96) — recursively checks the embedded IPv4 address
`is_not_global_unicast(ip)` (compat alias `is_private_ip`) in `buzz-core` starts from the IANA deny/exception table: denies ranges whose IPv4 or IPv6 Special-Purpose Address Space registry entry is non-global or blank (registries last updated 2025-10-09), carves out explicit globally-reachable exceptions inside otherwise-denied envelopes (e.g., PCP/TURN/DNS-SD anycast inside 2001::/23), and evaluates IPv4 embedded in IPv4-mapped, IPv4-compatible, and NAT64 well-known (64:ff9b::/96) space recursively against the IPv4 table. SIIT IPv4-translated (::ffff:0:0:0/96) follows the same recursive path. The local-use NAT64 prefix (64:ff9b:1::/48) is blocked wholesale. Conservative posture: `None`/blank entries are treated as non-global.

Applied in: `buzz-workflow` (CallWebhook action), `buzz-core` (shared utility).
Applied in: `buzz-auth` (JWKS boundary), `buzz-workflow` (CallWebhook action),
desktop `link_preview` (SSRF check).

### Audit Integrity

Expand Down
47 changes: 47 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ members = [
"crates/buzz-relay",
"crates/buzz-core",
"crates/buzz-conformance",
"crates/buzz-nip-fi-seal-test",
"crates/buzz-push-gateway",
"crates/buzz-db",
"crates/buzz-pubsub",
Expand Down Expand Up @@ -104,7 +105,7 @@ chrono = { version = "0.4", features = ["serde"] }
jsonwebtoken = { version = "10.4.0", default-features = false, features = ["aws_lc_rs"] }

# HTTP client (webhook delivery)
reqwest = { version = "0.13", features = ["json", "rustls"], default-features = false }
reqwest = { version = "0.13", features = ["json", "rustls", "stream"], default-features = false }

# Cryptography
sha2 = "0.11"
Expand Down
3 changes: 3 additions & 0 deletions crates/buzz-auth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ dev = []
[dev-dependencies]
# `use_pem` enables EncodingKey::from_ec_pem for minting ES256 test assertions.
jsonwebtoken = { version = "10.4.0", default-features = false, features = ["aws_lc_rs", "use_pem"] }
tokio = { workspace = true, features = ["test-util"] }

[dependencies]
buzz-core = { workspace = true }
base64 = { workspace = true }
chrono = { workspace = true }
jsonwebtoken = { workspace = true }
nostr = { workspace = true }
futures-util = { workspace = true }
reqwest = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true }
Expand Down
15 changes: 9 additions & 6 deletions crates/buzz-auth/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,15 @@ pub use rate_limit::{
pub use scope::{parse_scopes, Scope};

pub use nip_fi::{
AssertionKeySet, AssertionPolicyId, CanonicalCapabilities, ClientSubjectPosture,
ConfidentialAssertion, DenialClass, FederatedAssertionVerifier, FederatedIdentity,
FreshnessClass, IssuerKeySource, IssuerPolicy, IssuerPolicyError, IssuerRegistry,
RevalidationDependencies, SubjectClass, SubjectClassContract, TokenClass, TransportContractId,
VerifiedAssertion, VerifierError, CLIENT_ATTACHED_HEADER, NOSTR_PUBKEY_CLAIM,
OAUTH_CLIENT_ID_CLAIM,
validate_nip_fi_config, AdmissionError, AssertionKeySet, AssertionPolicyId, BindingProposal,
BindingProvenance, CanonicalCapabilities, ClientSubjectPosture, ConfidentialAssertion,
DenialClass, FederatedAssertionVerifier, FederatedIdentity, FederatedIdentityDiscovery,
FreshnessClass, HttpJwksFetcher, IssuerJwksConfig, IssuerKeySource, IssuerPolicy,
IssuerPolicyError, IssuerRegistry, JwksFetchError, JwksFetcher, NipFiMode, NipFiStartupError,
OperationIntent, PreparedDependencyVersions, ProductionJwksSource, ProofTransport,
ProtectedObjectKind, RevalidationDependencies, RouteCapability, SubjectClass,
SubjectClassContract, TokenClass, TransportContractId, VerifiedAssertion, VerifierError,
CLIENT_ATTACHED_HEADER, NOSTR_PUBKEY_CLAIM, OAUTH_CLIENT_ID_CLAIM,
};

#[cfg(any(test, feature = "test-utils"))]
Expand Down
42 changes: 42 additions & 0 deletions crates/buzz-auth/src/nip_fi/assertion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,45 @@ impl fmt::Debug for CanonicalCapabilities {
f.write_str("CanonicalCapabilities([REDACTED])")
}
}

/// Test-only construction path for [`VerifiedAssertion`].
///
/// This module is compiled under `#[cfg(test)]` (direct crate tests) or when
/// the `test-utils` feature is enabled. Integration tests in `buzz-relay` and
/// other crates enable `buzz-auth/test-utils` to access this path.
#[cfg(any(test, feature = "test-utils"))]
pub mod test_support {
use super::*;

/// Mint a minimal [`VerifiedAssertion`] for use in integration tests.
///
/// The returned assertion has:
/// - `issuer` and `subject` as provided
/// - A single `authority_deadline` at the provided timestamp
/// - Empty capabilities
/// - A placeholder compact JWS (`"test-jws"`) that will fail real
/// revalidation — the pg_integration mock verifier bypasses that check
pub fn minimal_verified_assertion(
issuer: &str,
subject: &str,
authority_deadline: chrono::DateTime<chrono::Utc>,
) -> VerifiedAssertion {
use crate::nip_fi::config::{AssertionPolicyId, TransportContractId};

VerifiedAssertion::seal(
issuer.to_string(),
subject.to_string(),
None, // asserted_key
CanonicalCapabilities::from_pairs(vec![]),
vec![authority_deadline],
AssertionPolicyId::for_test([0u8; 32]),
TransportContractId::for_test([0u8; 32]),
RevalidationDependencies::new(
"test-key-id".to_string(),
1,
authority_deadline,
"test-jws".to_string(),
),
)
}
}
Loading
Loading