diff --git a/backend/src/__tests__/database-metrics.test.ts b/backend/src/__tests__/database-metrics.test.ts index a89a33e7..b2b00055 100644 --- a/backend/src/__tests__/database-metrics.test.ts +++ b/backend/src/__tests__/database-metrics.test.ts @@ -41,7 +41,7 @@ describe('DatabaseService - container metrics', () => { }); it('aggregates metrics into minute buckets', () => { - const baseTime = Date.now(); + const baseTime = Math.floor(Date.now() / 60000) * 60000; // floor to minute start // Insert two metrics within the same minute db.addContainerMetric({ container_id: 'bucket-test', diff --git a/docs/api-reference/overview.mdx b/docs/api-reference/overview.mdx index d0eae5c5..5dad2700 100644 --- a/docs/api-reference/overview.mdx +++ b/docs/api-reference/overview.mdx @@ -88,7 +88,7 @@ This applies to all stack endpoints: read, write, deploy, down, restart, stop, s ## Rate limiting -All `/api/` endpoints are subject to a global rate limit of **100 requests per minute** per IP address. When exceeded, the server responds with `429 Too Many Requests`: +All API endpoints are rate-limited per IP address. Authentication endpoints have stricter limits to prevent brute-force attacks. When exceeded, the server responds with `429 Too Many Requests`: ```json { @@ -96,11 +96,7 @@ All `/api/` endpoints are subject to a global rate limit of **100 requests per m } ``` -Authentication endpoints (`/api/auth/*`) have an additional, stricter limit of **5 requests per 15 minutes** to prevent brute-force attacks. Both limits apply independently — a login request counts against both the global limit and the auth limit. - -The global rate limit is configurable via the `API_RATE_LIMIT` environment variable (requests per minute, default `100`). - -Standard rate limit headers (`RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`) are included in all API responses. +Standard rate limit headers (`RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`) are included in all API responses, so clients can self-throttle accordingly. ## License tier requirements diff --git a/docs/features/audit-log.mdx b/docs/features/audit-log.mdx index d85a969f..381068c5 100644 --- a/docs/features/audit-log.mdx +++ b/docs/features/audit-log.mdx @@ -91,4 +91,4 @@ Cleanup runs automatically as part of Sencho's periodic maintenance cycle. ## Security at rest -As of this release, sensitive database values (such as remote node API tokens) are encrypted at rest using AES-256-GCM. The encryption key is stored as a separate file outside the SQLite database, ensuring that database file exposure alone does not compromise secrets. +Sensitive database values (such as remote node API tokens) are encrypted at rest. The encryption key is stored separately from the database, ensuring that database file exposure alone does not compromise secrets. diff --git a/docs/features/multi-node.mdx b/docs/features/multi-node.mdx index 0292eb0b..48e380e0 100644 --- a/docs/features/multi-node.mdx +++ b/docs/features/multi-node.mdx @@ -81,11 +81,11 @@ Click the **pencil icon** on any remote node row to edit its name, URL, or token ### Token security -Node tokens are JWTs with a default **1-year expiry** that grant full control over the remote Sencho instance. Treat them like passwords: +Node tokens are JWTs with a configurable expiry that grant full control over the remote Sencho instance. Treat them like passwords: - **Rotate immediately** if a token is compromised: Settings → Nodes → Generate Token on the remote instance. The old token is invalidated instantly. -- Tokens **expire after 1 year** by default. Generate a new token on the remote instance to renew access. -- Tokens are **encrypted at rest** using AES-256-GCM in Sencho's SQLite database. Even if the database file is extracted, the tokens cannot be read without the instance's encryption key. +- Tokens have a configurable expiry. Generate a new token on the remote instance to renew access. +- Tokens are **encrypted at rest** in Sencho's database. Even if the database file is extracted, the tokens cannot be read without the instance's encryption key. - Tokens are scoped to `node_proxy` — they cannot access the host console or container exec terminals. Interactive shell access always requires a real browser session on that specific instance. ### Transport encryption diff --git a/docs/operations/self-hosting.mdx b/docs/operations/self-hosting.mdx index df2f27e6..8b055a66 100644 --- a/docs/operations/self-hosting.mdx +++ b/docs/operations/self-hosting.mdx @@ -36,7 +36,7 @@ Sencho requires three volume mounts to function correctly: | `/opt/compose:/opt/compose` | Your compose project files (must follow 1:1 path rule) | Yes | - The data directory contains `sencho.db` (all settings, nodes, alerts, metrics) and `encryption.key` (used to encrypt secrets at rest). Losing this directory means losing your Sencho configuration entirely. + The data directory contains your Sencho database and encryption keys. Losing this directory means losing your Sencho configuration entirely. --- @@ -89,9 +89,8 @@ Quick reference for all environment variables. See [Configuration](/getting-star | `JWT_SECRET` | *(required)* | Secret key for signing JWT tokens | | `COMPOSE_DIR` | `/app/compose` | Path to compose project files (1:1 rule applies) | | `PORT` | `3000` | HTTP server listen port | -| `DATA_DIR` | `/app/data` | Database and encryption key directory | +| `DATA_DIR` | `/app/data` | Persistent data directory | | `NODE_ENV` | `production` | Set automatically in Docker image | | `FRONTEND_URL` | *(empty)* | Frontend origin for CORS; leave empty for same-origin | -| `API_RATE_LIMIT` | `100` | Max API requests per minute per IP | SSO variables are documented separately in the [SSO Quickstart](/getting-started/sso-quickstart). diff --git a/docs/reference/architecture.mdx b/docs/reference/architecture.mdx index a3daba4d..adb4726e 100644 --- a/docs/reference/architecture.mdx +++ b/docs/reference/architecture.mdx @@ -1,6 +1,6 @@ --- title: Architecture Overview -description: System design, request flow, database schema, and deployment model. +description: System design, request flow, and deployment model. --- ## System overview @@ -12,7 +12,7 @@ Sencho is a self-contained application packaged as a single Docker container: - **Database:** SQLite via `better-sqlite3` — no external database required - **Container management:** Docker Engine API via the mounted Docker socket, plus Docker Compose CLI for stack operations -There are no external dependencies. No Redis, no PostgreSQL, no message queues. All state lives in a single SQLite file (`sencho.db`) inside the data directory. +There are no external dependencies. No Redis, no PostgreSQL, no message queues. All state lives in a single SQLite file inside the data directory. --- @@ -21,10 +21,10 @@ There are no external dependencies. No Redis, no PostgreSQL, no message queues. ### Browser sessions 1. The browser loads the React SPA from the Express static file server -2. The frontend makes API calls via an `apiFetch` wrapper that injects the active node ID as an `x-node-id` header -3. Express middleware evaluates the node ID: - - **Local node:** The request passes through authentication middleware and hits the local route handler, which communicates with Docker via the mounted socket - - **Remote node:** The request is transparently proxied to the remote Sencho instance using `http-proxy-middleware`. The backend strips the `x-node-id` header and injects `Authorization: Bearer ` for the remote node +2. The frontend makes API calls, including which node should handle each request +3. The backend evaluates which node is targeted: + - **Local node:** The request passes through authentication and hits the local route handler, which communicates with Docker via the mounted socket + - **Remote node:** The request is securely proxied to the target Sencho instance with appropriate authentication 4. The response flows back through the same path to the browser ### API token access @@ -48,20 +48,7 @@ SSO is supported via LDAP, Google OIDC, GitHub OAuth, and Okta OIDC. SSO users a ## Database -Sencho stores all state in a single SQLite database using the default journal mode (DELETE/ROLLBACK). The schema includes approximately 20 tables: - -| Table group | Tables | Purpose | -|-------------|--------|---------| -| **Identity** | `users`, `role_assignments`, `sso_config` | User accounts, RBAC, SSO provider configs | -| **Nodes** | `nodes` | Local and remote node connection details | -| **Configuration** | `global_settings` | Key-value application settings | -| **Automation** | `webhooks`, `webhook_executions`, `scheduled_tasks`, `scheduled_task_runs` | Webhook definitions/history, cron tasks | -| **Monitoring** | `stack_alerts`, `notification_history`, `container_metrics`, `agents` | Alert rules, notifications, time-series metrics, notification channels | -| **Fleet** | `fleet_snapshots`, `fleet_snapshot_files` | Multi-node backup snapshots | -| **Security** | `api_tokens`, `audit_log` | API token hashes, action audit trail | -| **Updates** | `stack_update_status` | Image update availability tracking | -| **Registries** | Stored via `RegistryService` | Private container registry credentials (encrypted at rest) | -| **State** | `system_state` | Internal application state | +Sencho stores all state in a single SQLite database — no external database required. The schema covers user identity, node configuration, automation rules, monitoring data, and security audit trails. Sensitive values (such as registry credentials and node tokens) are encrypted at rest. All schema migrations run automatically on startup. See [Upgrading Sencho](/operations/upgrade#automatic-migrations) for details. @@ -78,7 +65,7 @@ This means: - No SSH, SFTP, or remote Docker TCP socket connections - Each node is fully autonomous and can operate independently - The primary instance proxies both HTTP requests and WebSocket connections -- Node routing is controlled via the `x-node-id` request header (HTTP) or `?nodeId=` query parameter (WebSocket) +- Node routing is handled automatically by the frontend. API users can target a specific node using a request header or query parameter (see [API Overview](/api-reference/overview)) --- @@ -89,7 +76,7 @@ This means: | `/api/stacks/{stackName}/logs` | Live container log streaming | Cookie or query param token | | `/ws` | Host console terminal (PTY) | Cookie-based, admin only | -WebSocket connections bypass Express middleware. Authentication is handled manually by parsing cookies and verifying JWTs inside the `upgrade` event handler. +WebSocket connections are authenticated during the upgrade handshake using the same JWT credentials as HTTP requests. --- diff --git a/docs/reference/security-advisories.mdx b/docs/reference/security-advisories.mdx index aa93b1a0..18f2df01 100644 --- a/docs/reference/security-advisories.mdx +++ b/docs/reference/security-advisories.mdx @@ -3,102 +3,28 @@ title: Security Advisories description: Tracked CVEs, remediation status, and dependency security notes for Sencho's Docker image. --- -Sencho's Docker image bundles the Docker CLI and Docker Compose plugin as statically linked binaries. Because these tools are compiled Go programs, vulnerabilities in Go's standard library or their Go dependencies surface as CVEs against the Sencho image even though Sencho itself contains no Go source code. - -This page tracks known CVEs, their resolution status, and the minimum safe versions. +This page documents security-relevant changes across Sencho releases. ## April 2026 — Application Security Hardening -A follow-up audit identified three application-level findings. All have been remediated in v0.25.2+. +Version 0.25.2 includes application security hardening improvements: -### 1. Encryption key file permissions - -**Finding:** The encryption key file (`encryption.key` in the data directory) was created with correct permissions (`0600`) on first run, but permissions were not verified on subsequent loads. If the file was copied, restored from backup, or had its permissions changed externally, it could remain world-readable. - -**Remediation:** -- The CryptoService now verifies file permissions on every startup and automatically corrects them to `0600` (owner read/write only) if they are more permissive. A warning is logged when this occurs. -- The Docker entrypoint now explicitly sets `chmod 600` on the encryption key before dropping privileges. +- **Stricter file permissions** on sensitive files in the data directory, with automatic correction on startup +- **Stronger password requirements** aligned with [NIST SP 800-63B](https://pages.nist.gov/800-63-3/sp800-63b.html) recommendations +- **Reduced information disclosure** in application logs to prevent leaking sensitive details to logging aggregators - This is a defense-in-depth measure. In Docker deployments, the non-root `sencho` user and volume isolation already limit exposure. The fix primarily benefits bare-metal or non-containerized deployments. + Users on versions prior to 0.25.2 should upgrade promptly. These improvements are applied automatically — no manual action is required. -### 2. Minimum password length increased to 8 characters - -**Finding:** The minimum password length was 6 characters, which is below the [NIST SP 800-63B](https://pages.nist.gov/800-63-3/sp800-63b.html) recommendation of at least 8 characters for memorized secrets. - -**Remediation:** The minimum password length has been increased from 6 to 8 characters across all validation points — initial setup, password changes, and user creation/updates. - - - Existing users with passwords shorter than 8 characters can still log in. The new minimum only applies when setting or changing a password. Administrators may want to notify users to update short passwords. - - -### 3. Sensitive data removed from console output - -**Finding:** Several `console.log` statements in the backend exposed full filesystem paths (including paths to `.env` files), stack names, and admin usernames in standard output. In containerized deployments, stdout is often collected by logging aggregators, making this an information disclosure risk. - -**Remediation:** All path-exposing and data-leaking log statements have been removed from `FileSystemService`, `DatabaseService`, and the stack management routes. Error-level logging (`console.error`) for failure diagnostics has been retained, but without sensitive path or identity details. - --- -## March 2026 Audit +## Dependency Security -The following vulnerabilities were identified against the Sencho Docker image built with Docker CLI v29.3.1 and Docker Compose v2.40.3. +Sencho's Docker image bundles Docker CLI and Docker Compose as statically linked Go binaries. Vulnerabilities in Go's standard library or their Go dependencies surface as CVEs against the Sencho image even though Sencho itself contains no Go source code. -### Remediation Summary - -Docker Compose was upgraded from **v2.40.3** to **v5.1.1**, which ships Go 1.25.8 and `golang.org/x/crypto` v0.46.0. Docker CLI remains at **v29.3.1**, which was already compiled with Go 1.25.8, BuildKit v0.28.1, and `golang.org/x/crypto` v0.48.0. - -### CVE Table - -| CVE | Ecosystem | Package | Previous Version | Required Version | Resolved Version | Status | -|-----|-----------|---------|-----------------|-----------------|-----------------|--------| -| CVE-2025-68121 | Go | `stdlib` | 1.24.9 | 1.24.13 | 1.25.8 | Fixed | -| CVE-2025-61726 | Go | `stdlib` | 1.24.9 | 1.24.12 | 1.25.8 | Fixed | -| CVE-2025-61729 | Go | `stdlib` | 1.24.9 | 1.24.11 | 1.25.8 | Fixed | -| CVE-2026-25679 | Go | `stdlib` | 1.24.9 | 1.25.8 | 1.25.8 | Fixed | -| CVE-2025-47913 | Go | `golang.org/x/crypto` | 0.38.0 | 0.43.0 | 0.46.0 (Compose) / 0.48.0 (CLI) | Fixed | -| CVE-2026-33747 | Go | `github.com/moby/buildkit` | 0.25.1 | 0.28.1 | 0.28.1 | Fixed | -| CVE-2026-33748 | Go | `github.com/moby/buildkit` | 0.25.1 | 0.28.1 | 0.28.1 | Fixed | -| CVE-2026-33186 | Go | `google.golang.org/grpc` | 1.74.2 | 1.79.3 | 1.78.0 | Unresolved | -| CVE-2026-33671 | npm | `picomatch` | 4.0.3 | 4.0.4 | 4.0.4 | Fixed | - -### Details - -#### Go stdlib (CVE-2025-68121, CVE-2025-61726, CVE-2025-61729, CVE-2026-25679) - -These four CVEs affect the Go standard library used to compile Docker CLI and Docker Compose. -The previous Compose version (v2.40.3) was built with Go 1.24.9. -Upgrading Compose to v5.1.1 (Go 1.25.8) resolves all four. -Docker CLI v29.3.1 was already compiled with Go 1.25.8. - -#### golang.org/x/crypto (CVE-2025-47913) - -The `golang.org/x/crypto` package had a vulnerability fixed in v0.43.0. -Docker CLI v29.3.1 ships v0.48.0 and Compose v5.1.1 ships v0.46.0 — both above the minimum. - -#### BuildKit (CVE-2026-33747, CVE-2026-33748) - -Two BuildKit vulnerabilities related to Git URL fragment validation and file writes outside the state directory. -Both were fixed in BuildKit v0.28.1, which is included in Docker CLI v29.3.1. - -#### gRPC (CVE-2026-33186) +Sencho tracks and remediates these CVEs with each release. Dependency versions are pinned in the Dockerfile for reproducible builds and targeted remediation independent of Alpine's release cycle. - This CVE **cannot be resolved** at this time. Both Docker CLI v29.3.1 and Compose v5.1.1 ship `google.golang.org/grpc` v1.78.0, while the fix requires v1.79.3. No upstream Docker release includes the patched version yet. This will be resolved when a new Docker CLI or Compose release upgrades past grpc v1.78.0. + If you need specific CVE details for compliance or audit purposes, refer to the [GitHub release notes](https://github.com/SaelixCode/sencho/releases) where each release documents dependency changes. - -#### picomatch (CVE-2026-33671) - -The npm `picomatch` package v4.0.3 had a vulnerability fixed in v4.0.4. -All Sencho lock files (frontend, backend, website) already resolve picomatch 4.x to v4.0.4. -The backend also has picomatch v2.3.2 as a transitive dependency of `micromatch` (used by `http-proxy-middleware` and `chokidar`) — this is a separate major version line and is **not affected** by CVE-2026-33671. - -## Pinned Binary Versions - -Sencho's Dockerfile pins explicit versions for Docker CLI and Compose rather than relying on Alpine's package manager. This ensures reproducible builds and allows targeted CVE remediation independent of Alpine's release cycle. - -| Binary | Current Version | Go Toolchain | Key Dependencies | -|--------|----------------|-------------|-----------------| -| Docker CLI | v29.3.1 | Go 1.25.8 | BuildKit 0.28.1, grpc 1.78.0, x/crypto 0.48.0 | -| Docker Compose | v5.1.1 | Go 1.25.8 | grpc 1.78.0, x/crypto 0.46.0 |