Skip to content

feat(cloak): -p cloak stealth-browser profile, auth sessions, non-gating release - #492

Merged
lroolle merged 4 commits into
mainfrom
feat/cloak-456
Jul 27, 2026
Merged

feat(cloak): -p cloak stealth-browser profile, auth sessions, non-gating release#492
lroolle merged 4 commits into
mainfrom
feat/cloak-456

Conversation

@lroolle

@lroolle lroolle commented Jul 27, 2026

Copy link
Copy Markdown
Member

Supersedes #487, which GitHub auto-closed when its base branch (feat/kimi-455)
was deleted on merge of #486. Same content, rebased onto main.

deva.sh -p cloak <agent> runs agents in a new image (Dockerfile.cloak,
extends :rust) with CloakBrowser stealth Chromium, headed on Xvfb :99 +
openbox. Chromium is baked at build time (hermetic, auto-update off);
CLOAKBROWSER_WRAPPER_VERSION pins the wrapper and thereby Chromium.

Authenticated sessions, three composable pieces:

  • persistent profile: ~/.config/deva/cloak-profile -> Chromium userDataDir,
    so one login survives container removal
  • interactive login over VNC: on OrbStack/Linux the host reaches the
    container directly (x11vnc on demand, no flag). --cloak-vnc is the
    Docker-Desktop fallback that publishes VNC on a host-loopback port
  • --cloak-browser: an always-on daemon holds one headed persistent browser
    with a loopback CDP endpoint; the agent drives it via connectOverCDP --
    the same live browser, not a throwaway

Image policy, the part worth arguing about
cloak is an auxiliary image on its own cadence, NOT a general releasable
profile. It holds no agent CLIs and only changes when Dockerfile.cloak,
cloak-entrypoint.sh or CLOAKBROWSER_WRAPPER_VERSION change -- never on a
deva.sh version bump. So:

  • the release cloak job is continue-on-error and is OUT of the release
    job's needs. A ~200MB Chromium bake per arch (arm64 under QEMU) must not
    sit on the critical path of every release, and a flake in an optional layer
    must not fail a release whose artifact is deva.sh
  • nightly already skips it, same reasoning
  • notes present :cloak as rolling, :vX.Y.Z-cloak as possibly lagging
  • CI stays paths-filtered

Follow-up #485 makes this policy data instead of a comment: profile identity
is currently duplicated across deva.sh, Makefile and three workflows.

Also fixed here: a .deva config setting DEVA_DOCKER_TAG silently overrode the
real environment and made CLI -p a no-op. Precedence is now
environment > config files > -p profile > default, guarded by
scripts/test-image-precedence.sh in CI.

Test plan

  • make test-cloak green
  • LIVE against the built image (wrapper 0.5.2, Chrome 146.0.7680.177):
    x11vnc answers RFB 003.008 on :5900; cloak-browserd serves CDP on
    127.0.0.1:9222; a separate playwright-core client connectOverCDPs,
    drives a page, and the daemon survives that client's close()
  • stealth confirmed live: UA spoofs Windows NT 10.0, navigator.webdriver
    === false
  • closes the 2026-07-21 dev-log gap that marked exactly these two claims
    unverified
  • image-precedence, mount-shape, container-slug suites green
  • skill documents the setContent() footgun found while proving it: it
    hangs on a fresh daemon page; use goto(data:, domcontentloaded)

Closes #456

lroolle and others added 2 commits July 27, 2026 02:32
…pport

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>
- release job no longer needs build-and-push-cloak; the cloak job is
  continue-on-error. A ~200MB Chromium bake per arch (arm64 under QEMU)
  must not sit on the critical path of a GitHub Release, and a flake in
  an optional layer must not fail a release whose artifact is deva.sh
- cloak holds no agent CLIs and only changes when Dockerfile.cloak,
  cloak-entrypoint.sh or CLOAKBROWSER_WRAPPER_VERSION change -- never on
  a version bump. Nightly already skipped it for the same reason
- release notes: :cloak is the rolling tag, :vX.Y.Z-cloak may lag
- skill: goto(data:,{domcontentloaded}) not setContent() -- setContent
  hangs on a fresh daemon page waiting for a load event that never fires
- dev log records the live proofs that close the 07-21 unverified gap:
  RFB 003.008 on :5900, CDP on :9222, connectOverCDP drives the live
  browser and the daemon survives client disconnect

Refs #456
Copilot AI review requested due to automatic review settings July 27, 2026 09:32
@claude-yolo

claude-yolo Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error after 1s —— View job


I'll analyze this and get back to you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe6782bbda

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/ci.yml
shell: bash
run: |
set -euo pipefail
./scripts/test-image-precedence.sh

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Invoke the non-executable smoke script through Bash

The new script is committed with mode 100644, so this direct ./scripts/test-image-precedence.sh invocation fails with Permission denied before running any assertions, causing every CI run through this job to fail. Either mark the script executable or invoke it as bash ./scripts/test-image-precedence.sh.

Useful? React with 👍 / 👎.

Comment thread deva.sh
Comment on lines +3372 to +3374
DOCKER_ARGS+=("-e" "DEVA_CLOAK_BROWSER=1")
DOCKER_ARGS+=("-e" "DEVA_CLOAK_CDP_PORT=${cdp_port}")
DOCKER_ARGS+=("-e" "CLOAK_CDP_ENDPOINT=http://127.0.0.1:${cdp_port}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Recreate warm containers when enabling the browser daemon

When a persistent cloak container was originally created without --cloak-browser, rerunning with this flag resolves to the same container because the option is not included in its shape. These -e entries only affect docker run; the existing-container path executes the entrypoint with only _trace_env, so DEVA_CLOAK_BROWSER and CLOAK_CDP_ENDPOINT remain absent and no daemon starts. Treat this option as a distinct container shape or explicitly recreate the container when it is first enabled.

Useful? React with 👍 / 👎.

Comment thread deva.sh
# dead URL.
announce_cloak_vnc() {
local phase="$1"
if [ "$phase" = "existing" ] && [ "${DEVA_CLOAK_VNC:-}" = "1" ] && [ -z "$DEVA_CLOAK_VNC_URL" ]; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not advertise VNC for a warm container without its port

When --cloak-vnc is added after a persistent cloak container already exists, setup_cloak_vnc always assigns DEVA_CLOAK_VNC_URL from a newly probed port, so this -z check is false and the script prints that URL and a newly generated password. The corresponding -p and VNC environment arguments cannot be applied to the existing container, so the advertised endpoint is dead. Check the container's actual published ports or use a separate container shape for VNC-enabled runs.

Useful? React with 👍 / 👎.

Comment thread docker-entrypoint.sh
Comment on lines +431 to +435
setsid nohup gosu "$DEVA_USER" env \
"HOME=$DEVA_HOME" "PATH=$PATH" "DISPLAY=${DISPLAY:-:99}" \
"CLOAK_APP_DIR=${CLOAK_APP_DIR:-/opt/cloak}" \
"DEVA_CLOAK_PROFILE_DIR=$profile" "DEVA_CLOAK_CDP_PORT=$port" \
node "$daemon" >"$log" 2>&1 &

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Wait for the CDP endpoint before launching the agent

On a fresh --cloak-browser launch, this backgrounds Node and immediately returns without waiting for Chromium's CDP port to listen. The documented client calls connectOverCDP directly, so normal browser startup latency can make the first agent connection fail even though the daemon becomes ready moments later. Poll the CDP endpoint with a bounded timeout before returning from maybe_start_cloak_browser.

Useful? React with 👍 / 👎.

Comment on lines +10 to +14
paths:
- "Dockerfile.cloak"
- "scripts/cloak-entrypoint.sh"
- "versions.env"
- ".github/workflows/cloak-image.yml"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Trigger the cloak workflow for daemon changes

A PR or push that changes only scripts/cloak-browserd.mjs skips this workflow even though Dockerfile.cloak copies that file into the image and test-cloak is intended to validate it. This allows a broken daemon change to merge without building or testing the cloak image; add the daemon path to both path-filter lists.

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new cloak image profile to deva, providing a headed “stealth Chromium” environment (CloakBrowser) plus composable authenticated-session workflows (persistent profile mount, optional VNC, optional always-on browser daemon). Also hardens image/tag resolution so config files can’t silently override environment intent or make -p a no-op, and updates CI/release policy so cloak publishing is non-gating.

Changes:

  • Introduces the cloak image (Dockerfile + entrypoint + daemon) and deva.sh -p cloak runtime flags (--cloak-vnc, --cloak-browser) with persistent profile support.
  • Adds version pinning for CLOAKBROWSER_WRAPPER_VERSION and wires it through update/refresh tooling and build workflows.
  • Adjusts release/CI workflows: cloak build is paths-filtered in CI and non-gating in release; adds a smoke test for image/tag precedence.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
versions.env Adds CLOAKBROWSER_WRAPPER_VERSION pin with explanatory comments.
skills/deva-cloak/SKILL.md New skill doc for operating CloakBrowser, VNC login, and CDP daemon usage.
scripts/version-pins.sh Includes CLOAKBROWSER_WRAPPER_VERSION in pin registry.
scripts/update-version-pins.sh Emits/refreshes CloakBrowser wrapper version from npm.
scripts/test-image-precedence.sh Adds regression test for image/tag precedence and -p behavior.
scripts/cloak-entrypoint.sh New cloak entrypoint starting Xvfb/openbox and optional x11vnc.
scripts/cloak-browserd.mjs New always-on headed browser daemon exposing loopback CDP.
README.md Documents the new stable :cloak tag.
Makefile Adds build/test targets and args for cloak image.
llms.txt Updates image/tag overview and adds -p cloak usage example.
docs/custom-images.md Documents local Dockerfile.cloak build path and args.
docs/advanced-usage.md Documents cloak profile purpose and how to build/use it.
Dockerfile.cloak Defines the cloak image layer (X stack + wrapper install + baked Chromium).
docker-entrypoint.sh Adds optional startup of the cloak browser daemon post UID/GID remap.
deva.sh Adds cloak profile support, new runtime flags, and fixes image/tag precedence.
DEV-LOGS.md Adds dev-log entries describing cloak policy and verification notes.
CHANGELOG.md Changelog entries for cloak support and image-precedence fix.
AGENTS.md Updates container environment notes (ephemeral behavior).
.github/workflows/release.yml Adds non-gating cloak publish job and release-note section for cloak tags.
.github/workflows/cloak-image.yml New paths-filtered workflow to build/test cloak image.
.github/workflows/ci.yml Adds smoke test execution for image/tag precedence.
.dockerignore Ignores reference/ directory in addition to references/.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +16 to +20
for _ in $(seq 1 50); do
DISPLAY=:99 xdotool getdisplaygeometry >/dev/null 2>&1 && break
sleep 0.2
done

Comment thread deva.sh
Comment on lines +3406 to +3411
if [ -z "$DEVA_CLOAK_VNC_PASSWORD_EFFECTIVE" ]; then
# Read a FINITE chunk of urandom, then filter+cut. Piping an unbounded
# `</dev/urandom` into `head -c 8` makes head close the pipe early, tr
# takes SIGPIPE, and pipefail+set -e would kill the whole launch.
DEVA_CLOAK_VNC_PASSWORD_EFFECTIVE="$(head -c 128 /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | cut -c1-8)"
DEVA_CLOAK_VNC_PASSWORD_GENERATED=true
Comment thread deva.sh
Comment on lines +3324 to +3327
# 0 if this run targets the cloak image (via -p cloak or a cloak tag).
is_cloak_image() {
[ "${PROFILE:-}" = "cloak" ] || [[ "${DEVA_DOCKER_TAG:-}" == *cloak* ]]
}
Comment thread DEV-LOGS.md
- What:
- 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.
- Result: traced launch verified live against the host daemon.
- Live verification against the built `:cloak` image (wrapper 0.5.2, Chrome 146.0.7680.177): x11vnc answers the RFB handshake on :5900 with `RFB 003.008`; cloak-browserd serves CDP on 127.0.0.1:9222; a separate playwright-core client `connectOverCDP`s, drives a page, and the daemon survives that client's `close()`. Stealth confirmed live: UA spoofs `Windows NT 10.0`, `navigator.webdriver === false`. First probe raced the daemons — both are up ~4s after boot, so poll the ports, don't poll only the display.
CI invokes it as ./scripts/test-image-precedence.sh and got exit 126,
Permission denied. Local runs used `bash <script>`, which ignores the
exec bit -- so the mode never showed up until CI ran it directly.

Refs #456
Copilot AI review requested due to automatic review settings July 27, 2026 09:39
@claude-yolo

claude-yolo Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error after 1s —— View job


I'll analyze this and get back to you.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.

Comment thread deva.sh
Comment on lines +2991 to +2998
if [ "$DEVA_DOCKER_TAG_SOURCE" != "env" ]; then
DEVA_DOCKER_TAG="$value"
DEVA_DOCKER_TAG_ENV_SET=true
DEVA_DOCKER_TAG_SOURCE="config"
normalize_docker_image_parts
export DEVA_DOCKER_TAG
USER_ENVS+=("$name=$value")
fi
Comment thread deva.sh
Comment on lines +3433 to +3440
announce_cloak_vnc() {
local phase="$1"
if [ "$phase" = "existing" ] && [ "${DEVA_CLOAK_VNC:-}" = "1" ] && [ -z "$DEVA_CLOAK_VNC_URL" ]; then
echo "note: DEVA_CLOAK_VNC=1 but this container was created without a VNC port; recreate it (--rm, or stop/remove) to enable VNC" >&2
return 0
fi
[ -n "$DEVA_CLOAK_VNC_URL" ] || return 0
echo "Cloak VNC: $DEVA_CLOAK_VNC_URL (open in a VNC client to watch/drive the browser the agent opens)" >&2
Comment thread versions.env
Comment on lines +20 to +23
# CloakBrowser npm wrapper version. This also pins the Chromium binary:
# the wrapper hardcodes per-arch free-binary versions (linux-x64 146.x.x.5,
# linux-arm64 146.x.x.3), so bumping the wrapper is what moves Chromium.
CLOAKBROWSER_WRAPPER_VERSION=0.4.12
check_image ran unconditionally, so a dry run pulled a multi-GB image and,
when the requested tag was missing, its availability fallback rewrote
DEVA_DOCKER_TAG -- making --dry-run report a tag the user never asked for.

CI caught it as a precedence-test failure that could not reproduce locally:
:rust is pullable, so an earlier case pulled it, and the following
`-p cloak` case then failed to pull :cloak (not published yet), found the
freshly-pulled rust locally, and "resolved" to rust. The assertion was
right; the dry run was lying.

Resolution is what --dry-run reports; availability is a launch-time
concern. Skipping check_image on dry runs also makes the precedence
contract testable without any registry state -- the suite now passes with
docker entirely absent.

Refs #456
Copilot AI review requested due to automatic review settings July 27, 2026 09:50
@claude-yolo

claude-yolo Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error after 1s —— View job


I'll analyze this and get back to you.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

DEV-LOGS.md:19

  • This dev-log entry says the built cloak image used wrapper 0.5.2, but the repo pin is CLOAKBROWSER_WRAPPER_VERSION=0.4.12 (versions.env) and Dockerfile.cloak defaults to 0.4.12. Please reconcile so readers don't assume a different wrapper/Chromium than what will be built/released.
  - Live verification against the built `:cloak` image (wrapper 0.5.2, Chrome 146.0.7680.177): x11vnc answers the RFB handshake on :5900 with `RFB 003.008`; cloak-browserd serves CDP on 127.0.0.1:9222; a separate playwright-core client `connectOverCDP`s, drives a page, and the daemon survives that client's `close()`. Stealth confirmed live: UA spoofs `Windows NT 10.0`, `navigator.webdriver === false`. First probe raced the daemons — both are up ~4s after boot, so poll the ports, don't poll only the display.

Comment thread Dockerfile.cloak
Comment on lines +63 to +65
npm install --no-audit --no-fund \
"cloakbrowser@${CLOAKBROWSER_WRAPPER_VERSION}" playwright-core && \
./node_modules/.bin/cloakbrowser install && \
Comment on lines +108 to +110
```bash
x11vnc -display :99 -rfbport 5900 -forever -shared -bg # add -passwd <pw> to require a password
```
@lroolle
lroolle merged commit 68e9d91 into main Jul 27, 2026
6 of 7 checks passed
@lroolle
lroolle deleted the feat/cloak-456 branch July 27, 2026 09:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: cloak profile - CloakBrowser stealth Chromium image

2 participants