Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
0f2fb53
Check Host and Origin on every Drive request and websocket.
ishu86 Sep 3, 2026
e3c33bd
Put desktops on their own network so only cased can reach them.
ishu86 Sep 3, 2026
cfd82d4
Proxy the live view through cased with an explicit upstream header list.
ishu86 Sep 3, 2026
075d40a
Return structured cased errors to the model and call the skill secret…
ishu86 Sep 3, 2026
ce54f0b
Serve files from the computer as downloads, never as pages.
ishu86 Sep 3, 2026
695ce63
Require the desk token on the VNC websocket and exit if Xvfb never st…
ishu86 Sep 3, 2026
aca8099
Send Anthropic the screenshot, not its base64.
ishu86 Sep 3, 2026
cc3ff07
Gate exec, actions and files during credential injection and scope fi…
ishu86 Sep 3, 2026
8a8344f
Deliver the assist link, flatten prompts for ntfy, and sign the appro…
ishu86 Sep 3, 2026
12d37df
Write threads.json atomically and trim the small duplications.
ishu86 Sep 3, 2026
b9d6b6d
Check Host and Origin, open the token-in-URL doors, and authenticate …
ishu86 Sep 3, 2026
81a92a6
Add an HTTP test for the auth and origin gates.
ishu86 Sep 3, 2026
12f243f
Clear the password field before the injection gate drops.
ishu86 Sep 3, 2026
db81076
Send the solver a page URL without its query string.
ishu86 Sep 3, 2026
fba884f
Cap file uploads, keep desk calls off the event loop, and require htt…
ishu86 Sep 3, 2026
8d92a2e
Build the observe script from the shared selectors.
ishu86 Sep 3, 2026
2865cce
Keep the vault directory private and drop a computer's schedules with…
ishu86 Sep 3, 2026
78c6f30
Pin the desktop image's base, packages and noVNC tarball, and add a h…
ishu86 Sep 3, 2026
e66b41f
Recreate a missing container on wake instead of raising AttributeErro…
ishu86 Sep 3, 2026
a42313d
Pin GitHub Actions to commits and drop CI's default write token.
ishu86 Sep 3, 2026
3a153b1
Keep vault secrets out of argv and build the create body as JSON.
ishu86 Sep 3, 2026
f3ab428
Bump Pillow past its advisories and add websockets for the live relay.
ishu86 Sep 3, 2026
4bdaaa0
Run the session keeper on its own thread.
ishu86 Sep 3, 2026
fd1aa03
Relay the live desktop view through cased.
ishu86 Sep 3, 2026
3543ff3
Fail abandoned login attempts, refuse sub-minute intervals, and keep …
ishu86 Sep 3, 2026
b9a5d81
Make the session keeper non-reentrant and forget deleted computers.
ishu86 Sep 3, 2026
53d706a
Delete dead assist and link helpers and the auth_attempts aliases.
ishu86 Sep 3, 2026
a488bfb
Point the assist tests at a host the new gate allows.
ishu86 Sep 3, 2026
894e596
Merge branch 'harden/wp-d' into harden/audit
ishu86 Sep 3, 2026
ccb04a8
Merge branch 'harden/wp-f' into harden/audit
ishu86 Sep 3, 2026
c82923c
Merge branch 'harden/wp-e' into harden/audit
ishu86 Sep 3, 2026
73495bd
Merge branch 'harden/wp-b' into harden/audit
ishu86 Sep 3, 2026
03c4c87
Merge branch 'harden/wp-c' into harden/audit
ishu86 Sep 3, 2026
78a2026
Drop the WP-B store stubs now that the real helpers have landed.
ishu86 Sep 3, 2026
47d8983
Hoist the auth_attempts import and pin httpx for the test client.
ishu86 Sep 3, 2026
062900f
Describe the security model as the code now enforces it.
ishu86 Sep 4, 2026
fa13b08
Say that the solver's page URL has its query stripped.
ishu86 Sep 4, 2026
2b7f9cd
Say that in-container software, not just a compromise, can reach cased.
ishu86 Sep 4, 2026
6dd7d33
Say that desktops share a network and the desk token is what separate…
ishu86 Sep 4, 2026
7663857
Allow a login over http to the desktop's own loopback.
ishu86 Sep 4, 2026
195b056
Calibrate A1's screenshot floor to what a real desktop measures.
ishu86 Sep 4, 2026
f313ed1
Give the acceptance fixture a name no human would pick.
ishu86 Sep 4, 2026
e1f31bf
Hold the SQLite lock until shared cursors have been read.
ishu86 Sep 5, 2026
fed5082
Isolate acceptance tests and clean up only the computers they create.
ishu86 Sep 5, 2026
9bada3b
Recheck credential origins and enforce desktop request limits.
ishu86 Sep 5, 2026
a6ec2a8
Send ntfy approval actions to the signed public HTTPS URL.
ishu86 Sep 5, 2026
727c187
Update cryptography and the desktop build tools past their advisories.
ishu86 Sep 5, 2026
4fd771c
Document the verified hardening fixes and remaining security limits.
ishu86 Sep 5, 2026
7051d42
Put the first run in the README and move detailed setup into guides.
ishu86 Sep 5, 2026
659b71d
Keep working notes in docs/ out of the published tree.
ishu86 Sep 5, 2026
f94a849
Keep setup guides in the README and highlight managed Case.
ishu86 Sep 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,16 @@ CASE_LOCAL=1
# Legacy: separate reply topic for handoff answers only. Must not equal TOPIC.
# CASE_NTFY_ANSWER_TOPIC=

# Public hostname when a reverse proxy fronts the API (adds /assist links to
# notifications so a human can help from their phone). Unset on a laptop.
# Public hostname served over HTTPS by a reverse proxy in front of the API.
# Adds Assist links and signed approval buttons to ntfy notifications. Leave
# unset on a laptop; those links and buttons are omitted.
# CASE_PUBLIC_HOST=

# Extra hostnames Drive and cased will answer to (comma-separated). Only needed
# when something fronts them under another name; any other Host gets a 403.
# CASE_PUBLIC_HOST is allowed without being repeated here.
# CASE_ALLOWED_HOSTS=

# Unlock the schedule_* MCP tools (recurring agent runs). Off by default to
# keep the tool surface small.
# CASE_MCP_SCHEDULES=1
Expand Down
20 changes: 9 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@ on:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- uses: actions/setup-python@v5
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.12"
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "22"

Expand All @@ -42,12 +45,7 @@ jobs:
- name: Drive UI tests
run: |
npm --prefix web ci --omit=dev # serve.mjs imports openai at module load
node web/web-ui/test_serve.mjs
node web/web-ui/test_ntfy.mjs
node web/web-ui/test_phone.mjs
node web/web-ui/test_telegram.mjs
node web/web-ui/test_nav.mjs
node web/web-ui/test_deploy.mjs
npm --prefix web test

# Case is dual-licensed and sold commercially, which is only possible if the
# project can sublicense every contribution. Deliberately no third-party CLA
Expand All @@ -58,7 +56,7 @@ jobs:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- name: Contributor has signed the CLA
env:
AUTHOR: ${{ github.event.pull_request.user.login }}
Expand Down Expand Up @@ -91,7 +89,7 @@ jobs:
spdx:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- name: Every source file declares its license
run: |
missing=0
Expand Down
18 changes: 9 additions & 9 deletions .github/workflows/publish-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,17 @@ jobs:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
with:
context: ./image
platforms: ${{ matrix.platform }}
Expand All @@ -58,7 +58,7 @@ jobs:
mkdir -p /tmp/digests
# filename is the bare hex; the merge job puts the sha256: prefix back
touch "/tmp/digests/${DIGEST#sha256:}"
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: digest-${{ strategy.job-index }}
path: /tmp/digests/*
Expand All @@ -71,21 +71,21 @@ jobs:
contents: read
packages: write
steps:
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
path: /tmp/digests
pattern: digest-*
merge-multiple: true
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Tags
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
with:
images: ${{ env.IMAGE }}
tags: |
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ __pycache__/
# Local git worktrees
.worktrees/

# Working notes — not part of the published repo
docs/

# Agent tooling state
.claude/

Expand Down
48 changes: 30 additions & 18 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,42 @@ New files take the `SPDX-License-Identifier` of their directory (see

## Tests (no Docker)

Use Python 3.12 and Node 22, matching CI.

```bash
python3 -m venv .venv
.venv/bin/pip install -r requirements-dev.txt
for f in tests/test_*.py; do
case "$f" in tests/test_acceptance.py) continue ;; esac
.venv/bin/python "$f" || exit 1
done
npm --prefix web ci
npm --prefix web test
```

## Acceptance tests (Docker)

```bash
python3 -m venv .venv && .venv/bin/pip install -r requirements-dev.txt
.venv/bin/python tests/test_lifecycle.py
.venv/bin/python tests/test_dockerd.py
.venv/bin/python tests/test_token.py
.venv/bin/python tests/test_deskd.py
.venv/bin/python tests/test_browse.py
node web/web-ui/test_serve.mjs
node web/web-ui/test_phone.mjs
node web/web-ui/test_ntfy.mjs
node web/web-ui/test_telegram.mjs
node web/web-ui/test_nav.mjs
node web/web-ui/test_deploy.mjs
docker build -t case-desk:acceptance image
CASE_ACCEPTANCE_IMAGE=case-desk:acceptance .venv/bin/python -m pytest -q tests/test_acceptance.py
```

Acceptance tests (`tests/test_acceptance.py`) need Docker and a running cased.
The suite starts its own cased on a random loopback port, with a temporary vault
and token. It removes only computers created by that run. Failed runs retain
logs and screenshots in the printed scratch directory. `CASE_KEEP=1` also keeps
the primary test computer and its volume for inspection.

A7 needs ntfy and a phone. A8 is skipped unless `CASE_A8=1` is set,
because it restarts the Docker VM and interrupts every container using it.
Run that check only on a dedicated test machine.

## Layout

- `control-plane/` REST API (composition root: `cased.py`)
- `image/` desktop container
- `mcp/case_mcp.py` MCP wrapper
- `web/web-ui/` Drive UI
- `compose.yaml` self-host stack
- `control-plane/`: REST API (composition root: `cased.py`)
- `image/`: desktop container
- `mcp/case_mcp.py`: MCP wrapper
- `web/web-ui/`: Drive UI
- `compose.yaml`: self-host stack

Be decent to people: [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.ui
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Drive UI. Proxies cased + noVNC on the compose network.
# Drive UI. Proxies the API and live desktop through cased.
FROM node:22-slim
WORKDIR /app
COPY web/package.json web/package-lock.json ./
Expand Down
Loading
Loading