Files
2026-05-27 23:29:53 +01:00

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.trustedPeers is Set(["peer-a"]) and authorize is called with senderPeerId: "peer-a" and a valid signature
  • THEN the call does NOT throw

Scenario: rejects a peer not in a non-empty allowlist

  • WHEN policy.trustedPeers is Set(["peer-a"]) and authorize is called with senderPeerId: "peer-b"
  • THEN the call throws NetworkIntraRelayError(RELAY_PEER_UNTRUSTED)

Scenario: empty allowlist admits any peer

  • WHEN policy.trustedPeers is 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 authorize throws NetworkIntraRelayError(RELAY_SIGNATURE_INVALID)

Scenario: missing signature when policy requires one

  • WHEN policy.requireSignature is true and the input lacks a signature
  • THEN authorize throws NetworkIntraRelayError(RELAY_SIGNATURE_INVALID)

Scenario: missing signature when policy does not require one

  • WHEN policy.requireSignature is false and the input lacks a signature
  • THEN authorize does NOT throw on the signature check

Scenario: per-peer rate-limit exhaustion

  • WHEN a single peer consumes more than rateLimit.burstSize tokens before the clock advances enough to refill the bucket
  • THEN the next authorize call from that peer throws NetworkIntraRelayError(RELAY_RATE_LIMIT_EXCEEDED) with context.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 authorize calls throw NetworkIntraRelayError(RELAY_RATE_LIMIT_EXCEEDED) with context.scope === "global"

Scenario: isRateLimited inspection without consuming

  • WHEN isRateLimited(peerId) is called
  • THEN the result reflects whether authorize would 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_CODES is inspected
  • THEN it contains RELAY_SIGNATURE_INVALID, RELAY_PEER_UNTRUSTED, RELAY_RATE_LIMIT_EXCEEDED mapped 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 IntraRelayPlugin is constructed without security
  • THEN the security getter returns null

Scenario: security getter returns a manager when configured

  • WHEN IntraRelayPlugin is constructed with a valid security config
  • THEN the security getter returns a RelaySecurityManager whose isRateLimited and authorize methods 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.