Skip to content

[fix] neutralize COMPOSE_PROFILES leak, log swallowed persist error - #183

Merged
malickyeu merged 1 commit into
release/v1.6.1from
fix/profiles-env-precedence
Aug 14, 2026
Merged

[fix] neutralize COMPOSE_PROFILES leak, log swallowed persist error#183
malickyeu merged 1 commit into
release/v1.6.1from
fix/profiles-env-precedence

Conversation

@malickyeu

Copy link
Copy Markdown
Contributor

Summary

Fix-forward from a post-merge review of #180 (Compose profiles: deployed-vs-selected state + badges), which surfaced two findings:

  • COMPOSE_PROFILES can activate more than what gets persisted. handleDeployProject persists exactly body.Profiles as "last deployed profiles". But docker compose up also honors COMPOSE_PROFILES from the server process's own environment or a project's own .env file (compose auto-loads .env from its working directory). Verified empirically against a real docker compose up (Compose v5.4.0): once any --profile flag is passed, Compose uses it exclusively and ignores COMPOSE_PROFILES entirely — so the leak specifically happens when no profiles are selected (no --profile flag at all), where Compose falls back to COMPOSE_PROFILES and can activate services the deploy request never asked for. Those services then get misbadged "Not in active profile" even though they're genuinely running — the exact bug [add] compose profiles: deployed-vs-selected state + badges #180 was built to fix, reintroduced by a side channel.

    Fix: ComposeUpFiles now always sets COMPOSE_PROFILES= (empty) on the subprocess environment, so the explicit --profile flags (built from the caller's authoritative profiles argument) are the sole profile source. Confirmed empirically that a process-env override wins over a project's own .env file too (shell/process env takes precedence over .env for compose-level variables) — details and the full precedence order are written up in docs/gotchas.md.

  • The persist error for "last deployed profiles" was completely swallowed. Kept the existing behavior (a storage hiccup must not turn a successful deploy into a reported failure), but the failure is no longer invisible — it's now logged server-side, matching the existing convention for other best-effort store writes in this codebase (e.g. monitor: insert alert event: %v). Decided a server log is the right layer rather than also polluting the project.deploy audit entry — that trail records user actions, and this is an infra hiccup unrelated to what the user did.

Type of change

  • Bug fix
  • New feature
  • Docs only
  • Refactor / chore

Checklist

  • go test -short ./... and go vet ./... pass
  • gofmt gate is clean (gofmt -l $(git ls-files '*.go') after staging)
  • Frontend type-checks (cd web && npx tsc --noEmit) — N/A (no web/src changes)
  • Rebuilt and committed web/dist — N/A (no web/src changes)
  • Added/updated tests for the change
  • Updated docs/ and added a CHANGELOG.md entry for user-facing changes

Notes for reviewers

  • New test TestHandleDeployProject_EnvFileComposeProfilesDoesNotLeakIn drives a real docker compose up against a fixture project whose .env sets COMPOSE_PROFILES=extra (a profile not in the deploy request) and asserts, via docker ps on the actual compose-labeled containers (not just the persisted DB value), that the extra service never started. Mutation-tested: reverting the COMPOSE_PROFILES= override makes it fail with the extra service running.
  • New test TestHandleDeployProject_PersistFailureIsLoggedNotSwallowed forces SetLastDeployedProfiles to fail against a real sqlite write, by opening a second raw connection to the same db file and installing a trigger that aborts writes to just that one column on just that project's row (closing the whole store, as TestRBACFailsClosedOnStoreError does elsewhere, would also break the handler's earlier reads before it ever reaches the write under test). Asserts the deploy response still reports ok:true while the failure shows up in the log. Mutation-tested: removing the log.Printf call makes it fail.
  • Both real-Docker deploy test runs verified twice in a row leave zero leftover containers.
  • Ran go test ./internal/api/... ./internal/docker/... ./internal/store/... (no -short, real Docker daemon) in addition to go test -short ./... — both green.

Post-merge review of #180 found two issues in handleDeployProject:

- An operator's own environment or a project's .env file can set
  COMPOSE_PROFILES, which docker compose falls back to whenever no
  --profile flag is on the command line (i.e. an empty profile
  selection) — activating more than what gets persisted as "last
  deployed profiles" and misbadging a running service "Not in active
  profile". ComposeUpFiles now sets COMPOSE_PROFILES="" on the
  subprocess so the explicit --profile flags are the sole source.
- A failed SetLastDeployedProfiles write was completely swallowed.
  It still doesn't fail the deploy response, but now logs.
Copilot AI lite review requested due to automatic review settings August 14, 2026 20:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@malickyeu
malickyeu merged commit 33be4db into release/v1.6.1 Aug 14, 2026
1 check passed
@malickyeu
malickyeu deleted the fix/profiles-env-precedence branch August 14, 2026 21:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants