Skip to content

init never writes a credential into the schema as a default (BL-208, P0) - #97

Merged
rabbilyasar merged 3 commits into
mainfrom
rabbilyasar/bl-208-import-credentials
Oct 28, 2026
Merged

rabbilyasar merged 3 commits into
mainfrom
rabbilyasar/bl-208-import-credentials

Conversation

@rabbilyasar

Copy link
Copy Markdown
Owner

Fixes #94

In one line: init no longer copies a password into the committed env.schema.toml (or the .env.example built from it) when a value holds a credential or a variable's name means one.

Problem (BL-208, P0 under AGENTS.md §4)

  • init judged each .env value secret or not by its name and a value check, then wrote every non-secret value as a schema defaultValue.
  • REDIS_URL=redis://:password@cache:6379 became a default, password included.
  • On the V2-1 · Top-level schema: in envshield.yml #18 trial, ..._PASSPHRASE, GOOGLE_API_CREDENTIALS and DATABASE_URL-style variables did too.

Root cause and design

Both schema writers decide through importer._classify_variable, the import path behind init and init --from and the merge of other config files. So the fix covers the whole class in one place:

  1. value_embeds_credential:
    • A URL whose userinfo has a password is secret whatever its name. That covers any scheme, user:pass@ or :pass@, percent-encoded or not; a malformed URL is judged by its text.
    • So is a connection string with a password=/pwd= field.
    • A URL with only a username keeps its default.
  2. Name check:
    • A keyword's plural now matches as a whole token (CREDENTIALS, KEYS, TOKENS). MONKEYS and AUTHORS still don't.
    • New keywords: passphrase and passwd.
  3. Output: init names each variable whose value held a password.

Alternatives

  • Marking every *_URL secret by name: rejected. It would drop safe defaults (API_BASE=https://…) and still miss credentials under other names.
  • Never writing defaults at all: rejected. It would lose the useful non-secret defaults (ports, flags, public URLs).

Security

Invariant: a secret value must never enter an output representation; here, a committed file. The tests exercise the class, not the reported case. More names now count as secret (plurals, passphrase), which is the safe direction. setup uses the same name check to decide which prompts hide input, so it hides those too.

Compatibility

Re-importing may now mark a few more variables secret and drop their default. An existing schema's hand-set entries are kept as before.

Tests

  • envshield/tests/test_import_credentials.py (27 tests). Every credential form: Redis :pass@, Postgres, mongodb+srv with an encoded password, AMQP, ADO.NET, libpq, ODBC.
  • The class goes through init, init --from (.env, a Compose manifest, a Python module with --as-local-values, --force) and the merge path. Each asserts the password is in neither env.schema.toml, .env.example nor the output.
  • Against the old importer, 17 of the 27 fail.
  • Full suite: 2368 passed, 1 skipped. ruff check and ruff format --check clean.

Re-run on the trial repositories

Fresh init on cal.com, Documenso, Formbricks, Plane and PostHog, with a credential-bearing postgres:// URL for every *_URL/*_URI/*_DSN key:

  • no password reached any schema or .env.example (17 variables would have leaked in cal.com alone);
  • the PASSPHRASE/CREDENTIALS names are secret with no default;
  • each run names the variables it marked secret.

Not fixed here

BL-209 / #96 (P1, needs a decision): a project that ran init before this fix may already have a password committed in its schema. scan, the pre-commit hook and check all pass it.

Branch note

The branch merges main rather than being rebased: the force-push a rebase needs was blocked. Squash-merging gives one commit either way.

Product / market impact

Table stakes: "secret values never leave the place they belong" (§1).

🤖 Generated with Claude Code

https://claude.ai/code/session_01376EvgPvifpHL8LP5BYoPc

…ue (BL-208)

Both schema writers decide through importer._classify_variable, so the
fix is there:

- value_embeds_credential: a URL whose userinfo has a password (any
  scheme, `user:pass@` or `:pass@`) or a connection string with a
  password=/pwd= field is secret, so it gets no default, whatever its
  name. A URL with only a user name keeps its default.
- The name check also matches a keyword's plural as a whole token
  (CREDENTIALS, KEYS, TOKENS) and the keywords passphrase and passwd.
- init names each variable whose value held a password.

Tests cover every credential form through init, init --from (.env,
Compose, a Python module with --as-local-values, --force) and the merge
path; 17 of the 27 fail on the old importer.

Fixes #94.
@gitguardian

gitguardian Bot commented Oct 28, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
39174296 Triggered Generic Password b19192a envshield/tests/test_import_credentials.py View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@rabbilyasar
rabbilyasar merged commit c6b6c30 into main Oct 28, 2026
4 checks passed
@rabbilyasar
rabbilyasar deleted the rabbilyasar/bl-208-import-credentials branch October 28, 2026 19:25
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.

init writes a password from a URL into the committed schema as a defaultValue (BL-208)

1 participant