Skip to content

feat: support configurable quote character for dotenv export - #389

Open
mateuscmtropical wants to merge 2 commits into
Infisical:mainfrom
mateuscmtropical:feature/export-quote-char
Open

mateuscmtropical wants to merge 2 commits into
Infisical:mainfrom
mateuscmtropical:feature/export-quote-char

Conversation

@mateuscmtropical

@mateuscmtropical mateuscmtropical commented Sep 4, 2026

Copy link
Copy Markdown

Description 📣

Implements #1103 (possibility to export env-s with different quote character).

infisical export always wrapped values in single quotes. Single quoted dotenv values have no escape sequences, so a multiline value (e.g. a PEM private key) can't be represented with a real newline — you'd have to post-process the file afterward.

  • adds --dotenv-quote-char (default ', existing output unchanged), accepting ' or "
  • double-quote mode lets the existing multiline encoding decode back into a real newline on read
  • only affects the dotenv format now (see security fix below for why dotenv-export was excluded)
  • validated immediately after flag parsing, before any network call or the --template path

Security fix (added after review)

The repo's automated reviewer flagged that double-quote mode combined with --format=dotenv-export let a secret containing $(cmd) execute code when the output was sourced by a shell. Investigating further turned up that dotenv-export's output was never safe to source, independent of this PR:

  • the default single-quote wrapping didn't escape an embedded ' in the value
  • the secret's own name was interpolated unescaped (Infisical only forbids : and / in a secret name, so ;, $, =, space are all valid)
  • a name starting with - (e.g. -p) got read as an export flag — on dash this dumps the whole environment to stdout instead of failing
  • a name containing = or ending in + let a secret silently overwrite or append to an unrelated variable (PATH, LD_PRELOAD, ...), since export parses NAME=VALUE/NAME+=VALUE after quote removal regardless of quoting

Fix: dotenv-export now delegates to dotenv-eval's existing posixShellQuote for the value, and a secret name is only accepted if it matches a portable shell variable name (^[A-Za-z_][A-Za-z0-9_]*$); anything else aborts the export with an error instead of producing a partial or unsafe file. --dotenv-quote-char no longer applies to dotenv-export (always wrapped safely) or dotenv-eval (already was).

Breaking change: dotenv-export/dotenv-eval output is now export -- KEY='VALUE' (was export KEY='VALUE'), and a secret name that isn't a valid shell identifier now fails the whole export instead of producing a line that silently misbehaves on source. A secret literally named after a shell control variable (PATH, IFS, LD_PRELOAD, ...) is intentionally still allowed — that's the name being used with its expected effect, not smuggling, and isn't something this CLI can safely second-guess.

Every finding above was verified by hand (built the real output, sourced it in bash/dash/sh, confirmed the exploit before the fix and its absence after) and reviewed adversarially across independent rounds by multiple AI models before being folded in here.

Type ✨

  • Bug fix
  • New feature
  • Improvement
  • Breaking change
  • Documentation

Tests 🛠️

Ran go test ./packages/cmd/... -vet=off -count=1 (all passing).

Verified against the real dotenv npm package that double-quoted output round-trips correctly for values with trailing backslashes, embedded quotes, backslash+quote combinations, and multiline private keys:

infisical export --env=dev --dotenv-quote-char='"' > .env

Verified the shell-injection fix by generating dotenv-export/dotenv-eval output for adversarial secrets and sourcing it in bash, dash and sh:

# before the fix, each of these executed code or leaked/corrupted an unrelated
# env var on `source`; after the fix, export aborts with an error instead
infisical export --format=dotenv-export --dotenv-quote-char='"'   # rejected outright
# secret value: x' ; touch pwned ; echo '
# secret name:  FOO=bar; touch pwned #
# secret name:  -p
# secret name:  PATH=/tmp/evil
# secret name:  PATH+

`infisical export` always wrapped values in single quotes. Single quoted
dotenv values have no escape sequences, so a multiline value (e.g. a PEM
private key) can't be represented with a real newline.

Adds --dotenv-quote-char (default: '), letting the value be wrapped in
double quotes instead, which lets the existing multiline encoding decode
back into a real newline on read. Only affects the dotenv/dotenv-export
formats; validated up front, before any network call.

Closes Infisical/infisical#1103
@mateuscmtropical
mateuscmtropical marked this pull request as ready for review September 4, 2026 23:52
@greptile-apps

greptile-apps Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a configurable single- or double-quote wrapper for dotenv and dotenv-export output while preserving the existing single-quote default.

  • Registers and validates the new --dotenv-quote-char flag before secret retrieval.
  • Routes the selected wrapper through the dotenv formatters.
  • Adds formatter and validation coverage for quotes, backslashes, multiline values, and unaffected formats.
  • Double-quote mode creates a command-injection path when dotenv-export output is consumed by a shell.

Confidence Score: 3/5

The PR is not safe to merge until double-quote mode stops making secret contents executable when dotenv-export output is sourced.

The new mode emits arbitrary secret values verbatim inside shell double quotes, where command substitutions and quote boundaries remain active; this creates a concrete command-execution path for a documented shell-oriented format.

Files Needing Attention: packages/cmd/export.go

Security Review

Double-quoted dotenv-export output writes arbitrary secret values into shell assignments without escaping shell expansions or delimiters. Sourcing output containing command substitutions, backticks, or injected quote boundaries can execute secret-controlled commands.

Important Files Changed

Filename Overview
packages/cmd/export.go Adds configurable dotenv quoting, but applies verbatim double-quoted values to shell-oriented dotenv-export output, enabling shell interpretation.
packages/cmd/export_test.go Adds broad unit coverage for formatting and validation, although it does not test shell consumption of double-quoted dotenv-export output.

Reviews (1): Last reviewed commit: "feat: support configurable quote charact..." | Re-trigger Greptile

Comment thread packages/cmd/export.go
// something parsers only do for double quoted values. That is the whole reason
// to pick it over the single quote default.
func quoteDotEnvValue(env models.SingleEnvironmentVariable, quoteChar string) string {
return quoteChar + escapeNewLinesIfRequired(env) + quoteChar

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.

P1 security Double Quotes Enable Injection

When dotenv-export uses the new double-quote mode, secret contents remain active shell syntax. A value such as $(command) is emitted as export KEY="$(command)", so sourcing the generated shell-environment output executes the command. Embedded double quotes can also end the assignment and inject more shell syntax. Keep dotenv-export shell-safe regardless of the selected quote character, or reject double-quote mode for this format.

How this was verified: Arbitrary secret values flow directly into a double-quoted export assignment without escaping command substitutions, backticks, dollar signs, or double quotes.

Knowledge Base Used: Secret workflows

The repo's review bot flagged that --dotenv-quote-char's double-quote
mode let a secret containing $(cmd) or a backtick execute code when
the dotenv-export output was sourced by a shell. Investigating further
turned up that this output was never safe to source, independent of
this flag: the default single-quote wrapping didn't escape an embedded
single quote in the value, and the secret's own name was interpolated
completely unescaped, so a secret named e.g. "FOO=bar; touch pwned #"
or one starting with "-" (misread as an export flag, which on dash
dumps the whole environment to stdout) also executed or leaked on
source. A name containing "=" or ending in "+" let a secret silently
overwrite or append to an unrelated variable (PATH, LD_PRELOAD, ...),
since export re-parses NAME=VALUE/NAME+=VALUE after quote removal
regardless of quoting.

dotenv-export now delegates to dotenv-eval's existing posixShellQuote
for the value, and a secret name is only accepted if it matches a
portable shell variable name; anything else aborts the export with an
error instead of producing a partial or unsafe file. --dotenv-quote-char
no longer affects dotenv-export (always wrapped safely) or dotenv-eval
(already was); it still works as before for the plain dotenv format.

Verified each finding by hand: generating the real output and sourcing
it in bash, dash and sh, both before and after the fix. Reviewed
adversarially across several rounds by independent AI models until no
new finding surfaced.
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.

1 participant