Skip to content

create: .env.local wire format has no quoting — multi-line values truncate, and hand-appended quoted values can't be safely unquoted #75

Description

@ikeikeikeike

Surfaced by the high-effort retrospective /review of PR #14 (post_create .env.local env injection).

Symptom (two faces of the same root cause)

  1. Multi-line values truncate. parseEnvLocal (internal/cli/create.go, added by fix(create): inject .env.local into post_create child env (bypass bash source) #14) splits the whole file on every \n and treats each resulting fragment as an independent KEY=VALUE line. envwriter.Write (internal/envwriter/envwriter.go) has no line-count guard and serializes any value verbatim via fmt.Fprintf("%s=%s\n", k, v). If a .bough.yaml env_local: template renders a genuinely multi-line value (e.g. via a Sprig nindent/toYaml call), the file itself becomes structurally ambiguous the moment it's written — parseEnvLocal then reads only the first physical line of that value, silently drops continuation lines lacking =, or worse, promotes a continuation line that coincidentally contains = into a bogus unrelated entry.

  2. Quoted values can't be safely unquoted. A post_create script that hand-appends to .env.local using ordinary dotenv authoring convention (KEY="value") gets the literal quote characters passed into the child process's env, because parseEnvLocal intentionally does no shell-style unquoting (see its doc comment, and TestParseEnvLocal_ValuesWithShellMetachars's WITH_QUOTES case, which asserts quotes survive unchanged — this is PR fix(create): inject .env.local into post_create child env (bypass bash source) #14's own tested, deliberate contract, not an oversight).

Why this needs a coordinated fix, not a parseEnvLocal-only patch

Both symptoms trace back to the same design gap: .env.local's wire format (KEY=value\n, no escaping, no quoting) cannot round-trip a value containing a literal newline, and cannot distinguish "the value is "abc" with literal quote characters" from "the value is abc and the quotes are just dotenv-convention decoration." Fixing the read side (parseEnvLocal) alone would require guessing at a quoting/escaping convention the write side (envwriter.Write) never established — and stripping quotes unconditionally risks silently corrupting a value that genuinely needs a literal leading/trailing quote character (a different, arguably worse bug than the one it fixes).

Options (needs a product decision, not a unilateral pick)

  • Adopt a real quoting/escaping format for .env.local on both envwriter.Write and parseEnvLocal (e.g. Go-string-literal-style escaping, or reuse an existing dotenv-parsing library on read plus matching writer discipline) — most correct, touches both the render and post_create paths.
  • Leave hand-appended quoting as "the operator's problem" (document it) and only guard against multi-line values by having envwriter.Write reject/error on a value containing \n rather than silently emitting a corrupt file — cheaper, narrower, still leaves quote-stripping unaddressed.
  • Status quo (do nothing) — both are edge cases (no current bough-shipped template produces multi-line values or relies on quoted post_create appends), but silent data corruption rather than a loud error either way if someone does hit it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions