fix(security): harden upstream critical surfaces (#32)

* fix(security): harden upstream critical surfaces

Refs #30

* fix(security): close pre-landing review gaps

Refs #30

* fix(security): close official release blockers
This commit is contained in:
Duy /zuey/ authored and GitHub committed 2026-05-20 16:33:49 +07:00
1 parent 2a48e62c6b
commit 532ff91d8e
59 files changed
+2194 -397

No files matched your search

+3 -3
View File
@@ -978,7 +978,7 @@ Team activity and audit trail.
## 20. Secure CLI Credentials
CLI authentication credentials for secure command execution. Requires **admin role** (full gateway token or empty gateway token in dev/single-user mode).
CLI authentication credentials for secure command execution. Requires **admin role** (gateway token or empty-token local/dev fallback).
| Method | Path | Description |
|--------|------|-------------|
@@ -1014,7 +1014,7 @@ CLI authentication credentials for secure command execution. Requires **admin ro
## 21. Runtime & Packages Management
Manage system (apk), Python (pip), and Node (npm) package installation in the GoClaw runtime container. These endpoints do not inspect host-level runtimes. Requires authentication. When `GOCLAW_GATEWAY_TOKEN` is empty (dev/single-user mode), all users get admin role and can manage packages.
Manage system (apk), Python (pip), and Node (npm) package installation in the GoClaw runtime container. These endpoints do not inspect host-level runtimes. Requires authentication. Empty-token admin access is limited to loopback local development or explicit `GOCLAW_ALLOW_INSECURE_NO_AUTH=1`; external binds require `GOCLAW_GATEWAY_TOKEN`.
### List Installed Packages
@@ -1178,7 +1178,7 @@ Workspace file management.
| `GET` | `/v1/storage/files` | List files with depth limiting |
| `GET` | `/v1/storage/files/{path...}` | Read file (JSON or raw) |
| `POST` | `/v1/storage/files` | Upload file (admin) |
| `DELETE` | `/v1/storage/files/{path...}` | Delete file/directory |
| `DELETE` | `/v1/storage/files/{path...}` | Delete file/directory (admin) |
| `PUT` | `/v1/storage/move` | Move/rename file (admin) |
| `GET` | `/v1/storage/size` | Stream storage size (Server-Sent Events, cached 60 min) |
+15 -4
View File
@@ -32,6 +32,15 @@ Or in WebSocket `connect`:
The gateway token is compared using **constant-time comparison** (`crypto/subtle.ConstantTimeCompare`) in both HTTP and WebSocket auth paths to prevent timing attacks. The comparison reveals no information about where the provided token first differs from the expected token.
Externally reachable deployments must configure a gateway token. If `gateway.token` / `GOCLAW_GATEWAY_TOKEN` is empty while the gateway binds to `0.0.0.0`, `::`, or a non-loopback address, startup fails before the health endpoint reports ready.
Empty-token compatibility is only for local development:
- bind `GOCLAW_HOST` to loopback (`127.0.0.1`, `localhost`, or `::1`), or
- set `GOCLAW_ALLOW_INSECURE_NO_AUTH=1` explicitly.
The explicit opt-in applies to both HTTP and WebSocket. Do not use it on shared hosts, Docker ports exposed outside the machine, or production deployments.
---
## 2. API Keys
@@ -100,7 +109,7 @@ GoClaw tries authentication methods in this priority order:
1. **Gateway token** (exact match via constant-time comparison) → `RoleAdmin` or `RoleOwner` for configured owner IDs
2. **API key** (SHA-256 hash lookup in `api_keys` table) → role from scopes
3. **Browser pairing** (sender ID must be paired with "browser" device type) → `RoleOperator` (HTTP only; requires `X-GoClaw-Sender-Id` header)
4. **No auth configured** (backward compatibility: if no gateway token is set) → full-access dev mode
4. **No auth configured and local/dev mode explicitly allowed** → full-access dev mode
5. **No valid auth found** → `401 Unauthorized`
### HTTP Request Flow
@@ -116,13 +125,15 @@ flowchart TD
G -->|Yes| H[Derive role from scopes]
G -->|No| I{Gateway token configured?}
I -->|Yes| J[401 Unauthorized]
I -->|No| K[Full-access backward compat]
I -->|No| K{Local/dev fallback allowed?}
K -->|No| J
K -->|Yes| O[Full-access backward compat]
C -->|Check paired device| L{Device paired?}
L -->|Yes| M[RoleOperator]
L -->|No| J
E --> N[Authenticate request]
H --> N
K --> N
O --> N
M --> N
```
@@ -164,7 +175,7 @@ On successful API key authentication, `last_used_at` is updated asynchronously (
### Backward Compatibility
If no gateway token is configured (`gateway.token` is empty in `config.json`), unauthenticated requests run in backward-compatibility full-access mode. This enables self-hosted deployments without strict authentication. Once a gateway token is configured, all requests must authenticate or use browser pairing.
If no gateway token is configured (`gateway.token` is empty in `config.json`), unauthenticated requests run in backward-compatibility full-access mode only for loopback local development or when `GOCLAW_ALLOW_INSECURE_NO_AUTH=1` is set. Once a gateway token is configured, all requests must authenticate or use browser pairing.
---
+1 -1
View File
@@ -158,7 +158,7 @@ GoClaw determines the tenant from the credentials used to connect:
| **API key** (tenant-bound) | Auto from key's `tenant_id` | Normal SaaS integration |
| **API key** (system-level) + `X-GoClaw-Tenant-Id` | Header value (UUID or slug), while keeping the key's original role | Cross-tenant tools |
| **Browser pairing** | Master tenant by default, or a membership-validated tenant hint | Dashboard operators |
| **No credentials** | Master tenant | Dev/single-user mode |
| **No credentials** | Master tenant | Loopback local development or explicit `GOCLAW_ALLOW_INSECURE_NO_AUTH=1` only |
**Owner IDs:** Configured via `GOCLAW_OWNER_IDS` env var (comma-separated). Only owners get cross-tenant access with the gateway token. Default: `system`.
+1 -3
View File
@@ -119,7 +119,6 @@ Parity enforced by `ui/web/src/__tests__/i18n-tts-key-parity.test.ts` (vitest).
---
<<<<<<< HEAD
## Image Generation
Native `image_generation` support in the Codex provider (`POST /codex/responses`) + passthrough in the OpenAI-compat path.
@@ -138,7 +137,7 @@ Native `image_generation` support in the Codex provider (`POST /codex/responses`
**Persistence:** `internal/agent/media.go persistAssistantImages()` writes final images to `{workspace}/media/{sha256}.{ext}`, returns `MediaRef` entries, clears inline `Images[]`. Idempotent on hash. Invoked from `pipeline.FinalizeStage` via `Deps.PersistAssistantImages` callback.
**Web UI:** Download filename resolver (`imageGenDownloadName`) in `ui/web/src/components/chat/media-gallery.tsx`. Image generation works automatically when the agent has the `create_image` tool — no user-facing toggle.
=======
## Webhook Subsystem
External systems invoke agents or send channel messages via webhooks without gateway tokens.
@@ -188,7 +187,6 @@ Raw webhook secret encrypted at rest via AES-256-GCM using `GOCLAW_ENCRYPTION_KE
All webhook calls logged with canonical `{"body_hash":"<sha256-hex>","meta":{...}}` shape in `webhook_calls.request_payload` (JSON).
Used by idempotency checker to detect body mismatches on replay.
>>>>>>> a83f4090 (fix(webhooks): address post-review findings (K1-K10))
---