Skip to content

Commit c0df051

Browse files
authored
Add optional Runpod untrusted-caller lab
Isolate an opt-in CPU Serverless adversarial-caller lab from the core assurance claim, with fail-closed immutable-image preflight, signed local-controller records, bounded live execution, and unconditional cleanup.
1 parent 9616660 commit c0df051

51 files changed

Lines changed: 12941 additions & 7 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: Runpod lab contract check (no Runpod resources)
2+
3+
'on':
4+
workflow_dispatch:
5+
inputs:
6+
worker_image:
7+
description: Immutable linux/amd64 worker reference (registry/repository@sha256:...)
8+
required: true
9+
type: string
10+
max_duration_minutes:
11+
description: Bound validated for the local-only live runner
12+
required: true
13+
default: "10"
14+
type: choice
15+
options:
16+
- "5"
17+
- "10"
18+
- "15"
19+
- "20"
20+
- "30"
21+
permissions:
22+
contents: read
23+
24+
jobs:
25+
validate-only:
26+
name: Validate inputs and fake worker
27+
runs-on: ubuntu-24.04
28+
timeout-minutes: 10
29+
env:
30+
WORKER_IMAGE: ${{ inputs.worker_image }}
31+
MAX_DURATION_MINUTES: ${{ inputs.max_duration_minutes }}
32+
steps:
33+
- name: Check out source
34+
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
35+
with:
36+
persist-credentials: false
37+
38+
- name: Install pinned uv
39+
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
40+
with:
41+
version: "0.12.5"
42+
enable-cache: false
43+
44+
- name: Install the locked local test environment
45+
run: uv sync --frozen --extra dev
46+
47+
- name: Run contract validation without Runpod infrastructure
48+
shell: bash
49+
run: |
50+
./examples/runpod-untrusted-caller/scripts/run-live.sh \
51+
--validate-only \
52+
--worker-image "$WORKER_IMAGE" \
53+
--max-duration-minutes "$MAX_DURATION_MINUTES"

‎.github/workflows/verify.yml‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,25 @@ jobs:
2121
with:
2222
persist-credentials: false
2323

24+
- name: Install pinned uv
25+
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
26+
with:
27+
version: "0.12.5"
28+
enable-cache: false
29+
30+
- name: Install TPM build prerequisites
31+
run: |
32+
sudo apt-get update
33+
sudo apt-get install --yes --no-install-recommends \
34+
build-essential libtss2-dev pkg-config
35+
36+
- name: Audit locked core and worker dependencies
37+
run: |
38+
uv sync --frozen --extra dev
39+
uv run --frozen pip-audit
40+
uv run --frozen pip-audit \
41+
-r examples/runpod-untrusted-caller/requirements.lock
42+
2443
- name: Validate Compose configuration
2544
run: docker compose config --quiet
2645

@@ -33,3 +52,20 @@ jobs:
3352
- name: Remove containers, volumes, and network
3453
if: always()
3554
run: docker compose down --volumes --remove-orphans
55+
56+
- name: Build isolated untrusted-caller worker
57+
run: |
58+
docker build --platform linux/amd64 \
59+
--tag atcap-runpod-holder:ci \
60+
examples/runpod-untrusted-caller
61+
62+
- name: Run worker smoke without network or privileges
63+
run: |
64+
docker run --rm --platform linux/amd64 --network none --read-only \
65+
--env TMPDIR=/run/worker-tmp \
66+
--tmpfs /run/worker-tmp:rw,noexec,nosuid,size=16m \
67+
--cap-drop ALL --security-opt no-new-privileges \
68+
atcap-runpod-holder:ci sh -c \
69+
'python /opt/worker/self_test.py && python /opt/worker/handler_self_test.py'
70+
test "$(docker image inspect atcap-runpod-holder:ci --format '{{.Architecture}}')" = amd64
71+
test "$(docker image inspect atcap-runpod-holder:ci --format '{{.Config.User}}')" = 10001:10001

‎README.md‎

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ endorsement, and no upstream issue is claimed closed.
1414
| Clean verification | `docker compose build --pull --no-cache` followed by `docker compose up --abort-on-container-exit --exit-code-from verify verify` |
1515
| Expected smoke result | The full suite under branch-enabled combined coverage, real-`swtpm` integration, lint, format, type, security, and installed-package checks pass; the verifier prints `container smoke: PASS` and exits `0`. |
1616
| Reviewer map | [Audit guide](docs/audit-guide.md) and [threat model](docs/threat-model.md) |
17+
| Optional adversarial lab | [Runpod as an untrusted disposable holder](examples/runpod-untrusted-caller/README.md); excluded from the core assurance claim, with no Runpod resources or charges in ordinary CI. |
1718
| Report a vulnerability | Follow the repository's [security policy](SECURITY.md). |
1819

1920
## What the experiment establishes
@@ -188,16 +189,38 @@ excludes the Linux-only `swtpm` profile; Compose is authoritative for that path.
188189
```sh
189190
uv sync --frozen --python 3.12 --extra dev
190191
TZ=UTC ./scripts/verify-coverage.sh -m 'not swtpm'
192+
uv run --frozen pytest examples/runpod-untrusted-caller/tests -m 'not swtpm'
191193
uv run --frozen ruff check .
192194
uv run --frozen ruff format --check .
193195
uv run --frozen mypy
196+
uv run --frozen mypy --strict examples/runpod-untrusted-caller/lab \
197+
examples/runpod-untrusted-caller/bounded_capture.py \
198+
examples/runpod-untrusted-caller/deadline_supervisor.py \
199+
examples/runpod-untrusted-caller/billing_observation.py \
200+
examples/runpod-untrusted-caller/evidence_manifest.py \
201+
examples/runpod-untrusted-caller/provider_readback.py \
202+
examples/runpod-untrusted-caller/handler.py \
203+
examples/runpod-untrusted-caller/handler_self_test.py \
204+
examples/runpod-untrusted-caller/self_test.py \
205+
examples/runpod-untrusted-caller/lab_test_support.py
194206
uv run --frozen bandit -q -r src
195207
uv run --frozen bandit -q scripts/verify-package.py
208+
uv run --frozen bandit -q -r examples/runpod-untrusted-caller/lab
209+
uv run --frozen bandit -q \
210+
examples/runpod-untrusted-caller/bounded_capture.py \
211+
examples/runpod-untrusted-caller/deadline_supervisor.py \
212+
examples/runpod-untrusted-caller/billing_observation.py \
213+
examples/runpod-untrusted-caller/evidence_manifest.py \
214+
examples/runpod-untrusted-caller/provider_readback.py \
215+
examples/runpod-untrusted-caller/handler.py \
216+
examples/runpod-untrusted-caller/handler_self_test.py \
217+
examples/runpod-untrusted-caller/self_test.py
196218
package_dist_dir="$(mktemp -d)"
197219
uv run --frozen python -m build --no-isolation --outdir "${package_dist_dir}"
198220
uv run --frozen python scripts/verify-package.py --dist-dir "${package_dist_dir}"
199221
find "${package_dist_dir}" -depth -delete
200222
uv run --frozen pip-audit
223+
uv run --frozen pip-audit -r examples/runpod-untrusted-caller/requirements.lock
201224
uv run --frozen detect-secrets scan --all-files \
202225
--exclude-files '(^|/)(\.git|\.venv|\.mypy_cache|\.pytest_cache|\.ruff_cache|build|dist)/' .
203226
```
@@ -207,9 +230,12 @@ verification step rather than a frozen behavioral result.
207230

208231
## Deliberately deferred
209232

210-
Live TPM hardware, production AK enrollment, cloud evidence, elaborate provenance
211-
DAGs, fuzzing, Sigstore, published SBOMs, external workloads, broad CLI work,
212-
availability engineering, and external services are outside this experiment.
233+
Live TPM hardware, production AK enrollment, cloud attestation, elaborate
234+
provenance DAGs, fuzzing, Sigstore, published SBOMs, broad CLI work, and
235+
availability engineering are outside the core experiment. The optional Runpod
236+
example tests application behavior against a remote untrusted caller; it does
237+
not extend the core assurance ceiling or treat any cloud service as an
238+
attestation root.
213239

214240
## License
215241

‎compose.yaml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ services:
3333
condition: service_healthy
3434
environment:
3535
ATCAP_SWTPM_TCTI: swtpm:host=swtpm,port=2321
36+
ATCAP_LAB_COMMIT_SHA: ${ATCAP_LAB_COMMIT_SHA:-0000000000000000000000000000000000000000}
3637
TPM2TOOLS_TCTI: swtpm:host=swtpm,port=2321
3738
command: ["./scripts/container-smoke.sh"]
3839
security_opt:

‎docs/audit-guide.md‎

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,16 +345,38 @@ Host checks use the committed lock and exclude only the Linux `swtpm` marker:
345345
```sh
346346
uv sync --frozen --python 3.12 --extra dev
347347
TZ=UTC ./scripts/verify-coverage.sh -m 'not swtpm'
348+
uv run --frozen pytest examples/runpod-untrusted-caller/tests -m 'not swtpm'
348349
uv run --frozen ruff check .
349350
uv run --frozen ruff format --check .
350351
uv run --frozen mypy
352+
uv run --frozen mypy --strict examples/runpod-untrusted-caller/lab \
353+
examples/runpod-untrusted-caller/bounded_capture.py \
354+
examples/runpod-untrusted-caller/deadline_supervisor.py \
355+
examples/runpod-untrusted-caller/billing_observation.py \
356+
examples/runpod-untrusted-caller/evidence_manifest.py \
357+
examples/runpod-untrusted-caller/provider_readback.py \
358+
examples/runpod-untrusted-caller/handler.py \
359+
examples/runpod-untrusted-caller/handler_self_test.py \
360+
examples/runpod-untrusted-caller/self_test.py \
361+
examples/runpod-untrusted-caller/lab_test_support.py
351362
uv run --frozen bandit -q -r src
352363
uv run --frozen bandit -q scripts/verify-package.py
364+
uv run --frozen bandit -q -r examples/runpod-untrusted-caller/lab
365+
uv run --frozen bandit -q \
366+
examples/runpod-untrusted-caller/bounded_capture.py \
367+
examples/runpod-untrusted-caller/deadline_supervisor.py \
368+
examples/runpod-untrusted-caller/billing_observation.py \
369+
examples/runpod-untrusted-caller/evidence_manifest.py \
370+
examples/runpod-untrusted-caller/provider_readback.py \
371+
examples/runpod-untrusted-caller/handler.py \
372+
examples/runpod-untrusted-caller/handler_self_test.py \
373+
examples/runpod-untrusted-caller/self_test.py
353374
package_dist_dir="$(mktemp -d)"
354375
uv run --frozen python -m build --no-isolation --outdir "${package_dist_dir}"
355376
uv run --frozen python scripts/verify-package.py --dist-dir "${package_dist_dir}"
356377
find "${package_dist_dir}" -depth -delete
357378
uv run --frozen pip-audit
379+
uv run --frozen pip-audit -r examples/runpod-untrusted-caller/requirements.lock
358380
uv run --frozen detect-secrets scan --all-files \
359381
--exclude-files '(^|/)(\.git|\.venv|\.mypy_cache|\.pytest_cache|\.ruff_cache|build|dist)/' .
360382
```
@@ -369,8 +391,9 @@ docker compose up --abort-on-container-exit --exit-code-from verify verify
369391
docker compose down --volumes --remove-orphans
370392
```
371393

372-
`container-smoke.sh` checks simulator usability first and then runs the complete
373-
pytest suite under branch-enabled combined coverage, Ruff lint and formatting,
394+
`container-smoke.sh` checks simulator usability first and then runs the core
395+
pytest suite under branch-enabled combined coverage plus the isolated lab's
396+
no-Runpod-infrastructure fake-transport and real-local-`swtpm` tests, Ruff lint and formatting,
374397
strict mypy, Bandit, and strict package verification. Coverage.py combines
375398
statement and branch opportunities for its configured `fail_under`: the `85.00%`
376399
combined floor is below the measured `85.66%` pre-change host combined baseline.
@@ -385,6 +408,14 @@ environment. `tests/test_verify_package.py` fixes duplicate-member, duplicate
385408
advisory result changes with its online database and neither is part of the TPM
386409
behavioral smoke.
387410

411+
The optional live runner performs a separate fail-closed worker gate before any
412+
Runpod mutation: it anonymously pulls the configured immutable `linux/amd64`
413+
digest and runs both deployed self-tests with no network, a read-only root
414+
filesystem, non-root identity, dropped capabilities, and
415+
`no-new-privileges`. The worker Dockerfile rejects an empty dependency lock,
416+
runs `pip check`, and imports the copied handler during the build. These are
417+
local startability checks, not evidence that Runpod executed that image.
418+
388419
The Docker path has a digest-pinned base-image index and a committed Python lock.
389420
It is not bit reproducible:
390421
apt package versions come from floating Debian repositories, and the

‎docs/threat-model.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,4 +270,7 @@ to its trust domain.
270270

271271
The reference Compose topology is a verification environment, publishes no
272272
simulator port, and uses one disposable `swtpm` instance. It is not a production
273-
network or deployment model and provisions no external service.
273+
network or deployment model and provisions no external service. The isolated
274+
[Runpod example](../examples/runpod-untrusted-caller/README.md) is outside this
275+
core assurance claim and treats its remote host, control plane, queue, worker,
276+
registry metadata, and output as untrusted.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
*
2+
!Dockerfile
3+
!handler.py
4+
!handler_self_test.py
5+
!self_test.py
6+
!requirements.lock
7+
!lab/
8+
!lab/__init__.py
9+
!lab/errors.py
10+
!lab/worker.py
11+
!lab/worker_wire.py
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# This exact multi-platform base index is the same reviewed Python 3.12.11
2+
# Bookworm base used by the verification harness. Build and deploy linux/amd64.
3+
FROM python:3.12.11-slim-bookworm@sha256:519591d6871b7bc437060736b9f7456b8731f1499a57e22e6c285135ae657bf7
4+
5+
ARG TARGETPLATFORM
6+
7+
ENV PYTHONDONTWRITEBYTECODE=1 \
8+
PYTHONUNBUFFERED=1 \
9+
PATH=/opt/worker-venv/bin:${PATH}
10+
11+
RUN test "$TARGETPLATFORM" = "linux/amd64" \
12+
&& groupadd --gid 10001 worker \
13+
&& useradd --uid 10001 --gid 10001 --create-home --shell /usr/sbin/nologin worker \
14+
&& python -m venv /opt/worker-venv \
15+
&& chown -R worker:worker /opt/worker-venv
16+
17+
WORKDIR /opt/worker
18+
19+
COPY --chown=worker:worker requirements.lock ./requirements.lock
20+
RUN test -s requirements.lock \
21+
&& python -m pip install --no-cache-dir --require-hashes --requirement requirements.lock \
22+
&& python -m pip check \
23+
&& python -c 'import ca2a_runtime, cryptography, joserfc, pydantic, rfc8785, runpod'
24+
25+
COPY --chown=worker:worker handler.py handler_self_test.py self_test.py ./
26+
COPY --chown=worker:worker \
27+
lab/__init__.py \
28+
lab/errors.py \
29+
lab/worker.py \
30+
lab/worker_wire.py \
31+
./lab/
32+
33+
# Import the exact copied entrypoint and shared modules during the build. This
34+
# makes a missing runtime dependency or malformed source a build failure rather
35+
# than a worker that can never join the Runpod dispatcher.
36+
RUN python -c 'import handler, lab.worker, lab.worker_wire'
37+
38+
USER 10001:10001
39+
40+
CMD ["python", "-u", "handler.py"]

0 commit comments

Comments
 (0)