Bot Module Security
Bot modules execute third-party code that can send chat messages and perform moderation actions. Lumio enforces multiple security layers to protect streamers, viewers, and the platform.
V8 isolation
Every handler invocation runs inside an isolated V8 sandbox with strict resource limits:
| Limit | Sync path (commands, moderation) | Async path (keywords, patterns, events, timers) |
|---|---|---|
| CPU timeout | 500ms | 10s |
| Memory | 256 MB heap | 256 MB heap |
| Response size | 4 MB | 4 MB |
Each extension install gets its own V8 isolate. Isolates share no state — one extension cannot access another extension's memory, variables, or closures.
Database isolation
Each extension install gets its own PostgreSQL schema (ext_\{install_id\}). Extensions cannot query core Lumio tables (users, accounts, overlays) or other extensions' schemas.
Cache isolation
ctx.cache operations are scoped to ext-cache:\{install_id\}:* in Redis. The Rust worker prepends this prefix before executing any Redis command. Extensions cannot access other extensions' cache keys or any internal Lumio Redis keys.
Egress restrictions
ctx.fetch() only allows HTTPS requests to hostnames declared in the egress.allowHosts manifest field. Private IP ranges (10.*, 172.16-31.*, 192.168.*, 127.*, link-local) are blocked at two layers:
- URL hostname check — literal IP addresses in the URL are rejected
- Post-DNS-resolution check — after DNS resolution, every resolved IP is validated against private ranges, preventing DNS rebinding attacks
Redis isolation
Extension JavaScript has no direct Redis access. There is no ctx.redis API. All Redis operations (ctx.cache, cooldowns, rate limits, pub/sub) are performed by trusted Rust code in the Worker. Redis keys are constructed from validated install IDs — extension input is never interpolated into key names.
Permission tiers
Extensions declare required permissions in lumio.config.json. Each permission goes through store review:
| Permission | Review level | Notes |
|---|---|---|
chat:read | Standard | Reading chat messages |
chat:send | Standard | Sending chat replies |
events:read | Standard | Receiving platform events |
chat:delete | Enhanced manual review | Deleting chat messages |
chat:ban | Enhanced manual review | Banning or timing out users |
Extensions requesting chat:ban or chat:delete undergo additional manual code review during the store approval process. Reviewers verify that moderation logic is sound and that the extension cannot be exploited to mass-ban or mass-delete.
Rate limits
All actions are rate-limited per installation to prevent abuse:
Handler invocation limits
| Category | Limit | Scope |
|---|---|---|
| Command execution | 60/min | Per install |
| Keyword/pattern execution | 100/min | Per install |
| Event handler execution | 50/min | Per install |
| Timer execution | 1/interval | Per install |
Action output limits
| Action | Limit | Scope |
|---|---|---|
| Chat send | 30/min | Per install |
| Ban | 5/min | Per install |
| Timeout | 10/min | Per install |
| Delete message | 20/min | Per install |
| Cache operations | 200/min | Per install |
Rate limits are enforced at two layers:
- Input: Limits how often a handler is invoked
- Output: Limits what actions the handler can produce, regardless of which handler type generated them
A keyword handler that returns a ban action is still subject to the 5/min ban limit. This prevents bypassing moderation rate limits by splitting actions across handler types.
Trigger limits
Each extension is limited in how many triggers it can declare:
| Trigger type | Maximum per extension |
|---|---|
| Commands | 20 |
| Keywords | 50 |
| Patterns (regex) | 10 |
| Keyword minimum length | 3 characters |
| Pattern maximum length | 200 characters |
Regex patterns are validated for safety. The Rust regex crate guarantees linear-time matching (no backreferences or lookaheads), making ReDoS attacks impossible by construction. Pattern matching runs in Rust on the bot side — JavaScript RegExp is never used for trigger matching.
Moderation fail-closed policy
When the sync moderation path times out (500ms), the bot applies a configurable default policy:
| Policy | Behavior |
|---|---|
allow (default) | Message passes through |
hold | Message is held and auto-deleted after 5 seconds if no response |
The policy is configured per account in bot module settings, not per extension. Most streamers use allow to avoid false positives from slow responses.
Audit log
Every moderation action executed by a bot module extension is logged to the platform audit log with:
- Source:
extension:\{install_id\} - Metadata: Extension name, extension ID, handler name
- Action: The specific moderation action (ban, timeout, delete)
- Reason: Prefixed with the extension name for traceability
Account owners can view extension-originated moderation actions separately from manual and built-in module actions in the dashboard audit log.
Kill switch
Every account has a "Pause all extension modules" toggle in the dashboard. When activated:
- A signal is published to all connected bots and the Worker
- All extension bot module processing stops immediately for that account
- All pending deferred jobs for that account are cancelled immediately
- Built-in modules (link protection, word filter, spam protection) continue operating normally
The kill switch is designed for emergencies — if a malicious extension starts banning legitimate users, the streamer can stop all extension modules with one click.
Built-in module priority
Built-in modules (LinkProtection, WordFilter, SpamProtection) always run before extension modules. If a built-in module blocks a message, extension moderate() handlers are not invoked. Built-in modules execute in under 1ms with no V8 overhead.
Error isolation
Handler errors are contained within the isolate:
| Scenario | Behavior |
|---|---|
| Handler throws an exception | Error logged, no message sent to chat |
| Handler times out (sync) | Command silently dropped, moderation applies default policy |
| Handler times out (async) | Handler killed, error logged |
| Handler returns invalid data | Treated as null response, warning logged |
ctx.db error | Promise rejects inside handler |
ctx.fetch blocked | Promise rejects with EgressDenied error |
| Rate limit exceeded | Handler not invoked, message silently dropped |