mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 17:05:21 +00:00
Harden public intake bounds against untrusted growth (#1451)
* Bound public rate-limit buckets against attacker-keyed growth. Keep the NIP-13 PoW sender bypass, skip content-bucket minting on sender reject, and evict idle/oldest entries at a hard cap. Co-authored-by: Cursor <cursoragent@cursor.com> * Stop Cashu-looking text from skipping long-message guards. Oversized public content always collapses and takes the plain formatting path so remote tokens cannot force layout/regex DoS. Co-authored-by: Cursor <cursoragent@cursor.com> * Cap teleported geohash participant markers. Bound the set with FIFO eviction, clear it on channel switch, and prune markers that leave the visible participant list. Co-authored-by: Cursor <cursoragent@cursor.com> * Bound untrusted Nostr relay frames and event tags. Reject oversized inbound messages before JSON parse, cap tag arrays/values at decode, and stop logging raw tag contents. Co-authored-by: Cursor <cursoragent@cursor.com> * Fail soft when Noise handshake state is unexpectedly missing. Replace the initiator startHandshake force unwrap with a guard that throws invalidState instead of crashing. Co-authored-by: Cursor <cursoragent@cursor.com> * Cap geohash nickname cache from remote Nostr events. FIFO-evict at capacity, clear on channel switch, and prune nicknames that leave the visible participant list. Co-authored-by: Cursor <cursoragent@cursor.com> * Avoid overlapping exclusive access in the rate limiter. Make bucket helpers static so inout dictionary updates do not conflict with a mutating call on self. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix rate-limiter tests for mutating allow under #expect. Call allow outside the macro so Swift Testing does not capture an immutable copy of the struct. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop unused WebSocket data helper; reset rate limiter on panic wipe. dataWithinInboundLimit replaced the unbounded path, and panic clear should not leave public intake buckets behind. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -290,7 +290,65 @@ struct NostrProtocolTests {
|
||||
#expect(object["limit"] as? Int == 42)
|
||||
}
|
||||
|
||||
|
||||
@Test func inboundNostrEventRejectsTooManyTags() throws {
|
||||
var eventDict = Self.validInboundEventDict()
|
||||
eventDict["tags"] = Array(
|
||||
repeating: ["g", "u4pruyd"],
|
||||
count: TransportConfig.nostrMaxEventTags + 1
|
||||
)
|
||||
|
||||
#expect(throws: NostrError.invalidEvent) {
|
||||
_ = try NostrEvent(from: eventDict)
|
||||
}
|
||||
}
|
||||
|
||||
@Test func inboundNostrEventRejectsTooManyTagValues() throws {
|
||||
var eventDict = Self.validInboundEventDict()
|
||||
eventDict["tags"] = [Array(
|
||||
repeating: "value",
|
||||
count: TransportConfig.nostrMaxEventTagValues + 1
|
||||
)]
|
||||
|
||||
#expect(throws: NostrError.invalidEvent) {
|
||||
_ = try NostrEvent(from: eventDict)
|
||||
}
|
||||
}
|
||||
|
||||
@Test func inboundNostrEventRejectsOversizedTagValues() throws {
|
||||
var eventDict = Self.validInboundEventDict()
|
||||
eventDict["tags"] = [[
|
||||
"g",
|
||||
String(repeating: "a", count: TransportConfig.nostrMaxEventTagValueBytes + 1)
|
||||
]]
|
||||
|
||||
#expect(throws: NostrError.invalidEvent) {
|
||||
_ = try NostrEvent(from: eventDict)
|
||||
}
|
||||
}
|
||||
|
||||
@Test func inboundNostrEventAcceptsTagsWithinLimits() throws {
|
||||
var eventDict = Self.validInboundEventDict()
|
||||
eventDict["tags"] = [["g", "u4pruyd"], ["t", "teleport"]]
|
||||
|
||||
let event = try NostrEvent(from: eventDict)
|
||||
|
||||
#expect(event.tags.count == 2)
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
private static func validInboundEventDict() -> [String: Any] {
|
||||
[
|
||||
"id": String(repeating: "0", count: 64),
|
||||
"pubkey": String(repeating: "1", count: 64),
|
||||
"created_at": 1_234_567,
|
||||
"kind": NostrProtocol.EventKind.ephemeralEvent.rawValue,
|
||||
"tags": [["g", "u4pruyd"]],
|
||||
"content": "hello",
|
||||
"sig": String(repeating: "2", count: 128)
|
||||
]
|
||||
}
|
||||
|
||||
private static func base64URLDecode(_ s: String) -> Data? {
|
||||
var str = s.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
|
||||
let rem = str.count % 4
|
||||
|
||||
Reference in New Issue
Block a user