Skip to content

feat: add Cloud Hypervisor filesystem write planner - #7660

Merged
lpcox merged 4 commits into
mainfrom
lpcox-ch-filesystem-write-policy-planner
Aug 23, 2026
Merged

feat: add Cloud Hypervisor filesystem write planner#7660
lpcox merged 4 commits into
mainfrom
lpcox-ch-filesystem-write-policy-planner

Conversation

@lpcox

@lpcox lpcox commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a pure, strongly typed policy-planning foundation for filesystem.allowWrite on the Cloud Hypervisor runtime. This PR is inert: nothing is wired into runtime execution, src/cloud-hypervisor/virtiofsd.ts is untouched, and filesystem.allowWrite is still rejected for the Cloud Hypervisor runtime by src/filesystem-policy.ts. No behavior change.

New module: src/cloud-hypervisor/filesystem-write-policy.ts

planCloudHypervisorFilesystemWrites(
  exports: readonly CloudHypervisorDirectoryExport[],
  allowWrite: string[] | undefined,
  options?: { internalTags?: Iterable<string> },
): CloudHypervisorFilesystemWritePlan

Semantics

  • undefinedrestricted: false, every export keeps its declared mode (disposition: 'unrestricted').
  • [] → every writable non-internal export becomes read-only; AWF-owned exports named in internalTags stay writable (mirrors the always-writable Docker mounts).
  • Each allowed path must be absolute, normalized, free of .., exist on the host, and resolve beneath an existing read-write export target.
  • Narrowing only: a read-only export is never upgraded and no host path outside an existing rw export is ever exposed.
  • Guest → host translation uses the export-target-relative path against the export source.
  • Symlink escapes are rejected with the same realpath-equality invariant used by src/services/agent-volumes/filesystem-write-policy.ts.
  • Existing files and directories are both supported (kind: 'file' | 'directory').
  • Duplicates are normalized (including trailing slashes) and descendants of an allowed ancestor are dropped.
  • Only an exact match against an export target keeps the whole export writable. A strict ancestor (e.g. / above /workspace) is not itself a path reachable within the export, so it goes through the same host resolution as any other candidate and is rejected if it does not resolve.
  • Entries covered by an internal (internalTags) read-write export are validated and consumed rather than reported as unmatched, but emit no overlay because the whole export is already writable. A nested internal path still passes the same existence / realpath-equality / file-or-directory checks, so /tmp/gh-aw/missing or a symlink escape under an internal export is still rejected.
  • Overlapping exports resolve to the deepest matching export; a deeper read-only export is not widened through a shallower writable one. Current export validation forbids overlap, so this is forward-compatibility only.
  • Errors match the existing filesystem.allowWrite wording: filesystem.allowWrite path must be absolute without '..': … and filesystem.allowWrite path is not an existing path within a writable Cloud Hypervisor export: … (all unmatched paths reported at once).

Host root mode vs guest mount mode

Read-only enforcement for a selectively writable export is a host-side property, so each plan entry carries two modes rather than one:

disposition hostRootMode guestMountMode
unrestricted declared export mode declared export mode
read-only ro ro
writable rw rw
selective ro rw

A selective export must not be mounted read-only in the guest. virtio-fs submounts are attached through d_automountfuse_dentry_automount()fc_mount()finish_automount(), and finish_automount() calls:

err = do_add_mount(mnt, mp, path, path->mnt->mnt_flags | MNT_SHRINKABLE);

(fs/namespace.c) — the announced submount inherits the parent mount's mnt_flags, including MNT_READONLY. (fs_context_for_submount() passes sb_flags = 0, so the submount superblock is not SB_RDONLY, but the inherited per-mount MNT_READONLY still denies writes.) A guest-level MS_RDONLY on a composite tree would therefore block writes to every writable node beneath it. The read-only host backing tree root — the same host-side read-only bind virtiofsd.ts already builds for ro exports — is what denies writes outside the overlays.

Output shape

A later integration PR can distinguish the cases and get the paths directly:

  • CloudHypervisorExportWritePlan: disposition (unrestricted | read-only | writable | selective), hostRootMode, guestMountMode, internal, overlays.
  • CloudHypervisorWritableOverlay: exportTag, guestPath, hostPath, relativePath, kind. Both paths are absolute, but canonical in different senses — guestPath is only lexically normalized (the guest filesystem does not exist at planning time), while hostPath is realpath-canonical and verified not to escape the export source.
  • CloudHypervisorFilesystemWritePlan: restricted, normalized allowedPaths, per-export exports, and a flattened overlays list.

Tests

src/cloud-hypervisor/filesystem-write-policy.test.ts — 22 focused cases covering: undefined, empty list, internal tag exemption, full-export allowance, strict-ancestor rejection, nested directory, existing file, source/target translation, duplicate/descendant normalization, relative and .. rejection, nonexistent path, symlink escape, read-only export (both target and nested path), unmatched path, multi-path error aggregation, deepest-export resolution, deeper-ro non-widening, multiple overlays per export, and the host-root/guest-mount mode invariant (an overlay-bearing export is always hostRootMode: 'ro' + guestMountMode: 'rw', and a rw host root is never published to a ro guest mount).

An internal exports block covers the internal cases specifically: exact internal target, valid nested internal path (consumed, no overlay), missing nested internal path (rejected), strict ancestor of the internal target (rejected), and symlink escape under the internal export (rejected).

Validation

  • npx jest src/cloud-hypervisor → 13 suites, 172 tests passed
  • npm test → 316 suites, 4989 tests passed
  • npm run build (tsc) → clean
  • npx eslint on the new files → 0 errors (only the pre-existing security/detect-non-literal-fs-filename warnings that the sibling Docker policy module also emits)

Docs

docs/cloud-hypervisor-foundation.md gains a "Write-policy planning (inert)" subsection describing the planner, the host-VFS enforcement rationale for the two modes, and the path-canonicality distinction.

Add a pure, strongly typed planner that computes how a filesystem.allowWrite
allowlist would narrow validated Cloud Hypervisor directory exports.

The planner classifies each export as unrestricted, read-only, fully writable,
or selectively writable, and returns canonical host/guest overlay paths so a
later integration can mount them without re-resolving symlinks. It only removes
write access: read-only exports are never widened and no host path outside an
existing read-write export is ever exposed.

The planner is inert. It is not wired into runtime execution and
filesystem.allowWrite remains rejected for the Cloud Hypervisor runtime.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings August 23, 2026 16:20
@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Documentation Preview

Documentation build failed for this PR. View logs.

Built from commit b81c682

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an inert Cloud Hypervisor planner for future filesystem.allowWrite enforcement.

Changes:

  • Introduces typed export and overlay planning.
  • Adds focused planner tests.
  • Documents the currently unwired foundation.
Show a summary per file
File Description
src/cloud-hypervisor/filesystem-write-policy.ts Implements write-policy planning.
src/cloud-hypervisor/filesystem-write-policy.test.ts Tests planner semantics and validation.
docs/cloud-hypervisor-foundation.md Documents the inert planner.

Review details

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 3/3 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment on lines +121 to +123
if (isPathAtOrBelow(entry.target, allowedPath)) {
matched.add(allowedPath);
return { export: entry, disposition: 'writable', effectiveMode: 'rw', internal, overlays: [] };
@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

✅ Coverage Check Passed

Overall Coverage

Metric Base PR Delta
Lines 93.71% 93.76% 📈 +0.05%
Statements 92.57% 92.62% 📈 +0.05%
Functions 92.88% 92.95% 📈 +0.07%
Branches 85.82% 85.92% 📈 +0.10%
📁 Per-file Coverage Changes (1 files)
File Lines (Before → After) Statements (Before → After)
src/log-directory-setup.ts 96.2% → 100.0% (+3.78%) 96.3% → 100.0% (+3.71%)
✨ New Files (1 files)
  • src/cloud-hypervisor/filesystem-write-policy.ts: 100.0% lines

Coverage comparison generated by scripts/ci/compare-coverage.ts

A selective export previously reported a single effectiveMode of "ro", which
would be wrong for the guest mount. virtio-fs submounts are attached through
d_automount, and finish_automount() calls
do_add_mount(..., path->mnt->mnt_flags | MNT_SHRINKABLE), so an announced
submount inherits MNT_READONLY from its parent mount. A guest-level MS_RDONLY
on a composite tree would therefore block writes to every writable node below
it.

Replace effectiveMode with hostRootMode and guestMountMode. Read-only
enforcement is a host-side property: a selective export stages a read-only host
backing tree root while the guest mount stays read-write so the overlays remain
writable.

Also clarify that overlay guestPath is only lexically normalized while hostPath
is realpath-canonical.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Copilot review passed with no inline comments.

@lpcox Add the ready-for-aw label to this PR to trigger agentic CI smoke tests.

@lpcox lpcox changed the title feat(cloud-hypervisor): add filesystem.allowWrite policy planner feat: add Cloud Hypervisor filesystem write planner Aug 23, 2026
@lpcox

lpcox commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator Author

@copilot address review feedback

Copilot AI commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

@copilot address review feedback

Fixed in 2a79de8: the full-export match now requires an exact match against the export target instead of isPathAtOrBelow(entry.target, allowedPath), so an ancestor path like / no longer widens /workspace — it falls through to host-path resolution and is rejected as unmatched. Added a regression test for this case.

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

📰 VERDICT: Smoke Docker Sbx has concluded. All systems operational. This is a developing story. 🎤

📰 BREAKING: Report filed by Smoke Docker Sbx

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

🔌 Smoke Services — All services reachable! ✅

🔌 Service connectivity validated by Smoke Services

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Smoke Copilot BYOK completed. Copilot BYOK mode operational. 🔓

🔑 BYOK report filed by Smoke Copilot BYOK

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Smoke Claude passed

Generated by Smoke Claude for #7660

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Chroot tests passed! Smoke Chroot - All security and functionality tests succeeded.

Tested by Smoke Chroot

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

🛡️ Smoke Copilot Network Isolation confirmed the egress allowlist is enforced. ✅

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • example.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "example.com"

See Network Configuration for more information.

🛡️ Egress verdict from Smoke Copilot Network Isolation

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Smoke Copilot BYOK AOAI (Entra) completed. Copilot AOAI BYOK (Entra) mode operational. 🔓

🪪 BYOK (AOAI Entra) report filed by Smoke Copilot BYOK AOAI (Entra)

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Contribution Check failed. Please review the logs for details.

Generated by Contribution Check for #7660

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤

📰 BREAKING: Report filed by Smoke Copilot

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Build Test Suite completed successfully!

Generated by Build Test Suite for #7660

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Smoke Copilot BYOK AOAI (api-key) completed. Copilot AOAI BYOK (api-key) mode operational. 🔓

🔑 BYOK (AOAI api-key) report filed by Smoke Copilot BYOK AOAI (api-key)

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

📡 Smoke OTel Tracing completed. All tracing scenarios validated. ✅

📡 OTel tracing validated by Smoke OTel Tracing

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Security Guard has started processing this pull request

@lpcox
lpcox deployed to aoai-model August 23, 2026 16:33 — with GitHub Actions Active
@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

✨ The prophecy is fulfilled... Smoke Codex has completed its mystical journey. The stars align. 🌟

Warning

Firewall blocked 3 domains

The following domains were blocked by the firewall during workflow execution:

  • msfeed2.pkgs.visualstudio.com
  • msfeed25.pkgs.visualstudio.com
  • registry.npmjs.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "msfeed2.pkgs.visualstudio.com"
    - "msfeed25.pkgs.visualstudio.com"
    - "registry.npmjs.org"

See Network Configuration for more information.

🔮 The oracle has spoken through Smoke Codex

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

✨ The prophecy is fulfilled... Smoke Codex has completed its mystical journey. The stars align. 🌟

Warning

Firewall blocked 3 domains

The following domains were blocked by the firewall during workflow execution:

  • msfeed2.pkgs.visualstudio.com
  • msfeed25.pkgs.visualstudio.com
  • registry.npmjs.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "msfeed2.pkgs.visualstudio.com"
    - "msfeed25.pkgs.visualstudio.com"
    - "registry.npmjs.org"

See Network Configuration for more information.

🔮 The oracle has spoken through Smoke Codex

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Chroot tests passed! Smoke Chroot - All security and functionality tests succeeded.

Tested by Smoke Chroot

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Smoke Copilot BYOK AOAI (api-key) completed. Copilot AOAI BYOK (api-key) mode operational. 🔓

🔑 BYOK (AOAI api-key) report filed by Smoke Copilot BYOK AOAI (api-key)

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Smoke Gemini reports failed. Facets need polishing...

💎 Faceted by Smoke Gemini

@lpcox
lpcox deployed to aoai-model August 23, 2026 17:07 — with GitHub Actions Active
@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

🔌 Smoke Services — All services reachable! ✅

🔌 Service connectivity validated by Smoke Services

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Smoke Copilot BYOK completed. Copilot BYOK mode operational. 🔓

🔑 BYOK report filed by Smoke Copilot BYOK

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Contribution Check completed successfully!

Generated by Contribution Check for #7660

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test: Copilot Engine@lpcox

Overall: PASS

📰 BREAKING: Report filed by Smoke Copilot
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

EGRESS_RESULT allow=pass deny=pass

✅ Allowed domain (github.com) reachable: allowed=200
✅ Non-allowed domain (example.com) blocked: OK: example.com was blocked (CONNECT tunnel failed, 403)

Overall status: PASS

cc @lpcox

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • example.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "example.com"

See Network Configuration for more information.

🛡️ Egress verdict from Smoke Copilot Network Isolation
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test: Claude Engine Validation

Check Result
API status ✅ PASS
gh check ✅ PASS
File status ✅ PASS

Overall result: PASS

Generated by Smoke Claude for #7660 · haiku45 · 55.8 AIC · ⊞ 4.5K ·
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Docker Sbx Smoke Test

  • ✅ GitHub MCP connectivity (verified via list_pull_requests)
  • ✅ GitHub.com connectivity (HTTP 200)
  • ✅ File write/read test

Recent merged PRs:

Overall: PASS

cc @lpcox

📰 BREAKING: Report filed by Smoke Docker Sbx
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test: Services Connectivity

  • Redis PING: ❌ (Temporary failure in name resolution)
  • PostgreSQL pg_isready: ❌ (no response)
  • PostgreSQL SELECT 1: ❌ (could not translate host name)

Overall: FAILhost.docker.internal did not resolve from the AWF sandbox.

🔌 Service connectivity validated by Smoke Services
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test Results: Copilot BYOK (Direct) Mode ✅

Overall Status: PASS

🔑 BYOK report filed by Smoke Copilot BYOK
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Merged PR titles:

  • Strip stale raw-network capabilities from agent images
  • chore(workflows): upgrade gh-aw prerelease
  • GitHub MCP review ❌
  • safeinputs-gh PR query ❌
  • Playwright title ✅
  • File write/read via bash ✅
  • Discussion query/comment ❌
  • npm ci + build ✅
    Overall: FAIL

Warning

Firewall blocked 3 domains

The following domains were blocked by the firewall during workflow execution:

  • msfeed2.pkgs.visualstudio.com
  • msfeed25.pkgs.visualstudio.com
  • registry.npmjs.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "msfeed2.pkgs.visualstudio.com"
    - "msfeed25.pkgs.visualstudio.com"
    - "registry.npmjs.org"

See Network Configuration for more information.

🔮 The oracle has spoken through Smoke Codex
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Chroot Version Comparison

Runtime Host Version Chroot Version Match?
Python Python 3.12.14 Python 3.12.14 ✅ YES
Node.js v24.19.0 v22.23.2 ❌ NO
Go go1.22.12 go1.22.12 ✅ YES

Result: Not all tests passed — Node.js version mismatch between host and chroot environment. smoke-chroot label not added.

Tested by Smoke Chroot
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

📡 OTel Tracing Smoke Test Results

Scenario Result
1. Module Loading (otel.js) ✅ Loaded successfully. isEnabled: true. Exports: startRequestSpan, setTokenAttributes, setBudgetAttributes, endSpan, endSpanError, shutdown, isEnabled, _provider, _ProxyAwareOtlpExporter, _FileSpanExporter, _FanOutSpanExporter, _parseEndpoints, _parseOtlpHeaders, _buildResourceSpans, _createOtlpWorkloadIdentity
2. Test Suite (otel*.test.js) ✅ 3 suites, 68/68 tests passed (otel.test.js, otel-fanout.test.js, otel-workload-identity.test.js)
3. Env Var Forwarding env-passthrough.ts forwards GITHUB_AW_OTEL_TRACE_ID / GITHUB_AW_OTEL_PARENT_SPAN_ID; api-proxy-env-config.ts forwards GH_AW_OTLP_ENDPOINTS, OTEL_EXPORTER_OTLP_ENDPOINT, and both trace context vars
4. Token Tracker Integration token-tracker-http.js contains the onUsage callback (OTEL hook point)
5. OTEL Diagnostics (spans exported) ⚠️ No live agent traffic ran in this validation pass, so no otel.jsonl span file was produced — expected since no LLM requests were proxied during static checks

Overall: ✅ All core scenarios pass. No regressions detected in OTEL module init, span/token attribute logic, env var propagation, or token-tracker integration.

📡 OTel tracing validated by Smoke OTel Tracing
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

feat: add Cloud Hypervisor filesystem write planner

  • GitHub MCP testing: ✅
  • GitHub.com connectivity: ✅
  • File I/O test: ✅
  • BYOK inference: ✅
    Running in direct BYOK mode (COPILOT_PROVIDER_API_KEY + COPILOT_PROVIDER_BASE_URL) via api-proxy → Azure OpenAI (Foundry, o4-mini-aw)
    Overall: PASS
    @lpcox

🔑 BYOK (AOAI api-key) report filed by Smoke Copilot BYOK AOAI (api-key)
Add label ready-for-aw to run again

@lpcox
lpcox deployed to aoai-model August 23, 2026 17:12 — with GitHub Actions Active
@github-actions

Copy link
Copy Markdown
Contributor

@lpcox
GitHub MCP connectivity: ✅
GitHub.com connectivity: ✅
File write/read: ✅
BYOK inference: ✅
Running in direct BYOK mode (AWF_AUTH_TYPE=github-oidc + AWF_AUTH_AZURE_* + COPILOT_PROVIDER_BASE_URL) via api-proxy → Azure OpenAI (Foundry, o4-mini-aw) authenticated via Microsoft Entra
Overall: PASS

🪪 BYOK (AOAI Entra) report filed by Smoke Copilot BYOK AOAI (Entra)
Add label ready-for-aw to run again

@lpcox
lpcox deployed to aoai-model August 23, 2026 17:12 — with GitHub Actions Active
@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Build Test Suite Results

Ecosystem Project Build/Install Tests Status
Bun elysia 1/1 passed ✅ PASS
Bun hono 1/1 passed ✅ PASS
C++ fmt N/A ✅ PASS
C++ json N/A ✅ PASS
Deno oak N/A 1/1 passed ✅ PASS
Deno std N/A 1/1 passed ✅ PASS
.NET hello-world N/A (ran, output correct) ✅ PASS
.NET json-parse N/A (ran, output correct) ✅ PASS
Go color 1/1 passed ✅ PASS
Go env 1/1 passed ✅ PASS
Go uuid 1/1 passed ✅ PASS
Java gson 1/1 passed ✅ PASS
Java caffeine 1/1 passed ✅ PASS
Node.js clsx passed ✅ PASS
Node.js execa passed ✅ PASS
Node.js p-limit passed ✅ PASS
Rust fd 1/1 passed ✅ PASS
Rust zoxide 1/1 passed ✅ PASS

Overall: 8/8 ecosystems passed — PASS

Note: Maven initially failed with Could not create local repository at /home/runner/.m2/repository (unrelated to the firewall — pre-existing permission issue on /home/runner/.m2 owned by root). Worked around by pointing Maven at a local repo path (-Dmaven.repo.local); no network/firewall issue was involved, and both Java projects then compiled and passed tests through the Squid proxy.

All ecosystems and projects completed successfully.

Generated by Build Test Suite for #7660 · auto · 77.3 AIC · ⊞ 12K ·
Add label ready-for-aw to run again

@lpcox
lpcox enabled auto-merge (squash) August 23, 2026 17:14
@lpcox
lpcox merged commit 6aab9a1 into main Aug 23, 2026
168 of 169 checks passed
@lpcox
lpcox deleted the lpcox-ch-filesystem-write-policy-planner branch August 23, 2026 17:19
lpcox added a commit that referenced this pull request Aug 23, 2026
Resolves one conflict in docs/cloud-hypervisor-foundation.md, where main's
write-policy planner (#7660) and this branch's host mount-tree enforcement each
added a section immediately before "## Limitations".

Both are kept. The planner section is a `###` under "## Guest and workspace", so
it stays first to remain under its parent heading, and the mount-tree section
follows as a new `##`. The two now cross-reference each other: the planner
decides which paths stay writable and emits `hostRootMode`/`guestMountMode`,
while the mount-tree layer stages the host tree that enforces it. Both remain
inert and independent of each other.

Also documents that the propagation check rejects `master:` and
`propagate_from:` as well as `shared:`, matching the slave-mount fix on this
branch.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 6cda9102-6304-49e1-a578-40a583561e2c
lpcox added a commit that referenced this pull request Aug 23, 2026
* feat: support filesystem allowWrite with Cloud Hypervisor

Wires the write-policy planner (#7660) into the host mount-tree
enforcement layer (#7661) so `filesystem.allowWrite` is actually
enforced by the Cloud Hypervisor microVM runtime. Both foundations
landed deliberately inert; this is the runtime integration.

`filesystem-write-enforcement.ts` is the only place the two meet. It
plans the policy in a dedicated startup stage before the boot loop, so
an invalid allowlist aborts before virtiofsd or the guest launches, and
translates each planner disposition into the merged mount-tree API:

- policy absent: no enforcement argument at all, byte-identical legacy
  behaviour and original export objects;
- unrestricted/fully writable: guest `rw`, no plan;
- fully read-only: guest `ro`, staged host root `ro`, zero-overlay plan;
- selectively writable: guest `rw`, staged host root `ro`, one overlay
  per allowed path.

A zero-overlay read-only export still gets a plan rather than falling
back to the legacy single bind plus remount, so a narrowed export always
uses the recursively-verified staged tree. No `internalTags` are passed:
Cloud Hypervisor has no analogue of Docker's always-writable log and
session-state binds, and exempting `tmp-gh-aw` would defeat the
narrowing a policy like `allowWrite: ["/tmp/gh-aw/agent"]` expresses.

Because the host tree is the boundary, a selective export is never
mounted read-only guest-side. `validateCloudHypervisorExports()` accepts
a read-only `workspace` only when a plan for that tag exists, so a
read-only workspace nothing enforces is still rejected. Unknown plan
tags stay fail-closed and planner validation is not duplicated.

Removes only the Cloud Hypervisor rejection from `filesystem-policy.ts`;
sbx and Docker-in-Docker still fail closed. Adds live-KVM smoke cases
proving an allowed directory and file write persists to the host while
sibling, parent, create, truncate, rename, and delete outside the
allowlist fail, plus empty-allowlist narrowing and a fail-closed abort.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix: gate compose write policy by runtime and repair allowWrite smoke probes

Two review blockers on the Cloud Hypervisor allowWrite integration.

1. CRITICAL — `filesystem.allowWrite` leaked into compose generation.
   `writeConfigs()` -> `generateDockerCompose()` -> `buildAgentVolumes()`
   called Docker's `applyFilesystemWritePolicy()` unconditionally. Compose
   still builds an agent service object for microVM runtimes (so infra
   containers can wire depends_on edges) even though it is omitted from the
   emitted file, so a Cloud Hypervisor policy was evaluated against compose
   bind mounts the agent never uses. Guest paths such as `/workspace/allowed`
   -- and the motivating `/tmp/gh-aw/agent` -- are not backed by any host bind
   mount, so they threw "not an existing path within a writable host mount"
   during writeConfigs(), long before the Cloud Hypervisor planner ran.

   Adds `resolveComposeFilesystemAllowWrite()`, which returns the policy only
   when `runtimeUsesComposeAgent()` is true, and routes both consumers through
   it: the `applyFilesystemWritePolicy()` call in volume-builder and the
   `dropUnbackedHostHomeOverlays()` toggle in optional-services. Docker and
   gVisor behaviour is unchanged; sbx still fails closed in filesystem-policy.

   Audited every other `filesystemAllowWrite` reader: the remaining ones are
   the type declaration, config mapping, the runtime compatibility gate, and
   the Cloud Hypervisor planner itself, all correct.

2. HIGH — the live smoke truncate probe killed the guest shell.
   The guest runs BusyBox ash, where a redirection failure on a POSIX
   *special* builtin is fatal. `! : > /workspace/input.txt` therefore exited
   the shell before the rename and delete probes ran, while the host
   post-checks still passed vacuously -- a write never attempted also never
   changes anything. Reproduced in busybox: the old chain exits 1 and never
   prints the rename/delete markers; `printf ''` continues correctly.

   Every write-denial probe is now a regular builtin or external command,
   wrapped in a subshell so a fatal error stays contained, and followed by a
   sentinel. A new `assert_sentinels` helper requires all of them after the
   case, so a shell that dies partway can no longer look like a pass.

Regression coverage: top-level `generateDockerCompose` tests proving a Cloud
Hypervisor config with `/workspace/allowed` neither throws nor rewrites any
emitted compose volume, while Docker/gVisor still narrow mounts and still fail
closed on an unbacked path; plus script-contract tests banning special-builtin
write probes and requiring each sentinel to be both emitted and asserted.

Full suite 319 suites / 5068 tests passing.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants