Skip to content

IO.get_env on Node truncates names at embedded NUL #1449

Closed

Description

@oxura

Bug

IO.get_env on the emitted Node lane treats an embedded NUL as the end of an environment-variable name. The native C and Bun lanes reject that name with Fail(ENOENT) instead. This reads a different variable than the caller named.

Reproducer

On main 8578b7c, with HOME set (as required by the existing tests/io/get_env.bend fixture):

import Base

def report(tag: String, r: Result<&1, &1, (U32 & String), String>) -> IO(Unit):
  match r:
    case Done{value}:
      IO.print(String.append(tag, "=set"))
    case Fail{error}:
      (code, message) = error
      IO.print(String.append(tag, String.append("=error:", U32.show(code))))

def main() -> IO(U32):
  do IO<U32>:
    exact : Result<&1, &1, (U32 & String), String> <- IO.get_env("HOME")
    a : Unit <- report("exact", exact)
    nul : Result<&1, &1, (U32 & String), String> <- IO.get_env("HOME\0suffix")
    b : Unit <- report("nul", nul)
    return 0

Emit a standalone .js and run it with Bun and Node; emit .c, compile with clang and run with --threads 1 --gpu off.

Observed actual Bend output:

Lane Exact HOME HOME\0suffix
C exact=set nul=error:2
Bun exact=set nul=error:2
Node exact=set nul=set

Cause / expected behavior

get_env.c already checks io_nul(name, n). get_env.js relies on Object.hasOwn(process.env, name), but Node's environment proxy truncates at NUL even for its own-property check. A direct host control with an owned sentinel key confirms Object.hasOwn(process.env, key + "\0suffix") === true on Node and false on Bun.

Reject NUL-containing names before asking the host environment, preserving the existing ENOENT result and exact-name / own-property semantics. No change to valid environment values, host FFI support, or error-code policy is needed.

Duplicate search for get_env found no matching open issue or PR (the closed #809 UTF-8 decoding report is a different boundary).

Activity

  1. oxura commented on Oct 9, 2026

    @oxura
    ContributorAuthor

    Fixed in #1451. The updated permanent fixture fails before on Node and passes afterward on C, Bun and Node; valid/missing environment lookups remain unchanged. Repo gate 54/54 and rendered documentation checked. Native checker/gates/caps are untouched.

    Update: #1450 appeared during verification with the same production guard. Closing the duplicate #1451 so #1450 remains the merge candidate; the stricter ENOENT regression, documentation, and actual before/after C/Bun/Node evidence remain linked in #1451.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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