Skip to content

Fix docker-compose credential corruption and blank local database credentials - #1676

Merged
Alex Weininger (alexweininger) merged 3 commits into
feat/CoRfrom
alexweininger-fix-docker-compose-credentials
Aug 18, 2026
Merged

Fix docker-compose credential corruption and blank local database credentials#1676
Alex Weininger (alexweininger) merged 3 commits into
feat/CoRfrom
alexweininger-fix-docker-compose-credentials

Conversation

@alexweininger

@alexweininger Alex Weininger (alexweininger) commented Aug 14, 2026

Copy link
Copy Markdown
Member

Problem

Reference docs under resources/ inlined concrete credentials — for example a literal DATABASE_URL: postgresql://user:password@host:5432/db. The azure-debug-generate skill then wrote the same shape into generated local dev files such as docker-compose.yml. This produced two distinct failure modes.

1. Fatal YAML corruption

A secret-redaction filter rewrites the credential literal to a masked value. In YAML a leading * is an alias indicator, so the moment a redacted value lands in docker-compose.yml the file stops parsing entirely.

This is why redaction is fatal here rather than merely cosmetic: the masked text isn't just an unusable value, it's a syntax error. Local dev is dead on arrival, and the error the user sees points at YAML syntax — not at the credential that caused it.

2. Silent blank credentials

Where compose did use ${POSTGRES_USER} / ${POSTGRES_PASSWORD}, the skill often generated no workspace-root .env. Docker Compose interpolates ${...} from .env or the shell environment — never from a service's own environment: block. With no .env, the variables resolve to empty strings and Postgres starts with blank credentials, producing auth failures that look like application bugs.

Fix

The guidance now:

  • Declares local credentials once in a workspace-root .env, and references them via ${...} everywhere else.
  • Forbids inlining a concrete user:password@host URL into any generated file, and explains the YAML-alias rationale so the rule isn't mistaken for style advice.
  • Requires .env to exist and to declare every variable compose references — including a note that .env.example is documentation only and nothing loads it.
  • Documents which host to use in a connection string: localhost for clients running on the host machine (VS Code debug target, npm task) vs. the postgres service name for clients running inside compose.
  • Notes that local.settings.json is plain JSON with no ${...} interpolation, and that a runtime settings file does not cover host-run tasks such as npm run db:migrate.

.env must be gitignored, and secrets belong in no commit

Per review feedback: the guidance told the agent to create a workspace-root .env holding real credential values, but never said it must be ignored.

.gitignore must now list .env before the file is created, stated at each point where it actually gets written (emulators/postgres.md, references/generate.md), with an explicit instruction to fix a missing or incomplete .gitignore first. shared-references/architecture.md § .gitignore Additions carries the rule in full, since that section defines the generated .gitignore contents: a secret that reaches a commit is compromised the moment it is pushed, deleting it in a later commit does not undo that because the value stays in history and in every clone and fork, recovery means rotating the credential rather than editing a file, and private repositories are no exception.

azure-project-scaffold/instructions.md makes the ordering explicit — .gitignore is written in Step 2 (Foundation), before Step 3 (Configuration & Environment) creates env files.

Audit: no reference doc can seed a masked value

The remaining reference docs carrying a concrete credential literal are fixed too. This matters because a reference doc is read by the agent before it writes anything — the literal is masked in the agent's context by secret redaction and then transcribed verbatim into the generated project. All concrete credential literals under resources/ are now eliminated, so no reference doc can seed a masked value into a generated project.

  • azure-project-plan/plan.md — the two connection-string table rows are templates the planning agent fills in, so a masked value propagates into .azure/project-plan.md and downstream into generated code. Highest-impact of the set.
  • shared-references/resilience.md and shared-references/service-abstraction.md — the sample test setup now builds the URL from process.env via a template literal (quoting changed from single quotes to backticks accordingly).

Verified with git grep -lE '://[A-Za-z0-9_.-]+:[A-Za-z0-9_.!$-]+@' HEAD -- resources/ → zero files.

Files changed

  • resources/agents/azure-debug-generate/references/emulators/postgres.md
  • resources/agents/azure-debug-generate/references/generate.md
  • resources/agents/azure-project-plan/plan.md
  • resources/agents/azure-project-scaffold/instructions.md
  • resources/agents/shared-references/architecture.md
  • resources/agents/shared-references/resilience.md
  • resources/agents/shared-references/runtimes/python.md
  • resources/agents/shared-references/runtimes/typescript.md
  • resources/agents/shared-references/service-abstraction.md

Markdown instruction files only — no extension source changes, nothing to compile.

Related

The accompanying evaluation-system detection for this defect ships separately in #1669.

…ocal dev config

The `azure-debug-generate` skill wrote concrete credentials directly into
generated files (e.g. a literal `user:password@host` DATABASE_URL in
docker-compose.yml and local.settings.json). That produced two distinct
failure modes for local development:

1. Fatal YAML corruption. Secret-redaction filters rewrite a concrete
   credential literal to a masked value. In YAML a leading `*` is an alias
   indicator, so a redacted value makes docker-compose.yml stop parsing
   entirely. Local dev is dead on arrival, and the error points at YAML
   syntax rather than the credential that caused it.

2. Silent blank credentials. Where compose did use ${POSTGRES_USER} /
   ${POSTGRES_PASSWORD}, the skill often generated no workspace-root .env.
   Compose interpolates ${...} from .env or the shell environment, never
   from a service's own `environment:` block, so the values resolved to
   empty strings and Postgres started with blank credentials. The result
   is an auth failure that looks like an application bug.

The guidance now declares local credentials once in a workspace-root .env,
references them via ${...} everywhere else, forbids inlining a concrete
user:password@host URL, and requires .env to exist and to declare every
variable compose references (.env.example is documentation only — nothing
loads it). postgres.md also documents which host to use from the host
machine vs. from another compose service.

Co-authored-by: Copilot App <[email protected]>
@alexweininger

Copy link
Copy Markdown
Member Author

I ran into this while testing. Verified that my change does fix it. However, I am worried that what Copilot wrote here is too verbose

…nce docs

Three reference docs still carried a concrete user:password@host URL, which
is the same defect fixed for the azure-debug-generate emulator and runtime
docs. A reference doc is read by the agent before it writes anything, so a
literal there is masked in the agent's context by secret redaction and then
transcribed into the generated project — where a masked value beginning
with `*` is a fatal YAML alias error, or resolves to a blank credential.

- azure-project-plan/plan.md: the two rows on the connection-string tables
  are templates the planning agent fills in, so a masked value propagates
  into .azure/project-plan.md and downstream into generated code.
- shared-references/resilience.md and service-abstraction.md: the sample
  test setup now builds the URL from process.env via a template literal
  (quotes changed from single quotes to backticks accordingly).

All concrete credential literals under resources/ are now eliminated.

Co-authored-by: Copilot App <[email protected]>
… commit

Review feedback: the guidance told the agent to create a workspace-root
.env holding real credential values but never said it must be ignored.

Adds the requirement at each point where the file is actually written, and
states the underlying rule plainly: a secret that reaches a commit is
compromised and must be rotated. Deleting it in a later commit does not
undo that, because the value stays in history and in every clone and fork.
Private repositories are no exception.

- emulators/postgres.md, references/generate.md: .gitignore must list .env
  before it is created; fix a missing or incomplete .gitignore first.
- shared-references/architecture.md: the rule stated in full where the
  .gitignore contents are defined.
- azure-project-scaffold/instructions.md: .gitignore is written in Step 2,
  before Step 3 creates env files, so the ordering is now explicit.

Also tightens the prose added earlier in this PR, which ran long without
adding information. Net length is roughly unchanged despite the new rule.

Co-authored-by: Copilot App <[email protected]>

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.

LGTM

I ran into this while testing. Verified that my change does fix it. However, I am worried that what Copilot wrote here is too verbose

Yeah I noticed this too. I think it's because it's reiterating the same reminders across multiple agents. Ideally I would just like the agent responsible for creating the .env the first time to add it to .gitignore and not have to keep reiterating / reminding the later agents about it. In practice though I know an agent could accidentally skip and I could see the benefit of having those reminders as fallbacks in later phases, but that does come at the cost of instruction bloat.

@alexweininger
Alex Weininger (alexweininger) merged commit 936082f into feat/CoR Aug 18, 2026
2 checks passed
@alexweininger
Alex Weininger (alexweininger) deleted the alexweininger-fix-docker-compose-credentials branch August 18, 2026 16:26
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.

3 participants