4.9 KiB
Capability Spec: network-intra-relay
Package: @suaveplan/network-plugin-intra-relay
Status: CANONICAL.
Every requirement below carries a
**Verified by:**line.
Requirements
Requirement: RelaySecurityManager authorises inbound relay messages
The package SHALL export a RelaySecurityManager class that runs three checks in order against every inbound relay message: trusted-peer allowlist, ed25519 signature validation, and per-peer + global token-bucket rate limit. The class SHALL throw a NetworkIntraRelayError carrying one of RELAY_PEER_UNTRUSTED, RELAY_SIGNATURE_INVALID, or RELAY_RATE_LIMIT_EXCEEDED on failure. The class SHALL accept a RelaySecurityManagerConfig carrying { policy: RelayAuthPolicy; rateLimit: RelayRateLimit; peerKeyResolver: PeerKeyResolver; clock?: () => number; signatureVerifier?: SignatureVerifier }.
Verified by: src/security/relay-security-manager.spec-conformance.test.ts
Scenario: admits a peer in the trusted-set allowlist
- WHEN
policy.trustedPeersisSet(["peer-a"])andauthorizeis called withsenderPeerId: "peer-a"and a valid signature - THEN the call does NOT throw
Scenario: rejects a peer not in a non-empty allowlist
- WHEN
policy.trustedPeersisSet(["peer-a"])andauthorizeis called withsenderPeerId: "peer-b" - THEN the call throws
NetworkIntraRelayError(RELAY_PEER_UNTRUSTED)
Scenario: empty allowlist admits any peer
- WHEN
policy.trustedPeersis the empty set - THEN any peer id is admitted (subject to the signature and rate-limit checks)
Scenario: rejects bad signature with RELAY_SIGNATURE_INVALID
- WHEN a message arrives with a signature that fails ed25519 verification against the resolved public key
- THEN
authorizethrowsNetworkIntraRelayError(RELAY_SIGNATURE_INVALID)
Scenario: missing signature when policy requires one
- WHEN
policy.requireSignatureistrueand the input lacks asignature - THEN
authorizethrowsNetworkIntraRelayError(RELAY_SIGNATURE_INVALID)
Scenario: missing signature when policy does not require one
- WHEN
policy.requireSignatureisfalseand the input lacks asignature - THEN
authorizedoes NOT throw on the signature check
Scenario: per-peer rate-limit exhaustion
- WHEN a single peer consumes more than
rateLimit.burstSizetokens before the clock advances enough to refill the bucket - THEN the next
authorizecall from that peer throwsNetworkIntraRelayError(RELAY_RATE_LIMIT_EXCEEDED)withcontext.scope === "per-peer"
Scenario: global rate-limit exhaustion separate from per-peer
- WHEN multiple peers collectively exhaust the global bucket while each peer's individual bucket still has tokens
- THEN further
authorizecalls throwNetworkIntraRelayError(RELAY_RATE_LIMIT_EXCEEDED)withcontext.scope === "global"
Scenario: isRateLimited inspection without consuming
- WHEN
isRateLimited(peerId)is called - THEN the result reflects whether
authorizewould currently fail on the rate-limit check, without consuming a token
Requirement: error-code registry extended with three security codes
The NETWORK_INTRA_RELAY_CODES registry SHALL include RELAY_SIGNATURE_INVALID, RELAY_PEER_UNTRUSTED, and RELAY_RATE_LIMIT_EXCEEDED as frozen members.
Verified by: src/error-codes.spec-conformance.test.ts
Scenario: registry exposes the three codes
- WHEN
NETWORK_INTRA_RELAY_CODESis inspected - THEN it contains
RELAY_SIGNATURE_INVALID,RELAY_PEER_UNTRUSTED,RELAY_RATE_LIMIT_EXCEEDEDmapped to"NET_INTRA_RELAY.<KEY>"values
Requirement: IntraRelayPlugin exposes a security getter
IntraRelayPlugin SHALL accept an optional security?: RelaySecurityManagerConfig field in its constructor config and SHALL expose a security getter returning RelaySecurityManager | null. Omitting the field SHALL leave security disabled (security returns null); supplying it SHALL construct a RelaySecurityManager wired to the supplied policy / rate-limit / resolver.
Verified by: src/plugin/intra-relay-plugin.spec-conformance.test.ts
Scenario: security getter returns null when no config supplied
- WHEN
IntraRelayPluginis constructed withoutsecurity - THEN the
securitygetter returnsnull
Scenario: security getter returns a manager when configured
- WHEN
IntraRelayPluginis constructed with a validsecurityconfig - THEN the
securitygetter returns aRelaySecurityManagerwhoseisRateLimitedandauthorizemethods are wired correctly
Requirement: types are exported for consumers
The package SHALL export RelayAuthPolicy, RelayRateLimit, and RelaySecurityCheckInput types from the root barrel and from a subpath export at ./security/security.
Verified by: tests/dep-graph.test.ts, package exports map.