- checkAuth() reads the expected token from a TOKEN_KINDS table (read -> HEALTH_TOKEN, ingest -> HOSTS_INGEST_TOKEN, claude-ingest -> CLAUDE_USAGE_INGEST_TOKEN); each ingestion token fails closed to a 503 naming its own variable. Still one gate, one tokenMatches() call. A route naming an unknown kind fails module load. - readBody() takes its ceiling per route: 4 KiB for /hosts/<id>, 64 KiB for /claude-usage. - claude-usage.js: pure, exported sanitizeClaudeUsage() for contract schemaVersion 1 (whitelist rebuild, invalid values dropped, list bounds). - POST /claude-usage: 400 on invalid JSON or schemaVersion, server-stamped receivedAt, atomic write (pid-suffixed temp, mode 600, rename), 204. - GET /claude-usage: re-sanitized snapshot with ageSeconds, stale and staleAfterSeconds (CLAUDE_USAGE_STALE_SECONDS, default 2700), or no_data. - Dockerfile COPY ships claude-usage.js. - auth.test.js: the checkAuth() equality allowlist loses the former `route.tokenKind === "ingest"` entry, which the table removes. The pin only got stricter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
41 lines
2.4 KiB
Text
41 lines
2.4 KiB
Text
PORT=3001
|
|
# HEALTH_TOKEN is read at runtime only (process.env at startup).
|
|
# On Coolify: MUST be is_runtime=true, is_buildtime=false.
|
|
# Buildtime ARG leaks the secret in clear in application_deployment_queues.logs.
|
|
HEALTH_TOKEN=change-me-to-a-strong-secret
|
|
LOGTO_HEALTH_URL=https://auth.lacompagniemaximus.com/oidc/.well-known/openid-configuration
|
|
# Directory read by GET /reports/scans and GET /defenseurs/findings. On Coolify
|
|
# it is its own read-only mount (/home/defenseur/defenseurs/reports ->
|
|
# /data/defenseurs/reports:ro), nested inside the read-only /data/defenseurs
|
|
# mount that holds status.json.
|
|
REPORTS_DIR=/data/defenseurs/reports
|
|
|
|
# --- Workstation snapshots (POST /hosts/<id>, GET /hosts) --------------------
|
|
# Directory the API WRITES workstation snapshots into, one <id>.json per host.
|
|
# Unlike the two /data/defenseurs mounts (read-only `:ro`; content owned by
|
|
# defenseur:defenseur, uid/gid 1001, group-writable but not by gid 1000), this
|
|
# one must be writable by uid 1000 (the `node` user the container runs as) or
|
|
# every ingest answers 500.
|
|
HOSTS_DIR=/data/hosts
|
|
# Comma-separated allowlist of host ids. Each entry must match
|
|
# ^[a-z0-9][a-z0-9-]{0,31}$ — anything else is logged and dropped at startup.
|
|
HOSTS_ALLOWED_IDS=thinkpad
|
|
# Bearer token for POST /hosts/<id>. Separate from HEALTH_TOKEN on purpose: a
|
|
# workstation agent should be able to write its own snapshot without gaining
|
|
# read access to the Defenseurs reports. Same Coolify rule as HEALTH_TOKEN —
|
|
# is_runtime=true, is_buildtime=false. Unset -> POST /hosts/<id> answers 503.
|
|
HOSTS_INGEST_TOKEN=change-me-to-a-strong-secret
|
|
# Age (seconds) past which GET /hosts reports a host as offline. Default 900.
|
|
HOSTS_STALE_SECONDS=900
|
|
|
|
# --- Claude usage snapshot (POST /claude-usage, GET /claude-usage) ---------
|
|
# Bearer token for POST /claude-usage, pushed by the ThinkPad agent. Distinct
|
|
# from HOSTS_INGEST_TOKEN and HEALTH_TOKEN: each one is refused on the others'
|
|
# routes. Same Coolify rule — is_runtime=true, is_buildtime=false. Unset ->
|
|
# POST /claude-usage answers 503.
|
|
CLAUDE_USAGE_INGEST_TOKEN=change-me-to-a-strong-secret
|
|
# File the API WRITES (atomic rename). Its directory must be its own read-write
|
|
# mount owned by uid 1000, like HOSTS_DIR.
|
|
CLAUDE_USAGE_PATH=/data/claude-usage/claude-usage.json
|
|
# Age (seconds) past which GET /claude-usage reports stale: true. Default 2700.
|
|
CLAUDE_USAGE_STALE_SECONDS=2700
|