Download
91 lines 5.5 KiBMarkdownModified 2026-10-31 raw

Working on Blinker

Blinker is a sandbox (blinker) and a coding agent built on it (blinker-agent), whose whole reason to exist is safety. Every change should leave them at least as safe, small and simple as they were.

Principles

  1. Security is paramount. Follow every best practice for safe Go:
    • Check every error. Never discard one with _ unless the comment says why that is safe. Failures fail closed: deny, refuse, or stop.
    • Validate everything that crosses a trust boundary: model output and tool arguments, policy and config files, command output, server responses. The model is the adversary in the threat model: it may be confused or prompt-injected.
    • The kernel sandbox is what enforces the policy. In-process checks decide what to ask the user and which sandbox mode to use; they are never the only thing standing between the model and a file. File tools do their I/O inside the sandbox too.
    • Never interpolate untrusted text into shell commands, sandbox profiles or regular expressions without validating or escaping it. Pass arguments as argv, not through a shell.
    • Strip terminal control sequences from anything shown to the user that came from the model, a file or a command, so it cannot fake a prompt. Approval prompts show the full script or diff, never a truncated one.
    • Secrets (API keys) stay in blinker's process. They never reach a sandboxed command's environment, a log, or the model.
    • Run go vet ./... and go test ./... before calling work done. The sandbox tests run against the real bwrap/sandbox-exec.
  2. Keep dependencies minimal. Use the standard library. Add a dependency only when doing without it is clearly worse, and say why in the commit message. The current direct dependencies are Bubble Tea, Bubbles, Lip Gloss, x/ansi, and x/vt with Ultraviolet (the TUI, and the terminal emulator for the sandbox command's box, all from Charm), uniseg and go-runewidth (text widths, as Bubbles measures them), golang.org/x/term, and golang.org/x/net (its HTML parser, for Fetch).
  3. Keep the codebase small. Don't overengineer: no speculative abstractions, options nobody asked for, or layers that exist "in case". Every line is attack surface and something a reviewer must read. Describe files with globs, not regular expressions. Regular expressions are fine for simple, specific pattern matching and extraction, but never in security-critical code (policy matching, sanitizing, parsing what decides access), where bugs hide in them: write that out plainly.
  4. Ask the user through the TUI. When information is required (a setting, an approval, a choice), prompt for it in the TUI. Don't add command-line flags or environment variables for it, and don't read from stdin outside the TUI. The sandbox command, blinker, asks only what its policy's host ask rules say to, in its box, or on the terminal when the command's output is redirected; it takes its policy as -p, --preview, and no other options unless asked for.
  5. Use XDG directories. Configuration goes under $XDG_CONFIG_HOME/blinker (default ~/.config/blinker), state such as history under $XDG_STATE_HOME/blinker (default ~/.local/state/blinker), on macOS too. Never write into the project except through the agent's tools.
  6. File formats: history is stored as XML, settings as JSON (config.json for the sandbox, agent.json for the agent), and the access policies (policies/<name>.policy, the agent's agent-safe and agent-unsafe among them, policies/commands/<command>.policy, and each project's policies/projects/<project>/{safe,unsafe}.policy) as text with one rule per line, described in the README. Blinker edits policies line by line, so their comments survive. Tools are bash scripts with a header (tools/<Name>.sh); the built-in ones are in internal/tooldef/tools/. The policies make install installs are in policies/.

Layout

  • cmd/blinker: the sandbox command, which runs a program under a policy, in a box of its own when run from a terminal (box.go).
  • cmd/blinker-agent: the agent's entry point and subcommands.
  • internal/config: the JSON settings files and XDG paths.
  • internal/llm: the OpenAI-compatible chat client (streaming, tool calls).
  • internal/policy: parsing and evaluating access policies.
  • internal/tooldef: reading tool files, and the built-in tools' definitions.
  • internal/sandbox: running commands under bubblewrap (Linux) or Seatbelt (macOS), each with its own network proxy, and previewing them (Linux: preview.go, apply.go). Everything that differs by OS is behind the platform interface (platform.go), implemented in bwrap.go and seatbelt.go; the few Linux-only system calls are in sys_linux.go.
  • internal/proxy: the allowlisting HTTP/CONNECT proxy.
  • internal/pty: opening pseudo-terminals.
  • internal/tools: the agent's tools: Read, Write, Patch, Safe_Bash, Unsafe_Bash, the built-in Commit and Fetch, and running the user's tools.
  • internal/diff: line diffs and applying unified diffs.
  • internal/diffview: showing unified diffs on a terminal, as delta does, for the agent's prompts and blinker --preview.
  • internal/htmlmd: turning web pages into Markdown, for Fetch.
  • internal/agent: the conversation loop.
  • internal/history: saved conversations (XML).
  • internal/tui: the Bubble Tea interface, including setup.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
# Working on Blinker

Blinker is a sandbox (`blinker`) and a coding agent built on it
(`blinker-agent`), whose whole reason to exist is safety. Every change
should leave them at least as safe, small and simple as they were.

## Principles

1. **Security is paramount.** Follow every best practice for safe Go:
   - Check every error. Never discard one with `_` unless the comment says
     why that is safe. Failures fail closed: deny, refuse, or stop.
   - Validate everything that crosses a trust boundary: model output and
     tool arguments, policy and config files, command output, server
     responses. The model is the adversary in the threat model: it may be
     confused or prompt-injected.
   - The kernel sandbox is what enforces the policy. In-process checks
     decide what to ask the user and which sandbox mode to use; they are
     never the only thing standing between the model and a file. File tools
     do their I/O inside the sandbox too.
   - Never interpolate untrusted text into shell commands, sandbox profiles
     or regular expressions without validating or escaping it. Pass
     arguments as argv, not through a shell.
   - Strip terminal control sequences from anything shown to the user that
     came from the model, a file or a command, so it cannot fake a prompt.
     Approval prompts show the full script or diff, never a truncated one.
   - Secrets (API keys) stay in blinker's process. They never reach a
     sandboxed command's environment, a log, or the model.
   - Run `go vet ./...` and `go test ./...` before calling work done. The
     sandbox tests run against the real `bwrap`/`sandbox-exec`.
2. **Keep dependencies minimal.** Use the standard library. Add a
   dependency only when doing without it is clearly worse, and say why in
   the commit message. The current direct dependencies are Bubble Tea,
   Bubbles, Lip Gloss, `x/ansi`, and `x/vt` with Ultraviolet (the TUI, and
   the terminal emulator for the sandbox command's box, all from Charm),
   `uniseg` and
   `go-runewidth` (text widths, as Bubbles measures them),
   `golang.org/x/term`, and `golang.org/x/net` (its HTML parser, for Fetch).
3. **Keep the codebase small.** Don't overengineer: no speculative
   abstractions, options nobody asked for, or layers that exist "in case".
   Every line is attack surface and something a reviewer must read.
   Describe files with globs, not regular expressions. Regular expressions
   are fine for simple, specific pattern matching and extraction, but never
   in security-critical code (policy matching, sanitizing, parsing what
   decides access), where bugs hide in them: write that out plainly.
4. **Ask the user through the TUI.** When information is required (a
   setting, an approval, a choice), prompt for it in the TUI. Don't add
   command-line flags or environment variables for it, and don't read from
   stdin outside the TUI. The sandbox command, `blinker`, asks only what its
   policy's `host ask` rules say to, in its box, or on the terminal when
   the command's output is redirected; it takes its policy as `-p`, `--preview`,
   and no other options unless asked for.
5. **Use XDG directories.** Configuration goes under `$XDG_CONFIG_HOME/blinker`
   (default `~/.config/blinker`), state such as history under
   `$XDG_STATE_HOME/blinker` (default `~/.local/state/blinker`), on macOS
   too. Never write into the project except through the agent's tools.
6. **File formats:** history is stored as XML, settings as JSON
   (`config.json` for the sandbox, `agent.json` for the agent), and the
   access policies (`policies/<name>.policy`, the agent's `agent-safe` and
   `agent-unsafe` among them, `policies/commands/<command>.policy`, and
   each project's `policies/projects/<project>/{safe,unsafe}.policy`) as
   text with one rule per line, described in the README. Blinker edits policies line by line, so their comments
   survive. Tools are bash
   scripts with a header (`tools/<Name>.sh`); the built-in ones are in
   `internal/tooldef/tools/`. The policies `make install` installs are in
   `policies/`.

## Layout

- `cmd/blinker`: the sandbox command, which runs a program under a policy,
  in a box of its own when run from a terminal (`box.go`).
- `cmd/blinker-agent`: the agent's entry point and subcommands.
- `internal/config`: the JSON settings files and XDG paths.
- `internal/llm`: the OpenAI-compatible chat client (streaming, tool calls).
- `internal/policy`: parsing and evaluating access policies.
- `internal/tooldef`: reading tool files, and the built-in tools' definitions.
- `internal/sandbox`: running commands under bubblewrap (Linux) or Seatbelt
  (macOS), each with its own network proxy, and previewing them (Linux:
  `preview.go`, `apply.go`). Everything that differs by OS is behind the
  `platform` interface (`platform.go`), implemented in `bwrap.go` and
  `seatbelt.go`; the few Linux-only system calls are in `sys_linux.go`.
- `internal/proxy`: the allowlisting HTTP/CONNECT proxy.
- `internal/pty`: opening pseudo-terminals.
- `internal/tools`: the agent's tools: Read, Write, Patch, Safe_Bash,
  Unsafe_Bash, the built-in Commit and Fetch, and running the user's tools.
- `internal/diff`: line diffs and applying unified diffs.
- `internal/diffview`: showing unified diffs on a terminal, as delta does,
  for the agent's prompts and `blinker --preview`.
- `internal/htmlmd`: turning web pages into Markdown, for Fetch.
- `internal/agent`: the conversation loop.
- `internal/history`: saved conversations (XML).
- `internal/tui`: the Bubble Tea interface, including setup.