Commit Graph

60 Commits

Author SHA1 Message Date
Quality Badge Bot de803e1e76 chore: update quality badges
- Pylint: 9.94/10
- Pyright: 0 errors

[skip ci]
2026-06-10 05:34:38 +00:00
Quality Badge Bot 8e084262a0 chore: update quality badges
- Pylint: 9.94/10
- Pyright: 4 errors

[skip ci]
2026-06-10 04:07:25 +00:00
Quality Badge Bot 660b9b3810 chore: update quality badges
- Pylint: 9.94/10
- Pyright: 0 errors

[skip ci]
2026-06-09 05:46:00 +00:00
didericis-codex 1bebb7467f feat(backend): default to smolmachines 2026-06-09 03:27:31 +00:00
Quality Badge Bot ff495c1521 chore: update quality badges
- Pylint: 9.95/10
- Pyright: 0 errors

[skip ci]
2026-06-08 02:40:06 +00:00
Quality Badge Bot 8105e93031 chore: update quality badges
- Pylint: 9.93/10
- Pyright: 0 errors

[skip ci]
2026-06-07 15:57:03 +00:00
didericis-claude 63a3b9b50a docs: remove pipelock references from README, examples, and test docs
lint / lint (push) Successful in 1m27s
test / unit (push) Successful in 33s
test / integration (push) Successful in 46s
Update Quality Badges / update-badges (push) Successful in 1m8s
Pipelock was removed in PR #193. Update the five remaining places
where current documentation (README, examples/bottles/claude.md,
tests/README.md, docs/ci.md, sidecar_bundle.py comment) still
described the old pipelock + cred-proxy topology.
2026-06-06 05:08:59 +00:00
Quality Badge Bot 7e6e0b1f5a chore: update quality badges
- Pylint: 9.92/10
- Pyright: 0 errors

[skip ci]
2026-06-06 05:03:57 +00:00
Quality Badge Bot 7967d32f12 chore: update quality badges
- Pylint: 9.92/10
- Pyright: 18 errors

[skip ci]
2026-06-06 04:50:47 +00:00
didericis b0679dc4c3 docs: add pylint and pyright quality badges to README
test / integration (pull_request) Has been cancelled
test / unit (pull_request) Has been cancelled
Added badges to visually communicate code quality:
- pylint: 9.92/10 (0 reportable issues)
- pyright: 0 errors (100% type safe)

These badges clearly indicate the project's code quality standards
and type safety achievements to users and contributors.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-04 11:56:36 -04:00
didericis 98e4e2b7dc docs(readme): additional tweaks
test / unit (pull_request) Successful in 34s
test / integration (pull_request) Successful in 50s
test / unit (push) Successful in 42s
test / integration (push) Successful in 52s
2026-06-03 21:19:00 -04:00
didericis-claude 9eca46b408 docs: slim README to threat model, features, one diagram, one manifest
test / unit (pull_request) Successful in 45s
test / integration (pull_request) Successful in 56s
2026-06-03 21:29:32 +00:00
didericis-codex 941f316462 feat(git-gate): remove git remote host override plumbing 2026-06-02 18:17:24 +00:00
didericis-codex 68e5097534 fix(codex): make host-credential bottles actually authenticate
Debugging a live codex smolmachines bottle surfaced three independent
failures past the sign-in screen; fix each so forward_host_credentials
works end to end:

- codex_auth: dummy access/id tokens now inherit the *real* host token's
  exp instead of now+1h. Codex (0.135) refreshes when its local token's
  JWT exp lapses; with a placeholder refresh_token that refresh fails and
  drops to the sign-in screen. Aligning exp tracks the real token's life.

- prepare: set CODEX_CA_CERTIFICATE to the agent CA bundle for codex
  bottles. Codex is rustls and ignores the system store / NODE_EXTRA_CA_
  CERTS; it reads CODEX_CA_CERTIFICATE (fallback SSL_CERT_FILE) for custom
  roots across HTTPS + wss, so it must be pointed at the egress MITM CA or
  injection can't work without tls_passthrough.

- pipelock: auto tls_passthrough the Codex API hosts when
  forward_host_credentials is on. Egress injects the bearer before
  pipelock, whose header DLP then flags the JWT ("request header contains
  secret") and the retry storm trips its 429. passthrough host-gates the
  CONNECT but skips decrypt+rescan of egress-owned auth. The auto-added
  routes aren't in bottle.egress.routes, so the hosts are added explicitly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 22:24:17 -04:00
didericis-codex a6332b9535 fix(codex): provision dummy user auth state 2026-06-01 22:24:17 -04:00
didericis-codex 62dd7b2aa5 fix(codex): forward host credentials to api route 2026-06-01 22:24:17 -04:00
didericis-codex 711cb9c194 feat(codex): inject host credentials via egress 2026-06-01 22:24:17 -04:00
didericis-codex cea832b21d fix(codex): stop injecting api key placeholder
test / unit (pull_request) Successful in 27s
test / integration (pull_request) Successful in 41s
2026-05-29 02:39:37 -04:00
didericis dcd90cd45e docs(manifest): document + demo agent-level git.user
test / unit (pull_request) Successful in 35s
test / integration (pull_request) Successful in 57s
test / unit (push) Successful in 32s
test / integration (push) Successful in 44s
README manifest section documents the agent git.user overlay, the
bottle-only git.remotes boundary, and the claimed-not-vouched trust
note. Collapses the example: implementer carries its own identity
against the shared dev bottle instead of an identity-only bottle.

Refs #94

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-28 21:10:47 -04:00
didericis-codex fed006441d fix(pipelock): allow route ssrf ip policy
test / unit (pull_request) Successful in 28s
test / integration (pull_request) Successful in 44s
2026-05-28 19:32:31 -04:00
didericis-codex bcadc07d09 feat(pipelock): allow route tls passthrough policy
test / unit (pull_request) Successful in 37s
test / integration (pull_request) Successful in 58s
2026-05-28 19:19:40 -04:00
didericis-codex cdb1870b1c docs(agent): clarify claude oauth env
test / unit (pull_request) Successful in 29s
test / integration (pull_request) Successful in 43s
2026-05-28 18:20:09 -04:00
didericis-codex cacba087c9 docs(agent): document provider base bottles
test / unit (pull_request) Successful in 34s
test / integration (pull_request) Successful in 53s
Assisted-by: Codex
2026-05-28 18:00:38 -04:00
didericis-codex c08b09dc9f refactor!: rename project to bot-bottle
Assisted-by: Codex
2026-05-28 17:56:14 -04:00
didericis-codex 8875d8cc17 fix(agent): address provider review feedback
test / unit (pull_request) Successful in 35s
test / integration (pull_request) Successful in 47s
Assisted-by: Codex
2026-05-28 17:24:39 -04:00
didericis-codex c9291f97e6 docs: add project status positioning
test / unit (pull_request) Successful in 27s
test / integration (pull_request) Successful in 40s
2026-05-28 02:35:01 -04:00
didericis-codex 500fd910c4 feat(agent): add provider templates
test / unit (pull_request) Successful in 28s
test / integration (pull_request) Successful in 40s
Assisted-by: Codex
2026-05-28 02:18:53 -04:00
didericis-codex 59ee32cc8d refactor(manifest): key git config by host
test / unit (pull_request) Successful in 33s
test / integration (pull_request) Successful in 42s
2026-05-28 00:49:34 -04:00
didericis-claude 85104742ca docs(readme): document bottle extends: composition (PRD 0025)
test / unit (pull_request) Successful in 26s
test / integration (pull_request) Successful in 42s
2026-05-27 23:31:02 -04:00
didericis-claude d0712fb757 docs(readme): document git_user manifest field (issue #86)
test / unit (pull_request) Successful in 27s
test / integration (pull_request) Successful in 42s
test / unit (push) Successful in 26s
test / integration (push) Successful in 44s
Add a `git_user:` block to the example bottle frontmatter with a
one-paragraph note on what it does + that either field can be
set independently. Other doc surfaces (manifest module docstring,
provisioner module docstrings) were updated alongside the
implementation commits.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 23:00:59 -04:00
didericis-claude 7eda2a66ec feat(smolmachines): patch smolvm state DB to actually enforce per-bottle allowlist
test / unit (pull_request) Successful in 26s
test / integration (pull_request) Successful in 44s
Earlier commit framed this PR as "infrastructure landed, TSI
enforcement blocked on upstream smolvm 0.8.0." Found a clean
workaround that lets us enforce now.

Smolvm persists each machine's config (including
`allowed_cidrs`) as a JSON BLOB in
`~/Library/Application Support/smolvm/server/smolvm.db`,
`vms.data`. `machine create --allow-cidr X/32` silently writes
`allowed_cidrs: null` to that row when combined with `--from`,
but smolvm reads the row at `machine start` — so patching the
row between create and start sets the allowlist for real.

New `loopback_alias.force_allowlist(machine_name, cidrs)` opens
the SQLite DB, JSON-decodes the row, sets `allowed_cidrs`, and
writes back as BLOB (Text type silently corrupts smolvm's
later reads). launch.py calls it immediately after
`machine_create` and before `machine_start`.

Verified end-to-end on macOS / Docker Desktop:

  VM allowlist after start: ["127.0.0.16/32"]
  VM → 127.0.0.1:3000      → BLOCKED (Permission denied)
  VM → 8.8.8.8:53          → BLOCKED (Permission denied)
  VM → 127.0.0.16:<bundle> → CONNECTED

The DB-patch hack is correct only because smolvm reads
`allowed_cidrs` from the row at start time (not derived in-
process). When upstream honors `--allow-cidr` with `--from`,
the call becomes redundant — drop the call and the workaround
is gone.

Tests: 4 new for `force_allowlist` (BLOB round-trip; Linux
no-op; missing DB; missing row). Total 593 unit tests pass.

README + PRD updated to reflect the fix landed (no longer
"infrastructure pending upstream"). gitea#75 can close.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 16:55:03 -04:00
didericis-claude a919268d5e docs: honest framing of upstream smolvm 0.8.0 allowlist bug
test / unit (pull_request) Successful in 26s
test / integration (pull_request) Successful in 40s
PR #76 originally claimed the per-bottle alias scoping closed
gitea#75 ("agent can reach host loopback"). Verified
empirically that's not actually true: `smolvm 0.8.0 machine
create --from <smolmachine> --net --allow-cidr X/32` silently
drops the allowlist (`agent.config.json` shows `allowed_cidrs:
null`, and the running VM reaches all of `127.0.0.0/8`
regardless).

So the alias-allocation + alias-bind infrastructure is correct
pre-work, but the actual TSI enforcement is blocked on an
upstream smolvm bug. README + PRD 0023 + the module docstring
get reworded to say so plainly. gitea#75 stays open.

Workarounds tried (all dead-ends):
- `machine update --allow-cidr` doesn't exist
- stop-edit-`agent.config.json`-restart fails (smolvm removes
  the file on stop)
- `--smolfile` is mutually exclusive with `--from`
- `--image localhost:<port>/...` fails because smolvm's agent
  process can't reach host loopback during pull

When upstream lands a fix, our existing code (alias allocation,
port-bind, --allow-cidr in launch) will scope correctly without
further changes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 16:37:56 -04:00
didericis-claude 2edc1abb9a feat(smolmachines): per-bottle loopback alias scopes TSI to single /32
test / unit (pull_request) Successful in 27s
test / integration (pull_request) Successful in 41s
PR #74's Docker-Desktop fix routed the agent through
`127.0.0.1:<random>` loopback forwards, but TSI filters by IP
only — so the allowlist `127.0.0.1/32` let the agent VM reach
**any** host service on macOS loopback (postgres, dev servers,
other bottles' published ports, mDNSResponder, ...). Real
downgrade vs the docker backend's `--internal` network.

Resolution: per-bottle loopback alias.

- New `loopback_alias` module manages a pool of
  `127.0.0.16` .. `127.0.0.31` on `lo0`. macOS only routes
  `127.0.0.1` by default; the extras need `sudo ifconfig lo0
  alias`. `ensure_pool()` lazily adds the missing entries via
  one sudo prompt on first launch per reboot — aliases persist
  on `lo0` until reboot, so subsequent launches skip the
  prompt entirely.
- `allocate(slug)` picks the lowest-numbered unused alias by
  inspecting running bundle containers' port-binding HostIps.
  No on-disk reservation — docker is the source of truth.
- Bundle bringup binds published ports to the allocated alias
  (`docker run -p <alias>::<port>`) instead of `127.0.0.1`.
- TSI allowlist becomes the alias's /32 — narrows reachability
  to this bottle's bundle only.
- Linux native daemons share the host's network namespace;
  `127.0.0.0/8` works without aliases, so the module no-ops on
  non-Darwin and returns `127.0.0.1` from `allocate`.

Tracking issue closed: gitea/issues/75.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 16:23:17 -04:00
didericis-claude d7cef27584 feat(smolmachines): PRD 0022 sandbox-escape suite green under smolmachines (PRD 0023 chunk 5)
test / unit (pull_request) Successful in 26s
test / integration (pull_request) Successful in 43s
Final PRD 0023 chunk. The PRD 0022 attack suite was already
backend-agnostic — it goes through get_bottle_backend(), so the
right dispatch happens based on CLAUDE_BOTTLE_BACKEND. Two
cleanups to make it actually run cleanly under
CLAUDE_BOTTLE_BACKEND=smolmachines:

- setUpClass raises unittest.SkipTest with a useful message when
  CLAUDE_BOTTLE_BACKEND=smolmachines but smolvm isn't on PATH, or
  when the host isn't macOS (libkrun + TSI single-IP allowlist is
  macOS-only in v1). Without this, the test would die deep inside
  backend.prepare's smolmachines_preflight rather than skipping.

- test_5_readme_push_blocked switches from a hardcoded
  `git://git-gate/...` remote URL (only resolvable on docker via
  the bundle's short alias) to the bottle's declared upstream URL
  (`ssh://git@unreachable.invalid:22/throwaway.git`). The agent's
  ~/.gitconfig insteadOf rewrite — set up by provision_git on both
  backends — transparently redirects to the gate, so the same test
  exercises docker's `git://git-gate/...` and smolmachines's
  `git://<bundle_ip>:9418/...` URLs without branching on backend.

README gets a "Backend selection" subsection under Quickstart
documenting CLAUDE_BOTTLE_BACKEND, the macOS-only v1 scope for
smolmachines, and the `curl -sSL .../install.sh | sh` install
prerequisite — per PRD 0023's acceptance criteria.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 16:12:10 -04:00
didericis-claude 45c821a8f3 docs(smolmachines): note loopback-scope limitation + tracking issue
test / unit (pull_request) Successful in 26s
test / integration (pull_request) Successful in 43s
PR #74's Docker-Desktop pivot widened the smolmachines TSI
allowlist from `<bundle-ip>/32` to `127.0.0.1/32` (TSI can't
filter by port, and docker bridge IPs aren't reachable from
macOS networking). The agent VM can therefore reach any service
on macOS's loopback while the bottle is running — not just the
bundle's published ports.

README gets a "Smolmachines backend" subsection under Quickstart
spelling this out as a known v1 limitation. PRD 0023 grows a new
open question #8 with the proposed v2 fix (per-bottle loopback
alias + TSI allowlist scoped to that /32, via sudo
`ifconfig lo0 alias`).

Tracking issue: gitea.dideric.is/didericis/claude-bottle/issues/75.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 15:58:30 -04:00
didericis 62f6f8db34 refactor(sidecars): bundle is the only shape (PRD 0024 chunk 5)
test / unit (pull_request) Successful in 21s
test / integration (pull_request) Successful in 43s
The CLAUDE_BOTTLE_SIDECAR_BUNDLE feature flag is gone. Every
bottle ships with the agent + bundle pair — no opt-in, no legacy
four-sidecar fallback.

Changes:

- Renderer (compose.py): bottle_plan_to_compose unconditionally
  emits {agent, sidecars}. Deleted _pipelock_service,
  _git_gate_service, _egress_service, _supervise_service helpers.
  _agent_service.depends_on collapses to ["sidecars"].

- sidecar_bundle.py: deleted sidecar_bundle_enabled (the flag
  parser). SIDECAR_BUNDLE_IMAGE + container-name helper stay.

- pipelock_apply.py: docker cp + docker restart now target
  sidecar_bundle_container_name(slug). Bundle restart bounces
  all four daemons together (per-daemon reload is the eventual
  feature, not v1).

- Per-sidecar modules trimmed:
  - egress.py: dropped EGRESS_IMAGE, EGRESS_DOCKERFILE,
    build_egress_image, egress_url. Kept EGRESS_PORT, CA paths,
    egress_container_name (still used by the renderer's network
    aliases).
  - git_gate.py: dropped GIT_GATE_IMAGE, GIT_GATE_DOCKERFILE,
    build_git_gate_image. Kept git_gate_host + GIT_GATE_PORT.
  - supervise.py: dropped SUPERVISE_IMAGE, SUPERVISE_DOCKERFILE,
    build_supervise_image, supervise_url.

- Deleted Dockerfile.{egress,git-gate,supervise}. The bundle's
  Dockerfile.sidecars is the only sidecar image now.

- test_compose.py: deleted TestPipelockAlwaysPresent,
  TestConditionalGitGate, TestConditionalEgress,
  TestConditionalSupervise, TestFullMatrix (legacy-shape only),
  TestSidecarBundleFlag (flag is gone). TestSidecarBundleShape
  drops its patch.dict wrapper. TestAgentAlwaysPresent's
  depends_on cases collapse to one.

- test_pipelock_apply.py: bringup container name uses
  sidecar_bundle_container_name(slug) to match the production
  target.

- README.md Architecture section rewritten to describe the
  agent + bundle pair.

Net: -626 lines.

Test status: 498 unit + 27 integration + 1 skipped (chunk-4
pending — superseded by this chunk's rewrite). Locally verified
end-to-end bottle launch produces exactly 2 containers
(claude-bottle-<slug> + claude-bottle-sidecars-<slug>).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 01:37:21 -04:00
didericis 958a8845a6 docs: rewrite README manifest section + ship MD examples (PRD 0011)
test / unit (pull_request) Successful in 12s
test / integration (pull_request) Successful in 22s
The "Manifest" section now describes the per-file MD layout under
~/.claude-bottle/{bottles,agents}/, the filename-as-key convention,
the YAML subset constraints, and the trust boundary (bottles are
home-only by filesystem layout). Includes a working bottle example
with comments inside the frontmatter and a working agent example
showing the Markdown body as the system prompt.

Drops claude-bottle.example.json. The new examples/ tree —
examples/bottles/dev.md, examples/agents/implementer.md,
examples/agents/researcher.md — verifies the parser end-to-end via
Manifest.from_md_dirs(examples/, None).
2026-05-24 22:19:44 -04:00
didericis 6b91506706 docs: redraw README architecture to show pipelock as HTTP/S chokepoint
test / unit (pull_request) Successful in 15s
test / integration (pull_request) Successful in 22s
The previous diagram showed three parallel egress lanes — agent ↔
pipelock, agent ↔ git-gate, agent ↔ cred-proxy — each going off-box
independently. That was true of an earlier shape but is now wrong on
two counts:

1. cred-proxy's outbound HTTPS routes through pipelock (set when
   the SSRF / CA-trust wiring landed). All cred-proxy upstream
   bytes pass pipelock's allowlist + body scanner.
2. git-gate's SSH push/fetch is direct out the egress network and
   has never gone through pipelock — pipelock is HTTP-only.

Reflect both: the diagram now collapses to one HTTP/HTTPS chokepoint
(pipelock) that the agent and cred-proxy share, plus a separate SSH
lane for git-gate. Prose paragraph above the diagram updated to call
out the "everything except SSH" framing explicitly.

Verified against the current code: HTTPS_PROXY=pipelock set on the
agent in launch.py and on cred-proxy in DockerCredProxy.start;
git-gate's create-args carry no proxy env vars.
2026-05-24 14:23:26 -04:00
didericis 32b62cbacc feat(cred_proxy)!: cred-proxy is the only Anthropic auth path
test / unit (pull_request) Successful in 13s
test / integration (pull_request) Successful in 23s
Removes the legacy `CLAUDE_BOTTLE_OAUTH_TOKEN` -> `CLAUDE_CODE_OAUTH_TOKEN`
forward in prepare.py. Bottles that need claude-code to authenticate
must declare a cred_proxy route with role: "anthropic-base-url" — there
is no fallback that hands the token to the agent directly.

Drops the now-dead BottleSpec.forward_oauth_token field, the CLI
setter that read CLAUDE_BOTTLE_OAUTH_TOKEN from the host env at
prepare time, and the forward_oauth_token=False arg in the six
pipelock integration tests.

PRD 0010 and README updated; the dev ~/claude-bottle.json gains an
anthropic-base-url route so the implementer/researcher agents keep
working.

BREAKING: bottles previously relying on the implicit OAuth forward
will now produce an agent environ without any Anthropic credential.
Verified with --dry-run: a bottle with no anthropic-base-url route
yields env_names: [] (no token at all); a bottle that declares the
route yields ANTHROPIC_BASE_URL plus a non-secret placeholder for
CLAUDE_CODE_OAUTH_TOKEN.
2026-05-24 12:56:09 -04:00
didericis fcbbc4484d refactor(cred_proxy): flat routes, role-driven provisioning (PRD 0010)
test / unit (pull_request) Successful in 14s
test / integration (pull_request) Successful in 22s
Replace bottle.tokens (with Kind enum and hardcoded per-kind
route/auth tables) with bottle.cred_proxy.routes — each route
declares its own path, upstream, auth_scheme, token_ref, and
optional role[]. The manifest is now the source of truth for the
proxy's runtime route table; adding an upstream is a manifest edit,
not a code change.

Agent-side rewrites move from per-kind dispatch to per-role tags
on routes:
  anthropic-base-url -> set ANTHROPIC_BASE_URL=<proxy><path>
  npm-registry       -> write ~/.npmrc registry=
  git-insteadof      -> write ~/.gitconfig [url] insteadOf, keyed
                        off route.upstream (suppressed when
                        bottle.git brokers the same host)
  tea-login          -> add a ~/.config/tea/config.yml login

Roles are a list (string accepted as sugar). A gitea route
typically carries ["git-insteadof", "tea-login"]. Singleton roles
(anthropic-base-url, npm-registry) appear on at most one route.

token_env slots are assigned per distinct TokenRef in declaration
order — two routes sharing a token_ref (e.g. github API + git
endpoints) share a slot.

Drops: TOKEN_KINDS, _KIND_ROUTES, _KIND_AUTH_SCHEME, _TOKEN_DEFAULT_HOST,
cred_proxy_route_path_for_gitea, the kind field on CredProxyUpstream,
and the kind-based hardcoding in pipelock_token_hosts (now derives
from route.UpstreamHost).

Legacy bottle.tokens manifests now die with a hint pointing at
bottle.cred_proxy.routes + this PRD. Tests rewritten end-to-end.
Docs + example.json + the dev ~/claude-bottle.json updated to match.
2026-05-13 21:49:55 -04:00
didericis 431e7481ef docs: README + example.json for cred-proxy (PRD 0010)
test / unit (pull_request) Successful in 15s
test / integration (pull_request) Successful in 29s
- Architecture diagram gains the cred-proxy lane (agent talks plain
  HTTP via bearer-auth-injection; sidecar talks HTTPS to the real
  upstream with the manifest token).
- Adds a cred-proxy entry under the sidecar bullet list, with a
  pointer to PRD 0010.
- Manifest example illustrates the `tokens` array on a bottle.
- Auth section notes that declaring an `anthropic` token routes
  CLAUDE_BOTTLE_OAUTH_TOKEN through the sidecar instead of into
  the agent's environ.
- claude-bottle.example.json gains an `agentic` bottle declaring
  all four token kinds, plus a paired `agentic-helper` agent.
2026-05-13 16:32:46 -04:00
didericis 3f4708f970 docs(demo): add end-to-end demo with recorded GIF
test / unit (push) Successful in 22s
test / integration (push) Successful in 31s
Squashes the demo-build arc: initial GIF + scripts, refactor to drive
recording through real cli.py, theme/timing tweaks, and the switch to
prompt-driven probes.
2026-05-13 15:33:28 -04:00
didericis 30d92bef48 docs: drop ssh from README/example, supersede PRD 0007 (PRD 0009)
test / unit (pull_request) Successful in 13s
test / integration (pull_request) Successful in 21s
- README architecture diagram drops the socat/ssh image box and
  the agent's ~/.ssh/config; the prose-bullets section drops the
  ssh image; the manifest example swaps `ssh:` for `git:` so
  someone copy-pasting it picks up the new shape.
- claude-bottle.example.json: `default` bottle's `"ssh": []` is
  gone (now just an empty bottle); the gitea-dev example already
  uses `git:` since the ExtraHosts work.
- PRD 0007 carries a "Superseded by PRD 0009" header at the top
  with a one-paragraph block explaining why; the file stays so
  the rationale of the prior design is still in-tree.
- git_gate.py: drop the now-stale shadow-route mention from a
  docstring (the validator went away in the manifest layer).
2026-05-12 23:57:50 -04:00
didericis 9b7bcc0149 docs(git-gate): document ExtraHosts on bottle.git entries
test / unit (pull_request) Successful in 12s
test / integration (pull_request) Successful in 19s
- example manifest swaps the gitea-dev bottle from ssh: to git:
  and shows ExtraHosts pinning gitea.dideric.is to its Tailscale IP
- README's git-gate paragraph names the field and the case it
  solves (upstream resolvable on the host but not from the gate
  container's default DNS)
- PRD 0008's manifest-field bullet mentions the field for parity
2026-05-12 23:18:46 -04:00
didericis 76a56c0700 docs(readme): git-gate is now a bidirectional mirror
test / unit (pull_request) Successful in 11s
test / integration (pull_request) Successful in 21s
Architecture diagram + bullet now reflect that the gate fronts
every git operation, not just push: pre-receive gitleaks-gates
the push path; an access-hook refreshes from upstream before each
upload-pack so fetch / clone / pull / ls-remote see whatever the
upstream has at that moment (fail-closed if unreachable).
2026-05-12 22:36:16 -04:00
didericis bea433015f docs(readme): add git-gate to architecture diagram
test / unit (pull_request) Successful in 13s
test / integration (pull_request) Successful in 17s
Bumps the sidecar count from two to up to three; the diagram and
bullet list now cover the git-gate alongside pipelock and ssh-gate,
including the ~/.gitconfig pushInsteadOf wiring that fires the
agent's git push through the gate.
2026-05-12 21:19:20 -04:00
didericis 4790f8bcc1 docs(readme): add architecture diagram for agent/pipelock/ssh-gate
test / unit (push) Successful in 12s
test / integration (push) Successful in 13s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 18:01:43 -04:00
didericis d75cc9325f feat(bottles): implement bottle factory abstraction per PRD 0003
test / run tests/run_tests.py (pull_request) Successful in 16s
Introduce claude_bottle/bottles/ with a Bottle Protocol and a
get_bottle_factory() that dispatches on CLAUDE_BOTTLE_PLATFORM
(default "docker"). Move every Docker-specific subprocess.run call
from cli/start.py, plus the orchestration of build, networks, the
pipelock sidecar, container launch, and per-container provisioning
(prompt, skills, ssh, .git), into create_docker_bottle.

Drop bottles[].runtime from the manifest schema. Auto-detect whether
gVisor is registered with the daemon and pass --runtime=runsc when it
is; the preflight shows the resolved runtime so the choice is visible.
Manifests still carrying 'runtime' get a clear error pointing at the
auto-detect behavior, rather than silent ignore.

Out of scope: cli/cleanup.py and cli/list.py still call docker
directly. They enumerate active bottles across the host, which is a
separate concern from "create a bottle" and is left for a follow-up
that introduces a list_active/cleanup primitive on the factory.
2026-05-10 22:15:05 -04:00
didericis 79604fded7 docs: reposition README around scoped-agent wedge and note DoH
test / run tests/run_tests.py (push) Successful in 21s
Lead with what the project does for the user — scoped Claude Code
agents on self-hosted infrastructure with per-agent secret and egress
limits — instead of the 2024-coded "isolated container" framing.
Tagline, "Why claude-bottle?" intro, and goals list now name secret
minimization, egress allowlisting, and self-hosted operation as the
load-bearing properties.

Also adds a sentence to the security model noting that DoH is
blocked structurally by the existing egress allowlist, since the
bottle has no L3 path off-box except through pipelock's hostname-
allowlisted CONNECT proxy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 03:00:19 -04:00
didericis fe232744a6 docs: reframe security model around secret exposure and exfiltration
test / run tests/run_tests.py (push) Successful in 20s
Soften the "container is the boundary against reaching the host"
framing in favor of what the design actually leans on: granting each
bottle only the secrets it needs, and constraining where those
secrets can travel via pipelock. The container is one layer rather
than the load-bearing one.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 02:42:12 -04:00