Files
bot-bottle/docs/prds/0038-smolmachines-env-contract.md
T
didericis-claude 717a9126e1
test / integration (pull_request) Successful in 56s
test / unit (pull_request) Successful in 38s
test / unit (push) Successful in 31s
test / integration (push) Successful in 42s
docs: mark PRD 0038 Active
2026-06-02 14:38:44 +00:00

3.7 KiB

PRD 0038: smolmachines Env Contract and Secret-Safe Injection

  • Status: Active
  • Author: didericis-codex
  • Created: 2026-06-02
  • Issue: #135

Summary

Make smolmachines env handling match Docker's contract: resolve manifest env entries through resolve_env(), keep secret and interpolated values out of host argv, and document or enforce an explicit env contract for the backend.

Problem

bot_bottle/backend/smolmachines/prepare.py builds the guest env from bottle.env directly, bypassing resolve_env(). Entries like ?prompt and ${HOST_VAR} can reach the guest literally rather than being prompted or resolved. In contrast, Docker resolves env through resolve_env() before writing a mode-600 env file.

smolmachines/smolvm.py renders env as -e KEY=VALUE on smolvm machine create argv, and SmolmachinesBottle.agent_argv / exec prepend env KEY=VALUE … onto the smolvm machine exec argv. Any literal or resolved secret value is therefore visible in the host process table.

The two backends have no shared env contract document. Divergence will silently widen as new manifest env features are added.

Goals / Success Criteria

  • Manifest env entries are resolved through resolve_env() before being injected into the smolmachines guest, matching Docker behaviour.
  • No manifest env value (literal or resolved) appears on host argv during machine creation or exec.
  • Define and document an explicit smolmachines env contract covering literals, ?prompt secrets, and ${HOST_VAR} interpolations.
  • Unit tests cover: literal passthrough, prompted-secret resolution, host-var interpolation, and the no-argv-leak invariant.

Non-goals

  • No changes to the Docker env path.
  • No changes to manifest schema or resolve_env() itself.
  • No changes to smolmachines networking or mount handling.
  • No new runtime dependencies.

Scope

In scope:

  • bot_bottle/backend/smolmachines/prepare.py env resolution.
  • bot_bottle/backend/smolmachines/smolvm.py machine-create argv.
  • bot_bottle/backend/smolmachines/bottle.py agent_argv / exec env injection.
  • bot_bottle/env.py if helper changes are needed to support the smolmachines path.
  • Unit tests in tests/unit/ covering the above.

Out of scope:

  • Integration tests that start a live smolmachines VM.
  • Docker backend changes.
  • Dashboard or CLI changes.

Design

Run smolmachines env through resolve_env() at prepare time, exactly as Docker does. After resolution, inject env into the guest through a mechanism that does not expose values on host argv — for example by writing a mode-600 env file into the machine's state directory and loading it at exec time, or by passing env through smolvm's stdin if the tool supports it.

If smolvm provides no stdin or env-file injection path, document this as a known limitation and at minimum move env values behind a per-invocation tmpfile rather than inline argv.

The env contract for smolmachines should mirror Docker's:

  • Literals: passed as-is after resolution.
  • ?prompt entries: prompted at prepare time; resolved value injected, never on argv.
  • ${HOST_VAR} entries: interpolated from the operator's env at prepare time; resolved value injected, never on argv.

Testing Strategy

  • Unit tests for prepare.py asserting resolve_env() is called and that resolution results are used rather than raw bottle.env values.
  • Unit tests for smolvm.py machine-create argv asserting no env value appears inline.
  • Unit tests for bottle.py exec path asserting the same argv invariant.

Run:

  • python3 -m unittest tests.unit.test_smolmachines_prepare
  • python3 -m unittest discover -s tests/unit

Open Questions

  • Does smolvm machine create support an env-file flag or stdin injection that avoids -e KEY=VALUE argv?