Commit Graph

15 Commits

Author SHA1 Message Date
didericis c91395425c docs(prds): add PRD 0008 git gate
test / unit (pull_request) Successful in 12s
test / integration (pull_request) Successful in 13s
Per-bottle sidecar that fronts the agent's git remotes, runs
gitleaks via a pre-receive hook, and only forwards to the real
upstream on a clean scan. Upstream push credentials live in the
gate, not the agent — so a misbehaving agent cannot push a
secret-bearing commit past it.
2026-05-12 18:24:33 -04:00
didericis a3d77cd015 fix(ssh-gate): listen on the upstream port so URL-supplied ports work
test / unit (pull_request) Successful in 12s
test / integration (pull_request) Successful in 12s
Bug: git fetch failed with "connect to host
claude-bottle-ssh-gate-implementer port 30009: Connection refused".
OpenSSH treats a URL-supplied port (the user's remote was
ssh://git@gitea.dideric.is:30009/...) as overriding the
~/.ssh/config Port directive, so even though the config wrote
Port 30000 the agent dialed :30009 — where nothing was listening
because the gate had been assigned BASE_LISTEN_PORT + index.

Fix: the gate's listen port now equals the upstream port. Same
script, same socat, just port = entry.Port. Two entries on the
same upstream port are rejected at prepare time (the gate is one
container with a flat port space).

Re-smoked: probe nc github.com via the gate at :22, banner came
back as expected.

PRD 0007 updated to record the design refinement.
2026-05-12 16:19:07 -04:00
didericis b2927b1483 docs(prd): note gate image must be self-sufficient at boot on 0007
test / unit (pull_request) Successful in 12s
test / integration (pull_request) Successful in 13s
The gate's agent-facing leg sits on the `--internal` network, so
the forwarder image cannot rely on apk/apt at startup. Surfaced
by the DNS spike — a placeholder using `apk add socat` died
silently and gave a false-negative DNS-on-internal result.
2026-05-12 15:50:34 -04:00
didericis cb0f0f133d docs(prd): resolve gate-DNS open question on 0007
test / unit (pull_request) Successful in 12s
test / integration (pull_request) Successful in 14s
Spike: container on a `--internal` user-defined network resolves
another container's name via the embedded resolver at 127.0.0.11
and reaches it over TCP, while egress to the public internet
remains blocked. The PRD's design assumption holds — no design
change needed.
2026-05-12 15:48:55 -04:00
didericis 02a0fe679d docs(prd): 0007 SSH egress gate
test / unit (pull_request) Successful in 18s
test / integration (pull_request) Successful in 15s
PRD 0006 enabled pipelock's native TLS interception, which broke
git fetch over SSH from inside the agent: pipelock's SNI gate
rejects the SSH banner that follows CONNECT. Document the
architectural fix — a dedicated per-agent TCP-forwarder sidecar
built from bottle.ssh entries — so pipelock can stay maximally
strict on the HTTPS path with no SSH carve-outs.
2026-05-12 15:41:26 -04:00
didericis f44e884d8a docs(prd): fold 0006 walkthrough resolutions into the design
test / unit (pull_request) Successful in 15s
test / integration (pull_request) Successful in 14s
After the open-question walkthrough, all four collapsed:

- Q1 (mount semantics): resolved to `docker cp` between
  `docker create` and `docker start`, mirroring the existing
  pipelock YAML handling. No bind mount, no UID/permission
  concern. Folded into §Proposed Design > CA lifecycle as
  "Sidecar install".
- Q2 (cert validity / TTL): pre-decided in the question text.
  Per-bottle ephemerality is enforced by regenerating per launch,
  not by short validity windows. Pipelock's defaults are fine.
  Folded into §Proposed Design as a one-line "Per-bottle
  ephemerality" note.
- Q3 (`passthrough_domains` shape): not v1 scope; the shape is
  pre-recorded so the follow-up is mechanical. Moved into
  §Out of scope.
- Q4 (stage-dir cleanup ordering): reading start.py confirmed
  the ExitStack-then-outer-finally order is correct. Folded into
  §Proposed Design as a "Teardown" note.

The §Open questions section is dropped. None of the four was a
real design question — they were verifications and pre-decided
items left in for defensiveness.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 14:22:59 -04:00
didericis 6716f091c1 docs(prd): add 0006, enable pipelock's native TLS interception
test / unit (pull_request) Successful in 12s
test / integration (pull_request) Successful in 13s
Supersedes the abandoned PR #8 (`mitmproxy-tls-interception`),
which built a mitmproxy + addon chain on the (falsified) premise
that pipelock could not MITM. Empirical proof from the impl-time
spike: with `tls_interception: { enabled: true, ca_cert, ca_key }`
in pipelock's config, pipelock answered a credential POST over
HTTPS with `STATUS=403 / body: blocked: request body contains
secret: GitHub Token` and emitted both `scanner:"tls_intercept"`
and `scanner:"body_dlp"` events. Standalone, no second proxy.

Net change vs PR #8: one sidecar instead of two, no vendored
addon, no addon-verdict pattern matching, no HTTPS-trust /
DNS / lookup workarounds. Same end-state behavior — pipelock's
DLP fires on plaintext for HTTPS hosts in the allowlist.

Also cleaning up the now-stale TLS-research notes:

- `docs/research/tls-mitm-for-pipelock.md` is removed. Its
  entire premise (mitmproxy in front of pipelock) is moot now
  that pipelock does the work natively. The mechanics of CONNECT
  bumping and the CA-lifecycle considerations it documented are
  the same as what pipelock implements; the PRD restates the
  parts that matter for the integration.
- `docs/research/pipelock-assessment.md` had two stale claims
  corrected: the "Pipelock does not perform TLS inspection (no
  CA trust injection)" line in §Scope gaps and the
  "no TLS termination" cell in the comparison table. Both now
  point at the `tls_interception` config and `pipelock tls`
  CLI instead.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 14:15:44 -04:00
didericis 45203e2cd6 docs(prd): add 0004 split out provisioners
test / unit (pull_request) Successful in 12s
test / integration (pull_request) Successful in 13s
2026-05-11 19:36:39 -04:00
didericis f0b67a3e94 docs(prd): update PRD 0003 to reflect the shipped design
test / run tests/run_tests.py (pull_request) Successful in 14s
Renames the file and rewrites the body around what actually shipped:
class-based BottleBackend ABC (not a free create_docker_bottle
function), the two-phase prepare/launch split, the backend/docker/
subpackage layout, env.py reshaped into a backend-neutral ResolvedEnv,
and PipelockProxy split between top-level and backend/docker/.
2026-05-11 14:47:17 -04:00
didericis 70a22fa210 refactor: rename platform abstraction to backend
test / run tests/run_tests.py (pull_request) Successful in 21s
Across the package:
  - claude_bottle/platform/         -> claude_bottle/backend/
  - platform/docker/platform.py     -> backend/docker/backend.py
  - class BottlePlatform            -> BottleBackend
  - class DockerBottlePlatform      -> DockerBottleBackend
  - get_bottle_platform()           -> get_bottle_backend()
  - env var CLAUDE_BOTTLE_PLATFORM  -> CLAUDE_BOTTLE_BACKEND
  - dict _PLATFORMS                 -> _BACKENDS

"Backend" is shorter and more established as the term for a
pluggable strategy-pattern implementation. "Platform" was vague
(could mean OS, hardware, cloud) and mildly redundant — Docker is
itself a platform.

The previous PRD section claiming "the Backend protocol was
rejected" referred to a low-level run/exec/cp/network_connect
protocol; the name was never the reason. The PRD is updated to
describe that rejected design by shape rather than by name.

The bottle/agent concepts and the manifest schema are unchanged.
2026-05-10 23:59:38 -04:00
didericis d5c056f36e docs(prd): add 0003 bottle factory abstraction
test / run tests/run_tests.py (pull_request) Successful in 17s
2026-05-10 21:56:10 -04:00
didericis cc5e772519 docs: replace stale .sh paths with claude_bottle/*.py equivalents
test / run tests/run_tests.py (push) Successful in 13s
Cleans up references to the pre-refactor bash layout (cli.sh,
lib/*.sh, scripts/*.sh) across README, Dockerfile, the pipelock PRD,
and research notes. Refreshes line numbers in the oauth-token note
against the current cli/start.py.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 00:27:25 -04:00
didericis 4694db1201 PRD 0002: Test pipeline on Gitea Actions (#3)
test / run tests/run_tests.py (push) Successful in 20s
2026-05-09 02:48:03 -04:00
didericis ba7616a4ae PRD 0001: Per-agent egress proxy via pipelock (#1) 2026-05-08 01:56:43 -04:00
didericis c45f384fb8 Initial commit 2026-05-07 22:45:36 -04:00