Stu Mason · Folkestone, Kent
One entryMerged PR· StuMason/coolify-mcp· TypeScript

PR #256 merged: security: mask db passwords, connection URLs, and compose bodies on list_resources include_full (#209)

additions
+193
deletions
-3
files changed
4

Summary

Closes #209 — the follow-up audit of system({ action: 'list_resources', include_full: true }) sensitive surfaces.

Method: source audit instead of live sampling

Rather than hitting a live instance (which only shows the resource types that happen to be deployed), this audit read the Coolify v4.1.2 sourceResourcesController, all 8 Standalone* database models, Application, and Service. Ground truth findings:

  • No model in the /resources path defines $hidden — every attribute serializes.
  • Laravel serializes encrypted casts as decrypted plaintext in toArray() — so postgres_password, mysql_root_password, etc. come back in the clear on every database row.
  • internal_db_url / external_db_url are $appends accessors on all eight database types that embed the password in a connection URL. This is the sneaky one: Redis's password leaks ONLY through these URLs (its column was moved to env vars in migration 2024_10_16), so masking password columns alone would miss it.
  • mariadb_root_password isn't even encrypted-cast upstream (unlike the mysql equivalent) — same exposure either way at the API.
  • Nested environment_variables[] is confirmed ABSENT at v4.1.2 — the relation is lazy and the controller never loads it. Good news for the issue's main suspect.

Changes

SENSITIVE_RESOURCE_FIELDS gains (all masked to '***' on include_full: true unless reveal: true):

  • 9 database password columns (postgres, mysql ×2, mariadb ×2, mongo, keydb, dragonfly, clickhouse) + redis_password defensively
  • internal_db_url, external_db_url
  • docker_compose_raw, docker_compose, docker_compose_pr_raw, docker_compose_pr — Coolify resolves SERVICE_PASSWORD_* placeholders into resolved compose
  • custom_labels — Traefik basic-auth labels carry htpasswd hashes

Plus a defensive nested environment_variables[] walker (masks value/real_value per entry, mirroring maskEnvVar) in case any Coolify version/fork inlines the relation — the nested copy would otherwise bypass the env_vars pipeline entirely.

Null values stay null (null = "no secret set" is signal). Full compose/config content remains available via get_application / get_service / reveal: true.

Out of scope (per the issue)

  • dockerfile, custom_nginx_configuration, *_conf, init_scripts — config, not credentials; left visible.
  • Field renaming/API tidy.

Verification

  • npm test: 412/412 (6 new tests: password columns, URL appends, compose/labels, nested env walker, reveal round-trips)
  • One existing test updated: exact raw round-trip assertion now lives under reveal: true (which is the behavior that round-trips post-#209)
  • Recommend a live /smoke-test after merge as belt-and-braces — this PR's ground truth is the v4.1.2 source, pinned to the estate's current version

🤖 Generated with Claude Code