Skip to content

feat: stage host mount trees for virtiofsd exports - #7661

Merged
lpcox merged 4 commits into
mainfrom
lpcox-ch-mount-tree-enforcement
Aug 23, 2026
Merged

feat: stage host mount trees for virtiofsd exports#7661
lpcox merged 4 commits into
mainfrom
lpcox-ch-mount-tree-enforcement

Conversation

@lpcox

@lpcox lpcox commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

Cloud Hypervisor v53 and virtiofsd v1.10 have no per-path readonly option, and a guest-side ro mount is not a security boundary — a compromised guest can remount. This PR adds the host-side primitive that makes selective write access enforceable: a private, recursively read-only staged host mount tree with nested writable bind overlays, enforced by the host VFS.

This is an inert foundation. With no enforcement argument, VirtiofsdManager behaves exactly as before — including byte-identical virtiofsd arguments. Nothing calls the new API yet; runtime wiring lands separately.

Independent of and not stacked on the policy planner PR (#7660). It does not touch src/filesystem-policy.ts or src/cloud-hypervisor/exports.ts, and implements no filesystem.allowWrite parsing or planning.

API

VirtiofsdManager.start(exports, enforcement?) accepts an optional VirtiofsdMountEnforcement, which maps an export to a VirtiofsdExportMountPlan describing canonical writable overlay source/destination pairs. The shape is generic — it describes host paths, not policy — so the planner can target it without this PR knowing about policy at all.

Exports without a plan keep the existing path, so partial enforcement is supported. A plan naming an export tag that does not exist is rejected at start(): silently dropping it would leave that export unrestricted read-write, so a renamed or mistyped tag has to fail rather than downgrade.

Staging sequence

  1. mount --rbind <export source> <staged root>
  2. mount --make-rprivate <staged root> — propagation cannot leak back to the host
  3. every mount at/under the root is remounted ro,nosuid,nodev, deepest-first, before any overlay exists
  4. each overlay: mount --bind (non-recursive, so a writable directory never exposes nested submounts) then an explicit mount -o remount,bind,rw,nosuid,nodev
  5. /proc/self/mountinfo is parsed and the tree is verified; only then does virtiofsd start, with --announce-submounts

Overlays support both directories and single files, and targets must already exist.

ro=recursive is deliberately not used

libmount's recursive option argument looked like the obvious mechanism. It is not safe here. Verified on a live kernel (util-linux 2.39.3, the version on GitHub-hosted Ubuntu 24.04, kernel 6.12):

mount -o rbind,ro=recursive,nosuid=recursive,nodev=recursive src dst   -> rc=0, submounts still rw
mount -o remount,bind,ro=recursive,nosuid=recursive,nodev=recursive p  -> rc=0, submounts still rw

Both succeed silently while leaving carried-in submounts writable — exactly the kind of failure that looks fine in CI and isn't. The per-mount remount loop was verified to work correctly on the same host. The mountinfo verifier is what caught this, which is the argument for keeping verification independent of tool exit codes.

Preflight now only requires util-linux >= 2.23 (for --make-rprivate) and rejects a non-util-linux mount with a clear error.

Security properties

  • Fail closed. If recursive read-only cannot be proven from mountinfo, staging rolls back and start fails. Tool exit codes are never the sole evidence.
  • No shell. Every mount call is an argv array through a MountTreeTools abstraction; nothing is string-concatenated.
  • No broad catches. Failures roll back with the original error preserved and re-thrown.
  • Propagation cannot leakmake-rprivate plus a mountinfo assertion that no staged mount is shared:.
  • Overlay sources must be canonical (realpath equality), contained in the export source, non-symlink, and of the declared kind. Destinations must exist, may not overlap, and are capped.
  • Destinations are canonicalized before the bind and must satisfy realpath equality plus containment under the staged root. lstat alone is insufficient — it only reveals a symlink in the final component, while the kernel resolves every intermediate component when it binds. A tools -> /etc symlink carried in from the export would otherwise let destination tools/sudoers lstat as an ordinary file and then bind over the host's /etc/sudoers. The staged root is itself required to be canonical so that comparison is meaningful.
  • A read-only export may not receive overlays at all.
  • The staged root must be disjoint from the export source, so the recursive bind can never nest the staged tree inside itself.

Residual limitation (documented): overlay sources live in the still-writable export, so a setup-time TOCTOU window exists between validation and bind. Destinations have no window — they are canonicalized inside the already-read-only private tree. Mitigated by re-canonicalising immediately before each bind.

Guest semantics

Staged exports stay rw in the guest on purpose. The host tree is the boundary; making the guest mount read-only would defeat the writable overlays and provides no security value anyway. --announce-submounts is added only when a tree is staged, so default output is unchanged.

Cleanup

Overlay children unmount deepest-first, then the root with umount -R (a plain umount of an rbind root carrying submounts fails EBUSY — found by the tests). Partial-start failures roll back what was staged and preserve the original error; anything that survives teardown is retained and retried on the next stop rather than being silently dropped.

Validation

  • 5019/5019 tests pass repo-wide; 202/202 in src/cloud-hypervisor, including 46 mount-tree tests and 8 manager-level tests: no-enforcement unchanged behaviour, partial enforcement leaving unplanned exports on the legacy path, unknown plan tag failing closed, read-only staged export, selective directory and file overlays, exact command ordering, explicit --announce-submounts, invalid/outside/symlink/overlapping paths, intermediate and final destination symlink escapes (asserting no --bind is ever invoked), non-canonical staged root, self-nesting rejection, partial setup failure, daemon startup failure, and reverse cleanup.
  • tsc --noEmit clean; eslint clean (3 pre-existing execa warnings); markdownlint clean.
  • Real-Linux smoke validation, privileged ubuntu:24.04, 18/18 checks: staging + verifier pass; root, sibling, and file-overlay-parent writes denied EROFS; carried-in submount recursively read-only; directory and single-file overlay writes persist to the export source; no shared propagation; nosuid/nodev applied recursively; both symlink escapes rejected with the host target untouched and no mount created outside the tree; cleanup leaves zero mounts and removes the staging directory; export source untouched. This ran against the exact commands the implementation emits, and is what drove the ro=recursive rewrite. No permanent test tooling was added — the harness was disposable, matching existing integration patterns.

Cloud Hypervisor v53 and virtiofsd v1.10 have no per-path readonly option,
and a guest-side `ro` mount is not a security boundary. Add an optional,
strongly typed enforcement input to `VirtiofsdManager.start()` that stages a
private host mount tree per export: recursive bind, private propagation,
recursive read-only enforcement, then nested writable bind overlays. When a
tree is staged, virtiofsd is launched with `--announce-submounts`.

The API is inert: with no enforcement argument the manager behaves exactly as
before, including byte-identical virtiofsd arguments. Runtime wiring lands
separately.

libmount's `ro=recursive` option argument is deliberately not used. On
util-linux 2.39.3 it exits 0 while leaving carried-in submounts read-write,
so enforcement instead remounts each mount in the tree read-only
deepest-first. Every tree is verified against `/proc/self/mountinfo` before
virtiofsd starts and staging fails closed if the boundary cannot be proven.

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:36
@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 13fc71b

@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.75% 93.82% 📈 +0.07%
Statements 92.60% 92.70% 📈 +0.10%
Functions 92.95% 93.09% 📈 +0.14%
Branches 85.91% 86.06% 📈 +0.15%
📁 Per-file Coverage Changes (2 files)
File Lines (Before → After) Statements (Before → After)
src/cloud-hypervisor/virtiofsd.ts 76.2% → 77.7% (+1.48%) 72.5% → 74.8% (+2.33%)
src/log-directory-setup.ts 96.2% → 100.0% (+3.78%) 96.3% → 100.0% (+3.71%)
✨ New Files (1 files)
  • src/cloud-hypervisor/mount-tree.ts: 98.4% lines

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

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 inert host-side mount-tree enforcement for selective writable virtiofsd exports.

Changes:

  • Stages recursively read-only private mount trees with writable overlays.
  • Adds mount verification, rollback, and cleanup.
  • Documents and extensively tests enforcement behavior.
Show a summary per file
File Description
src/cloud-hypervisor/virtiofsd.ts Integrates optional staged mount plans.
src/cloud-hypervisor/virtiofsd.test.ts Tests manager staging and cleanup.
src/cloud-hypervisor/mount-tree.ts Implements mount-tree enforcement.
src/cloud-hypervisor/mount-tree.test.ts Tests validation and lifecycle behavior.
docs/cloud-hypervisor-foundation.md Documents the enforcement design.

Review details

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

  • Files reviewed: 5/5 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment on lines +422 to +429
function assertPrivatePropagation(entry: MountInfoEntry): void {
const shared = entry.optionalFields.some((field) => field.startsWith('shared:'));
if (shared) {
throw new Error(
`Staged mount tree propagation would leak: ${entry.mountPoint} is a shared mount`,
);
}
}
Comment on lines +350 to +357
private async assertOverlayDestination(overlay: ResolvedOverlay): Promise<void> {
const stats = await this.options.dependencies.statPath(overlay.stagedDestination);
assertStatsMatchKind(
stats,
overlay.kind,
`writable overlay destination ${overlay.stagedDestination}`,
);
}
… tags

Two issues found in review of the host mount-tree enforcement foundation.

`lstat` on an overlay destination only reveals a symlink in the final
component, while the kernel resolves every intermediate component when it
binds. A `tools -> /etc` symlink carried in from the export made destination
`tools/sudoers` lstat as an ordinary file and then bind over the host's
`/etc/sudoers`, escaping the staged root before any post-bind verification ran.
Destinations are now canonicalized and must satisfy realpath equality and
containment under the staged root, mirroring source validation, and the staged
root itself must be canonical for that comparison to mean anything. Verified on
a live kernel: both intermediate and final symlink escapes are rejected before
any bind reaches the kernel, and the host target is untouched.

An enforcement plan naming an export tag that does not exist was silently
discarded, so a renamed or mistyped tag would downgrade that export to
unrestricted read-write. Plans are now reconciled against exports at start and
an unknown tag throws. Exports without a plan still use the existing path, so
partial enforcement stays supported.

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): stage host mount trees for virtiofsd exports feat: stage host mount trees for virtiofsd exports 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

Addressed the propagation review feedback in bad9321: staged mounts now reject shared:, master:, and propagate_from: fields, with regression coverage.

@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

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

📡 OTel tracing validated by Smoke OTel Tracing

@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 failed. Please review the logs for details.

Generated by Contribution Check for #7661

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Smoke Claude passed

Generated by Smoke Claude for #7661

@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

✨ 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

🛡️ 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

🔌 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

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

Build Test Suite completed successfully!

Generated by Build Test Suite for #7661

@lpcox
lpcox deployed to aoai-model August 23, 2026 17:14 — with GitHub Actions Active
@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 Gemini reports failed. Facets need polishing...

💎 Faceted by Smoke Gemini

@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

Copy link
Copy Markdown
Contributor

🚀 Security Guard has started processing this pull request

@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 OTel Tracing completed. All tracing scenarios validated. ✅

📡 OTel tracing validated by Smoke OTel Tracing

@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

Copy link
Copy Markdown
Contributor

Smoke Test: Claude Engine Validation

Check Status
API ✅ PASS
gh CLI ✅ PASS
File access ✅ PASS

Overall result: PASS

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

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test: Copilot Engine — PASS ✅

  • MCP connectivity: ✅ (list_pull_requests OK)
  • github.com connectivity: ✅ (HTTP 200)
  • File write/read: ✅

Recent merged PRs:

Overall: PASS

cc @lpcox

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

@github-actions

Copy link
Copy Markdown
Contributor

✅ Smoke Test: Copilot BYOK (Direct Mode) — PASS

  • ✅ GitHub MCP connectivity
  • ✅ GitHub.com HTTP connectivity (HTTP 200)
  • ✅ File write/read test
  • ✅ BYOK inference via api-proxy → api.githubcopilot.com

Running in direct BYOK mode with COPILOT_PROVIDER_API_KEY forwarded to api-proxy sidecar.

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

@github-actions

Copy link
Copy Markdown
Contributor

EGRESS_RESULT allow=pass deny=pass

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

Overall status: PASS

@lpcox — network isolation egress enforcement verified for this PR.

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: Docker Sbx@lpcox

Overall: PASS

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

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test Results: Services Connectivity

  • ❌ Redis PING: Temporary failure in name resolution
  • ❌ PostgreSQL pg_isready: no response
  • ❌ PostgreSQL SELECT 1: could not translate host name "host.docker.internal"

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

Chroot Version Comparison Results

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

Overall: FAILED — Node.js version differs between host and chroot environment (v24.19.0 vs v22.23.2). The smoke-chroot label was not added since not all tests passed.

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

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test: API Proxy OTel Tracing — Results

Scenario 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.

Scenario 2 — Test Suite: 3 suites / 68 tests passed, 0 failed (otel.test.js, otel-fanout.test.js, otel-workload-identity.test.js).

Scenario 3 — Env Var Forwarding: env-passthrough.ts forwards GITHUB_AW_OTEL_TRACE_ID and GITHUB_AW_OTEL_PARENT_SPAN_ID to the agent; api-proxy-env-config.ts forwards GH_AW_OTLP_ENDPOINTS, OTEL_EXPORTER_OTLP_ENDPOINT, GITHUB_AW_OTEL_TRACE_ID, and GITHUB_AW_OTEL_PARENT_SPAN_ID to api-proxy.

Scenario 4 — Token Tracker Integration: token-tracker-http.js contains the onUsage callback hook (4 references) used as the OTEL integration point.

Scenario 5 — OTEL Diagnostics: Spans were exported — /tmp/gh-aw/otel.jsonl contains 1 valid OTLP/JSON resourceSpans record with gh-aw.agent.setup span, correct trace/span IDs, GenAI/GitHub Actions resource attributes, and status code set. Note: the primary log path checked by the workflow (/tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/otel.jsonl) was not populated; the file landed at /tmp/gh-aw/otel.jsonl instead in this run's environment — token-usage.jsonl tracking is present and correct.

Overall: All 5 scenarios pass. No regressions found.

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

@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 ✅ PASS
.NET json-parse N/A ✅ PASS
Go color pass ✅ PASS
Go env pass ✅ PASS
Go uuid pass ✅ 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 required a workaround — ~/.m2 was pre-existing and root-owned (permission denied for ~/.m2/repository), so settings.xml was configured with <localRepository>/tmp/gh-aw/agent/m2repo</localRepository> in addition to the Squid proxy settings. All other ecosystems ran without issues.

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

@github-actions

Copy link
Copy Markdown
Contributor

@lpcox

  • GitHub MCP Testing (merged PR titles): ✅
  • GitHub.com Connectivity: ✅
  • File Write/Read: ✅
  • 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

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

@github-actions

Copy link
Copy Markdown
Contributor

Recent merged PRs:

  • feat: add Cloud Hypervisor filesystem write planner
  • Strip stale raw-network capabilities from agent images
  • GitHub MCP review: ❌
  • safeinputs-gh PR query: ❌
  • Playwright title check: ✅
  • Temp file write/read: ✅
  • AWF build: ✅
  • Discussion query/comment: ❌
  • 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

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

Copy link
Copy Markdown
Contributor

GitHub MCP Testing: ✅
GitHub.com Connectivity: ✅
File Write/Read Test: ✅
BYOK Inference Test: ✅
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
cc @lpcox @Copilot

🪪 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:31 — with GitHub Actions Active
@lpcox
lpcox enabled auto-merge (squash) August 23, 2026 17:36
@lpcox
lpcox merged commit 62de637 into main Aug 23, 2026
165 of 167 checks passed
@lpcox
lpcox deleted the lpcox-ch-mount-tree-enforcement branch August 23, 2026 17:37
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