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
461 changes: 461 additions & 0 deletions backend/cortex_backend/execution/recipe_provider.py

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions docs/adr/0001-capability-tiered-agentic-execution-harness.md
Original file line number Diff line number Diff line change
Expand Up @@ -942,12 +942,14 @@ pass without executing code.
- Keep the typed recipe, signed bundle, broker transport, and trusted artifact boundary
provider-independent until the sandbox qualification gate passes.
- The owner-bound copy-in, exact-claim output validation, quarantine, hashing, and
atomic publication boundary is implemented; collect opt-in aggregate reliability
metrics, never content, after the provider is qualified.

**Implementation gate:** parser and artifact-boundary adversarial suites pass; no source
overwrite is possible. **Release gate:** parser fuzzing, artifact security review, and
fixed-function provider qualification must pass before any provider is enabled.
atomic publication boundary is implemented. The fixed-function provider core is
qualification-only; collect opt-in aggregate reliability metrics, never content,
after the provider is sandbox-qualified.

**Implementation gate:** parser, artifact-boundary, and qualification-provider adversarial
suites pass; no source overwrite is possible. **Release gate:** parser fuzzing, artifact
security review, hostile decoder corpus, OS sandbox qualification, and fixed-function
provider lifecycle health must pass before any provider is enabled.

### Phase 3 — `scratch.auto.v1` arbitrary WebAssembly code

Expand Down
3 changes: 2 additions & 1 deletion docs/adr/0001-phase2-artifact-boundary.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ WebP, finite JSON, and ordinary UTF-8 text are recognized. Portable executables,
ELF/OLE files, shortcuts, shebang scripts, archives, active HTML/SVG/JavaScript,
and common shell/PowerShell launchers are rejected. Unknown non-active bytes may be
stored as `application/octet-stream`; this is metadata safety, not permission to decode
or execute them. Image decoding remains a later provider qualification gate.
or execute them. Production image decoding remains behind the separate sandboxed
[recipe provider qualification gate](0001-phase2-recipe-provider.md).

### 3. Private output staging and exact claims

Expand Down
20 changes: 14 additions & 6 deletions docs/adr/0001-phase2-evidence.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# ADR-0001 Phase 2 evidence log

- **Phase:** 2 — signed image recipes and calculator/check primitives
- **Status:** Typed contract, signed-manifest verification, native broker transport, signed bundle installation, and trusted artifact boundary complete; provider and release gates remain open
- **Scope:** Provider-independent validation and deterministic trusted primitives only
- **Status:** Typed contract, signed-manifest verification, native broker transport, signed bundle installation, trusted artifact boundary, and qualification-only provider core complete; OS sandbox/provider and release gates remain open
- **Scope:** Provider-independent contracts plus a qualification-only fixed-function core
- **Source decision:** [Capability-tiered agentic execution harness](0001-capability-tiered-agentic-execution-harness.md)
- **Contract ADR:** [Phase 2 typed recipe and primitive contract](0001-phase2-recipe-contract.md)

Expand All @@ -19,8 +19,8 @@
| Authenticated broker contract | **Complete (transport-neutral)** | Bounded versioned frames, direction-specific HMAC keys, canonical messages, peer ACL/integrity policy, and owner-scoped authorization are covered by adversarial tests. |
| Native named-pipe adapter/DACL/peer-token binding | **Complete (transport-only)** | Protected local pipe, expected PID, OS token identity, X25519/HKDF handshake, direction keys, and close-on-error lifecycle are covered by native broker tests. |
| User-artifact copy-in, output validation, and publication | **Complete (boundary only)** | Explicit owner/turn grants, bounded stable snapshots, link/reparse/hardlink/sparse/ADS rejection, byte-derived MIME policy, exact output claims, quarantine, hash/size limits, atomic repository publication, rollback, and cleanup categories are covered by `tests/test_phase2_artifact_boundary.py`. |
| Image decoding and provider-produced image outputs | **Blocked / next gate** | The boundary records safe bytes and metadata only; codecs, decompression, thumbnails, and provider execution remain intentionally unimplemented. |
| Production sandbox provider and execution route | **Blocked / next gate** | The native pipe is transport-only; no Wasmtime, AppContainer, Job Object, subprocess, lifecycle provider, or production execution route was added. |
| Fixed-function image provider core | **Complete (qualification-only)** | `RecipeImageProvider` validates allowlisted PNG/JPEG/WebP bytes, verifies/loads one frame with Pillow bomb/resource limits, applies only parsed steps, strips metadata, revalidates encoded output, checks cancellation, and remains disabled until external sandbox health passes. |
| OS sandbox provider and provider-produced image outputs | **Blocked / next gate** | The core has no process, AppContainer/LPAC, Job Object, broker, watchdog, or lifecycle route; hostile decoder qualification and external review remain required. |

## Security invariants

Expand Down Expand Up @@ -66,6 +66,12 @@
while quarantine/cleanup failures surface for supervisor recovery.
18. Artifact records are opaque IDs; repository read/delete/purge operations remain
confined to the configured artifact root and verify the stored SHA-256.
19. The fixed-function provider accepts only immutable bytes and parsed plans, uses an
independent format allowlist, treats decoder warnings as errors, rejects multiple
frames, enforces hard byte/pixel/dimension/memory/step caps, and revalidates output.
20. Provider startup requires an external available sandbox health result; dependency
or codec failure, cancellation, decoder failure, and output metadata/size failure
leave the provider disabled and return stable categories only.

## Re-run target

Expand All @@ -76,6 +82,7 @@ python -m pytest tests/test_phase2_broker.py -q
python -m pytest tests/test_phase2_native_broker.py -q
python -m pytest tests/test_phase2_bundle_installer.py -q
python -m pytest tests/test_phase2_artifact_boundary.py -q
python -m pytest tests/test_phase2_recipe_provider.py -q
python -m compileall -q backend\cortex_backend\execution tests
python -m pytest -q
python tools/generate_contracts.py
Expand All @@ -86,8 +93,9 @@ npm.cmd test --prefix frontend -- --run
```

**Validation result (2026-07-21):** 16 Phase 2 contract tests, 9 signed-manifest tests,
7 broker-contract tests, 9 native-broker tests, 7 bundle-installer tests, and 16
artifact-boundary tests passed; the full Python suite passed (187 tests total) with one
7 broker-contract tests, 9 native-broker tests, 7 bundle-installer tests, 16
artifact-boundary tests, and 17 recipe-provider tests passed; the full Python suite
passed (204 tests total) with one
native-platform skip and one pre-existing `pytest-asyncio` deprecation warning.
Frontend lint, typecheck, production build, and all 39 frontend tests passed. Contract
generation, compileall, and `git diff --check` passed. No production execution
Expand Down
6 changes: 4 additions & 2 deletions docs/adr/0001-phase2-native-broker.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ADR-0001 Phase 2 native broker adapter

- **Status:** Implemented and verified; user-artifact staging and provider enablement remain blocked
- **Status:** Implemented and verified; provider enablement remains blocked
- **Parent:** [Phase 2 authenticated broker contract](0001-phase2-broker-contract.md)
- **Scope:** Windows named-pipe transport, protected DACL, OS peer identity, and
authenticated session-key establishment
Expand Down Expand Up @@ -56,7 +56,9 @@ and payloads are not exposed.

This ADR does not launch or supervise a process, create an AppContainer or Job
Object, install a signed bundle, copy files, decode images, publish artifacts,
enable a lifecycle provider, or expose a model tool. Those remain separate gates.
enable a lifecycle provider, or expose a model tool. Artifact copy-in/publication is
covered by the separate [trusted artifact boundary](0001-phase2-artifact-boundary.md);
decoding and lifecycle enablement remain separate gates.

## Verification

Expand Down
12 changes: 7 additions & 5 deletions docs/adr/0001-phase2-recipe-contract.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ADR-0001 Phase 2 typed recipe and primitive contract

- **Status:** Typed contract, signed-manifest verification, native broker transport, signed bundle installation, and trusted artifact boundary implemented and verified; provider enablement remains blocked
- **Status:** Typed contract, signed-manifest verification, native broker transport, signed bundle installation, trusted artifact boundary, and qualification-only provider core implemented and verified; sandbox/provider enablement remains blocked
- **Parent:** [Capability-tiered agentic execution harness](0001-capability-tiered-agentic-execution-harness.md)
- **Depends on:** [Phase 1 production lifecycle gate](0001-phase1-production-lifecycle.md)
- **Scope:** Typed fixed-function image plans, calculator/check primitives, canonical
Expand Down Expand Up @@ -54,9 +54,10 @@ This ADR does not authorize:
verification plus storage installation are implemented separately in
[the signed-manifest ADR](0001-phase2-signed-manifest.md) and
[the bundle installation ADR](0001-phase2-bundle-installation.md);
- image decoding, codecs, thumbnails, archive extraction, or provider-produced content
handling beyond the
trusted [artifact boundary](0001-phase2-artifact-boundary.md);
- production image decoding, sandbox execution, thumbnails, archive extraction, or
provider-produced content handling beyond the qualification-only
[recipe provider core](0001-phase2-recipe-provider.md) and trusted
[artifact boundary](0001-phase2-artifact-boundary.md);
- production execution beyond the transport-only
[native broker adapter](0001-phase2-native-broker.md);
- Wasmtime/WASI, AppContainer/LPAC, Job Object, host process, or any other provider;
Expand All @@ -68,7 +69,8 @@ packaged application remains on the explicitly disabled lifecycle from Phase 1.
## Required next gates

1. Qualify the fixed-function provider inside the OS sandbox and wire it only through
a passing lifecycle health check after external review.
a passing lifecycle health check after external review; the current provider core
intentionally does not satisfy this gate by itself.

## Verification

Expand Down
112 changes: 112 additions & 0 deletions docs/adr/0001-phase2-recipe-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# ADR-0001 Phase 2 fixed-function recipe provider qualification

- **Status:** Qualification core implemented and verified; OS sandbox and lifecycle enablement remain blocked
- **Phase:** 2 - fixed-function image provider
- **Parent:** [Capability-tiered agentic execution harness](0001-capability-tiered-agentic-execution-harness.md)
- **Depends on:** [Phase 2 typed recipe contract](0001-phase2-recipe-contract.md) and [trusted artifact boundary](0001-phase2-artifact-boundary.md)
- **Scope:** Qualification-only decode/transform/re-encode core. It is not a production provider route.

## Context and research basis

The typed recipe and artifact boundary are now complete, but image bytes still need
format-aware decoding before a fixed-function transform can be considered safe. Image
decoders process attacker-controlled bytes in native libraries and can encounter
decompression bombs, malformed metadata, multi-frame inputs, truncated data, and
resource exhaustion. Pillow's current security guidance recommends independent byte
and format validation, an explicit format allowlist, treating decompression warnings as
errors, strict pixel/resource limits, metadata stripping, pinned dependencies, and an
isolated subprocess for the decoder. See the official [Pillow security guidance](https://pillow.readthedocs.io/en/stable/handbook/security.html),
[Image.open/verify/load reference](https://pillow.readthedocs.io/en/stable/reference/Image.html),
and [Python support matrix](https://pillow.readthedocs.io/en/stable/installation/python-support.html).
The repository pins the supported Pillow line as `>=12.3,<12.4`; signed bundle
hashing and runtime packaging remain a later sandbox gate and are not implied by this
Python dependency declaration.

## Decision

`RecipeImageProvider` is a qualification-only core. It is deliberately not imported
by the application lifecycle or exported as an execution API. A future `RecipeExecutor`
may call this core only after the Windows sandbox has been independently constructed
and attested.

### Input and decoder boundary

1. The input is immutable bytes plus an already parsed `ImageTransformPlan`; no path,
filename, MIME claim, source text, network target, or plugin name is accepted.
2. Bytes are first checked by the trusted artifact MIME sniffer. Only PNG, JPEG, and
WebP magic types are passed to Pillow, and Pillow receives the corresponding
`formats` allowlist. TIFF, GIF, SVG, PDF, RAW, archives, executables, and active
content are not accepted by this provider.
3. Pillow's `verify()` pass is followed by a fresh `load()` pass. Truncated images are
disabled, decompression-bomb warnings are promoted to errors, and bomb/resource
errors map to stable `resource_limit` failures.
4. Only one frame is accepted. Width, height, total pixels, encoded input bytes, and
estimated decoded bytes are checked before and after pixel loading and after every
transform step.

### Fixed-function transform and output boundary

The only operations are the schema's grayscale, contrast, brightness, crop, resize,
and rotate steps. Crop bounds are rechecked against the current image dimensions;
the provider never trusts the parser alone. Each step checks cancellation and produces
a fresh image object. There is no expression evaluation, dynamic import, native
extension selection, filesystem access, or network capability.

Outputs are encoded only as PNG, JPEG, or lossless WebP with fixed encoder settings.
Metadata containers are cleared and EXIF, ICC, XMP, comments, and PNG text are not
carried forward. The encoded bytes are then independently sniffed, reopened, fully
loaded, verified for format/dimensions/one-frame status, checked for metadata and size,
and hashed before a `RecipeProviderResult` is returned. The provider never writes a
file or publishes an artifact; the existing artifact boundary remains the only
publication path.

### Health and enablement gate

The provider starts disabled. `start()` requires an externally supplied available
`RuntimeHealth` from the future sandbox probe and a local Pillow capability check for
PNG/JPEG/WebP. Missing sandbox health, unavailable codecs, or any failed probe leaves
the provider disabled. There is no host-process, in-process `exec`, virtual-environment,
or weaker-provider fallback. `stop()` is monotonic and clears the enabled state.

The current `RuntimeHealth.ready()` test fixture is only a contract test. It is not an
attestation of an AppContainer/LPAC, Job Object, restricted handle set, signed bundle,
named-pipe identity, CPU watchdog, or memory limit. Those controls remain required
before this provider can be wired into `ExecutionLifecycle`.

## Qualification ceilings

| Resource | Ceiling |
| --- | ---: |
| Encoded input | 100 MiB |
| Encoded output | 128 MiB |
| Pixels | 64 megapixels |
| Width or height | 16,384 pixels |
| Estimated decoded memory | 256 MiB |
| Transform steps | 8 |
| Frames | 1 |

The ceilings are hard caps in `RecipeProviderLimits`; callers cannot raise them by
configuration. CPU time, process-tree lifetime, and OS memory enforcement are not
implemented by this core and therefore cannot satisfy the release gate.

## Failure contract

Only stable categories cross the provider boundary: `provider_disabled`,
`sandbox_unverified`, `sandbox_unavailable`, `recipe_dependency_missing`,
`recipe_codec_unavailable`, `invalid_input`, `unsupported_format`,
`unsupported_frames`, `decode_failed`, `invalid_plan`, `resource_limit`,
`input_too_large`, `output_too_large`, `encode_failed`, `output_invalid`,
`output_metadata_present`, `cancelled`, and `cancellation_check_failed`.
Decoder messages, paths, bytes, OS errors, and stack traces are not returned.

## Verification and next gate

`tests/test_phase2_recipe_provider.py` covers disabled/health-gated startup,
allowlisted transforms, deterministic output hashes, metadata stripping, malformed and
active inputs, malformed JPEGs, animated WebP rejection, pixel/decoded-memory/output
limits, crop bounds, cancellation, stop behavior, and non-raiseable configuration caps.

The next gate is a separate Windows sandbox qualification: launch the provider out of
process under the signed bundle, AppContainer/LPAC and Job Object controls, restricted
handles/environment, native broker identity, watchdog, and accounting; run the hostile
decoder corpus there; then wire it to `ExecutionLifecycle` only after external review.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ uvicorn>=0.51,<0.52
httpx>=0.27,<1
pywebview>=6.2,<6.3
cryptography>=49,<50
Pillow>=12.3,<12.4
Loading
Loading