Skip to content

sec: enforce channel ACL prefixes (presence- + reserved _) - #24

Merged
EthanY33 merged 1 commit into
mainfrom
sec/channel-acl-prefixes
May 7, 2026
Merged

sec: enforce channel ACL prefixes (presence- + reserved _)#24
EthanY33 merged 1 commit into
mainfrom
sec/channel-acl-prefixes

Conversation

@EthanY33

@EthanY33 EthanY33 commented May 7, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #8.

The original handler had two prefix-related gaps:

  1. presence-* channels (Pusher's protocol convention for authenticated channels with member metadata) were treated as public. Anyone with a socket could subscribe to presence-room-1 and read every publish.
  2. _*-prefixed channels were rejected only on publish. Subscribe ignored the rule, so an attacker could subscribe to a server-reserved name before the server ever publishes there.

This PR replaces the inline strings.HasPrefix checks with two named, data-driven helpers:

// _-prefixed names are reserved on both subscribe and publish.
func channelReserved(name string) bool

// private-* and presence-* require a signed token whose HMAC includes
// the channel name; a token for private-X cannot subscribe private-Y.
func channelRequiresAuth(name string) bool

The authRequiredPrefixes slice is package-level data, so a new prefix is a one-liner.

What changed

  • internal/conn/handler.go:
    • New channelReserved / channelRequiresAuth helpers + authRequiredPrefixes slice.
    • handleSubscribe: reserved -> error; auth required -> verify; otherwise public.
    • handlePublish: same channelReserved check.
  • internal/conn/handler_test.go: 3 new test cases.

Test plan

  • go build ./... clean.
  • go test ./internal/conn/... passes (3 new tests + existing).
  • Manual: subscribe channel=presence-room without token returns AUTH_FAILED.
  • Manual: subscribe channel=presence-room with a token signed for that channel returns subscribed.
  • Manual: subscribe channel=_internal returns RESERVED_CHANNEL.

Conflict notes

internal/conn/handler.go overlaps with PR #20 (channel GC + sub rate limit), PR #17 (channel name length cap), and PR #23 (token jti / replay cache). All overlaps are at distinct lines / additive — small textual rebase whichever lands second.

Closes #8.

The original handler had two prefix-related gaps:

1. presence-* channels (Pusher protocol convention for authenticated
   channels with member metadata) were treated as public — anyone with
   a socket could subscribe to presence-room-1 and read every publish.
2. _-prefixed channels were rejected only on publish. Subscribe ignored
   the rule, so an attacker could grab a server-reserved name before
   the server ever uses it.

Adds two helpers in handler.go:
- channelReserved(name) — true for _-prefixed names. Rejected on both
  subscribe and publish.
- channelRequiresAuth(name) — true for private-* and presence-*.
  Token verified the same way as the existing private-* path; the token
  is bound to the channel name in the HMAC, so a private-X token won't
  let you subscribe to private-Y.

The auth-required prefix list is package-level data so adding a new
prefix is a one-liner.

Tests:
- TestSubscribePresenceRequiresAuth: presence-room without token ->
  AUTH_FAILED.
- TestSubscribePresenceValidHMAC: with a token signed for presence-room
  -> subscribed.
- TestSubscribeReservedRejected: _internal -> RESERVED_CHANNEL.
@EthanY33
EthanY33 force-pushed the sec/channel-acl-prefixes branch from d1b7447 to 4ffdbf4 Compare May 7, 2026 23:13
@EthanY33
EthanY33 merged commit 26100b0 into main May 7, 2026
2 checks passed
@EthanY33
EthanY33 deleted the sec/channel-acl-prefixes branch May 7, 2026 23:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[High] Private-channel auth only checks "private-" prefix

1 participant