Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7adc340
docs: design opaque cache payload symlinks
MarcoPorcellato Aug 29, 2026
b99d2ff
docs: plan opaque cache payload symlink implementation
MarcoPorcellato Aug 29, 2026
b1b5548
docs: require exact-head candidate rebuild
MarcoPorcellato Aug 29, 2026
2f2a51a
feat: add bounded opaque cache payload traversal
MarcoPorcellato Aug 29, 2026
c4150bf
fix: complete bounded cache payload traversal
MarcoPorcellato Aug 29, 2026
1bb446a
fix: account for opaque links in cache payload inventory
MarcoPorcellato Aug 29, 2026
5a7efca
fix: preserve payload inventory node accounting
MarcoPorcellato Aug 29, 2026
57153fd
fix: preserve symbolic links when reusing cache payloads
MarcoPorcellato Aug 29, 2026
e9faf9e
fix: own cache staging before fallible preparation
MarcoPorcellato Aug 29, 2026
b260dcd
fix: validate opaque payloads before cache promotion
MarcoPorcellato Aug 29, 2026
0500ebe
docs: define opaque cache payload link boundary
MarcoPorcellato Aug 29, 2026
f03734d
docs: pin control-plane symlink boundary
MarcoPorcellato Aug 29, 2026
d3e0d37
docs: remove duplicate symlink boundary wording
MarcoPorcellato Aug 29, 2026
a6ece7b
docs: clarify payload link boundary grammar
MarcoPorcellato Aug 29, 2026
dff0044
docs: remove cache payload spec trailing whitespace
MarcoPorcellato Aug 29, 2026
07f2963
test: shorten payload socket fixture path
MarcoPorcellato Aug 29, 2026
4223288
docs: retain cache deletion contract
MarcoPorcellato Aug 29, 2026
0dc8e6f
test: own short Unix socket fixture directory
MarcoPorcellato Aug 29, 2026
dbc9869
fix: harden cache payload review findings
MarcoPorcellato Aug 29, 2026
ed15a0a
fix: preserve cache inventory lock state
MarcoPorcellato Aug 29, 2026
ca5871f
Merge remote-tracking branch 'origin/main' into docs/cache-payload-sy…
MarcoPorcellato Aug 29, 2026
235d9ba
docs: normalize cache payload spec metadata
MarcoPorcellato Aug 29, 2026
80af79d
fix: gate macOS clone test instrumentation
MarcoPorcellato Aug 29, 2026
0ba9777
fix: close cache payload qualification gaps
MarcoPorcellato Aug 29, 2026
72c4960
fix: own staging before data preparation
MarcoPorcellato Aug 29, 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Semantic Versioning after its first public release.

### Added

- Unix cache generations now preserve ordinary opaque payload links during inventory and reuse; strict control paths remain link-free, failed preparation owns cleanup before reuse, and native candidate qualification remains pending.

- Added the adoption-help issue-form source and a 1280 × 640 social-preview PNG
upload candidate. GitHub form rendering/live chooser remains unverified until
the remote gate; preview upload is owner-only and pending.
Expand Down
18 changes: 16 additions & 2 deletions docs/CACHE_AND_WORKSPACE.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,26 @@ markers are reported as incomplete; they are never silently promoted.
commit-ci-preflight cache inventory --json
```

Inventory validates ownership again, rejects symlinks and unexpected entry
names, walks at most 100,000 nodes, and emits a deterministically sorted report.
Inventory validates ownership again, rejects control-plane and payload-root
symlinks and unexpected entry names, walks at most 100,000 nodes, and emits a
deterministically sorted report.
The default reporting budget is 20 GiB and can be overridden with
`--disk-budget-bytes`. Exceeding the budget is reported; it does not trigger
automatic eviction.

## Control plane and opaque payload links

The cache has a strict control plane and an opaque payload plane; control-plane and payload-root links still fail closed, while permitted payload-descendant links remain opaque. Inventory counts a link's stored target
length as bytes, never target content, never follows a payload link target on
the host, and retains the 100,000-node bound. Payload inspection covers
relative, absolute, broken, recursive, and outside-root links. CCP never follows a payload link target on the host.

Unix reuse preserves these opaque links. Windows link-bearing payload reuse remains unsupported and fails closed. A payload link is counted as one node and one non-directory object. Cache payloads remain mutable, unattested performance
state, not trusted content. Standard-library path traversal is qualified under
CCP's cooperative entry-lock/trusted-local-actor model; a non-cooperative local
actor concurrently replacing a checked path remains unsupported and is not
claimed prevented.

## Schema 1.2 capacity preflight

The inventory budget remains reporting-only. A schema `1.2` `[storage]` policy
Expand Down
6 changes: 6 additions & 0 deletions docs/TESTING_AND_FAULT_INJECTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ containment boundaries only; they do not prove a real admission root, host
cleanup, a published receipt, or another platform. Process lists do not prove
release.

## Opaque cache-payload symbolic links

Deterministic coverage includes `src/cache.rs::complete_payload_symlinks_are_preserved_across_generation_reuse`, `src/cache.rs::failed_payload_preflight_removes_the_new_staging_generation`, `src/cache.rs::forced_fallback_copy_failure_removes_only_its_owned_staging_generation`, and `src/cache.rs::staging_cleanup_unlinks_payload_links_without_touching_targets`. Payload measurement is covered by `src/cache_payload.rs::payload_measurement_counts_links_without_following_targets`; fallback semantics and complete operation tracing by `src/cache_payload.rs::fallback_copy_preserves_each_link_target_and_external_sentinel` and `src/cache_payload.rs::fallback_copy_records_only_payload_paths_and_every_copy_operation`.

On macOS, deterministic tests distinguish attempted-and-succeeded clone reuse and deliberately forced fallback; the clone-success and forced-fallback tests preserve link identity. Injected fallback-copy failure occurs only after one successful copied object and proves exact owned-staging cleanup. `src/cache.rs::data_directory_preparation_failure_removes_owned_staging_and_releases_the_entry_lock` and `src/cache.rs::manifest_write_failure_removes_owned_staging_and_releases_the_entry_lock` cover the pre-data-root and pre-manifest failure windows. Owner-drop tests preserve unrelated and identity-mismatched staging directories. Unix fallback copy preserves each link target without following it and records metadata, directory enumeration, link reads, regular-file source/destination pairs, directory creation, and link creation without treating either copy path or a link target as a host path. Windows link-bearing payload reuse remains fail-closed and unsupported. These are deterministic source tests, not a native CCP receipt or native qualification.

## Managed-cache pin contract

The managed-cache pin tests are deterministic contract tests over an owned
Expand Down
6 changes: 3 additions & 3 deletions docs/THREAT_MODEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,13 @@ Boundary rules:
| T12 | Workflow privilege escalation | Minimal permissions, pinned official actions, no secrets/cache/deployment credential | Compromise of GitHub or a pinned action commit remains upstream risk |
| T13 | Marketplace action execution during migration | Migration assistant parses bounded YAML as data and emits inert classifications | Human reviewers can still make a bad manual translation |
| T14 | GitHub expression or secret misinterpretation | Unsupported expressions, permissions, secrets, reusable workflows, and arbitrary actions fail closed or require review | Compatibility is deliberately incomplete |
| T15 | Cache poisoning or cache-use race | Versioned ownership marker, content-addressed keys, completion marker, active-run lock, opt-in managed-cache pin with spawn-boundary revalidation | Caches accelerate execution but are not attestation evidence; undeclared paths are not pinned |
| T16 | Destructive cleanup | 0.1.0 exposes preview-only cleanup; resolved-root and containment checks | Operators retain responsibility for manual filesystem deletion |
| T15 | Cache poisoning or cache-use race | Versioned ownership marker, content-addressed keys, completion marker, active-run lock, opt-in managed-cache pin with spawn-boundary revalidation; opaque payload links are separated from link-free control paths | Caches are mutable, unattested performance state; undeclared paths are not pinned, and payload links are not trusted content |
| T16 | Destructive cleanup | 0.1.0 exposes preview-only cleanup; resolved-root and containment checks | Operators retain responsibility for manual filesystem deletion; manual deletion remains unsupported |
| T17 | Image drift | OCI digest is mandatory and included in plan, receipt, and policy | A multi-platform index can resolve to different platform manifests by design |
| T18 | Dependency compromise | Committed lockfile, exact critical pins, SPDX SBOM, bundled notices, advisory review | Registry and compiler compromise cannot be eliminated locally |
| T19 | Platform overclaim | Native receipts name OS/architecture; emulation and runtime probes are separate; PASS/PENDING/NOT_RUN are explicit | Benchmark qualification is narrower than full runtime qualification |
| T20 | Identity overclaim | Structural, integrity, policy, and identity levels are separate; identity is not implemented | No cryptographic proof of operator or machine exists in 0.1.0 |
| T21 | Symlink or filesystem race | Canonical path checks, managed roots, create-new/atomic writes, runtime revalidation, existing advisory-lock pin held through the guarded child lifecycle | Cooperative mutators must use the same lock and revalidate after acquisition; manual deletion remains unsupported; privileged local actors remain trusted |
| T21 | Symlink or filesystem race | Canonical path checks, managed roots, create-new/atomic writes, runtime revalidation, existing advisory-lock pin held through the guarded child lifecycle; control-plane paths remain link-free while payload links are opaque | A containerized project process can resolve payload links in its mount namespace; CCP does not claim hostile-code sandboxing or cache-content trust. Standard-library path traversal is qualified under the cooperative entry-lock/trusted-local-actor model; a non-cooperative local actor concurrently replacing a checked path remains unsupported and is not claimed prevented |
| T22 | Evidence parser denial of service | One MiB remote input cap, strict unknown-field rejection, bounded summaries | Base verifier compilation still consumes bounded remote time |
| T23 | Release substitution | Local SHA-256 manifest, SBOM, notices, checksum verification instructions | Checksums are not signatures and must come through an independent channel |
| T24 | Unsafe upgrade or rollback | Isolated install, version smoke test, preserved previous binary, versioned schemas and cache markers | Operator mistakes remain possible; no automatic updater exists |
Expand Down
110 changes: 110 additions & 0 deletions docs/adr/0006-opaque-cache-payload-symlinks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# ADR 0006: Treat cache-payload symbolic links as opaque objects

- Status: Accepted
- Date: 2026-08-29
- Decision owner: Marco Porcellato
- Design:
`docs/superpowers/specs/2026-08-29-cache-payload-symlink-design.md`

## Context

CCP's managed cache has a strict link-free control layout, but the `data`
directory contains mutable output produced by containerized project tooling.
Ordinary virtual environments and package-manager caches use symbolic links.
The current producer can promote those payloads and then reject them while
preparing the next generation because its recursive clone and copy helpers
treat every payload link as a control-plane escape.

That contradiction prevents safe persistent-cache reuse and can leave a new
staging directory without cleanup ownership when preparation fails before the
generation owner is constructed.

## Decision

CCP will keep every cache-root, entry, marker, manifest, lock, journal,
generation, payload-root, and ancestry object strictly free of symbolic links.
Only descendants of an already validated plain `data` directory may contain a
symbolic link.

Within that payload plane, CCP treats each link as opaque:

- inspect the link object with no-follow metadata;
- count it as one bounded non-directory object;
- preserve its stored target when copying on supported Unix platforms;
- never stat, open, canonicalize, traverse, or otherwise follow its target on
the host.

Relative, absolute, broken, recursive, and outside-root target text therefore
does not grant host authority. A project process may later resolve a link only
inside the existing container mount namespace. Cache payloads remain mutable,
unattested performance state.

Windows link-bearing payload reuse remains fail-closed until a separate native
design can preserve reparse semantics without guessing a broken target's file
or directory type.

Prepared-generation cleanup ownership begins immediately after creation of the
owned staging root and before fallible clone or copy work. Cleanup remains
identity-bound, entry-locked, and limited to the exact staging directory.
Whole-generation removal must unlink internal payload links without traversing
their targets; external sentinel fixtures make this a tested security
invariant.

No configuration, cache-key, receipt, policy, generation-manifest, promotion-
journal, or inventory JSON schema changes are made.

## Consequences

Benefits:

- CCP can reuse normal Unix package-manager and environment caches;
- the host no-follow boundary is explicit and testable;
- control-plane link rejection remains unchanged;
- failed preparation no longer creates new unowned staging residue;
- cache and receipt schemas remain compatible.

Costs:

- traversal must use separate strict-control and opaque-payload policies;
- inventory's `files` count explicitly includes payload links;
- Unix fallback copy requires link-preserving logic and platform-specific
tests;
- Windows link-bearing cache reuse remains unsupported pending native
qualification;
- clone, copy, inventory, promotion, recovery, and cleanup tests must share the
same boundary contract.

## Rejected alternatives

- **Disable persistent reuse:** loses the intended performance and credit
benefit without fixing the lifecycle defect.
- **Delete or rotate affected caches:** mutates operator state and only hides
the next recurrence.
- **Follow targets that appear contained:** grants authority to untrusted
payload text and breaks broken or recursive links.
- **Permit links throughout an entry:** weakens ownership, lock, manifest,
journal, and promotion invariants.
- **Materialize targets as files or directories:** changes cache semantics and
may copy host data outside the payload.
- **Infer Windows link kind from its target:** cannot safely handle broken or
outside-root targets.

## Verification gates

1. Boundary tests reject links in every control-plane and payload-root
position.
2. Unix tests preserve relative, absolute, broken, recursive, and external-
target links without target traversal.
3. Inventory remains bounded, deterministic, and schema-compatible.
4. Clone and fallback copy produce equivalent link-bearing payloads.
5. Injected preparation failures remove only their exact owned staging path
while holding the entry lock, without changing external link targets.
6. Promotion and recovery never resolve payload targets.
7. Existing digest, receipt, policy, manifest, journal, and link-free cache
fixtures remain compatible.
8. Formatting, warnings-denied build, strict Clippy, all-target tests,
independent review, and one separately authorized two-generation candidate
qualification pass before any installed producer replacement.

No cache mutation, installed producer replacement, adopter run, receipt
publication, push, PR, merge, or release is authorized by this ADR.
Loading