# CL-0021: Credential Embedded in Connection-String Env Value

**Severity:** HIGH

**References:**
- [OWASP Docker Security Cheat Sheet — Rule #12: Utilize Docker Secrets for Sensitive Data Management](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-12-utilize-docker-secrets-for-sensitive-data-management)
- [Compose `secrets:` top-level element](https://docs.docker.com/reference/compose-file/secrets/)
- [RFC 3986 §3.2.1 — userinfo deprecation for passwords](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.1)

## What it detects

Environment variable **values** that contain a literal `scheme://user:password@host` userinfo segment, regardless of the key name.

```yaml
services:
  app:
    environment:
      DATABASE_URL: postgresql://postgres:hunter2@db:5432/app  # fires
      MONGO_URL: mongodb://root:example@mongo:27017/           # fires
      AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow  # fires
```

The value-side regex matches any URI scheme (`[a-zA-Z][a-zA-Z0-9+.\-]*`) followed by `://user:password@`, where the password half is non-empty and contains no `$` (variable substitution). The username half may be empty: RFC 3986 §3.2.1 permits it, and `redis://:password@host` — the standard Redis URL form — still leaks the password, so it fires.

Skipped:

- Password half contains `${VAR}` or `$VAR` — the credential is parameterized. (A `$`-valued *username* with a literal password still fires: the password leaks regardless.)
- Userinfo password is empty (`scheme://user:@host`) — no credential to leak.
- No userinfo at all (`scheme://host/path`) — nothing to flag.
- Empty or non-string value.

This rule is **key-name-agnostic and value-shaped**, the inverse of [CL-0020](CL-0020.md). They are independent and may both fire on the same service when both surfaces apply (e.g., a separate `DB_PASSWORD: hunter2` and a `DATABASE_URL: postgres://app:hunter2@db/app`).

## Why it matters

The exposure surface is identical to CL-0020 — env vars propagate through `docker inspect`, `/proc/<pid>/environ`, `docker compose config`, process listings, and CI logs. Embedding the credential in a URL doesn't add or remove any leak path, it just hides the credential from key-pattern scanning.

This is the largest detection gap that key-pattern rules miss in real-world Compose files. In a corpus of 1554 real `docker-compose.yml` files, 151 contained a literal `user:password@` segment in an `environment:` value — none of which would have been caught by credential-shaped key matching alone.

RFC 3986 §3.2.1 deprecates passing passwords in URI userinfo regardless of Docker context: *"Use of the format 'user:password' in the userinfo field is deprecated."* This rule covers the Compose-specific exposure on top of the general URI hygiene concern.

## Fix

Three remediation patterns, in order of preference:

### 1. Move the credential to `secrets:` and reassemble the URL at startup (best)

```yaml
services:
  app:
    image: myapp:1.0
    secrets:
      - db_password
    # Entrypoint reads /run/secrets/db_password and exports
    # DATABASE_URL=postgresql://app:$(cat /run/secrets/db_password)@db/app
    # before exec'ing the workload.

secrets:
  db_password:
    file: ./secrets/db_password.txt   # add to .gitignore
```

### 2. Use the image's `*_FILE` convention for the password component (when supported)

Some clients (e.g., Postgres, libpq via `PGPASSFILE`) read the password from a file referenced by env, leaving the rest of the connection string in plain env vars.

### 3. Substitute the password from process env (interim improvement)

```yaml
services:
  app:
    environment:
      DATABASE_URL: postgresql://app:${DB_PASSWORD}@db/app
```

This pulls the credential from the operator's environment instead of committing it to the file. Less secure than option 1 (the credential still ends up in container env), but a meaningful improvement over a literal value and a one-line change.

## See also

- [CL-0020](CL-0020.md) — credential-shaped env keys with literal values (key-side detection of the same exposure surface).
- [CL-0010](CL-0010.md) — host PID/IPC namespace sharing (cross-container `/proc/<pid>/environ` exposure).
