Skip to content

v0.6.0

Choose a tag to compare

@github-actions github-actions released this 27 Sep 15:50
· 125 commits to main since this release
6362849

evnx v0.6.0

The first release since 0.5.0 that contains new code. 0.5.1 and 0.5.2 were
release engineering; ten pull requests have landed since.

Security — a notice carried forward

⚠️ Anyone who ran evnx validate --fix on v0.4.x or earlier should rotate the
secret it generated.
The value was derived from roughly 25 bits of time
rather than randomness — 48 of its 64 hexadecimal characters were always 0 —
and the output announced it as "Generated secure secret", so there was nothing to
suggest it needed replacing. Where such a value ended up as a session-signing key
or a JWT secret, it is guessable by anyone who knows approximately when it was
generated.

⚠️ Anyone who ran evnx migrate --to github on a version before 0.5.0 should
rotate everything it uploaded, at the source.
Values were base64-encoded rather
than encrypted: the function was named encrypt_for_github and the payload went
into a field called encrypted_value, so both the code and the wire format
described encryption that had not happened.

Both were fixed in 0.5.0. This notice is repeated here because it was written
into docs/releases/v0.5.0.md and nothing published that file — the release body
was generated from a template that read no file at all, so the notice went unread
across three releases. That is fixed in this release too; see Fixed below.

Affected packages, all < 0.5.0: crates.io/evnx, PyPI/evnx, npm/@evnx/cli.
See the repository's security advisories for ranges and remediation.

Why this is 0.6.0 and not 0.5.3

Two changes alter behaviour rather than only adding to it, and in 0.x semver
puts a compatibility change in the minor position:

  • Escape sequences in a multiline double-quoted value are now interpreted,
    matching what a single-line value always did. A multiline value containing a
    backslash changes meaning. See Changed below.
  • A value recognised only by its shape, behind a build-time public prefix, is
    raised from low to medium. A --severity medium gate that passed can now
    fail. See Added below.

Neither is large, and no correct program depended on either — but both are the
kind of change someone should read a version number and expect.

Added

  • A secret behind a build-time public prefix is now called what it is. scan
    reported a Stripe key in VITE_STRIPE_SECRET_KEY correctly as high confidence,
    then told you to git filter-repo and force push — work that contains nothing,
    because the value is compiled into every bundle already served. Someone
    following it literally would rewrite history and believe they were done.

    A finding whose variable carries NEXT_PUBLIC_, VITE_, REACT_APP_,
    EXPO_PUBLIC_, NUXT_PUBLIC_, GATSBY_, VUE_APP_, STORYBOOK_ or
    PUBLIC_ now says which tool inlines it, names the prefix-stripped variable to
    rename it to, and leads the next steps with renaming rather than history
    rewriting. The prefix is in the JSON as public_prefix, so CI can tell
    "leaked" from "published". (#78)

    Severity is raised where it was wrong: a known provider format behind a public
    prefix is high, and a value recognised only by shape is raised to
    medium — enough to be seen, not enough to fail a --severity high gate on
    an analytics key that is meant to ship. ⚠️ Escalation only ever raises a
    finding that already exists, so PUBLIC_KEY=<a public key> and
    NEXT_PUBLIC_API_URL=<a url> are still not findings at all.

  • .ipynb files are scanned. Identical JSON was scanned as .json and
    ignored as .ipynb — a filter, not a parser. Notebooks are where ML work keeps
    keys, and a key often appears twice: once in a source cell and again in an
    outputs cell where it was echoed. (#78)

  • vault share, vault revoke and vault role accept both --with and
    --user.
    share took --with while the other two took --user for the
    same thing, so whichever you learned first was wrong half the time. Neither is
    deprecated. (#78)

  • scripts/verify-claims.sh — documented claims about evnx are now checked by
    running it. Each claim has a stable id and a verdict of CONFIRMED, REFUTED
    or SKIP, and the exit status mirrors evnx's own contract: 0 all confirmed,
    1 something refuted, 2 could not run. "No claims ran" is 2, because a
    verification script that exits 0 when it verified nothing is the bug it exists
    to catch. (#74)

Fixed

  • The GitHub Release body now carries this changelog. release.yml generated
    the body from an inline template — installation instructions for the seven
    channels, and nothing about the release itself — so anything written about a
    version never reached the page people land on. The rotation notice above lived
    in docs/releases/v0.5.0.md, which no workflow reads, and went unread across
    three releases as a result.

    The section between ## [VERSION] and the next heading is now lifted from
    CHANGELOG.md and placed above the installation instructions, so a reader
    who needs to rotate a credential does not have to scroll past seven package
    managers to learn it. A missing heading warns rather than failing the release,
    and the body falls back to installation instructions alone.

  • evnx scan no longer reports a clean result for a scan that examined nothing.
    dist/, build/, node_modules/ and target/ are skipped on a directory
    walk, and naming a file inside one was silently dropped — so a CI gate pointed
    at a build artifact printed ✓ No secrets detected, 0 files scanned and exit
    0, on a file holding a live Stripe key. The extension allowlist did the same
    to anything it did not recognise: evnx scan key.pem scanned nothing and called
    it clean.

    Two changes, the second being the general one: a path you name is always
    scanned
    — the default exclusions and the allowlist exist to stop a directory
    walk reading a million vendored files, and were never a veto over an argument —
    and zero files scanned is never success, now exit 2, because nothing was
    found and nothing was looked at. (#71)

  • The private-key detector could not fire outside a .env file.
    -----BEGIN RSA PRIVATE KEY----- and its four siblings went undetected in every
    source and .pem file. extract_tokens splits on whitespace and keeps only
    tokens longer than 20 characters, and the header splits into -----BEGIN,
    RSA, PRIVATE, KEY----- — every piece too short to survive. Undetectable in
    exactly the files private keys live in.

    Fixed by making the built-in detectors rules on the same engine as custom
    ones
    , matched against whole lines through PatternSet. Custom
    [[scan.patterns]] were never affected, so a rule a user wrote was strictly
    stronger than one evnx shipped — that was the real defect. A dead
    get_patterns() table of ~20 detectors, which nothing called while a
    hand-written Regex::is_match chain did the work, is gone with it. (#72)

  • evnx migrate's missing-file hint named --env, a flag it does not have.
    It is --source-file. (#70)

  • The crate no longer ships environment files. .env.backup and
    .envx/placeholders.json were committed in March 2026 and shipped to crates.io
    in 0.5.0, 0.5.1 and 0.5.2, because this repository's own .gitignore used the
    narrow .env/.env.local convention that evnx init had already moved away
    from. The repo now uses the entries evnx init writes, and CI fails if
    cargo package --list contains either path. Published versions cannot be
    altered; this stops it recurring. (#73)

  • npm publishing downloaded every artifact in the release run, not just the
    binaries.
    docker/build-push-action uploads a build record of its own — a
    .dockerbuild blob that is not a plain zip — and unzipping it killed the 0.5.2
    npm publish with ADM-ZIP: Invalid or unsupported zip format. Two earlier
    failed tags had hidden it, because the docker job only produced one once the
    release got that far. Filtered to ^evnx-. (#69)

  • npm publishing now reads the registry back instead of trusting the publish
    step.
    npm publish returning 0 means the tarball was accepted — not that the
    version resolves, and not that the latest dist-tag moved. The smoke test also
    gave up after 90 seconds, so 0.5.2 published correctly to all six packages and
    went red anyway.

    The registry is now polled with a 300-second budget and the install runs once,
    after it is known to be there. Three things are newly checked: that latest
    actually points at the new version (the test installed a pinned version, so a
    stale tag was invisible), that all five platform packages resolve — they are
    optionalDependencies, which npm skips without failing, so a missing one gave a
    clean install and a binary absent at runtime on that architecture — and that
    evnx --version prints what was published. A failed upstream release is also a
    failure here rather than a silent skip, which used to leave a green check beside
    a red release. (#75)

  • An escaped \" no longer ends a multiline value. Parser::is_closed_quote
    tested ends_with('"'), so for B="line1 \"q\" the final character was a
    quote — the trailing one of an escaped \" — and the parser concluded the value
    had closed, reporting Invalid format at line 2: missing '=' separator on the
    continuation line. The check now requires the run of backslashes before the
    closing quote to be of even length. Single-quoted and backtick values are
    literal and were never affected.

    The same bug had a second site: the continuation branch used
    strip_suffix(ml_quote), so a value could survive its opening line and then be
    cut short by a continuation line ending in \". Both now go through one shared
    function, along with evnx doctor — see below. (#77)

  • evnx doctor no longer reports the continuation lines of a multiline value as
    invalid syntax.
    It checked .env syntax line by line, with no notion of a
    value spanning lines, so a PEM key produced two or three complaints about a file
    that validate, convert and scan all accepted. It now tracks the open quote
    using the parser's own helpers, so the two cannot disagree about where a value
    ends.

    doctor also now reports a quoted value whose quote never closes. The
    line-based check could not represent that at all, so it called such a file clean
    while validate failed on it — the same disagreement in the opposite direction. (#77)

Changed

  • ⚠️ Escape sequences in a multiline double-quoted value are now interpreted,
    matching the single-line behaviour.
    The accumulated value was inserted raw, so
    "x\ty" produced a tab on one line but a literal backslash and t across two —
    the same quote character with different escape rules depending on the line count,
    which also made \" inexpressible in a multiline value at all.

    This changes existing values. A multiline double-quoted value containing a
    backslash now resolves it. PEM keys, certificates and base64 blobs contain no
    backslashes and are unaffected; a multiline JSON value with \n or \" inside
    it is not. Use single quotes for literal content. (#77)

Documentation

  • The README said multiline and array values "will not parse correctly". Two
    of its three examples parse fine, and multiline values have worked all along —
    the claim came from a verification step that read an exit code without reading
    the message: it wrote a two-line value, ran evnx validate --env .env.ml, got
    exit 2 and recorded a parse error. The 2 was .env.example does not exist;
    validate never parsed the file.

    The claim had spread into the planning material, where it became a roadmap item
    proposing "parser support for quoted multi-line values" — work to build a
    feature that already existed. It also meant the README contradicted the docs
    site, where concepts-migrate-intro.mdx correctly said multi-line values are
    handled. Rewritten from what the binary actually does. (#76)

Installation

Linux / macOS (shell script)

curl -sSL https://raw.githubusercontent.com/urwithajit9/evnx/main/scripts/install.sh | bash

Homebrew (macOS and Linux)

brew install urwithajit9/evnx/evnx

Scoop (Windows, user-local)

scoop bucket add evnx https://github.com/urwithajit9/scoop-evnx
scoop install evnx

Winget (Windows, system-wide)

winget install urwithajit9.evnx

npm / Node.js

npm install -g @evnx/cli

Python (pipx)

pipx install evnx

Cargo

cargo install evnx --all-features

cargo install compiles from source with default = [], so plain
cargo install evnx gives you a binary without the auth,
vault and cloud commands. Every other channel above ships them
already — the binaries are built with --all-features.

Checksums

SHA256 checksums are provided for each binary. Verify after download:

sha256sum -c evnx-*.tar.gz.sha256

What's Changed

  • fix(ci): npm publish downloads only the binary artifacts by @urwithajit9 in #69
  • fix(migrate): the missing-file hint named a flag migrate does not have by @urwithajit9 in #70
  • fix(scan): a scan that examined nothing is not a clean scan by @urwithajit9 in #71
  • refactor(scan): built-in detectors are rules on the same engine as custom ones by @urwithajit9 in #72
  • fix: stop shipping environment artifacts, and follow our own .gitignore advice by @urwithajit9 in #73
  • test: add scripts/verify-claims.sh — assert on messages, not exit codes by @urwithajit9 in #74
  • fix(ci): read the npm registry back instead of trusting the publish step by @urwithajit9 in #75
  • docs: multiline values are supported — correct the record, and name what is not by @urwithajit9 in #76
  • fix(parser): an escaped quote no longer ends a multiline value, and doctor agrees by @urwithajit9 in #77
  • feat(scan): a secret behind a public prefix is already published, not merely leaked by @urwithajit9 in #78
  • chore(release): 0.6.0 by @urwithajit9 in #79
  • fix(ci): the release body carries the changelog, so a notice can actually reach it by @urwithajit9 in #80

Full Changelog: v0.5.2...v0.6.0

npm / Node.js

npm install -g @evnx/cli          # global install
npx @evnx/cli --version           # zero-install usage
npm install --save-dev @evnx/cli  # project dev dependency