Skip to content

[TRACKING] security: red-steel remediation — 3/26 landed, S2 in flight, S3–S6 open - #630

Draft
CueCrux-Myles wants to merge 26 commits into
mainfrom
codex/crux-red-steel-remediation-2026-07-30
Draft

CueCrux-Myles wants to merge 26 commits into
mainfrom
codex/crux-red-steel-remediation-2026-07-30

Conversation

@CueCrux-Myles

Copy link
Copy Markdown
Contributor

Draft, not ready to merge. Opened to get 26 commits of security remediation off a single machine and under review. The branch had never been pushed — it existed only in one local checkout, so a disk failure would have lost all of it.

No code was changed to open this. The branch is exactly as its author left it on 2026-07-31.

What's in it

26 commits, 215 files, +41,050 / −6,254. Security remediation across auth, tenant isolation, and CI:

  • Auth / authority — fail closed for OAuth-only auth; enforce mutation route scopes; bind actor and tenant authority on work; attenuate device grants; authenticate device revocation; bound device grant state; bound public session creation; attenuate the OpenAI shim's MCP authority.
  • Tenant isolation — isolate fact lifecycle by tenant; isolate JWT fact-backed tenants; reserve daemon control namespaces; unify the audit-export privacy policy; tenant-scoped observe eviction and MCP actor reads.
  • Supply chain / CI — pin GitHub Actions immutably; isolate privileged workflow tokens; isolate pull-request CI runners; harden container delivery; remove the yanked ZIP crypto path; validate C2PA certificate trust paths.
  • Containment — contain repository scanning (adds repo_scan_policy); harden daemon browser responses; authenticate self-update manifests; bind coordination and punchcard ownership.

Milestone gates M5–M22 for this work are already recorded in PlanCrux (landed separately in PlanCrux#177), so the plan-side record exists; only the code was stranded.

Why it is a draft

The branch is 202 commits behind main and a test merge produces 32 conflicts. I ran that merge in a scratch worktree and aborted it — nothing here is resolved yet.

Conflicts cluster in four places:

Area Files Nature
.github/workflows 9 This branch pins Actions immutably; main has since edited the same workflows
crates/corecrux-workspace-scan/src 4 Structural. main extracted workspace_scan* into its own crate (#593); this branch wraps those same call sites in the new repo_scan_policy containment
crates/corecruxd/src/http + src 10 Handler-level auth/tenant changes against 202 commits of drift
corecruxd/examples, corecrux-memory, crux-mcp, corecruxctl, docs 9 Incidental

The workspace-scan cluster is the one that needs care rather than mechanical resolution: both sides are correct and they touch the same lines. repo_scan_policy::execute(root, workspace_scan::run_scan_regex_at) has to survive the move into corecrux-workspace-scan, and the containment must not be silently dropped while reconciling the paths.

What this PR is asking for

Resolving 32 conflicts across security-critical code — auth boundaries, tenant isolation, CI token scope — is not safe to do mechanically, and the person who wrote these fixes is best placed to confirm intent survives the rebase. Suggested order:

  1. Rebase or merge main in, resolving the workspace-scan cluster first since it is the only structural one.
  2. Re-run the full gate: cargo test --workspace, clippy -D warnings, unwrap-ratchet.sh, check-agent-docs.sh --exec, and cargo build --workspace --all-targets (examples are in the conflict set and --tests alone will not build them).
  3. Split into smaller PRs if the merge queue struggles with 215 files.

Until then this branch is at least backed up and visible.

🤖 Generated with Claude Code

CueCrux-Myles and others added 26 commits July 30, 2026 09:38
Use one authentication-posture predicate for transport requests, discovery metadata, and daemon bind validation. Add missing and unknown bearer regressions for OAuth-only deployments.\n\nagent:codex-work
Require admin:write for structural governance mutations, derive secure route-auth defaults from listener and auth posture, preserve semantic read/capability reachability, and pin enforce mode across shipped packaging.\n\nagent:codex-work
Derive work and orchestrator authority from authenticated identity and tenant context, isolate cross-tenant access, require canonical human gate provenance, protect governed entities, and namespace unmapped automation principals.

agent:codex-work
Bind verified principal and tenant per request, enforce exact-name scope intersections, isolate session storage, and preserve tenant-distinct receipts.

Agent: Codex
Co-Authored-By: OpenAI Codex <codex@openai.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>
Default wired HTTP fact-backed surfaces to verified JWT tenant stamping while retaining explicit off and shadow migration modes. Freeze the posture at startup, fail closed on ambiguous authority, and tenant-bind paired reads, caches, receipts, overlays, candidates, replay, and result-envelope flows.

Co-Authored-By: OpenAI Codex <codex@openai.com>
Constrain repository and workspace scans to canonical allowed roots with descriptor-rooted traversal, symlink and hardlink containment, bounded admission, and global tenant authority. Make scan persistence, watchers, startup recovery, legal holds, and explicit compaction fail closed under durability uncertainty.

Co-Authored-By: OpenAI Codex <codex@openai.com>
Update stale actor-attribution fixtures for the tenant partition introduced before M17. Work collaborators remain mutually visible inside the work tenant, while legacy default-tenant facts remain isolated and retain a null actor.

Co-Authored-By: OpenAI Codex <codex@openai.com>
Apply one console-compatible browser security policy to direct daemon routes and the desktop proxy. Deny foreign framing without breaking same-origin Studio and 3D tiles, replace hostile upstream policy headers, and regression-test all response classes plus daemon/proxy parity.

Co-Authored-By: OpenAI Codex <codex@openai.com>
Require reviewed full commit SHAs for every external workflow action, with the verifier-required SLSA version tag as the sole exact exception. Keep Rust toolchain selection explicit after SHA pinning and extend the trusted workflow policy with adversarial YAML, reusable-workflow, and local-action checks.

Co-Authored-By: OpenAI Codex <codex@openai.com>
Build activation tenant options with DOM nodes and textContent instead of parsing response values as HTML. Mark the synthetic add-tenant option with DOM-owned metadata so a real __add_new__ tenant remains selectable, and execute the shipped inline script against hostile values in a dependency-free mock DOM regression.

Co-Authored-By: OpenAI Codex <codex@openai.com>
Stop receipt and daemon crates from re-enabling ZIP defaults so the unused aes-crypto graph is absent. Make the CLI Deflate backend explicit with zlib-rs, add a compressed-import round trip, refresh root and fuzz locks, and enforce yanked-package denial in the existing cargo-deny CI job.

Co-Authored-By: OpenAI Codex <codex@openai.com>
Minimize the Docker build context, tighten the runtime and local Compose posture, add a digest-pinned shared deployment example, and enforce exact fail-closed Docker/Compose/publication contracts. Build and scan one candidate digest, sign and attest it, then promote supported tags only after every security gate succeeds.

Co-Authored-By: OpenAI Codex <codex@openai.com>
Update the eviction test helper for the tenant-aware fact-store API so the integrated workspace suite compiles and exercises the intended default tenant.

Co-Authored-By: OpenAI Codex <codex@openai.com>
Make device grant issuance transactional, cap all retained public state, enforce absolute refresh expiry, and preserve backward-compatible CLI credential handling.\n\nExecPlan: crux-daemon-red-steel-remediation-2026-07-30\n\nCo-Authored-By: Codex <codex@openai.com>
@CueCrux-Myles

Copy link
Copy Markdown
Contributor Author

Rebase attempted — 32/32 conflicts resolved, 13 errors left

Pushed to redsteel/rebase-onto-main (a395663f). It does not compile yet — committed so the resolution is reviewable rather than living in one worktree. This branch is untouched.

The 40 → 13 correction

The first build showed ~40 errors, which read like deep API divergence. It wasn't. 28 were a single cause: the moved scan code needs libc (×21), uuid (×4), rustix (×3) and tree-sitter-rust (×1), and the new crate's manifest didn't declare them. Wiring four dependencies cleared them. Only 13 are real divergence.

Decisions worth reviewing

Structural — repo_scan_policy moved into corecrux-workspace-scan. It and the scanners are mutually recursive (~330 check_deadline/charge_* calls out, plus callbacks back into walk_dir/read_scan_bytes), so no crate boundary can sit between them. Its only tie to corecruxd was repo_registry::MAX_SCAN_SNAPSHOT_BYTES — itself an alias for a constant the policy defines, used twice, in tests. corecruxd re-exports it; its call sites are unchanged.

Auth handlers — took main wholesale. traces, dossier, storybook, auth_device, work, repos, loopback_auth. Main's load_scan takes a &TenantScope capability; this branch still passes a raw tenant string — precisely what main's fix removed. Taking this branch's version there would have regressed a live tenant-isolation fix. Those handler edits are deferred, not dropped.

Symlink walk — took this branch. Both sides independently fixed the same descent bug. The shared tail binds &metadata (this branch's); main's binds file_type and wouldn't compile. This branch also adds a depth bound and the policy hook, and still hands a symlink-to-non-directory to visit, which is the behaviour main documented.

Privacy prefixes — union, verified. 56 and 57 entries, computed against both sides, nothing dropped. Losing main's __escrow__:: alone would have let wrapped DEK ciphertext sync to a remote.

Both-sides-additive — kept both halves. main.rs (scan admission + M6 retention pruning) and repo_watch.rs (journal-poisoned guard + vanished-root data-loss guard, on WatchAttemptError since String can't distinguish permanent from retryable).

Workflows. SHA pins kept throughout; main's per-job CARGO_HOME seeding (the md3 disk-leak fix) kept in mutants.yml. sdk-python keeps main's newer version — pypa/gh-action-pypi-publish is a Docker action and can't be SHA-pinned.

⚠ Needs author sign-off

c2pa_x509 — both sides took documented, opposite positions on an unvalidatable certificate chain: this branch Some(false) (fail closed), main None described as "a deliberate" non-failure. I took fail-closed because that failure mode is loud, but it's a security judgement belonging to whoever wrote it.

The 13 remaining

Crate n What
crux-mcp 4 tools import request_loopback_authority, loopback_bearer_token_for_passport{,_with_scopes}, RequestLoopbackAuthority — main's loopback_auth doesn't define them
corecrux-workspace-scan 3 parse_result (ast:1232), failures (manifests:56), type mismatch (manifests:117)
corecruxctl 3 checks_skipped (c2pa_x509:318, :390), if/else type mismatch (:275) — fallout from the fail-closed choice

An ExecPlan scoping the bridge follows.

🤖 Generated with Claude Code

@CueCrux-Myles

Copy link
Copy Markdown
Contributor Author

Disposition: retire this branch as the delivery vehicle; replay in slices

Assessed today against current main. The work is still needed — nothing here has been superseded. repo_scan_policy has 13 references on this branch and 0 on main; the workflows on main are still only partially pinned (a mix of SHAs and mutable @v7 tags), and runner-policy.yml / check_workflow_runner_policy.py do not exist there at all.

Why not finish the merge

Drift velocity, measured rather than estimated. On 2026-08-07 alone main took 11 merges; redsteel/rebase-onto-main ended the day 76 commits behind, having been 241 behind earlier. A 215-file branch that needs a day of conflict work is outrun by the days it takes to do that work. The serial merge queue compounds it: a 215-file entry costs a full ~30 min build, and any red restarts it.

Replaying the 26 commits onto current main individually: 5 apply clean, 21 conflict — but mostly 1–2 files each. Sliced replay is tractable where the whole-branch merge is not.

This branch stays pushed as the reference for the resolution reasoning already done — the WIP commit message is the best record of it (auth handlers → take main; privacy prefixes → union, 56+57 verified; symlink walk → take red-steel; the workspace-scan crate extraction analysis).

First slice shipped

#653a3e2eabf fail closed for OAuth-only auth, 2d5c085d harden daemon browser responses, 014cd05a remove the yanked ZIP crypto path. Workspace check clean, 2312 tests pass, clippy at parity with main.

Two things that need a human, not a merge resolution

  1. f5ccc46e is a policy fork, not a conflict. It moves every CI job including test and coverage to ubuntu-latest, and its checker classifies merge_group as untrusted — so the merge queue could not use self-hosted runners either. main has since doubled down the other way (ci: make CARGO_HOME portable, and stop pinning to one runner pool #631's pool split, the shared CARGO_HOME design, ci-fallback.yml existing because ubuntu-latest is the fallback). The exposure it addresses is real — untrusted PR code does run on privileged self-hosted runners today — but the remedy is a threat-model and cost call.

    Related: affd78ac's pins are stale by construction; applying them verbatim would pin actions backwards to their 07-30 SHAs. The durable value is the enforcement mechanism, with pins regenerated against current versions.

  2. bd440c26 (c2pa_x509) still needs author sign-off — both sides took documented, opposite positions on an unvalidatable certificate chain.

A live finding surfaced while slicing

41ee2515 compiles against main and fails: a work-tenant query_facts returns 3 rows where it asserts 2, the extra being a fact written under the default tenant. main and this branch disagree about cross-tenant visibility of default-tenant facts, and ba642868 / 426ea8a0 treat that as a leak to close. That makes the tenant-isolation slice the highest-value one remaining.

Full slice breakdown, verification status, and decision log: PlanCrux/.agent/execplans/redsteel-remediation-replay-2026-08-07.md.

Suggest keeping this PR open as the tracking issue until the slices land, then closing it unmerged.

🤖 Generated with Claude Code

@CueCrux-Myles CueCrux-Myles changed the title [DRAFT] security: red-steel remediation — 26 commits, needs rebase onto main [TRACKING] security: red-steel remediation — 3/26 landed, S2 in flight, S3–S6 open Aug 9, 2026
@CueCrux-Myles

Copy link
Copy Markdown
Contributor Author

S2 resolved — #671 open, all checks green

Not the way this branch proposed. f5ccc46e is dropped, and the reason is that its premise reads differently against the live repository settings than against the workflow files alone:

  • approval_policy: all_external_contributors — every fork PR needs maintainer approval before any workflow runs
  • forkCount: 0

Untrusted code reaching the self-hosted pool is gated behind a human click, with nobody positioned to attempt it. That does not justify reversing #631's pool split and the shared CARGO_HOME design. The gate is now documented and enforced rather than replaced.

The correction that mattered more: escalating S2 as one decision was the error. It was three — runner topology, token scope, action pinning — and the latter two were cheap, un-gated, and independent of the question they had been parked behind. Both were live exposures:

Setting Was
default_workflow_permissions write, repo-wide
sha_pinning_required false, with 95 mutable action refs

#671

commit
d3497de1 5 workflows (ci, ci-fallback, buf, private-paths, semver) declared no permissions: and inherited the repo default of write. Now explicit.
9d8a436b 98 action refs across 18 workflows pinned to commit SHAs.
75530ce1 scripts/check_workflow_policy.py + runner-policy.yml — 5 rules, 15 unit tests — so the first two stay true.

Both are re-derivations, not ports. 8df28581 predates most of the workflows it would touch, and affd78ac's SHAs would pin actions backwards to their 07-30 versions.

One finding worth carrying forward

dtolnay/rust-toolchain reads its channel from the ref it was invoked by. Pinning the ref — which the policy requires — silently changes which Rust the job installs. All 18 call sites needed an explicit toolchain: input. The MSRV (1.88.0) job passing green on #671 is the evidence this was caught rather than shipped; the checker now has a rule so it cannot recur.

Separately: reusable workflows are exempt from GitHub's sha_pinning_required and may stay on tags, which is what makes enabling that policy compatible with the SLSA generator at all.

Status

3 of 26 landed (#653), S2's three resolved via #671 — one dropped on the merits, two shipped. S3–S6 remain, and S3 (tenant isolation) is still the largest security delta.

bd440c26 (C2PA trust paths) continues to need author sign-off; it is not in #671.

Plan and decision log: PlanCrux/.agent/execplans/redsteel-remediation-replay-2026-08-07.md.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant