This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
# Security threat model
|
||||
|
||||
## 1. Assets
|
||||
|
||||
- Unraid host and storage.
|
||||
- Docker/container metadata and internal topology.
|
||||
- Prometheus metrics and labels.
|
||||
- Service URLs and availability data.
|
||||
- OIDC identities/roles.
|
||||
- Pulse configuration, alerts, incidents and audit.
|
||||
- Notification/probe credentials.
|
||||
- Database backups.
|
||||
- Server access path used by Codex during deployment.
|
||||
|
||||
## 2. Trust boundaries
|
||||
|
||||
- Browser to Pulse.
|
||||
- Pulse to Authentik.
|
||||
- Pulse to PostgreSQL.
|
||||
- Pulse to Prometheus.
|
||||
- Pulse agent to host/Unraid/Docker.
|
||||
- Probe worker to network targets.
|
||||
- Notification worker to external channels.
|
||||
- Codex workspace to production server.
|
||||
|
||||
## 3. Primary threats and controls
|
||||
|
||||
### M0 discovered deployment posture
|
||||
|
||||
The target host already runs Nginx Proxy Manager, Authentik, Grafana, and multiple Docker/Compose projects. Pulse treats all of them as external protected resources. Existing containers with broad privileges, including any Docker socket access, are not reused as a Pulse pattern. Pulse must be isolated on dedicated resources, integrate with the proxy and OIDC provider additively, and remain server-side for all Unraid/Prometheus access.
|
||||
|
||||
M0 did not identify a local Prometheus service; this is an explicit datasource uncertainty, not permission to substitute an unreviewed source. Missing or stale telemetry must map to `Unknown`. No hostname, port, network, volume, or path is trusted until the deployment task rechecks ownership and conflicts.
|
||||
|
||||
### Unrestricted Docker/host control
|
||||
|
||||
Threat: compromise of web/API leads to host root-equivalent access.
|
||||
|
||||
Controls:
|
||||
- no unrestricted socket in web/API;
|
||||
- separate agent/proxy;
|
||||
- endpoint allowlist;
|
||||
- read-only capability contract;
|
||||
- non-root API;
|
||||
- network separation;
|
||||
- architecture test of compose/mounts;
|
||||
- no mutation API in v1.
|
||||
|
||||
### SSRF from service probes
|
||||
|
||||
Threat: user config probes metadata, loopback, admin services or redirects.
|
||||
|
||||
Controls:
|
||||
- role restriction;
|
||||
- scheme/port allowlist;
|
||||
- DNS resolution validation before and after redirect;
|
||||
- block metadata/link-local/unspecified by default;
|
||||
- configurable LAN allowlist;
|
||||
- response size/time limits;
|
||||
- no arbitrary methods/body;
|
||||
- redacted logging;
|
||||
- tests for DNS rebinding/redirect escape.
|
||||
|
||||
### Query abuse
|
||||
|
||||
Threat: expensive or injection-like Prometheus queries cause outage or expose labels.
|
||||
|
||||
Controls:
|
||||
- semantic query templates;
|
||||
- bounded scope/range/series/points;
|
||||
- server-side parameterization/escaping;
|
||||
- timeout/concurrency/rate limit;
|
||||
- advanced raw query separate permission;
|
||||
- audit and query cost metrics.
|
||||
|
||||
|
||||
### Optional hardware capabilities
|
||||
|
||||
Hardware sensors and GPU telemetry are optional read-only capabilities. The adapter accepts only normalized bounded snapshots from an approved source; absent support is disabled and unsupported support remains inspectable without being treated as a host failure. API/web never gains device, mount, namespace or Docker-socket access for these values.
|
||||
### Authentication/authorization bypass
|
||||
|
||||
Controls:
|
||||
- standards-based OIDC validation;
|
||||
- issuer/audience/nonce/state/PKCE;
|
||||
- secure cookies;
|
||||
- server-side RBAC;
|
||||
- WebSocket auth/origin/subscription auth;
|
||||
- CSRF protection where cookies are used;
|
||||
- role matrix tests;
|
||||
- break-glass disabled by default.
|
||||
|
||||
### XSS and dashboard import
|
||||
|
||||
Controls:
|
||||
- no arbitrary HTML/JS widgets;
|
||||
- sanitize Markdown;
|
||||
- schema validation;
|
||||
- safe chart labels/tooltips;
|
||||
- CSP;
|
||||
- escaped event/upstream text;
|
||||
- import size and complexity limits.
|
||||
|
||||
### Secret leakage
|
||||
|
||||
Controls:
|
||||
- external secret injection;
|
||||
- encrypted-at-rest channel/probe references;
|
||||
- redaction middleware;
|
||||
- no env dumps;
|
||||
- evidence policy;
|
||||
- secret scan;
|
||||
- diagnostic bundle allowlist;
|
||||
- backups exclude plaintext or are encrypted/secured.
|
||||
|
||||
### Supply chain
|
||||
|
||||
Controls:
|
||||
- lockfiles;
|
||||
- minimal maintained dependencies;
|
||||
- provenance/SBOM where feasible;
|
||||
- vulnerability scanning;
|
||||
- pinned base images;
|
||||
- non-root runtime;
|
||||
- update policy;
|
||||
- build in CI/clean environment.
|
||||
|
||||
### Database compromise/data integrity
|
||||
|
||||
Controls:
|
||||
- isolated network;
|
||||
- dedicated credentials;
|
||||
- TLS when remote;
|
||||
- least privilege;
|
||||
- migrations/transactions;
|
||||
- backup/restore;
|
||||
- input validation;
|
||||
- audit;
|
||||
- no exposed database port unless controlled testing override.
|
||||
|
||||
### Live/WebSocket abuse
|
||||
|
||||
Controls:
|
||||
- authentication before upgrade;
|
||||
- opaque HttpOnly sessions with an eight-hour sliding idle limit and a finite,
|
||||
operator-bounded absolute limit; renewal never exposes OIDC tokens to the
|
||||
browser, while a revocable session context propagates through HTTP upgrades
|
||||
so logout, absolute expiry and request/server cancellation also close an
|
||||
already established socket and release its subscriptions;
|
||||
- origin policy;
|
||||
- message/rate/size limits;
|
||||
- max subscriptions/series;
|
||||
- idle timeout/heartbeat;
|
||||
- bounded send queue and slow-client eviction;
|
||||
- no secret data in messages.
|
||||
|
||||
### Alert/notification abuse
|
||||
|
||||
Controls:
|
||||
- RBAC and audit;
|
||||
- versioned rules;
|
||||
- safe templates;
|
||||
- channel test rate limits;
|
||||
- idempotency;
|
||||
- recipient allowlist/policy;
|
||||
- no secret values in notification body.
|
||||
|
||||
### Deployment mistakes
|
||||
|
||||
Controls:
|
||||
- discovery and port/network/volume conflict checks;
|
||||
- backup touched configs;
|
||||
- isolated compose project;
|
||||
- offline validation;
|
||||
- health/smoke tests;
|
||||
- rollback;
|
||||
- no prune/delete/unrelated modifications;
|
||||
- production evidence.
|
||||
|
||||
## 4. Security headers
|
||||
|
||||
At minimum:
|
||||
- Content-Security-Policy;
|
||||
- frame restrictions;
|
||||
- nosniff;
|
||||
- strict referrer policy;
|
||||
- permissions policy;
|
||||
- HSTS when HTTPS deployment is stable;
|
||||
- secure/same-site/httpOnly cookies.
|
||||
|
||||
## 5. Container hardening
|
||||
|
||||
Where compatible:
|
||||
- non-root;
|
||||
- read-only root filesystem;
|
||||
- tmpfs for temporary paths;
|
||||
- drop all capabilities, add only required;
|
||||
- no-new-privileges;
|
||||
- seccomp/default profile;
|
||||
- resource limits;
|
||||
- explicit networks;
|
||||
- no public database/collector ports;
|
||||
- healthchecks;
|
||||
- immutable image digest in production record.
|
||||
|
||||
The agent may need narrow exceptions; document and test each.
|
||||
|
||||
### Deployment hardening pass (2026-08-04)
|
||||
|
||||
- Resource limits are set on all six `deploy/compose.yaml` services via the
|
||||
non-swarm `cpus`/`mem_limit`/`mem_reservation`/`memswap_limit` keys (the
|
||||
project runs plain `docker compose up`, not swarm); sizing rationale is
|
||||
inline in that file against `docs/architecture/SYSTEM_ARCHITECTURE.md` §7.
|
||||
- `pulse-postgres` now runs `read_only: true` with tmpfs for `/tmp` and
|
||||
`/var/run/postgresql`; all six services are now read-only-root. This
|
||||
closes the previously undocumented exception; see
|
||||
`docs/operations/DEPLOYMENT_UNRAID.md` §6/§8 for the required smoke test.
|
||||
- Immutable image digests are enforced by `deploy/verify-image-digests.sh`
|
||||
and CI. Every external registry image in `deploy/*.Dockerfile` is pinned to
|
||||
a verified digest; only Docker's built-in `scratch` rootfs is exempt because
|
||||
it has no registry manifest. See `deploy/IMAGE_DIGESTS.md` for the ledger.
|
||||
- `pulse-worker`/`pulse-agent` healthchecks now verify a heartbeat file's
|
||||
freshness instead of `kill -0 1`, and self-restart the container on
|
||||
staleness (`docker compose up` does not restart on "unhealthy" status
|
||||
alone). Contract for the Go runtime:
|
||||
`docs/operations/WORKER_AGENT_HEALTHCHECK_CONTRACT.md`.
|
||||
- `deploy/nginx.conf` now sends `Strict-Transport-Security` from
|
||||
`pulse-web` as defence-in-depth (§4), verified not to conflict with
|
||||
TLS terminating at Nginx Proxy Manager per ADR-0010.
|
||||
|
||||
## 6. Security acceptance
|
||||
|
||||
Required:
|
||||
- threat model review at M0 and M9;
|
||||
- SAST/dependency/image/secret scans;
|
||||
- auth/RBAC matrix tests;
|
||||
- SSRF suite;
|
||||
- WebSocket security suite;
|
||||
- dashboard import/XSS suite;
|
||||
- query limit/validation suite;
|
||||
- compose privilege/mount test;
|
||||
- backup secret inspection;
|
||||
- production exposure scan from permitted network.
|
||||
|
||||
### M7-02 implementation
|
||||
|
||||
The probe policy validates scheme, host and port before resolution, rejects loopback/link-local/metadata/multicast/unspecified addresses, and requires explicit CIDR permission for private LAN targets. The safe client repeats resolution at dial time and revalidates redirect destinations. Only bounded GET/HEAD requests with an allowlisted header set are accepted; responses are size/time limited. Policy changes are audit events containing no credentials or full request data.
|
||||
### M8-08 notification controls
|
||||
|
||||
Notification channel persistence accepts only secret references and rejects sensitive configuration keys such as token, password, secret, and authorization. Delivery bodies and subjects are bounded. Sender errors are newline-normalized, length-bounded, and redacted before persistence; bearer credentials are removed as a complete value. Test sends are rate limited and never write delivery records.
|
||||
Reference in New Issue
Block a user