If you discover a security vulnerability in Selva Office, please report it responsibly:
- Do not open a public GitHub issue
- Email security@selva.town with details
- Include steps to reproduce if possible
- We will acknowledge receipt within 48 hours
This project handles sensitive data including:
- LLM provider API keys (OpenAI, Anthropic, etc.)
- Agent orchestration data and execution logs
- Real-time collaboration session data (Colyseus)
- User authentication tokens and session state
- Database credentials and connection strings
- API keys and LLM credentials must never be committed to version control
- All secrets must be provided via environment variables or K8s secrets
- Agent execution logs must not contain user PII or credentials
- Colyseus room state must not persist sensitive data beyond session lifetime
- Logs must never contain passwords, tokens, or API keys
Selva Office includes a public demo mode (/demo) that allows unauthenticated visitors to experience the office environment with simulated agents. This mode intentionally bypasses the normal Janua JWKS authentication flow.
The demo page generates an unsigned JWT on the client side with the following claims:
| Claim | Value | Purpose |
|---|---|---|
org_id |
demo-public |
Identifies all demo sessions as belonging to a synthetic organization |
roles |
["demo"] |
Grants the minimum role required to join a Colyseus room |
The Colyseus server's verifyToken() function recognizes tokens with org_id: "demo-public" and skips JWKS signature verification for those tokens only. All other tokens proceed through the standard Janua JWKS validation path.
The demo mode is a product requirement for the landing page. Visitors must be able to try the office experience without creating an account or authenticating through Janua. Requiring real authentication for a sandbox demo would defeat its purpose as a low-friction onboarding tool.
The following controls ensure the demo bypass cannot be used to access real data or disrupt authenticated users:
-
Room isolation. Colyseus rooms are filtered by
orgIdviafilterBy(["orgId"]). Demo rooms (orgId: "demo-public") are completely separate from production rooms. A demo token cannot join or observe a real organization's room. -
Simulated agents only. The
DemoSimulatorpopulates 8 hardcoded agents that cycle through states on a timer. These agents are not connected to the worker process, nexus-api, or any real task queue. No actual LLM inference, git operations, or file system access occurs. -
Feature restrictions. The
OfficeExperiencecomponent accepts amodeparameter. Indemomode, the following features are hidden and their hooks skip all API calls and WebSocket connections:- Calendar integration (
useCalendar) - Skill marketplace (
useMarketplace) - Map editor
- Workflow editor
- Ops feed and metrics (
useEventStream) - Task dispatch (
useTaskDispatch) - Approval queue (
useApprovals) - Task board (
useTaskBoard)
- Calendar integration (
-
No API access. Demo tokens do not carry a valid signature. Any request from a demo client to nexus-api endpoints that require Bearer authentication will be rejected by the standard JWT validation middleware. The bypass exists only in the Colyseus
onAuthpath. -
No data persistence. Demo room state is ephemeral. When the last demo client disconnects, the room is destroyed. No demo data is written to PostgreSQL, Redis streams, or the artifact storage.
-
Auto-approval timeout. The
DemoSimulatorauto-approves pending approvals after 15 seconds if no human action occurs, preventing simulated agents from accumulating stale state.
- Room metrics. The Colyseus health endpoint (
GET :4303/health) reports active rooms byorgId. Abnormal growth indemo-publicrooms is visible in Grafana dashboards and triggers a Prometheus alert if count exceeds the threshold. - Connection rate. The dispatch rate limiter (
MessageRateLimiter, 10 req/60s per user) applies equally to demo connections. Rapid reconnection attempts are throttled at the WebSocket transport layer. - Log tagging. All Colyseus log entries for demo rooms include
org_id=demo-public, making it straightforward to filter demo traffic from production traffic in log aggregation. - No secret exposure. Because demo mode does not invoke the worker process, LLM providers, or nexus-api authenticated endpoints, there is no path from demo mode to secret material (API keys, database credentials, GitHub tokens).
| Risk | Severity | Mitigation |
|---|---|---|
| Demo token used to join a real org room | Low | filterBy(["orgId"]) prevents cross-org room access |
| Demo token used to call nexus-api | Low | Standard JWT validation rejects unsigned tokens |
| Resource exhaustion from demo room creation | Medium | Rate limiting on WebSocket connections; room auto-destroy on disconnect |
| Demo mode used as amplification vector | Low | No outbound API calls, no LLM inference, no git operations in demo path |
Demo mode is controlled by the NEXT_PUBLIC_DEMO_ENABLED environment variable (default: true). Setting it to false hides the "Try Demo" CTA on the landing page and prevents the demo page from rendering. The Colyseus demo token bypass remains in code but is unreachable without the client-side entry point.
| Version | Supported |
|---|---|
| 0.2.x | Yes |