Skip to content

v0.79.0

Choose a tag to compare

@magifd2 magifd2 released this 13 Sep 01:26
· 222 commits to main since this release

Security

  • The kernel reads the file (ADR-0086). The file tools never made
    ADR-0073's move to the kernel: they are in-process Go code, so
    sandbox.CredentialPath was the boundary and a miss there was the
    file's content in the transcript rather than a missing prompt. Since
    the lanes landed that matcher's list gained one entry while its
    matching rule changed six times, four of them in one release, each
    from a review finding and each a spelling the previous rule had not
    considered. read_file, view_image, read_document, file_info,
    search_files and summarize_file now perform their reads in a
    child of this binary under a profile built from the same
    internal/sandbox list, which denies credential material at the
    kernel — measured: cat .env and stat .env refused,
    .env.example read, grep -r skipping that one file, at 27 ms per
    spawn against a call that already costs a model round. A refused open
    is the operator's question, and on approval the read runs in process,
    which is the operator lane's authority applied to a file tool. The
    matcher stays only to raise the prompt without spending a spawn: a
    miss in it now costs a spawn and a blunter prompt, never a leak.
    Verified at startup like the shell lanes; where it cannot be
    verified the reads stay in process and the banner says so.

  • The walks stop hiding names (ADR-0086 §3, withdrawing ADR-0085
    §2). The kernel lists names and refuses content, so a name was never
    the secret and withholding it was a second rule over the same
    unbounded spelling domain. list_files and list_tree list a
    credential-named entry like any other and carry no credential code at
    all; search_files names the file it could not read
    ([not read: sub/.env — reading one needs the operator's approval]),
    the shape grep -r has. credentialSkipNote and the walk
    withholding are deleted, and with them the contradiction where lagent
    reported names and gem-agent reported a count.

  • Review fixes to the kernel read path (ADR-0086, independent
    review). The first cut denied file-read*, which covers
    file-read-metadata; Go's os.Root listing stats every entry, so one
    credential-named file failed the whole directory read and
    search_files answered no matches (0 files scanned) for the entire
    project — silently, exit 0. The deny is now file-read-data: content
    refused, stat left, the walk intact, and the [not read: …] note the
    ADR promised actually reached. Also fixed: the child pipe silently
    halved reads over 85 KB; file_info collapsed a refusal into "not
    found" so the operator was never asked; a cancelled search_files
    lost its partial result; the child did not get sandbox.ChildEnv; any
    EACCES became a credential question; the hidden subcommand ran any
    tool; a missing work directory failed every read and leaked an
    absolute path; the probe directory leaked; the profile lacked the tty
    hardening; the degradation note named no next command. The reason all
    of it shipped green is that no test ran the real child under the real
    profile — TestFileChildUnderTheRealProfile now does, and it is what
    caught the rest on re-run.

Changed

  • The operator's environment is no longer filtered, and the runtime's
    own variables no longer reach any child
    (ADR-0087). The read lane's
    environment scrub is deleted: it covered one child of six — the write
    and operator lane shells, the ! shell, MCP servers and hooks all
    received the environment whole — and the one it covered passed
    OPENAI_KEY, GH_PAT and GPG_PASSPHRASE while catching
    NPM_TOKEN. Which variable the program a command runs needs is not a
    question gem-agent can answer, and an allowlist would move the
    unbounded list rather than remove it. A bare env in the read lane
    now prints what you exported
    , as it already did in every other
    lane; the environment gem-agent is launched with reaches every child
    it spawns, and the remedy is the operator's: launch it from a shell
    that does not hold what you would not give the model.
    In exchange, the one set the runtime does know exhaustively — its own
    GEMAGENT_ namespace — is now handled correctly in the other
    direction. sandbox.ChildEnv removes GEMAGENT_PROJECT,
    GEMAGENT_LOCATION, GEMAGENT_MODEL, GEMAGENT_STATE_DIR and
    GEMAGENT_MCP_STDERR from every child, while the three exported for
    children (GEMAGENT_WORK_DIR, GEMAGENT_SESSION_ID,
    GEMAGENT_PROJECT_DIR) pass. The two halves partition the namespace
    and an architecture test refuses a GEMAGENT_ literal in neither, so
    a variable a later release reads for itself never reaches a child —
    the class system risk review R01 named, closed by construction rather
    than by a regex recognising the word key. A nested gem-agent in a
    shell lane now reads its own config file instead of inheriting the
    parent's; an MCP server wanting a GEMAGENT_ value takes it from the
    env block of the MCP configuration. A companion run from a shell
    lane that reads gem-agent's own state root — gem-usage-lens does,
    to measure an isolated runtime where it writes — no longer inherits
    GEMAGENT_STATE_DIR and takes it from its own --sessions-root or
    [sources] instead.