Stu Mason
Stu Mason

Activity

Issue Opened

Issue #209 opened: Audit list_resources include_full for other sensitive nested surfaces (env_vars, private_keys, etc.)

Follow-up to #204 (merged via #206).

#204 / #206 mask five top-level secret fields on each row returned by system({ action: 'list_resources', include_full: true }):

  • manual_webhook_secret_github
  • manual_webhook_secret_gitlab
  • manual_webhook_secret_gitea
  • manual_webhook_secret_bitbucket
  • http_basic_auth_password

But the raw Coolify /api/v1/resources row carries ~95 fields, and some of them are themselves references / nested collections that may surface additional sensitive data depending on the resource type and configuration. Worth a focused audit pass.

Surfaces to look at

  • environment_variables[] — appears nested on application / service / database rows in some Coolify configurations. Each entry carries value / real_value, which the dedicated env_vars pipeline masks. The nested copy on /resources is not routed through that pipeline today, so if it's present in the payload it's a leak vector.
  • private_keys references — applications point to keys by UUID. The UUIDs themselves are not secrets (you still need the API token to pull the private-key body), but if /resources ever inlines the key body alongside the reference, that needs masking.
  • docker_compose_raw — base64-encoded compose YAML. Not a secret on its own, but compose files frequently embed registry creds, env defaults, etc. that the user may not intend to expose.
  • custom_labels — base64-encoded Traefik label config. Same risk profile as compose.
  • Database-specific fields — root password, replication password, connection strings for postgres / mysql / mariadb / mongo / redis / clickhouse / dragonfly / keydb rows. If any of these are returned on the /resources row (vs. only on GET /databases/{uuid}), they need masking.

What to do

  1. Hit a live Coolify v4 instance with one of each resource type (app, service, every supported database, plus a couple with is_http_basic_auth_enabled and various webhook providers wired up).
  2. system({ action: 'list_resources', include_full: true, reveal: true }) and grep every value field for anything that looks like a credential or compose blob.
  3. For each new sensitive surface found:
    • Add to SENSITIVE_RESOURCE_FIELDS if it's a single top-level field.
    • Add a dedicated walker if it's nested (e.g. mirror maskEnvVar over the embedded environment_variables[]).
  4. Add a test per masked field, matching the existing pattern in coolify-client.test.ts > listResources > sensitive-field masking on include_full.

Out of scope

  • Field-level renaming or other API tidy. This is masking-only.
  • Anything that requires changing the Coolify-side response shape — we mask at the MCP boundary regardless.

Refs

  • #204 — original webhook secret + basic-auth masking issue
  • #206 — implementation PR
  • #159 / #182 — the v2.9.0 env_vars masking pattern this would mirror for nested env-var collections