Skip to content

v1.9.0

Choose a tag to compare

@github-actions github-actions released this 26 Sep 21:55
· 46 commits to main since this release
3354528

Security: launch-only grants on the MCP surface

  • gather.run took a config from tool arguments and ran whatever synthesizer or
    provenance command it named, fetched any network source it listed, and read any
    environment variable named as auth_env and sent its value as a bearer token to the host
    in the config. A model, or text a model was asked to read, could run commands and send a
    secret off the machine with one tool call. gather.pilot had the same exposure through a
    live manifest (auth_env, and a browser option naming any executable). Affected: every
    release with the MCP gather.run or gather.pilot tool, up to and including 1.8.3.
  • These now need a grant set at launch. GATHER_ALLOW_EXEC (gather mcp --allow-exec) names
    the commands a config may run. GATHER_ALLOW_NETWORK (--allow-network) names the network
    sources. GATHER_AUTH_ENV_ALLOW (--auth-env NAME@HOST) binds each credential variable to
    the one host it may be sent to, over https only. A pilot manifest's browser option other
    than chromium, or no_sandbox, needs that browser named in GATHER_ALLOW_EXEC.
  • Without the grant the call returns isError: true with structuredContent
    {"code": "GRANT_REQUIRED", "retryable": false, "setup": "<VARIABLE>", "detail": "<fixed sentence>"} before anything runs, connects or reads a credential. The server reads grants
    once at startup; nothing in a config, manifest or tool call widens them. A pilot refresh
    checks the grants on the same read of the stored manifest it captures from.
  • Tool descriptions changed: gather.run and gather.pilot now say which inputs need a launch
    grant, so a host sees why a call returns GRANT_REQUIRED.
  • Breaking for MCP hosts that relied on the old behavior: add the grant to the server's launch
    configuration. The CLI and the Python API run the operator's own config and are unchanged.

Security: child programs start from an absolute path in a private folder

  • pdftotext, yt-dlp, tesseract, whisper and the headless browser started by bare name
    from the server's working folder. On Windows a same-named .exe in that folder ran in place
    of the real tool, and on any platform a . entry on PATH did the same. yt-dlp also read a
    yt-dlp.conf from that folder, and a config can carry --exec. The synthesizer and
    provenance commands had the same lookup. Affected: every release up to and including 1.8.3.
  • Every child now starts through gather.spawn, which calls the vendored safe spawn helper
    (SAFE_SPAWN_VERSION 1.0.0, hash-pinned in VENDORED.sha256). The program resolves to an
    absolute path (GATHER_<TOOL> overrides win; relative and empty PATH entries never count),
    runs in a new private empty folder, and sees an environment allowlist instead of Gather's
    whole environment. yt-dlp gets --ignore-config. On Windows the child also gets
    NoDefaultCurrentDirectoryInExePath=1, and a batch-file target refuses cmd.exe
    metacharacters. Output stays bytes, so receipts hash exactly what the tool wrote.
  • Changes you may notice: a command given as a relative path is refused; a synthesizer that
    reads its API key from the environment needs GATHER_CHILD_ENV=KEY_NAME; yt-dlp no longer
    reads your user yt-dlp.conf; a python -m provenance command must be installed, not only
    present in the working folder.
  • tests/test_spawn_children.py plants a decoy named like each child (a real .exe on
    Windows) in the caller's folder with . on PATH, a fake API key in the environment and a
    yt-dlp.conf. 18 of its 19 first tests failed on the unfixed code. With yt-dlp installed,
    a real-tool test shows a planted yt-dlp.conf taking effect without the fix and having no
    effect through VideoSource.

Release workflow

  • The workflow grants nothing by default. build reads the repository, publish holds only
    id-token: write for trusted publishing, and a new github-release job holds only
    contents: write. Checkouts drop the token after cloning.
  • The PyPI upload uses skip-existing: true, so a re-run after a partial upload completes.
  • The build checks the tag against the package version and the vendored helper in the wheel
    against VENDORED.sha256, then writes SHA256SUMS.txt for the wheel and the sdist. The
    github-release job verifies those sums and creates the GitHub Release with the wheel, the
    sdist and SHA256SUMS.txt attached, or refreshes its files on a re-run. Before this, the
    release and its checksum file were made by hand.