Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
52 changes: 52 additions & 0 deletions .github/workflows/guest-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: strict-guest-candidate

on:
workflow_dispatch:

permissions:
contents: read

jobs:
build-candidate:
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- name: Check out source
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Refuse an unreviewed release configuration
run: python3 vm/guest/release.py release-readiness
- name: Prepare bounded disposable work volume
run: |
volume="leftovers-guest-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
docker volume create --driver local --opt type=tmpfs --opt device=tmpfs \
--opt o=size=6g,nosuid,nodev "$volume"
printf 'LEFTOVERS_GUEST_VOLUME=%s\n' "$volume" >> "$GITHUB_ENV"
- name: Fetch only locked inputs in the disposable builder
run: |
image=$(python3 vm/guest/release.py builder-image)
docker pull -- "$image"
docker run --rm --network bridge --read-only --cap-drop ALL --cpus=2 \
--memory=2g --memory-swap=2g --pids-limit=256 \
--security-opt no-new-privileges=true --tmpfs /tmp:rw,noexec,nosuid,size=64m \
--mount type=bind,src="$GITHUB_WORKSPACE",dst=/workspace,readonly \
--mount type=volume,src="$LEFTOVERS_GUEST_VOLUME",dst=/work,volume-nocopy \
"$image" \
/workspace/vm/guest/ci/build-in-container.sh fetch
- name: Build with network disabled
run: |
image=$(python3 vm/guest/release.py builder-image)
docker run --rm --network none --read-only --cap-drop ALL --cpus=2 \
--memory=2g --memory-swap=2g --pids-limit=256 \
--security-opt no-new-privileges=true --tmpfs /tmp:rw,noexec,nosuid,size=64m \
--mount type=bind,src="$GITHUB_WORKSPACE",dst=/workspace,readonly \
--mount type=volume,src="$LEFTOVERS_GUEST_VOLUME",dst=/work,volume-nocopy \
"$image" \
/workspace/vm/guest/ci/build-in-container.sh build
- name: Remove and verify bounded work volume
if: always()
run: |
test -n "${LEFTOVERS_GUEST_VOLUME:-}" || exit 0
docker volume rm --force "$LEFTOVERS_GUEST_VOLUME"
! docker volume inspect "$LEFTOVERS_GUEST_VOLUME" >/dev/null 2>&1
24 changes: 17 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ maintainer value and correctness, never PR count or token consumption for its ow
targets.
- Never send `GITHUB_TOKEN`, `GH_TOKEN`, a PAT, SSH agent, host credential directory, or runtime
socket into a coding/test sandbox.
- Treat host agents and the stock Docker/Podman runner as scout/rehearsal-only. Production
`run --execute` must fail before budget or discovery until the strict VM guest, credential-isolating
model mediation, and bounded post-stop result extraction are integrated and live-verified.
- The coding agent cannot push, comment, fork, or open a PR. Only `publisher.py` can write to GitHub.
- Remote writes require `draft-pr` mode, standing acknowledgement, and the `--publish` invocation
capability. Never auto-merge or mark ready for review.
Expand All @@ -32,10 +35,14 @@ maintainer value and correctness, never PR count or token consumption for its ow
3. Run `leftovers scout` and inspect the score breakdown and every gate result.
4. Confirm the reported spendable budget (which already excludes the reserve) covers the larger of
the configured minimum and the P95 estimate times the safety multiplier.
5. Use `leftovers run --execute` for dry runs. Inspect the hash-chained journal under the configured
state directory.
6. Only when the operator has authorized external writes, run with `--publish`; expect a draft PR.
7. Verify the cleanup receipt proves managed containers were removed before the workspace, and keep
5. In the current release, use `leftovers run --execute` only as a negative admission test: require
`policy_denied` before budget, discovery, acquisition, or model work.
6. Do not enable contribution execution until a separately reviewed strict VM runner includes the
guest policy, narrow model mediator, bounded result extractor, and live escape/resource/cleanup
evidence. Exercise that path without remote writes first.
7. Only when that boundary and the operator's external-write authorization are both present, run
with `--publish`; expect a draft PR.
8. Verify the cleanup receipt proves managed containers were removed before the workspace, and keep
the remote branch while the PR remains open.

One invocation attempts at most one issue. Do not loop inside a run to exhaust quota; allow the
Expand Down Expand Up @@ -69,9 +76,12 @@ PYTHONPATH=src python3 -m leftovers --config config/leftovers.example.toml \
training-run --mode process --profile auto
```

Process training is supplemental. A release-quality sandbox claim requires the Docker/Podman
training run and its successful cleanup evidence. The dashboard is a loopback-only read surface over
non-authoritative telemetry; do not publish or expose it through a public bind/proxy.
Process training is supplemental. Docker/Podman training and its cleanup receipt prove only the OCI
rehearsal contract; they are not production-isolation evidence because the container shares the host
kernel. A production boundary additionally requires the integrated strict VM guest, model mediator,
result extractor, and live adversarial evidence. Even then, do not claim absolute escape-proofing.
The dashboard is a loopback-only read surface over non-authoritative telemetry; do not publish or
expose it through a public bind/proxy.

Do not install host system packages. Keep the Python control plane dependency-free unless a reviewed
change clearly justifies a dependency. Preserve strict config validation, argv-array execution,
Expand Down
66 changes: 52 additions & 14 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ meter provider calls, impose a hard token ceiling, or replace a supported provid
9. **Dashboard:** physically read-only telemetry reader and loopback HTTP server. It has no command,
budget-ledger, publication-ledger, or GitHub mutation interface.

The local implementation uses a bounded temporary host directory mounted into a hardened container.
The production/high-assurance design places acquisition and the rootless container inside a fresh
VM/microVM, because containers share the host kernel.
The existing local Docker/Podman and host-agent paths are rehearsal-only. Production admission
rejects them before budget, discovery, or acquisition. Docker Sandboxes (`sbx`) is the active
integration candidate, but its boundary facade is source-disabled and its compatibility rehearsal
is shell-only: it performs no provider or Terra/high call. The custom
Virtualization.framework launcher is archival source-disabled research, not an operator activation
path. Neither candidate changes the non-overridable production gate.

## Lifecycle

Expand All @@ -38,6 +41,15 @@ scheduled -> budget_check -> discovering -> scoring -> selected -> preflight
-> approved -> publishing -> pr_open -> cleaning -> complete
```

An optional source-disabled GNHF-style proposal can compile an arbitrary
operator objective into bounded iterative-worker instructions. It is not a
lifecycle transition or execution backend: host GNHF invokes authenticated
coding CLIs and performs its own Git lifecycle, so it cannot satisfy the
worker credential boundary, controller-curated command rule, or publisher-only
write authority. Its required final hardening pass reports dependency issues
for human review, makes only scope-bound code/docs improvements, reruns curated
checks, and requires the ordinary frozen-diff review before approval.

Alternate outcomes are:

- `deferred`: unknown/insufficient quota or a temporary upstream/rate condition;
Expand All @@ -53,8 +65,10 @@ workspace deletion. If container cleanup cannot be proven, the bound workspace i
Every production and training run is tagged at creation. Model invocations record expected and
adapter-observed identities, lifecycle timestamps, controller/adapter heartbeats, and qualified
usage receipts. Training uses a separate controller-owned fixture, synthetic usage, unique state and
workspace roots, and a publisher-free issue source. UI grouping never makes synthetic usage part of
production quota totals.
workspace roots, and a publisher-free issue source. Its admission requires exact attestations for
the fixture runner, issue source, and lease factory; it also requires the fixed deterministic model
identity, no network or environment forwarding, no repair loop, and dry-run publication. UI grouping
never makes synthetic usage part of production quota totals.

## Candidate policy and scoring

Expand Down Expand Up @@ -91,15 +105,39 @@ signal. Every score retains its components and reasons in the journal.

## Execution boundary

The runner constructs runtime arguments itself. Agent/model output cannot add mounts, environment,
image, network, privileges, or runtime flags. The local container profile uses a read-only root,
network `none` by default, all capabilities dropped, no-new-privileges, bounded CPU/RAM/PIDs/files,
tmpfs, an arbitrary host UID, no ports/devices/socket, and a read-only nested `.git` mount.

Planning and review mount the workspace read-only. Implementation mounts only the repository writable.
Operator-curated setup commands may opt into `bridge`; verification always runs with `network=none`.
This is a deliberate sharp edge: autonomous profiles should pre-stage pinned dependencies and leave
setup networking disabled.
The production preflight rejects `agent.backend = "host"`, non-empty `agent.pass_environment`, any
global or repository bridge network, and the stock `AgentRunner`. The rehearsal runner still
constructs OCI arguments itself; agent/model output cannot add mounts, environment, image, network,
privileges, or runtime flags. Its profile uses a read-only root, network `none` by default, all
capabilities dropped, no-new-privileges, bounded CPU/RAM/PIDs/files, tmpfs, an arbitrary host UID, no
ports/devices/socket, and a read-only nested `.git` mount.

Planning and review mount the rehearsal workspace read-only. Implementation mounts only the
repository writable. Training cannot exercise a bridge override: an attempted override is rejected
before budget, discovery, workspace creation, or runtime inspection.

The active `sbx` candidate is intentionally narrower than an execution backend. Its probe pins the
CLI identity; checks one exact global `service/openai` secret inventory; samples a fixed OpenAI-allow
and non-OpenAI-deny network canary matrix; creates one clone-mode shell sandbox; and checks ports,
observed environment names, fixed clone-write canaries, and exact-name cleanup. Those finite checks
and the name-based lifecycle are useful
negative evidence, not an attestation of the complete daemon, policy, proxy, or credential boundary.
`SbxBoundary.provision()` remains source-disabled before command I/O, and `leftovers run --execute`
still denies before budget or discovery. A future activation must satisfy the full strict evidence
contract, including credential isolation, bounded post-stop extraction, fresh verification, and
proven cleanup.

The archival strict-VM manifest contains boot artifacts and resource limits only. Manifest v2 separates
root- or dedicated-account-owned immutable boot files from a launcher-owned private per-run directory
containing the sealed manifest, optional read-only request disk, and fresh preallocated writable
scratch disk. Hardware is fixed in code with zero network/socket/share/interactive devices, and
receipt v2 binds the exact manifest SHA-256. The manifest has no command or environment field. See
[`vm/README.md`](vm/README.md). The current one-epoch controller is source-disabled and accepts only
explicit fixture authorization; broker-shaped authorization is rejected because no verifier exists.
Its guest source rejects every action and emits no acceptable result. A future whole-cycle runner
must put acquisition, Git parsing, fixed check execution, and canonical diff generation inside that
boundary, while a separate dedicated-UID broker owns every launcher path and durable token/replay
ledger. A caller-supplied string or hash is never sufficient authority.

## Integrity and publication

Expand Down
4 changes: 4 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,18 @@ RUN apt-get update \
&& useradd --create-home --uid 10001 --shell /usr/sbin/nologin leftovers

WORKDIR /app
COPY --chown=leftovers:leftovers AGENTS.md ARCHITECTURE.md CONTRIBUTING.md LICENSE Makefile PROTOCOL.md README.md SECURITY.md pyproject.toml /app/
COPY --chown=leftovers:leftovers .github /app/.github
COPY --chown=leftovers:leftovers src /app/src
COPY --chown=leftovers:leftovers tests /app/tests
COPY --chown=leftovers:leftovers config /app/config
COPY --chown=leftovers:leftovers docs /app/docs
COPY --chown=leftovers:leftovers examples /app/examples
COPY --chown=leftovers:leftovers sandbox /app/sandbox
COPY --chown=leftovers:leftovers schemas /app/schemas
COPY --chown=leftovers:leftovers schedules /app/schedules
COPY --chown=leftovers:leftovers scripts /app/scripts
COPY --chown=leftovers:leftovers vm /app/vm

ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
Expand Down
26 changes: 25 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,33 @@ SANDBOX_IMAGE ?= leftovers-sandbox:latest
REHEARSAL_IMAGE ?= leftovers-rehearsal:local
REHEARSAL_REPORT ?= .leftovers/rehearsal-report.json

.PHONY: dashboard demo package-smoke rehearsal-image sandbox-image test test-local training-run \
.PHONY: dashboard demo guest-lock-check guest-release-preflight macos-package native-broker-check package-smoke \
rehearsal-image sandbox-image sbx-doctor sbx-rehearsal strict-vm-check test test-local training-run \
training-run-process validate

macos-package:
python3 scripts/build_macos_package.py

strict-vm-check:
sh vm/check.sh

native-broker-check:
sh vm/broker/check.sh

sbx-doctor:
./scripts/sbx-rehearsal.sh

sbx-rehearsal:
./scripts/sbx-rehearsal.sh --execute

guest-lock-check:
sh vm/guest/check-static.sh

# Intentionally fails until a reviewed builder image, public-key trust root,
# signer identities, reproducibility epoch, and provenance verifier are pinned.
guest-release-preflight:
python3 vm/guest/release.py release-readiness

test:
$(RUNTIME) build --tag $(TEST_IMAGE) .
$(RUNTIME) run --rm --network none $(TEST_IMAGE)
Expand Down
Loading