Skip to content

feat(runtime)!: add local() sandbox factory with opt-in env allowlist; remove sandbox magic strings#144

Merged
FredKSchott merged 2 commits into
mainfrom
local-sandbox-env-allowlist
May 14, 2026
Merged

feat(runtime)!: add local() sandbox factory with opt-in env allowlist; remove sandbox magic strings#144
FredKSchott merged 2 commits into
mainfrom
local-sandbox-env-allowlist

Conversation

@FredKSchott
Copy link
Copy Markdown
Member

@FredKSchott FredKSchott commented May 14, 2026

Closes #116. Supersedes #122 with a different API shape: instead of a processEnv: 'limited' | 'inherit' | object policy on init() that only applies to one sandbox, host env is configured directly on a local() SandboxFactory — keeping it where it lives and folding cleanly into the existing connector pattern.

Summary

  • Adds local() SandboxFactory exported from @flue/runtime/node. Configures the host-bound Node sandbox the same way every other connector configures itself, with a typed env option for opting host env vars into the agent's shell.
  • Restricts env exposure by default: only a small allowlist of shell essentials (PATH, HOME, USER, LOGNAME, HOSTNAME, SHELL, LANG, LC_ALL, LC_CTYPE, TZ, TERM, TMPDIR, TMP, TEMP) is inherited from process.env. Secrets like ANTHROPIC_API_KEY / GH_TOKEN / cloud-provider creds no longer reach the agent's bash tool unless explicitly forwarded.
  • Removes the sandbox: 'empty' and sandbox: 'local' magic strings. The TS union excludes both literals; the runtime throws with migration messages for JS callers and any-typed inputs. The default in-memory sandbox is now reached by omitting the option (or passing false); the host-bound sandbox is reached by sandbox: local().

Motivation

The previous behavior — sandbox: 'local' passing the full process.env to every command — meant the model's bash tool could read whatever credentials happened to be set on the host (API keys, deploy tokens, cloud SDK creds, etc.). The framing of "the runner / container is the isolation boundary" only covers the outer boundary; the inner boundary (host process → spawned shell) was leaky-by-default. The new model makes the inner boundary explicit, matching the original design intent of the framework where env exposure was opt-in by the agent author.

API

import { local } from '@flue/runtime/node';

init({
  sandbox: local({
    env: { GH_TOKEN: process.env.GH_TOKEN },
  }),
});

Set a key to undefined to drop a default. Pass env: { ...process.env } to opt into the full host env (explicit, grep-able).

Why this shape over init({ processEnv }) (#122)

  • processEnv only ever makes sense for the 'local' sandbox; pushing it onto init() means a top-level option that's silently ignored (or throws) for every other sandbox type. The factory form keeps the option where it logically belongs.
  • Aligns with how every other sandbox is configured (daytona(...), e2b(...), etc.) — 'local' stops being a one-off magic string and becomes another connector.
  • One canonical way to opt in (env: { KEY: process.env.KEY }) rather than three modes ('inherit' | 'limited' | object). The "opt into everything" escape hatch is explicit (env: { ...process.env }), so a reviewer can see it in a diff.
  • Naturally extends to future options on the local sandbox without growing init()'s surface.

Breaking changes

  • sandbox magic strings removed. init({ sandbox: 'empty' }) and init({ sandbox: 'local' }) no longer typecheck and throw at runtime if reached via JS / any. Migration:

    - init({ sandbox: 'empty', model: 'anthropic/claude-sonnet-4-6' });
    + init({ model: 'anthropic/claude-sonnet-4-6' });
    
    - init({ sandbox: 'local', model: 'anthropic/claude-sonnet-4-6' });
    + import { local } from '@flue/runtime/node';
    + init({ sandbox: local({ env: { GH_TOKEN: process.env.GH_TOKEN } }), model: 'anthropic/claude-sonnet-4-6' });

Verification

  • Type check + runtime + CLI builds: clean.
  • Runtime test suite: 41/41 pass.
  • flue run local-env-smoke example exercises the full SessionEnv surface plus three env-allowlist checks: default vars inherited, opt-ins visible, host-side sentinel NOT visible. All 10 checks pass.
  • Both magic-string runtime throws verified for JS callers.

FredKSchott and others added 2 commits May 13, 2026 21:12
`init({ sandbox: local() })` from `@flue/runtime/node` replaces the
`sandbox: 'local'` magic string with a proper SandboxFactory. Env
exposure is opt-in: only DEFAULT_LOCAL_ENV_ALLOWLIST (PATH, HOME,
locale, etc.) is inherited from process.env by default. Host env vars
like GH_TOKEN are passed explicitly via local({ env: { ... } }), which
keeps the agent's bash tool from surfacing host credentials to the
model by accident.

The `sandbox: 'local'` string still works (resolves to local() with
default options) but emits a one-time deprecation warning and is
flagged for removal via coordinated TODOs in client.ts and
build-plugin-node.ts.

createLocalSessionEnv() — the standalone helper that was previously
exported as public from @flue/runtime/node — now applies the same
allowlist. Direct callers can restore the old behavior with
env: { ...process.env }.
Replaces both magic strings with the new local() factory (for the
host-bound Node sandbox) and "omit the option / pass false" (for the
default in-memory sandbox). TS callers get compile errors from the
narrowed AgentInit['sandbox'] union; JS callers and `any`-typed inputs
hit runtime throws with migration messages.

Also trims @flue/runtime/node's public surface: createLocalSessionEnv,
DEFAULT_LOCAL_ENV_ALLOWLIST, and LocalSessionEnvOptions are no longer
re-exported. The helper has no external callers; local() is the public
entry point.

Co-authored-by: Charlike Mike Reagent <5038030+tunnckoCore@users.noreply.github.com>
Co-authored-by: stain lu <stainlu@newtype-ai.org>
@FredKSchott FredKSchott changed the title feat(runtime): add local() sandbox factory with opt-in env allowlist feat(runtime)!: add local() sandbox factory with opt-in env allowlist; remove sandbox magic strings May 14, 2026
@FredKSchott FredKSchott merged commit 821af63 into main May 14, 2026
@FredKSchott FredKSchott deleted the local-sandbox-env-allowlist branch May 14, 2026 04:28
@tunnckoCore
Copy link
Copy Markdown
Contributor

I love it 😻 It really made sense local to be just another sandbox/connector haha.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Process ENV on sandbox: local

2 participants