Limit password-session cookies to local-owner loopback clients and document device/session-token requirements for remote and multi-user v5 GA access.
14 KiB
Veritas Kanban Server - Security Guide
Overview
The Veritas Kanban server includes a flexible authentication and authorization system to protect API endpoints and WebSocket connections from unauthorized access.
Quick Start
Development (Localhost Bypass)
For local development, enable localhost bypass:
# .env
VERITAS_AUTH_ENABLED=true
VERITAS_AUTH_LOCALHOST_BYPASS=true
This allows unauthenticated requests from localhost/127.0.0.1 while still requiring auth for remote connections during development.
In NODE_ENV=production, localhost bypass is not honored for HTTP or WebSocket
auth, even if an old .env file still enables it.
Production
For production, configure API keys:
# .env
VERITAS_AUTH_ENABLED=true
VERITAS_AUTH_LOCALHOST_BYPASS=false
VERITAS_ADMIN_KEY=your-secure-admin-key
VERITAS_API_KEYS=agent1:key1:agent,dashboard:key2:read-only
Remote/Server Mode
Remote access must follow the v5 remote security posture in
ADR 0002. In
short: prefer one trusted origin for the web client, /api, and /ws; keep
auth enabled; disable localhost bypass outside loopback; use HTTPS, VPN, or a
trusted tunnel for browser/mobile sessions; and use exact origins instead of
wildcard CORS.
Authentication Methods
Clients can authenticate using any of these methods:
1. Authorization Header (Recommended)
curl -H "Authorization: Bearer your-api-key" \
http://localhost:3001/api/tasks
2. X-API-Key Header
curl -H "X-API-Key: your-api-key" \
http://localhost:3001/api/tasks
3. Query Parameter (WebSocket)
const ws = new WebSocket('ws://localhost:3001/ws?api_key=your-api-key');
HTTP requests do not accept API keys in query strings. Use headers for HTTP and
reserve the WebSocket api_key query fallback for clients that cannot send auth
headers during the upgrade.
Roles and Permissions
v5 planning note: the current role model is intentionally small. The planned multi-user model expands this into workspace-scoped
owner,admin,member,reviewer,read-only, andagentroles with scoped agent tokens. See v5 Identity, Workspace, and RBAC Model.
| Role | Read | Write | Admin Actions |
|---|---|---|---|
admin |
✅ | ✅ | ✅ |
agent |
✅ | ✅ | ❌ |
read-only |
✅ | ❌ | ❌ |
Role Details
- admin: Full access to all endpoints including sensitive operations
- agent: Can read/write tasks, run agents, manage worktrees. Intended for AI agents like OpenClaw
- read-only: Can perform read endpoints, including documented read-like POST checks. Suitable for dashboards and monitoring
Agent self-service routes are still permission-scoped. Read-like checks such as
agent routing and permission checks require agent:read; approval requests
require task:write; approval review, routing configuration, and permission
elevation require admin:manage.
v5 Auth Context
Authenticated REST requests and WebSocket connections now carry a shared auth context for the v5 RBAC migration:
| Field | Description |
|---|---|
role |
Current compatibility role: admin, agent, read-only |
userId |
Local fallback user ID until persisted users are enforced |
workspaceId |
Local fallback workspace ID until workspace scoping lands |
actorType |
user, agent, service, or localhost-bypass |
authMethod |
disabled, session, api-key, device-session, or localhost-bypass |
tokenName |
API key name when authenticated with a configured key |
permissions |
Role-derived permission list used by new route guards |
New v5 endpoints should prefer explicit permission guards over broad role checks. Legacy role guards remain supported while route coverage is migrated.
Browser password sessions are local-owner only in v5 GA. The server accepts the
session cookie only on loopback requests with loopback Host/Origin/Referer
metadata. Remote, server, PWA, and multi-user clients must authenticate with a
trusted device session or scoped API token so active workspace membership, role,
revocation, and downgraded scopes are revalidated.
The v5 authority surface is tracked in
docs/security/permission-coverage.json.
Run node scripts/check-permission-coverage.mjs to fail when a REST route
prefix, WebSocket event, CLI command, MCP tool, workflow step/action type,
command palette action, or tracked background job is added without a permission
classification.
The v5.0 hardening review is recorded in
docs/security/v5-security-review.md,
including fixed high/critical findings, accepted hardening risks, and the
password-session local-owner boundary.
Release compatibility, stale-client behavior, update channels, and rollback
limits are tracked in
docs/V5-COMPATIBILITY-AND-RELEASE-POLICY.md.
Compatibility errors and debug bundles must redact tokens, cookies, private
keys, local private paths, raw chat content, and task body text.
Configuration Reference
Environment Variables
| Variable | Default | Description |
|---|---|---|
VERITAS_AUTH_ENABLED |
true |
Enable/disable authentication |
VERITAS_AUTH_LOCALHOST_BYPASS |
false |
Allow unauthenticated localhost requests in development |
VERITAS_ADMIN_KEY |
(none) | Admin API key with full access |
VERITAS_API_KEYS |
(none) | Comma-separated API keys (format: name:key:role) |
API Key Format
name:key:role,name2:key2:role2
Example:
veritas:vk_abc123xyz:agent,dashboard:vk_def456uvw:read-only
Generating API Keys
Using OpenSSL
# Generate a random 32-character key
openssl rand -base64 32
Using the Built-in Function
import { generateApiKey } from './middleware/auth.js';
const key = generateApiKey('vk'); // e.g., vk_AbCdEf123...
API Endpoints
Auth Status (Unauthenticated)
Check the current authentication configuration:
curl http://localhost:3001/api/auth/status
Response:
{
"enabled": true,
"localhostBypass": false,
"configuredKeys": 2,
"hasAdminKey": true
}
Health Check (Unauthenticated)
curl http://localhost:3001/health
WebSocket Authentication
WebSocket connections are authenticated on connect:
// With API key
const ws = new WebSocket('ws://localhost:3001/ws?api_key=your-key');
ws.onclose = (event) => {
if (event.code === 4001) {
console.error('Authentication failed:', event.reason);
}
};
WebSocket Close Codes
| Code | Meaning |
|---|---|
1000 |
Normal close |
4001 |
Authentication required/failed |
Error Responses
401 Unauthorized
{
"error": "Authentication required",
"code": "AUTH_REQUIRED",
"hint": "Provide API key via Authorization header (Bearer <key>), X-API-Key header, or api_key query parameter"
}
403 Forbidden
{
"error": "Write access denied",
"code": "WRITE_FORBIDDEN",
"hint": "Your API key has read-only access"
}
Security Best Practices
-
Never commit API keys - Use environment variables or
.envfiles (add to.gitignore) -
Rotate keys regularly - Update API keys periodically, especially if compromised
-
Use HTTPS in production - API keys are transmitted in headers/URLs
-
Principle of least privilege - Use
read-onlyfor dashboards,agentfor automation -
Monitor access - The server logs connection attempts with role information
-
Keep remote mode explicit - Binding outside loopback, reverse proxying, tunneling, or serving mobile/PWA clients requires auth enabled, localhost bypass disabled, exact CORS/WebSocket origins, and redacted diagnostics. See ADR 0002.
Migrating from No Auth
If you're upgrading from an earlier version without authentication:
-
Before upgrading: Document all clients that access the API
-
During upgrade:
- Start with
VERITAS_AUTH_LOCALHOST_BYPASS=truefor smooth transition - Generate API keys for each client
- Update clients to include authentication headers
- Start with
-
After testing: Disable localhost bypass for production
Troubleshooting
"Authentication required" for localhost
Check that VERITAS_AUTH_LOCALHOST_BYPASS=true is set, or provide an API key.
"Invalid API key"
- Verify the key matches exactly (no extra spaces)
- Check that the key is in the
VERITAS_API_KEYSorVERITAS_ADMIN_KEYvariable - Ensure the format is correct:
name:key:role
WebSocket immediately closes
- Check browser console for the close reason
- Ensure the API key is passed as a query parameter:
?api_key=...
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Request Flow │
├─────────────────────────────────────────────────────────────┤
│ │
│ Client Request │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ CORS/JSON │ (express middleware) │
│ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ ┌───────────────────────┐ │
│ │ /health │──▶│ Bypass auth │ │
│ │ /api/auth/* │ │ (unauthenticated) │ │
│ └──────────────┘ └───────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ authenticate │ (middleware/auth.ts) │
│ │ │ │
│ │ - Check auth │ │
│ │ enabled │ │
│ │ - Localhost │ │
│ │ bypass? │ │
│ │ - Validate │ │
│ │ API key │ │
│ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Route Handler│ (req.auth available) │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Changelog
- v3.3.0 (2026-02-15): Task intelligence security hardening
- Crash-recovery checkpointing with auto-sanitization of 20+ secret patterns plus regex value detection
- XSS prevention in observational memory via
sanitizeCommentText() - DFS cycle detection in task dependencies prevents infinite loop attacks
- Input sanitization on agent filter (trim + 100 char cap)
- Zod validation on all dependency and checkpoint routes
- v3.0.0 (2026-02-09): Workflow engine security
- ReDoS protection on regex acceptance criteria
- Expression injection prevention in template evaluator
- Parallel DoS limits (max 50 concurrent sub-steps)
- Gate approval authentication and permission checks
- RBAC with ACL files for workflow access control
- Audit logging of all workflow changes
- v2.0.0 (2026-02-06): Multi-agent security
- Agent permission levels (Intern/Specialist/Lead) with enforcement
- Agent registry with heartbeat-based liveness tracking
- MCP SDK patched to ^1.26.0 (GHSA-345p-7cg4-v4c7)
- Rate limiting documentation (reverse proxy recommended for public deployments)
- v1.0.0 (2026-01-29): Initial authentication implementation
- API key authentication for HTTP and WebSocket
- Role-based authorization (admin, agent, read-only)
- Localhost bypass for development
- Configuration via environment variables