Skip to content

Commit d9720c3

Browse files
lroolleclaude
andcommitted
feat(cloak): add -p cloak stealth-browser profile and auth-session support
Second of two commits on this branch, on top of the kimi batch. Adds the CloakBrowser profile (#456) and the image/tag precedence fix it needs. - cloak profile: `-p cloak` runs agents in a CloakBrowser image (Dockerfile.cloak, extends :rust) -- source-patched stealth Chromium, headed on Xvfb :99 + openbox, binary baked at build (auto-update off). Ships the deva-cloak skill, make build-cloak/test-cloak, a paths-filtered cloak-image.yml, and release :cloak / :vX.Y.Z-cloak tags. - auth sessions: a persistent host profile mount (~/.config/deva/ cloak-profile -> userDataDir) so a login survives container removal. Interactive login over VNC is on-demand and direct-reach on OrbStack/Linux -- the agent starts x11vnc and you connect at the container IP; --cloak-vnc is the Docker-Desktop fallback that publishes a loopback port. --cloak-browser is an optional always-on cloak-browserd daemon the agent attaches to over CDP. - fix(precedence): env > config > -p > default, so an explicit -p beats a .deva DEVA_DOCKER_TAG (guarded by scripts/test-image-precedence.sh). Not built/smoke-tested locally (container network blocks apt); release CI builds the image on GitHub runners. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 743567d commit d9720c3

21 files changed

Lines changed: 1019 additions & 32 deletions

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ compose.yaml
3131

3232
# References directory (don't need in image)
3333
references/
34+
reference/
3435

3536
# Claude trace logs
3637
.claude-trace/

.github/workflows/cloak-image.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Cloak Image
2+
3+
# Paths-filtered on purpose: the cloak build bakes a ~200MB Chromium binary,
4+
# too heavy for every PR. Nightly also skips cloak deliberately — it only
5+
# refreshes agent CLI versions, none of which live in the cloak layer.
6+
# Release builds and pushes the real :cloak tag.
7+
on:
8+
pull_request:
9+
branches: [ main ]
10+
paths:
11+
- "Dockerfile.cloak"
12+
- "scripts/cloak-entrypoint.sh"
13+
- "versions.env"
14+
- ".github/workflows/cloak-image.yml"
15+
push:
16+
branches: [ main ]
17+
paths:
18+
- "Dockerfile.cloak"
19+
- "scripts/cloak-entrypoint.sh"
20+
- "versions.env"
21+
- ".github/workflows/cloak-image.yml"
22+
23+
jobs:
24+
build-and-test:
25+
name: Build and Test Cloak Image
26+
runs-on: ubuntu-latest
27+
steps:
28+
- name: Checkout
29+
uses: actions/checkout@v4
30+
31+
- name: Load pinned versions
32+
id: pins
33+
shell: bash
34+
run: |
35+
set -euo pipefail
36+
source ./scripts/version-pins.sh
37+
load_version_pins
38+
emit_github_outputs "$GITHUB_OUTPUT"
39+
40+
# Build on top of the published rust image: this validates the cloak
41+
# layer itself without rebuilding the whole base/rust stack in CI.
42+
- name: Build cloak image
43+
run: |
44+
make build-cloak-image \
45+
IMAGE_NAME=deva-smoke \
46+
CLOAK_TAG=ci-cloak \
47+
RUST_IMAGE=ghcr.io/thevibeworks/deva:rust \
48+
CLOAKBROWSER_WRAPPER_VERSION="${{ steps.pins.outputs.cloakbrowser_wrapper_version }}"
49+
50+
- name: Test cloak image
51+
run: |
52+
make test-cloak \
53+
IMAGE_NAME=deva-smoke \
54+
CLOAK_TAG=ci-cloak

.github/workflows/release.yml

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ jobs:
5555
ccx_version: ${{ steps.pins.outputs.ccx_version }}
5656
copilot_api_version: ${{ steps.pins.outputs.copilot_api_version }}
5757
playwright_version: ${{ steps.pins.outputs.playwright_version }}
58+
cloakbrowser_wrapper_version: ${{ steps.pins.outputs.cloakbrowser_wrapper_version }}
5859
rust_toolchains: ${{ steps.pins.outputs.rust_toolchains }}
5960
rust_default_toolchain: ${{ steps.pins.outputs.rust_default_toolchain }}
6061
rust_targets: ${{ steps.pins.outputs.rust_targets }}
@@ -216,10 +217,60 @@ jobs:
216217
RUST_DEFAULT_TOOLCHAIN=${{ needs.load-version-pins.outputs.rust_default_toolchain }}
217218
RUST_TARGETS=${{ needs.load-version-pins.outputs.rust_targets }}
218219
220+
build-and-push-cloak:
221+
name: Build and Push Cloak Profile Image
222+
runs-on: ubuntu-latest
223+
needs: [prepare, load-version-pins, build-and-push-rust]
224+
permissions:
225+
contents: read
226+
packages: write
227+
steps:
228+
- name: Checkout
229+
uses: actions/checkout@v4
230+
with:
231+
ref: ${{ needs.prepare.outputs.release_tag }}
232+
233+
- name: Set up QEMU
234+
uses: docker/setup-qemu-action@v3
235+
236+
- name: Set up Docker Buildx
237+
uses: docker/setup-buildx-action@v3
238+
239+
- name: Log in to Container Registry
240+
uses: docker/login-action@v3
241+
with:
242+
registry: ${{ env.REGISTRY }}
243+
username: ${{ github.actor }}
244+
password: ${{ secrets.GITHUB_TOKEN }}
245+
246+
- name: Extract metadata for cloak profile
247+
id: meta-cloak
248+
uses: docker/metadata-action@v5
249+
with:
250+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
251+
tags: |
252+
type=raw,value=${{ needs.prepare.outputs.release_tag }}-cloak
253+
type=raw,value=cloak
254+
255+
- name: Build and push cloak image
256+
uses: docker/build-push-action@v5
257+
with:
258+
context: .
259+
file: ./Dockerfile.cloak
260+
platforms: linux/amd64,linux/arm64
261+
push: true
262+
tags: ${{ steps.meta-cloak.outputs.tags }}
263+
labels: ${{ steps.meta-cloak.outputs.labels }}
264+
cache-from: type=gha
265+
cache-to: type=gha,mode=max
266+
build-args: |
267+
BASE_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.release_tag }}-rust
268+
CLOAKBROWSER_WRAPPER_VERSION=${{ needs.load-version-pins.outputs.cloakbrowser_wrapper_version }}
269+
219270
release:
220271
name: Create GitHub Release
221272
runs-on: ubuntu-latest
222-
needs: [prepare, build-and-push, build-and-push-rust]
273+
needs: [prepare, build-and-push, build-and-push-rust, build-and-push-cloak]
223274
permissions:
224275
contents: write
225276
steps:
@@ -258,6 +309,10 @@ jobs:
258309
echo "- \`ghcr.io/${{ env.IMAGE_NAME }}:${RELEASE_TAG}-rust\`" >> release_notes.md
259310
echo "- \`ghcr.io/${{ env.IMAGE_NAME }}:rust\`" >> release_notes.md
260311
echo "" >> release_notes.md
312+
echo "**Cloak Profile (CloakBrowser stealth Chromium, headed):**" >> release_notes.md
313+
echo "- \`ghcr.io/${{ env.IMAGE_NAME }}:${RELEASE_TAG}-cloak\`" >> release_notes.md
314+
echo "- \`ghcr.io/${{ env.IMAGE_NAME }}:cloak\`" >> release_notes.md
315+
echo "" >> release_notes.md
261316
echo "## Supported Architectures" >> release_notes.md
262317
echo "" >> release_notes.md
263318
echo "- linux/amd64" >> release_notes.md

AGENTS.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ runtime is Linux.
1919
- $HOME is /home/deva (not /root). sudo works without password.
2020
- Pre-installed: Node.js, Python (use `uv`, not pip), Go, git,
2121
gh, make, curl. pip is NOT in PATH.
22-
- Docker is available (socket mounted from host).
23-
- System packages and build caches persist across sessions.
22+
- Ephemeral container. Installed packages will not persist.
2423
- Container details are in DEVA_* environment variables.
2524
<!-- /deva:container-context -->

CHANGELOG.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2323
Makefile, Dockerfile(+rust), version-pins/upgrade/update scripts,
2424
ci/nightly/release workflows; tests added (release-utils registry,
2525
version-upgrade mock, install-tooling, and `test-kimi-auth.sh`).
26+
- cloak authenticated-session support (#456): composable pieces for logging
27+
into sites inside the stealth browser. The session persists via a host
28+
profile dir (`~/.config/deva/cloak-profile` -> Chromium `userDataDir`,
29+
auto-mounted for either path below) so a login done once survives container
30+
removal.
31+
- Interactive login over VNC: on OrbStack/native Linux the host reaches the
32+
container directly, so the agent starts `x11vnc` on the `:99` display on
33+
demand and you connect at the container IP -- no flag, no port publish.
34+
- `--cloak-vnc` (`DEVA_CLOAK_VNC=1`): Docker-Desktop fallback that publishes
35+
the VNC display on a host-loopback port (there the container IP is not
36+
routable). Auto-generates a VNC password (printed once) unless
37+
`DEVA_CLOAK_VNC_PASSWORD` is set; the published port is fixed at container
38+
create. Independent of `--cloak-browser`.
39+
- `--cloak-browser` (`DEVA_CLOAK_BROWSER=1`): an always-on CloakBrowser
40+
daemon (`cloak-browserd`) holds one headed, persistent browser on the
41+
cloak display with a loopback CDP endpoint. The agent drives it via
42+
`connectOverCDP(CLOAK_CDP_ENDPOINT)` -- the same live browser, not a
43+
throwaway. Starts post-UID-remap in docker-entrypoint so profile files
44+
match the mounted host owner.
45+
Documented in the `deva-cloak` skill.
46+
- cloak profile (#456): `deva.sh -p cloak <agent>` runs agents in a new
47+
image (`Dockerfile.cloak`, extends `:rust`) with CloakBrowser stealth
48+
Chromium, headed on Xvfb `:99` + openbox. The Chromium binary is baked
49+
at build time (hermetic, auto-update off); `CLOAKBROWSER_WRAPPER_VERSION`
50+
in versions.env pins the wrapper and thereby Chromium. Ships the
51+
`deva-cloak` skill (keyed off `DEVA_CLOAK=1`), `make build-cloak` /
52+
`test-cloak` targets, a paths-filtered `cloak-image.yml` CI workflow,
53+
and release pushes `:cloak` / `:vX.Y.Z-cloak` tags. Nightly skips cloak
54+
on purpose — that layer contains no agent CLIs.
2655

2756
### Fixed
2857
- `--trace` launch killed by `cp: cannot create regular file
@@ -37,6 +66,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3766
test became the subshell's exit status and `set -e` killed every
3867
non-verbose traced launch right after the banner; the subshell now
3968
exits 0 explicitly.
69+
- image/tag precedence: a `.deva` config file setting `DEVA_DOCKER_TAG`
70+
(or `DEVA_DOCKER_IMAGE`) silently overrode the real environment and made
71+
CLI `-p` a no-op — `deva.sh -p cloak` with a config `DEVA_DOCKER_TAG=rust`
72+
launched rust, and even `DEVA_DOCKER_TAG=cloak deva.sh ...` was clobbered
73+
back to the config value. Precedence is now environment > config files >
74+
`-p` profile > default, with explicit CLI `-p` beating a config-file tag.
75+
Guarded by `scripts/test-image-precedence.sh` in CI.
4076

4177
## [0.16.0] - 2026-07-14
4278

DEV-LOGS.md

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,40 @@
1313
- Minimal markdown markers, no unnecessary formatting, minimal emojis.
1414
- Reference issue numbers in the format `#<issue-number>` for easy linking.
1515

16-
# [2026-07-21] Dev Log: traced launches die silently in freshly built images #414
17-
- Why: after rebuilding images, every `--trace` launch died with "failed to launch ephemeral container" and no error.
16+
# [2026-07-21] Dev Log: cloak always-on browser + VNC + persistent profile #456
17+
- Why: three asks for auth sites in the cloak browser — (1) VNC auto-secured with a password, (2) an always-on browser the agent can drive freely, (3) mount what preserves the whole session to the host. Verified the load-bearing assumption first in a throwaway cloak container: a persistent CloakBrowser launched with `--remote-debugging-port` exposes a CDP endpoint a SEPARATE Playwright client reaches via connectOverCDP, drives, and the browser survives that client disconnecting. So one daemon can back both the human (VNC) and the agent (CDP).
1818
- What:
19-
- The #414 flock rework ended the CA-install subshell on `[ "$VERBOSE" = "true" ] && echo ...`; with VERBOSE unset the failed test became the subshell's exit status, and under `set -e` the entrypoint died right after the agent banner — silently, only in freshly built images (the entrypoint is baked). Fix: explicit `exit 0` before the subshell closes. Verified A/B: baked image dies, overlay-fixed image runs `deva.sh claude --trace --rm -Q -- --version` end-to-end (cctrace MITM up, trace file written). Rebuild images to pick this up.
20-
- Result: traced launch verified live against the host daemon.
19+
- scripts/cloak-browserd.mjs: daemon holding launchPersistentContext (headed :99, userDataDir, `--remote-debugging-port=9222 --remote-debugging-address=127.0.0.1`), relaunch-on-close, SIGTERM-clean. Absolute import of cloakbrowser (lives outside $CLOAK_APP_DIR). Verified end-to-end (daemon up → agent connectOverCDP drives it → persists after disconnect).
20+
- docker-entrypoint.sh maybe_start_cloak_browser: starts the daemon AFTER setup_nonroot_user (post UID/GID remap) so the mounted profile is written as the host UID — starting it in cloak-entrypoint (pre-remap) would leave 1001-owned files the agent can't touch. Idempotent under flock + a CDP-port probe: boot (`tail -f /dev/null`) starts it once under PID 1's tree, later agent execs find it up and skip. Verified branch logic with mocks (gate off, port-up skip, start path).
21+
- deva.sh: `--cloak-browser`/`DEVA_CLOAK_BROWSER` (env + CDP endpoint + auto-mount `~/.config/deva/cloak-profile``/home/deva/.cloak-profile`, first-writer-wins). `--cloak-vnc` implies it (blank display otherwise). setup_cloak_runtime = one image guard + one warning + correct order. VNC auto-password: generated when unset, printed once in the announce; the var name contains PASSWORD so deva's existing redaction masks it in --debug. announce_cloak_browser prints the CDP endpoint + host profile path.
22+
- Bug caught in test: the VNC password gen `tr -dc ... </dev/urandom | head -c8` took SIGPIPE under `set -o pipefail` (head closes the pipe) → killed the whole launch. Fixed to read a finite `head -c 128 /dev/urandom` chunk then filter+cut. This is the [[feedback_bash_set_u_arrays]] family of set-flag footguns.
23+
- Makefile test-cloak: assert x11vnc + `node --check` the daemon. Skill rewritten: always-on browser (agent uses connectOverCDP, NOT its own launch()), VNC login flow, exactly which dir to mount (userDataDir = whole session) and which NOT (/opt/cloakbrowser = baked binary).
24+
- Result: full dry-run matrix green (vnc implies browser+profile+port+auto-pass; browser-only no port; off-profile warns; explicit password respected). NOT verified here: x11vnc serving RFB and a live daemon inside the built image — this env's network 502s the apt mirrors so I can't rebuild/apt-install. `make build-cloak && make test-cloak` on the host confirms; the CDP daemon itself is proven against the existing image.
25+
26+
# [2026-07-21] Dev Log: cloak VNC for authenticated sites #456 (initial VNC cut; extended by the always-on-browser entry above)
27+
- Why: cloak agents need to reach sites behind a login. You can't mount the host's Chrome cookies — macOS encrypts cookie values with a Keychain key the Linux container can't reach, so the DB is undecryptable inside deva. And the interactive part of a login (MFA, captcha) needs a human at a real display, which headed-on-Xvfb doesn't offer. Verified the two load paths in the live container first: `launchContext({contextOptions:{storageState}})` loads (top-level `storageState` is silently dropped — a footgun, documented), and `launchPersistentContext({userDataDir})` persists cookies-with-expiry across reopens (session cookies are memory-only).
28+
- What: opt-in VNC into the cloak Xvfb display so a human logs in once, then a mounted profile carries the session to later headless runs.
29+
- Dockerfile.cloak: +x11vnc in the apt layer.
30+
- cloak-entrypoint.sh: when DEVA_CLOAK_VNC=1, start `x11vnc -display :99 -rfbport 5900 -forever -shared -bg`. Binds 0.0.0.0 in-container ON PURPOSE — the boundary is deva publishing to host loopback only; -localhost would make docker's port-proxy unreachable. DEVA_CLOAK_VNC_PASSWORD → -rfbauth, else -nopw with a loud warning.
31+
- deva.sh: `--cloak-vnc` flag (+ DEVA_CLOAK_VNC env), setup_cloak_vnc probes a free host port from 5900 and publishes `-p 127.0.0.1:PORT:5900` + passes the env in; guarded to the cloak image; announce_cloak_vnc prints the URL and warns when attaching to a warm container that was created without the port (reachability is fixed at create, same constraint as the trace UI).
32+
- Result: dry-run matrix verified (port+env only with -p cloak + vnc; off-profile warns; free-port probe skips a busy 5900→5901). Entrypoint VNC block branch/args verified via a mocked x11vnc (-nopw vs -storepasswd/-rfbauth). NOT verified here: x11vnc actually serving RFB — this env's network 502s the apt mirrors so x11vnc can't be installed/rebuilt in-container; confirm on the host rebuild (`make build-cloak`).
33+
34+
# [2026-07-21] Dev Log: traced launches dead + -p cloak no-op — two field breakages
35+
- Why: after rebuilding images, every `--trace` launch died with "failed to launch ephemeral container" and no error; and `-p cloak` (and even env `DEVA_DOCKER_TAG=cloak`) kept launching the rust image when `~/.config/deva/.deva` pinned `DEVA_DOCKER_TAG=rust`.
36+
- What:
37+
- Trace: the #414 flock rework ended the CA-install subshell on `[ "$VERBOSE" = "true" ] && echo ...`; with VERBOSE unset the failed test became the subshell's exit status, and under `set -e` the entrypoint died right after the agent banner — silently, only in freshly built images (the entrypoint is baked). Fix: explicit `exit 0` before the subshell closes. Verified A/B: baked image dies, overlay-fixed image runs `deva.sh claude --trace --rm -Q -- --version` end-to-end (cctrace MITM up, trace file written). Rebuild images to pick this up.
38+
- Precedence: `apply_config_value` unconditionally clobbered `DEVA_DOCKER_IMAGE/TAG` and set `*_ENV_SET=true`, so config files beat the real environment and `resolve_profile` treated `-p` as already-satisfied. Now `*_SOURCE` (env|config) is tracked: config never overrides env, and an explicit CLI `-p` (new `PROFILE_FROM_CLI`) overrides a config-sourced tag. Contract: environment > config files > `-p` > default. scripts/test-image-precedence.sh pins all four cases and runs in CI; it `env -u`s the deva container's own `DEVA_DOCKER_TAG` so the harness env can't fake results — which is exactly how the bug hid in earlier dry-run smokes.
39+
- Result: both repro commands (`deva -p cloak`, `DEVA_DOCKER_TAG=cloak deva -p cloak claude` with a rust-pinned config) now resolve `:cloak`; traced launch verified live against the host daemon.
40+
41+
# [2026-07-20] Dev Log: cloak profile — CloakBrowser stealth Chromium image #456
42+
- Why: agents in the sandbox need a browser that passes bot detection (Cloudflare, FingerprintJS, reCAPTCHA scoring); plain Playwright headless in the rust image fails those checks. CloakBrowser is a source-patched Chromium with the same Playwright API — run it headed on a virtual display inside the container.
43+
- What:
44+
- Dockerfile.cloak extends :rust: apt layer (xvfb/openbox/xdotool + anti-bot font packages), wrapper+playwright-core installed into CLOAK_APP_DIR=/opt/cloak (not global — bare ESM specifiers don't resolve from a global install), `cloakbrowser install` bakes the Chromium binary at build time with auto-update off (hermetic; the free v146 binary stays on GitHub Releases forever). Kept tini as PID 1 and restated `CMD ["claude"]` — redeclaring ENTRYPOINT resets the inherited CMD.
45+
- cloak-entrypoint.sh: clean stale X locks, Xvfb :99 (1920x1080x24), poll display-ready via xdotool (bounded ~10s), openbox for --start-maximized, then exec docker-entrypoint.sh.
46+
- deva.sh: `-p cloak` profile, ghcr tag `cloak`, local-tag fallback cloak > rust > latest; skills/deva-cloak documents agent-side usage (DEVA_CLOAK=1 marker, run from $CLOAK_APP_DIR).
47+
- Version pin CLOAKBROWSER_WRAPPER_VERSION (wrapper hardcodes per-arch Chromium versions, so it pins Chromium too) wired through versions.env, Makefile (+VERSION_QUERY_OVERRIDES), version-pins.sh VERSION_PIN_VARS, update-version-pins.sh heredoc+refresh — the first draft omitted the heredoc entry, so `make versions-pin` would have silently deleted the pin.
48+
- CI: paths-filtered cloak-image.yml builds on ghcr :rust and runs make test-cloak; release pushes :cloak and :vX.Y.Z-cloak (BASE_IMAGE = the release's -rust tag). Nightly skips cloak: that layer has no agent CLIs, and the ~200MB Chromium bake would double nightly cost for nothing.
49+
- Result: cloak rides feat/kimi-agent (#455) — bundling two features on one branch violates our one-branch-per-issue rule; noted for the PR split. Build verified via docker on the host socket; test-cloak asserts cloakbrowser info + display geometry.
2150

2251
# [2026-07-20] Dev Log: add kimi agent (Moonshot Kimi Code CLI) #455
2352
- Why: Moonshot shipped an official coding-agent CLI (@moonshot-ai/kimi-code, bin `kimi`); deva should launch it like claude/codex/gemini/grok. Traced the grok integration (commit 93a59e0) via ccx as the template.

0 commit comments

Comments
 (0)