hosystem Engagements

Sharibako — Injection Decision (Kamae 2.1)

created 2026-07-01
status decided
type decision
project sharibako
stage kamae-2.1
kamae-chain seed → system-design → **injection-decision** → readme → ho-overview
supersedes kamae-2 §7 (partial — the "no runtime injection" architectural commitment)
builds-on kamae-1-sharibako-seed, kamae-2-sharibako-system-design
next reflected in kamae-1 seed (parti), kamae-4 (ho sequence, adds ho-04.5), README, SECURITY.md, docs/architecture.md

A decision document. 2 committed "no runtime injection" as an architectural boundary. This document reopens that decision, records the new threat-model input that motivated the reopening, and commits the revised architecture: materialize and inject as peer output verbs.

This document is authoritative for anything the injection decision touches. Kamae 2 §7's line "No runtime injection... The Materializer's only output verb is materialize" is superseded here. All other Kamae 2 commitments (age-per-secret, file-per-secret, filesystem-as-schema, git-backed, four-component slice) stand unchanged.


What Kamae 2 originally decided

Kamae 2 §7 (MVP Architectural Commitments):

No runtime injection. The Materializer's only output verb is materialize (write .env file). It exposes no API for injecting env vars into running processes.

Kamae 1 Scope Boundaries mirrored this:

NOT a runtime secret injector. Sharibako materializes files at known paths; consumers (docker-compose, direnv, app loaders) read those files. Sharibako does not inject env vars into running processes.

The reasoning was scope discipline: a first-Swift project, ~1,500–3,000 LOC, deliberate limits.

What changed

The threat model needs an addition.

Kamae 1 stated the threat model as:

"Laptop gets stolen and someone reads the disk" — covered by age encryption — not "someone gains code execution on my machine."

That model considered offline disk-read attacks. It did not consider AI agents as workspace actors — a category that sits between "laptop stolen offline" and "code execution / malware." An AI coding agent running in the practitioner's workspace has file-read access to the same tree as the practitioner. It is not RCE; there is no exploit. The agent reads .env because it can read files.

Under the current materialize architecture, a project's .env is plaintext at rest. FileVault protects it against offline disk theft. Nothing protects it against an in-workspace agent that lists project files and reads one named .env. The transcript-leak problem Kamae 1 named as the top pain point ("I paste keys into Claude conversations") reasserts itself the moment the agent reads a materialized file — the leak is upstream of the paste.

This is not a hypothetical. It is the daily working reality of every persona named in Kamae 1 (indie developers, vibe coders, homelabbers — all using AI agents in their workspaces). Building a robust tool for those users means addressing the class, not skirting it. The threat-model gap alone justifies the reopening; no other argument is required.

The decision

Both materialize and inject are first-class output verbs. Sharibako exposes:

Both verbs share the same age-decrypt path, the same Vault Core, the same Touch ID gating. The only difference is where the decrypted value ends up — a file on disk (materialize) or an environment variable in a child process (run).

Neither verb is deprecated. Neither is a fallback. Users pick the right verb per situation. The rule to teach is one sentence:

Use run for interactive dev. Use materialize for anything that starts on its own — docker-compose services, systemd units, cron jobs.

Why not the alternative shapes

Three architectures were on the table. Sharibako commits to materialize + run as peers. The other two are declined, and the reasons matter for future readers.

Declined: reference-in-.env with a Sharibako-aware loader

The the opposition-research memo proposed: project files contain OPENAI_API_KEY=shari://shared/openai-personal; a Sharibako-specific loader replaces the standard dotenv and resolves references at process start.

Rejected because:

Runtime injection dominates reference-loaders on both simplicity and threat-model coverage.

Declined: materialize-only (the original Kamae 2 commitment)

Rejected because:

Committed: materialize + run as peers

Materialize handles consumers that can't be wrapped (docker-compose on a homelab host restarting on boot, systemd units, cron jobs). Run handles everything a developer launches at the terminal.

Both use the same age-decrypt path and the same Keychain unlock. Neither is second-class in the CLI, the GUI, or the docs.

Implementation shape

sharibako run — the CLI verb

Signature:

sharibako run [--scope <id>] [--] <command> [args...]

Behavior:

  1. Determine scope. If --scope given, use that. Else look up from .sharibako marker in cwd (walking up to find one, matching materialize's scope resolution).
  2. Unlock the age key via Keychain (Touch ID or password) or Linux passphrase. Same code path as get.
  3. Load all secrets for the scope from the Vault Core. Decrypt each into memory (looping the existing per-secret decrypt, or a new bulk helper). Resolve .link files to their shared targets.
  4. Compose an environment dict: parent process env merged with scope secrets (scope wins on conflict).
  5. fork() + exec() the child with that environment. Inherit stdin/stdout/stderr. Register signal handlers that forward SIGINT, SIGTERM, SIGHUP to the child's PID.
  6. wait() on the child. Return the child's exit status.
  7. On any exit path (normal, signal, error), zero the decrypted values in the wrapper's memory before returning. Best-effort — Swift's memory model does not guarantee wiping, but explicit withUnsafeMutableBytes + memset_s for each string reduces the window.

In Swift, this is Foundation.Process with .environment set to the composed dict, .standardInput/Output/Error = FileHandle.standard*, plus signal handlers registered via signal() or DispatchSourceSignal.

Ho-04.5 (see Kamae 4) owns the implementation. See that for scoped-in edge cases (empty scope, missing marker, unknown scope, mid-flight Keychain relock).

The Vault Core addition

One new function:

func get_all_secrets(scopeID: String) throws -> [String: String]

Loops list_scope(id) for keys, calls get_value on each, resolves .link targets, returns a dictionary. Reuses existing single-secret decrypt code — no new crypto path.

The GUI

The Workshop does not add a "Run" button in ho-05 or ho-06 — the injection use case is CLI-native. If GUI-side "launch a terminal with the scope's secrets loaded" turns out to be a user request, it lands post-v1. The Workshop's role remains: browse, edit, materialize, sync, manage links.

Touch ID frequency

run requires one Touch ID at the start of each invocation. If the wrapper survives long enough for a rerun (dev server restart, watch mode), the values are still in memory — but launching a new sharibako run invocation re-prompts. Same friction pattern as get. The sharibako-agent daemon (post-MVP) remains the escape hatch if this friction is felt.

Threat model — the addition

Kamae 1's threat model gets one new class:

Class 4: AI agents and other workspace actors with file-read access.

Adversary. A benign or semi-benign process — an AI coding agent, an IDE indexer, a language server, a search tool, a backup daemon — that has legitimate read access to the practitioner's project directories.

Attack surface. Anything on disk in a scanned directory. Specifically:

Coverage.

Mitigations for materialize users.

New CLI helpers

Two additions justified by the threat-model update:

What stays the same

Everything in Kamae 2 not listed above stands:

Reflected in

Open items handed to downstream hos

None blocking. Ho-04.5 owns:

Ho-09 (website + release) owns (if and when ship- site work justifies it):


Decision committed. Downstream documents updated in the same session. This file is a permanent record; not to be edited except for typographical fixes.

Rendered from the corpus, verbatim · source on GitHub →

ingested: sharibako @ a97b22af9b61 · ho-system @ 79e96b801a13 · the glossary · the colophon