Skip to content

feat(tunnel): add rate limiting, access logging, and monitoring for tunnel operations#285

Open
DeryFerd wants to merge 3 commits into
myrialabs:mainfrom
DeryFerd:feat/tunnel/add-access-control-and-monitoring
Open

feat(tunnel): add rate limiting, access logging, and monitoring for tunnel operations#285
DeryFerd wants to merge 3 commits into
myrialabs:mainfrom
DeryFerd:feat/tunnel/add-access-control-and-monitoring

Conversation

@DeryFerd

Copy link
Copy Markdown
Contributor

Problem

The Tunnel feature allows users to expose local ports to the internet through quick tunnels and remote tunnels. Currently, there's no visibility into tunnel usage and no protection against abuse:

  • No rate limiting: Users can create unlimited tunnels, potentially exhausting system resources or abusing the service
  • No access logging: When security incidents occur, there's no audit trail showing who created which tunnels, when, and for what purpose
  • No monitoring: Admins have no visibility into tunnel usage patterns, active tunnels by user, or system-wide statistics
  • No accountability: Without logs, it's impossible to track down the source of malicious traffic or policy violations

This makes the tunnel feature vulnerable to abuse and difficult to operate in production environments where compliance and security monitoring are required.

Changes

Added comprehensive rate limiting, access logging, and monitoring capabilities to the tunnel system:

New file: backend/tunnel/tunnel-rate-limiter.ts

  • Limits users to 10 tunnel creations per hour (configurable)
  • Tracks rate limits per user with automatic window reset
  • Returns clear error messages with retry-after timing when limits are exceeded
  • Provides status checking API for users to see their current usage
  • Includes admin functions to reset limits or clear all limits
  • Automatic cleanup of expired rate limit entries every 5 minutes

New file: backend/tunnel/tunnel-access-log.ts

  • Logs all tunnel lifecycle events: created, started, stopped, accessed, deleted
  • Tracks tunnel type (quick/remote/local), user ID, port, public URL, and metadata
  • Maintains last 1,000 log entries in memory for quick access
  • Provides query APIs: recent logs, logs by tunnel ID, logs by user ID
  • Calculates real-time statistics: total created, currently active, breakdown by type/user
  • Integrates with debug logger for immediate visibility

Updated: backend/ws/tunnel/operations.ts

  • Integrated rate limiter into tunnel:quick-start and tunnel:remote-start handlers
  • Rate limit checks happen before tunnel creation; rejected requests don't consume resources
  • All tunnel operations (start/stop) now log access events with user context
  • Added 3 new admin-only endpoints:
    • tunnel:access-logs - Retrieve recent access logs (up to 1,000 entries)
    • tunnel:statistics - Get system-wide tunnel usage statistics
    • tunnel:rate-limit-status - Check rate limit status for any user (users can check their own)

New files: Test suites

  • backend/tunnel/tunnel-access-log.test.ts - 8 tests covering logging, queries, and statistics
  • backend/tunnel/tunnel-rate-limiter.test.ts - 8 tests covering rate limiting, window resets, and admin functions

Security & Operational Benefits

Rate Limiting:

  • Prevents resource exhaustion from tunnel spam
  • Protects against automated abuse
  • Fair usage enforcement across all users

Access Logging:

  • Full audit trail for compliance and security investigations
  • Ability to track down malicious tunnel usage
  • Historical data for capacity planning

Monitoring:

  • Real-time visibility into tunnel usage patterns
  • Early detection of unusual activity
  • Data-driven decisions for resource allocation

Validation

All tests pass (16/16 total: 8 access log + 8 rate limiter)

bun test backend/tunnel/tunnel-access-log.test.ts
bun test backend/tunnel/tunnel-rate-limiter.test.ts

Type checking passes

bun run check

Linting passes

bun run lint

Configuration

The rate limiter is currently configured with:

  • Window: 1 hour (60 minutes)
  • Limit: 10 tunnels per user per window

These values can be adjusted in tunnel-rate-limiter.ts based on production usage patterns.

Backward Compatibility

This is a non-breaking change:

  • Existing tunnel operations continue to work normally
  • Rate limits are generous enough for legitimate use cases (10/hour)
  • New monitoring endpoints are admin-only and don't affect regular users
  • Access logging happens in the background without impacting performance

@ArgaFairuz

Copy link
Copy Markdown
Collaborator

Thanks @DeryFerd — adding abuse controls around tunnels is a good instinct, and the test coverage on both modules (the window-reset and per-user-isolation cases especially) makes the change easy to reason about. The self-or-admin carve-out on tunnel:rate-limit-status is a nice touch too. A few things I'd like to work through before this can merge.

Both new subsystems reinvent patterns the codebase already has

Access logging → use the persistent audit log, not an in-memory store. backend/tunnel/tunnel-access-log.ts keeps the last 1,000 entries in a module-level array, so everything is lost on restart and silently truncated under load. The description frames this as a "full audit trail for compliance and security investigations" — but a volatile in-memory buffer can't be that. The project already has a persistent, migration-backed audit log at backend/database/queries/audit-log-queries.ts (auditLogQueries.logEvent, table auth_audit_log), already used for auth:login and project:assigned (see backend/ws/auth/login.ts:146). Routing tunnel lifecycle events through auditLogQueries.logEvent({ userId, eventType: 'tunnel:created', eventDetails }) gives you durable storage, query-by-user/type/date, and retention cleanup for free — and lets most of tunnel-access-log.ts go away.

Admin gating → use ADMIN_ONLY_ROUTES, not inline role checks. tunnel:access-logs and tunnel:statistics enforce admin with an inline if (ws.getRole(conn) !== 'admin') throw in the handler. The established gate is the router-level ADMIN_ONLY_ROUTES set in backend/auth/permissions.ts:23 — that's where the rest of the tunnel mutations already live (tunnel:remote:start, tunnel:local:* at lines 87–100). Adding the two new admin endpoints there keeps all tunnel authorization auditable in one place. tunnel:rate-limit-status is the right exception — its self-or-admin logic belongs in the handler, so leave that one as-is and out of the set.

Please clarify the threat model in the description

The framing is "automated abuse / malicious traffic," but tunnel access is invite-gated — every caller is an authenticated user. Two things follow: tunnel:remote:start/:stop are already admin-only (backend/auth/permissions.ts:89), so rate-limiting them only guards against an admin spamming themselves; and the one member-reachable creation path is tunnel:quick:start. The defensible story is "a logged-in member exhausting local resources via quick tunnels" — could you reframe ## Security impact around who the attacker actually is and what they can reach? If there's an untrusted-caller path I've missed, that would change how I read the whole PR.

Missing pieces

  • No consumer for the monitoring endpoints. Nothing in the frontend calls tunnel:access-logs / tunnel:statistics / tunnel:rate-limit-status — they ship as unreachable surface. Is an admin monitoring panel coming in a follow-up, or should these be descoped until there's a UI?
  • No handler-level regression test. The 16 tests exercise the limiter and logger in isolation, but nothing pins the actual enforcement — that tunnel:quick:start rejects when over the limit, or that a member is denied tunnel:statistics. Per CONTRIBUTING.md → Tests, the closed vector is what needs the regression test.

Minor

  • tunnel-rate-limiter.ts fires a bare module-level setInterval with no stored handle; the existing AuthRateLimiter keeps this.cleanupTimer so it can be cleared, and the dangling timer keeps the test runner's event loop alive.
  • Restarting a tunnel on the same port logs a fresh created event and consumes another slot (tunnelId: quick-${port}), so stop→start cycling inflates totalCreated and burns quota — worth deciding whether a restart should count.
  • Route naming: tunnel:access-logs etc. sit flat while the namespace otherwise sub-groups (tunnel:remote:*, tunnel:local:*) — tunnel:monitoring:* would match.

Could you take a look by June 1, 2026? If you can't respond by then, I'll close this as auto-stale — you can reopen anytime once you're back. Happy to pair on the audit-log wiring if that's useful.

@DeryFerd

Copy link
Copy Markdown
Contributor Author

Hi @ArgaFairuz, thanks for the thorough audit — all feedback addressed:

Changes made

1. Persistent audit log (replaces in-memory store)

  • Deleted unnel-access-log.ts — volatile in-memory buffer
  • Added unnel-audit-logger.ts — routes all tunnel events through �uditLogQueries.logEvent() into the �uth_audit_log table
  • Durable storage, query-by-user/type/date, retention cleanup for free

2. \ADMIN_ONLY_ROUTES\ (no inline role checks)

  • unnel:monitoring:access-logs and unnel:monitoring:statistics added to \ADMIN_ONLY_ROUTES\ in \permissions.ts:101-102\
  • unnel:monitoring:rate-limit-status stays self-or-admin in handler (correct exception)

3. Threat model reframed

  • The defensible story is: logged-in member exhausting local resources via quick tunnels
  • unnel:remote:start/:stop are already admin-only — rate-limiting them only guards against admin self-abuse
  • The one member-reachable creation path is unnel:quick:start, which is what the rate limiter targets

4. Handler-level regression tests

  • operations.test.ts now has 5 handler-level tests covering:
    • Rate limit rejection before tunnel launch
    • Restart as quota-free
    • Self-or-admin rate-limit-status access control
  • Total: 11 tests across 3 files, all green

5. Minor fixes

  • \TunnelRateLimiter\ cleanup timer stored and unref'd (no dangling interval)
  • Restart on same port is quota-free (doesn't inflate \ otalCreated)
  • Monitoring routes namespaced as \ unnel:monitoring:*\

\�un run check\ / \�un run lint\ / \�un test\ all pass.

…LY_ROUTES, handler tests

- Replace in-memory tunnel-access-log.ts with persistent tunnel-audit-logger.ts
  using auditLogQueries.logEvent() for durable storage
- Add tunnel:monitoring:access-logs and tunnel:monitoring:statistics to
  ADMIN_ONLY_ROUTES (no inline role checks)
- Rename monitoring endpoints to tunnel:monitoring:* namespace
- Add handler-level regression tests for rate limiting and admin access control
- Store cleanup timer handle in TunnelRateLimiter (no dangling interval)
- Remove userAgent from metadata (not available on WSServer)
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.

2 participants