PR #256 merged: security: mask db passwords, connection URLs, and compose bodies on list_resources include_full (#209)
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 source — ResourcesController, all 8 Standalone* database models, Application, and Service. Ground truth findings:
- No model in the
/resourcespath defines$hidden— every attribute serializes. - Laravel serializes
encryptedcasts as decrypted plaintext intoArray()— sopostgres_password,mysql_root_password, etc. come back in the clear on every database row. internal_db_url/external_db_urlare$appendsaccessors 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 migration2024_10_16), so masking password columns alone would miss it.mariadb_root_passwordisn'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_passworddefensively internal_db_url,external_db_urldocker_compose_raw,docker_compose,docker_compose_pr_raw,docker_compose_pr— Coolify resolvesSERVICE_PASSWORD_*placeholders into resolved composecustom_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-testafter 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