| version | v0.36.5.0 |
|---|---|
| date | 2026-05-18 |
| feature_pitch | Shell jobs grow `inherit: [...]` — agent passes any snake_case config-key names; worker resolves values from its own `loadConfig()` at child-spawn. Names persist to the row; values resolve fresh. Validated pre-enqueue. |
- Shell-job pre-enqueue validator (
src/core/minions/handlers/shell-validate.ts, NEW). Called from BOTH submit surfaces BEFOREMinionQueue.add():gbrain jobs submit shell(CLI) andsubmit_jobMCP op forname='shell'. Validates cmd/argv/cwd/env shape,inheritarray of snake_case names, and fail-fasts if the worker can't resolve a requested name. No rejected payload ever lands inminion_jobs.data. inherit: [...]ShellJobParams field — free-form. Pass any snake_case config-key name (database_url,anthropic_api_key,voyage_api_key,groq_api_key,zeroentropy_api_key,my_custom_field, etc.). The validator does NOT police which keys you choose — same-uid trust model treats the agent as a peer.- Env-key derivation: name uppercased by default. One override:
database_url→GBRAIN_DATABASE_URL(plainDATABASE_URLis ambiguous in most Postgres-app contexts). - Prototype-pollution defense: snake_case regex blocks
__proto__/_leading/ uppercase. Value-resolver usesObject.hasOwn. - Defense-in-depth re-validation in the shell handler at job-pickup catches pre-existing rows AND any future submit path that forgets the pre-enqueue call.
gbrain doctor home_dir_in_worktreecheck warns when~/.gbrain/lives inside a git worktree. Handles.gitas directory or file.~/.gbrain/.gitignoreretroactive viaensureGitignore()called fromsaveConfig()ANDgbrain post-upgrade. Idempotent, never clobbers.- Output-side redaction (opt-in): set
redact_secrets: trueon the job params (or pass--redact-secretson the CLI) and the worker scrubs every occurrence of resolvedinherit:values fromstdout_tail/stderr_tail/error_textbefore persistence. Replacement token:<REDACTED:name>. Literal-string replace — defeats accidentalecho "$GBRAIN_DATABASE_URL", not adversarial encode-then-print. Default false (back-compat). - New canonical guide:
docs/guides/agent-to-gbrain.md. Two-domain framing for downstream agent authors: MCP ops via thin-client OAuth vslocalOnlyadmin ops via shell-jobinherit:.
The agent picks which secrets to inherit, per job, by name:
Worker resolves each name from loadConfig() and injects into the child env
under the derived key (database_url → GBRAIN_DATABASE_URL,
anthropic_api_key → ANTHROPIC_API_KEY, etc.). Names land in
minion_jobs.data and the shell-audit JSONL; values resolve fresh on every
spawn and don't persist from inherit: itself.
If the worker can't resolve a name, the validator fail-fasts at submit time
with a paste-ready gbrain config set <name> <value> hint.
You can still use env: for non-secret values or for secrets you've decided
are OK to persist in the row (e.g. correlation tokens). v0.36.5.0 doesn't
forbid that — the validator trusts the agent.
# 1. The new pattern works on your worker:
gbrain jobs submit shell --params \
'{"cmd":"gbrain stats","cwd":"/tmp","inherit":["database_url"]}' --follow
# Expect: page count, exit 0.
# 2. The doctor surfaces worktree risk if it exists:
gbrain doctor --json | grep -A1 home_dir_in_worktree
# 3. The retroactive gitignore landed:
test -f ~/.gbrain/.gitignore && cat ~/.gbrain/.gitignore
# Expect: file exists, contents = "*\n"
# 4. The audit-log records names not values:
tail -1 ~/.gbrain/audit/shell-jobs-*.jsonl | grep -o '"inherit":\[[^]]*\]'
# Expect: ["database_url"] (no URL value)PR #1137 documented two workarounds for the env-stripping behavior of shell
jobs: write database_url plaintext to ~/.gbrain/config.json, or pass
env: { GBRAIN_DATABASE_URL: ... } per-job. Both work. Both leave plaintext
secrets either on disk or in minion_jobs.data rows that travel with brain
DB dumps and shared brains.
The /cso audit cut an earlier proposal (encrypted vault + Unix-socket
broker + SO_PEERCRED + per-call tokens) as theater for a single-uid topology.
Codex's pre-landing review then caught the load-bearing bug in the rewritten
"minimum scope" plan: validation in the handler runs AFTER queue.add(),
so the headline "input-secrets never persist" was technically false. Fixed
pre-implementation by lifting the validator to a shared pre-enqueue module
called from both submit surfaces.
An interim draft used a closed INHERITABLE enum (hardcoded list of
allowed secret names with explicit shadow-key sets and inline-cmd regex
scans). Codex flagged bypasses; the deeper question surfaced: what does the
closed enum actually defend on a single-uid topology? Same-uid trust = same
trust domain; refusing to let the agent inherit voyage_api_key because
it's not on a hand-curated list is paternalism. The shipped design opens
inherit: to any snake_case config-key. The agent decides.
{ "cmd": "gbrain sync --skip-failed && gbrain embed --stale", "cwd": "/data/gbrain", "inherit": ["database_url", "anthropic_api_key", "voyage_api_key"] }