From 0cf48fc309197c178900340f44defaa787626279 Mon Sep 17 00:00:00 2001 From: Edvane Cristina Santos Daltro Date: Thu, 29 Oct 2026 15:46:46 -0400 Subject: [PATCH 1/4] test(sandbox): prove Linux protected mount ancestry and file masks --- .github/workflows/plugin-linux-mounts.yml | 52 ++++++ .../linux-mount-protection.md | 55 ++++++ .../complete-plugin-hook-runtime/tasks.md | 2 +- .../plugin-native-entry/linux_mounts.mts | 168 ++++++++++++++++++ 4 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/plugin-linux-mounts.yml create mode 100644 openspec/changes/complete-plugin-hook-runtime/linux-mount-protection.md create mode 100644 scripts/probes/plugin-native-entry/linux_mounts.mts diff --git a/.github/workflows/plugin-linux-mounts.yml b/.github/workflows/plugin-linux-mounts.yml new file mode 100644 index 00000000..0bf198fc --- /dev/null +++ b/.github/workflows/plugin-linux-mounts.yml @@ -0,0 +1,52 @@ +name: Linux plugin mount composition +on: + workflow_dispatch: + pull_request: + paths: + - scripts/probes/plugin-native-entry/linux_mounts.mts + - .github/workflows/plugin-linux-mounts.yml + push: + branches: [main] + paths: + - scripts/probes/plugin-native-entry/linux_mounts.mts + - .github/workflows/plugin-linux-mounts.yml +permissions: + contents: read +concurrency: + group: plugin-linux-mounts-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true +jobs: + mounts: + name: Mount composition (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04, ubuntu-24.04-arm] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Prepare namespace prerequisites + shell: bash + run: | + set -euo pipefail + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.* + sudo apt-get update + sudo apt-get install -y bubblewrap python3 + sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 || true + - name: Verify native controls and ancestor anchors + shell: bash + run: | + set -euo pipefail + node --experimental-strip-types scripts/probes/plugin-native-entry/linux_mounts.mts --report "$RUNNER_TEMP/linux-mounts.json" + - name: Retain native mount receipts + if: always() + uses: actions/upload-artifact@v4 + with: + name: linux-mounts-${{ matrix.os }} + path: ${{ runner.temp }}/linux-mounts.json + if-no-files-found: warn + retention-days: 14 diff --git a/openspec/changes/complete-plugin-hook-runtime/linux-mount-protection.md b/openspec/changes/complete-plugin-hook-runtime/linux-mount-protection.md new file mode 100644 index 00000000..4c2b9486 --- /dev/null +++ b/openspec/changes/complete-plugin-hook-runtime/linux-mount-protection.md @@ -0,0 +1,55 @@ +# Linux protected mount ancestry experiment + +This continues native proof task 2.1 and the policy-composition work in task 2.2. +It does not activate production plugin confinement. The existing Landlock entry +requires a private mount/PID/network namespace and protected subtree masks. +[Landlock's kernel documentation](https://docs.kernel.org/userspace-api/landlock.html#layers-of-file-path-access-rights) +explains that a grant from a path ancestor can allow operations on descendants +within a policy layer; a child grant is not a subtractive denial. + +The new experiment tests whether masking a private child or mounting an immutable +package read-only is sufficient to prevent moving its ancestor. Its explicit +unanchored writable control requires such parent/grandparent renames to succeed. +The candidate adds same-path bind mounts for every protected ancestor strictly +inside Location, in shallow-to-deep order, before mounting immutable objects and +masking private objects. Writable anchors must keep public sibling writes usable; +readonly Location uses readonly anchors. A protected ancestor should itself be a +busy mountpoint rather than relying on the child's read/write mode to protect it. + +A separate character-device mask control tests whether binding /dev/null over a +private file can still report a successful write. The candidate instead uses an +owner-created, empty, mode-000 regular file on a readonly bind mount. Both views +must leave the owner's actual private bytes intact. This file is new owned +metadata; no private regular file is opened in an owner holding SQLite locks. + +The driver runs system Python inside bubblewrap with cleared environment, dropped +capabilities, disabled nested user namespaces and private PID/network/IPC/UTS +namespaces. It exchanges a fixed JSON-RPC-shaped receipt, checks direct private +reads/writes, symlink reads, immutable writes, protected parent/grandparent renames, +ordinary public writes/renames, scratch writes and unchanged owner bytes. Paths +include spaces, quotes and a backslash; arguments never pass through a shell. +Reports bind the driver/workflow sources and checkout, record observed rows and +explicit pass/fail outcome, and are retained even if a later assertion fails. + +Five cases are required: writable unanchored control, writable anchored candidate, +readonly unanchored/anchored cases, and the writable anchored character-mask +counterexample. No failed bootstrap can count as a denial. The actor has a +15-second subprocess timeout and bounded output; each temporary fixture is owned +by the driver and removed after its actor settles. + +## Current verification and scope + +Local macOS checks pass TypeScript syntax, the actor's Python AST syntax, portable +ancestor ordering/sibling-prefix checks, workflow YAML and both Bash blocks, and +diff checks. Linux execution is unavailable locally. The new lightweight workflow +requires native Ubuntu x64 and arm64 receipts before the candidate can be treated +as proved. Expected control/candidate outcomes here remain hypotheses until those +jobs finish; no Linux native acceptance is claimed by these local checks. + +This is a mount primitive experiment, without Landlock or the packaged native +entry. It does not prove fs.read/shell/network/secret intersections, immutable +package/runtime/helper admission, pre-existing alias inventory, owner namespace +provisioning, generation lifetime or actual App/server enforcement. A successful +experiment must be followed by production Linux composition and real-helper +regressions. The approved support/design gates and all parent acceptance tasks +remain open. diff --git a/openspec/changes/complete-plugin-hook-runtime/tasks.md b/openspec/changes/complete-plugin-hook-runtime/tasks.md index 0a070618..273a437d 100644 --- a/openspec/changes/complete-plugin-hook-runtime/tasks.md +++ b/openspec/changes/complete-plugin-hook-runtime/tasks.md @@ -8,7 +8,7 @@ ## 2. Native capability enforcement - [ ] 2.1 Prove a native enforcement mechanism and document runtime bootstrap, read roots, writes without implied broad reads, threads, exec/fork/descendant behavior and OS/runtime support. Present any required design revision before integration. Native CI at `59cae487` passed ten jobs (90 cases) covering Node/Python on macOS/Linux arm64/x86_64 and native Linux executables, including pre-main reads/forks and immutable one-shot exec filtering. Evidence and remaining runtime/platform/integration gates: [native-proof.md](native-proof.md); parent task stays open pending the complete support decisions and acceptance evidence. -- [ ] 2.2 Implement plugin-specific confinement, reviewed runtime resolution and descriptor/environment isolation without weakening ordinary tools or protected roots. The content-bound helper copy foundation is documented in [image-preparation.md](image-preparation.md), with packaged macOS entry progress in [packaged-native-entry.md](packaged-native-entry.md) and Linux entry progress in [packaged-linux-entry.md](packaged-linux-entry.md); [Generation-bound final commit](launch-authority.md) adds synchronous revocation/start ordering, and [host bootstrap environment](bootstrap-environment.md) replaces implicit environment inheritance; [Runtime startup isolation](runtime-startup.md) adds package CWD/config selection and prepared workspace resources; [Private-root inventory](private-root-inventory.md) adds bounded metadata-only alias detection; [Native policy composition](native-policy-composition.md) adds the bounded Rust macOS filesystem/process builder and packaged-helper receipts; complete owner admission and production native launch integration remain open. +- [ ] 2.2 Implement plugin-specific confinement, reviewed runtime resolution and descriptor/environment isolation without weakening ordinary tools or protected roots. The content-bound helper copy foundation is documented in [image-preparation.md](image-preparation.md), with packaged macOS entry progress in [packaged-native-entry.md](packaged-native-entry.md) and Linux entry progress in [packaged-linux-entry.md](packaged-linux-entry.md); [Generation-bound final commit](launch-authority.md) adds synchronous revocation/start ordering, and [host bootstrap environment](bootstrap-environment.md) replaces implicit environment inheritance; [Runtime startup isolation](runtime-startup.md) adds package CWD/config selection and prepared workspace resources; [Private-root inventory](private-root-inventory.md) adds bounded metadata-only alias detection; [Native policy composition](native-policy-composition.md) adds the bounded Rust macOS filesystem/process builder and packaged-helper receipts; [Linux protected mount ancestry](linux-mount-protection.md) tests parent binding and regular-file mask primitives before Linux composition; complete owner admission and production native launch integration remain open. - [ ] 2.3 Enforce `fs.read`, `fs.write`, `shell`, network and secret intersections; refuse unsupported confinement with actionable plugin diagnostics. - [ ] 2.4 Add real-process positive/negative tests for native reads, home/Location/symlink bypass, spawning a shell/interpreter/native child, inherited restrictions, protected state, revocation and unsupported hosts. Obtain native macOS/Linux evidence and explicit Windows results/limits. diff --git a/scripts/probes/plugin-native-entry/linux_mounts.mts b/scripts/probes/plugin-native-entry/linux_mounts.mts new file mode 100644 index 00000000..4f94f629 --- /dev/null +++ b/scripts/probes/plugin-native-entry/linux_mounts.mts @@ -0,0 +1,168 @@ +/** Native Linux mount-composition experiment; not plugin launch admission. */ +import assert from "node:assert/strict"; +import { createHash } from "node:crypto"; +import { mkdtempSync, mkdirSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path"; +import { spawnSync } from "node:child_process"; +import { parseArgs } from "node:util"; + +const actor = String.raw` +import errno, json, os, sys +from pathlib import Path +p = json.loads(sys.argv[1]) +def attempt(action): + try: + action() + return {"ok": True, "errno": None} + except OSError as error: + return {"ok": False, "errno": error.errno} +def rename_back(source, destination): + os.rename(source, destination) + os.rename(destination, source) +def read_kind(path): + try: + value = Path(path).read_bytes() + return "empty" if value == b"" else "data" + except OSError: + return "denied" +renames = [] +siblings = [] +for label, root in p["roots"].items(): + parent = Path(root).parent + for level in range(2): + source = parent if level == 0 else parent.parent + destination = source.with_name(source.name + "-moved") + renames.append({"root": label, "level": level, + "result": attempt(lambda: rename_back(source, destination))}) + siblings.append(attempt(lambda: (source / "public-sibling").write_bytes(b"fixture-write"))) +immutable = [attempt(lambda path=path: Path(path).write_bytes(b"must-not-write")) + for path in p["immutable_data"]] +public = Path(p["project"]) / "public" +result = {"started": True, "renames": renames, "siblings": siblings, + "immutable_writes": immutable, + "private_directory_read": read_kind(p["private_data"]), + "private_file_read": read_kind(p["roots"]["private_file"]), + "private_writes": [attempt(lambda path=path: Path(path).write_bytes(b"must-not-write")) + for path in [p["private_data"], p["roots"]["private_file"]]], + "symlink_private_read": read_kind(p["alias"]), + "workspace_write": attempt(lambda: (Path(p["project"]) / "written").write_bytes(b"fixture-write")), + "public_rename": attempt(lambda: rename_back(public, public.with_name("public-moved"))), + "scratch_write": attempt(lambda: (Path(p["scratch"]) / "written").write_bytes(b"fixture-write"))} +print(json.dumps({"jsonrpc": "2.0", "id": "mount-proof", "result": result})) +`; + +type Layout = { + project: string; scratch: string; empty_mask: string; write: boolean; + roots: Record; immutable_data: string[]; + private_data: string; alias: string; +}; +function beneath(path: string, parent: string): boolean { + const rest = relative(parent, path); + return rest !== "" && rest !== ".." && !rest.startsWith(`..${sep}`) && !isAbsolute(rest); +} +function anchors(layout: Layout): string[] { + const paths = new Set(); + for (const root of Object.values(layout.roots)) { + for (let parent = dirname(root); beneath(parent, layout.project); parent = dirname(parent)) paths.add(parent); + } + return [...paths].sort((a, b) => a.split(sep).length - b.split(sep).length || a.localeCompare(b)); +} +function fixture(root: string, write: boolean): Layout { + const project = join(root, 'project "quote" \\ workspace'); + const scratch = join(root, "scratch"); + const empty_mask = join(root, "owned-empty-mask"); + writeFileSync(empty_mask, "", { mode: 0o000 }); + const roots = { package: join(project, "extensions/level/package"), + runtime_file: join(project, "toolchain/level/runtime-file"), + private_directory: join(project, ".owner/level/state"), + private_file: join(project, ".secrets/level/key") }; + for (const path of [roots.package, dirname(roots.runtime_file), roots.private_directory, + dirname(roots.private_file), scratch, join(project, "public")]) mkdirSync(path, { recursive: true, mode: 0o700 }); + const immutable_data = [join(roots.package, "data"), roots.runtime_file]; + const private_data = join(roots.private_directory, "data"); + for (const path of [...immutable_data, private_data, roots.private_file]) writeFileSync(path, "owned-fixture-only", { mode: 0o600 }); + // The symlink itself is created by the owner before the read-only package mount. + const alias = join(roots.package, "alias"); + return { project, scratch, empty_mask, write, roots, immutable_data, private_data, alias }; +} +function argumentsFor(layout: Layout, anchored: boolean, deviceMask: boolean): string[] { + const args = ["--die-with-parent", "--unshare-user", "--unshare-pid", "--unshare-net", + "--unshare-ipc", "--unshare-uts", "--disable-userns", "--cap-drop", "ALL", "--clearenv", + "--setenv", "PATH", "/usr/bin:/bin", "--ro-bind", "/", "/", "--proc", "/proc", "--dev", "/dev", + layout.write ? "--bind" : "--ro-bind", layout.project, layout.project]; + if (anchored) for (const parent of anchors(layout)) args.push(layout.write ? "--bind" : "--ro-bind", parent, parent); + for (const path of [layout.roots.package, layout.roots.runtime_file]) args.push("--ro-bind", path, path); + args.push("--perms", "0000", "--tmpfs", layout.roots.private_directory, "--remount-ro", layout.roots.private_directory, + "--ro-bind", deviceMask ? "/dev/null" : layout.empty_mask, layout.roots.private_file, + "--bind", layout.scratch, layout.scratch, "--chdir", layout.scratch, + "--", "/usr/bin/python3", "-I", "-S", "-c", actor, JSON.stringify(layout)); + return args; +} +function verify(observed: any, write: boolean, anchored: boolean, deviceMask: boolean): void { + assert.equal(observed.started, true, "a failed bootstrap cannot prove a mount denial"); + assert.equal(observed.renames.length, 8); + for (const entry of observed.renames) { + assert.equal(entry.result.ok, write && !anchored, `ancestor binding: ${JSON.stringify(entry)}`); + if (write && anchored) assert.equal(entry.result.errno, 16, "protected ancestors must be busy mountpoints"); + } + for (const entry of observed.siblings) assert.equal(entry.ok, write, "ancestor anchors must preserve sibling writes"); + for (const entry of observed.immutable_writes) assert.deepEqual(entry, { ok: false, errno: 30 }, "immutable data must be on read-only mounts"); + assert.equal(observed.private_directory_read, "denied"); + assert.equal(observed.private_file_read, deviceMask ? "empty" : "denied"); + assert.equal(observed.private_writes[0].ok, false); + assert.equal(observed.private_writes[1].ok, deviceMask, "a device mask is not a read-only regular file"); + assert.equal(observed.symlink_private_read, "denied"); + assert.equal(observed.workspace_write.ok, write); + assert.equal(observed.public_rename.ok, write, "ordinary public renames must remain usable"); + assert.equal(observed.scratch_write.ok, true); +} +function selfTest(): void { + const layout = { project: "/project", roots: { a: "/project/first/deep/private", b: "/project/second/file" } } as Layout; + assert.deepEqual(anchors(layout), ["/project/first", "/project/second", "/project/first/deep"]); + assert.equal(beneath("/project-other/file", "/project"), false); + const syntax = spawnSync("/usr/bin/python3", ["-I", "-S", "-c", "import ast, sys; ast.parse(sys.stdin.read())"], + { input: actor, encoding: "utf8", timeout: 10_000, env: { PATH: "/usr/bin:/bin" } }); + assert.equal(syntax.status, 0, syntax.stderr); +} + +const { values } = parseArgs({ options: { report: { type: "string" }, "self-test": { type: "boolean" } } }); +selfTest(); +if (!values["self-test"]) { + assert.equal(process.platform, "linux", "native mount receipts require Linux"); + assert(values.report, "--report is required for native receipts"); + const rows = []; + let outcome = "failed"; + try { + for (const [write, anchored, deviceMask] of [[true, false, false], [true, true, false], + [false, false, false], [false, true, false], [true, true, true]]) { + const root = realpathSync(mkdtempSync(join(tmpdir(), "cyber-linux-mounts-"))); + try { + const layout = fixture(root, write); + symlinkSync(layout.private_data, layout.alias); + const result = spawnSync("/usr/bin/bwrap", argumentsFor(layout, anchored, deviceMask), + { encoding: "utf8", timeout: 15_000, maxBuffer: 64 * 1024, env: { PATH: "/usr/bin:/bin" } }); + assert.equal(result.status, 0, `mount bootstrap failed: ${result.error ?? result.stderr}`); + const packet = JSON.parse(result.stdout); + assert.equal(packet.jsonrpc, "2.0"); + assert.equal(packet.id, "mount-proof"); + const ownerDataUnchanged = [...layout.immutable_data, layout.private_data, layout.roots.private_file] + .every(path => readFileSync(path, "utf8") === "owned-fixture-only"); + rows.push({ write, anchored, deviceMask, observations: packet.result, owner_data_unchanged: ownerDataUnchanged }); + verify(packet.result, write, anchored, deviceMask); + assert.equal(ownerDataUnchanged, true, "owner data changed"); + } finally { rmSync(root, { recursive: true, force: true }); } + } + outcome = "passed"; + } finally { + const sources = ["scripts/probes/plugin-native-entry/linux_mounts.mts", ".github/workflows/plugin-linux-mounts.yml"]; + const hash = createHash("sha256"); + for (const path of sources) hash.update(path + "\0").update(readFileSync(path)); + const report = { platform: process.platform, architecture: process.arch, checkout: process.env.GITHUB_SHA ?? null, + run_id: process.env.GITHUB_RUN_ID ?? null, outcome, sources, source_sha256: hash.digest("hex"), rows, + scope: "Mount primitive experiment only. No Landlock, native helper/runtime admission, network capability, Windows or production activation proof." }; + mkdirSync(dirname(resolve(values.report)), { recursive: true }); + writeFileSync(values.report, JSON.stringify(report, null, 2) + "\n", { mode: 0o600 }); + } + console.log(`Verified ${rows.length} mount cases with positive startup, public writes/renames and owner-data checks`); +} From ee7268c669b49171e8d886586e80771d87c1b507 Mon Sep 17 00:00:00 2001 From: Edvane Cristina Santos Daltro Date: Thu, 29 Oct 2026 15:48:12 -0400 Subject: [PATCH 2/4] test(sandbox): record kernel and mount runtime versions --- scripts/probes/plugin-native-entry/linux_mounts.mts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts/probes/plugin-native-entry/linux_mounts.mts b/scripts/probes/plugin-native-entry/linux_mounts.mts index 4f94f629..9bfe89e9 100644 --- a/scripts/probes/plugin-native-entry/linux_mounts.mts +++ b/scripts/probes/plugin-native-entry/linux_mounts.mts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { createHash } from "node:crypto"; import { mkdtempSync, mkdirSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; -import { tmpdir } from "node:os"; +import { release, tmpdir } from "node:os"; import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path"; import { spawnSync } from "node:child_process"; import { parseArgs } from "node:util"; @@ -131,6 +131,12 @@ selfTest(); if (!values["self-test"]) { assert.equal(process.platform, "linux", "native mount receipts require Linux"); assert(values.report, "--report is required for native receipts"); + const bwrap = spawnSync("/usr/bin/bwrap", ["--version"], + { encoding: "utf8", timeout: 10_000, maxBuffer: 4096, env: { PATH: "/usr/bin:/bin" } }); + assert.equal(bwrap.status, 0, "bubblewrap version query failed"); + const python = spawnSync("/usr/bin/python3", ["-I", "-S", "--version"], + { encoding: "utf8", timeout: 10_000, maxBuffer: 4096, env: { PATH: "/usr/bin:/bin" } }); + assert.equal(python.status, 0, "Python version query failed"); const rows = []; let outcome = "failed"; try { @@ -158,7 +164,8 @@ if (!values["self-test"]) { const sources = ["scripts/probes/plugin-native-entry/linux_mounts.mts", ".github/workflows/plugin-linux-mounts.yml"]; const hash = createHash("sha256"); for (const path of sources) hash.update(path + "\0").update(readFileSync(path)); - const report = { platform: process.platform, architecture: process.arch, checkout: process.env.GITHUB_SHA ?? null, + const report = { platform: process.platform, architecture: process.arch, kernel_release: release(), + bubblewrap_version: bwrap.stdout.trim(), python_version: python.stdout.trim(), checkout: process.env.GITHUB_SHA ?? null, run_id: process.env.GITHUB_RUN_ID ?? null, outcome, sources, source_sha256: hash.digest("hex"), rows, scope: "Mount primitive experiment only. No Landlock, native helper/runtime admission, network capability, Windows or production activation proof." }; mkdirSync(dirname(resolve(values.report)), { recursive: true }); From d4fcf3ee8e0dd1838bae1818a150d5ee19d6fb6c Mon Sep 17 00:00:00 2001 From: Edvane Cristina Santos Daltro Date: Thu, 29 Oct 2026 15:51:33 -0400 Subject: [PATCH 3/4] test(sandbox): retain observed Linux device mask denials --- .../linux-mount-protection.md | 26 ++++++++++++++++--- .../plugin-native-entry/linux_mounts.mts | 17 +++++++++--- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/openspec/changes/complete-plugin-hook-runtime/linux-mount-protection.md b/openspec/changes/complete-plugin-hook-runtime/linux-mount-protection.md index 4c2b9486..81a41d8d 100644 --- a/openspec/changes/complete-plugin-hook-runtime/linux-mount-protection.md +++ b/openspec/changes/complete-plugin-hook-runtime/linux-mount-protection.md @@ -16,8 +16,8 @@ masking private objects. Writable anchors must keep public sibling writes usable readonly Location uses readonly anchors. A protected ancestor should itself be a busy mountpoint rather than relying on the child's read/write mode to protect it. -A separate character-device mask control tests whether binding /dev/null over a -private file can still report a successful write. The candidate instead uses an +A separate character-device mask comparison records binding /dev/null over a +private file. The candidate uses an owner-created, empty, mode-000 regular file on a readonly bind mount. Both views must leave the owner's actual private bytes intact. This file is new owned metadata; no private regular file is opened in an owner holding SQLite locks. @@ -33,7 +33,7 @@ explicit pass/fail outcome, and are retained even if a later assertion fails. Five cases are required: writable unanchored control, writable anchored candidate, readonly unanchored/anchored cases, and the writable anchored character-mask -counterexample. No failed bootstrap can count as a denial. The actor has a +comparison. No failed bootstrap can count as a denial. The actor has a 15-second subprocess timeout and bounded output; each temporary fixture is owned by the driver and removed after its actor settles. @@ -53,3 +53,23 @@ provisioning, generation lifetime or actual App/server enforcement. A successful experiment must be followed by production Linux composition and real-helper regressions. The approved support/design gates and all parent acceptance tasks remain open. + + +## First native observations at `ee7268c6` + +Run [40943429793](https://github.com/CyberdyneCorp/cybercode-cli/actions/runs/40943429793) +failed both Linux jobs on the diagnostic's expected empty device read: both +observed a denial instead. The retained x64 report contains all five rows. Its +first four cases satisfy their expected mount assertions: all eight unanchored +writable ancestor renames succeed; all eight anchored writable attempts return +EBUSY; sibling/public writes and public renames remain usable; readonly cases +refuse workspace writes; regular-file masks refuse private reads/writes; all +owner bytes remain unchanged. This partial evidence does not turn a failed job +into acceptance. + +The expected successful device write/empty read was an unproved hypothesis and +is not claimed as an observed bypass. The comparison now requires the observed +read/write denial and records file kind, mode and nodev mount flag to explain the +actual view without assigning a cause merely from EACCES. Candidate regular-file +requirements and the successful unanchored rename counterexample are unchanged. +Fresh complete native runs are required after this diagnostic correction. diff --git a/scripts/probes/plugin-native-entry/linux_mounts.mts b/scripts/probes/plugin-native-entry/linux_mounts.mts index 9bfe89e9..7d1b6493 100644 --- a/scripts/probes/plugin-native-entry/linux_mounts.mts +++ b/scripts/probes/plugin-native-entry/linux_mounts.mts @@ -8,7 +8,7 @@ import { spawnSync } from "node:child_process"; import { parseArgs } from "node:util"; const actor = String.raw` -import errno, json, os, sys +import errno, json, os, stat, sys from pathlib import Path p = json.loads(sys.argv[1]) def attempt(action): @@ -39,7 +39,13 @@ for label, root in p["roots"].items(): immutable = [attempt(lambda path=path: Path(path).write_bytes(b"must-not-write")) for path in p["immutable_data"]] public = Path(p["project"]) / "public" -result = {"started": True, "renames": renames, "siblings": siblings, +mask_metadata = os.stat(p["roots"]["private_file"]) +mask_flags = os.statvfs(p["roots"]["private_file"]).f_flag +result = {"started": True, + "mask_info": {"character": stat.S_ISCHR(mask_metadata.st_mode), + "regular": stat.S_ISREG(mask_metadata.st_mode), + "mode": stat.S_IMODE(mask_metadata.st_mode), + "nodev": bool(mask_flags & os.ST_NODEV)}, "renames": renames, "siblings": siblings, "immutable_writes": immutable, "private_directory_read": read_kind(p["private_data"]), "private_file_read": read_kind(p["roots"]["private_file"]), @@ -109,9 +115,12 @@ function verify(observed: any, write: boolean, anchored: boolean, deviceMask: bo for (const entry of observed.siblings) assert.equal(entry.ok, write, "ancestor anchors must preserve sibling writes"); for (const entry of observed.immutable_writes) assert.deepEqual(entry, { ok: false, errno: 30 }, "immutable data must be on read-only mounts"); assert.equal(observed.private_directory_read, "denied"); - assert.equal(observed.private_file_read, deviceMask ? "empty" : "denied"); + assert.equal(observed.private_file_read, "denied"); + assert.equal(observed.mask_info.character, deviceMask); + assert.equal(observed.mask_info.regular, !deviceMask); assert.equal(observed.private_writes[0].ok, false); - assert.equal(observed.private_writes[1].ok, deviceMask, "a device mask is not a read-only regular file"); + assert.equal(observed.private_writes[1].ok, false); + if (deviceMask) assert.equal(observed.private_writes[1].errno, 13, "retain the observed device-mask denial"); assert.equal(observed.symlink_private_read, "denied"); assert.equal(observed.workspace_write.ok, write); assert.equal(observed.public_rename.ok, write, "ordinary public renames must remain usable"); From 2548fdeaf68fdfe6e13f8ecfa87e77ae41e5bcd7 Mon Sep 17 00:00:00 2001 From: Edvane Cristina Santos Daltro Date: Thu, 29 Oct 2026 15:58:12 -0400 Subject: [PATCH 4/4] docs(sandbox): record Linux protected mount primitive receipts --- .../linux-mount-protection.md | 38 +- .../complete-plugin-hook-runtime/tasks.md | 2 +- .../linux-mounts-d4fcf3ee/linux-arm64.json | 768 ++++++++++++++++++ .../linux-x64-initial-failed.json | 738 +++++++++++++++++ .../linux-mounts-d4fcf3ee/linux-x64.json | 768 ++++++++++++++++++ 5 files changed, 2310 insertions(+), 4 deletions(-) create mode 100644 scripts/probes/plugin-native-entry/evidence/linux-mounts-d4fcf3ee/linux-arm64.json create mode 100644 scripts/probes/plugin-native-entry/evidence/linux-mounts-d4fcf3ee/linux-x64-initial-failed.json create mode 100644 scripts/probes/plugin-native-entry/evidence/linux-mounts-d4fcf3ee/linux-x64.json diff --git a/openspec/changes/complete-plugin-hook-runtime/linux-mount-protection.md b/openspec/changes/complete-plugin-hook-runtime/linux-mount-protection.md index 81a41d8d..67da2b68 100644 --- a/openspec/changes/complete-plugin-hook-runtime/linux-mount-protection.md +++ b/openspec/changes/complete-plugin-hook-runtime/linux-mount-protection.md @@ -42,9 +42,8 @@ by the driver and removed after its actor settles. Local macOS checks pass TypeScript syntax, the actor's Python AST syntax, portable ancestor ordering/sibling-prefix checks, workflow YAML and both Bash blocks, and diff checks. Linux execution is unavailable locally. The new lightweight workflow -requires native Ubuntu x64 and arm64 receipts before the candidate can be treated -as proved. Expected control/candidate outcomes here remain hypotheses until those -jobs finish; no Linux native acceptance is claimed by these local checks. +requires native Ubuntu x64 and arm64 receipts. Those mount-only receipts are now +recorded below; local checks alone did not establish them. This is a mount primitive experiment, without Landlock or the packaged native entry. It does not prove fs.read/shell/network/secret intersections, immutable @@ -73,3 +72,36 @@ read/write denial and records file kind, mode and nodev mount flag to explain th actual view without assigning a cause merely from EACCES. Candidate regular-file requirements and the successful unanchored rename counterexample are unchanged. Fresh complete native runs are required after this diagnostic correction. + + +## Verified mount-only receipts at `d4fcf3ee` + +The corrected [x64 job](https://github.com/CyberdyneCorp/cybercode-cli/actions/runs/40943857344/job/123693935943) +and [arm64 job](https://github.com/CyberdyneCorp/cybercode-cli/actions/runs/40943857344/job/123693935579) +both pass all five cases. Both report Linux `7.0.0-1017-azure`, bubblewrap `0.9.0` +and system Python `3.12.3`. The exact passing reports are preserved at +[the evidence directory](../../../scripts/probes/plugin-native-entry/evidence/linux-mounts-d4fcf3ee/), +with the initial x64 failed report retained separately and explicitly marked failed. + +Both source manifests independently recompute to +`fc98524c490a23bc18e373783cdea32b55ceb400dee2d22a9b78c3d3f32660ed` +from fetched checkout `d6afa580`. Its verified parents are `7261e888` and +`d4fcf3ee`. The retained initial failed report also matches its own exact source +checkout and is not counted as passing acceptance. + +Every passing report retains positive actor startup and unchanged owner bytes. +Unanchored writable cases perform all eight protected ancestor moves. Anchored +writable cases refuse all eight with EBUSY while permitting all sibling writes, +public writes/renames and scratch writes. Readonly cases reject workspace writes +but keep scratch usable. Immutable writes refuse with EROFS; private reads and +writes are refused. The /dev/null comparison records a mode-0666 character device, +nodev mount flag and EACCES write result, rather than the originally hypothesized +successful device write. The regular-file candidate remains independent of that +device-specific outcome. + +The scoped review covered driver/actor separation, direct argument passing, +cleared child environment, namespace/capability flags, shallow-first anchor order, +positive controls, exact errors and unchanged owner data, bounded receipts and +source-bound failure reporting. No blocking issue was found within this mount +experiment. These receipts prove the primitive only: production Linux composition, +Landlock/helper integration and complete owner admission remain unimplemented. diff --git a/openspec/changes/complete-plugin-hook-runtime/tasks.md b/openspec/changes/complete-plugin-hook-runtime/tasks.md index 273a437d..58cc8213 100644 --- a/openspec/changes/complete-plugin-hook-runtime/tasks.md +++ b/openspec/changes/complete-plugin-hook-runtime/tasks.md @@ -8,7 +8,7 @@ ## 2. Native capability enforcement - [ ] 2.1 Prove a native enforcement mechanism and document runtime bootstrap, read roots, writes without implied broad reads, threads, exec/fork/descendant behavior and OS/runtime support. Present any required design revision before integration. Native CI at `59cae487` passed ten jobs (90 cases) covering Node/Python on macOS/Linux arm64/x86_64 and native Linux executables, including pre-main reads/forks and immutable one-shot exec filtering. Evidence and remaining runtime/platform/integration gates: [native-proof.md](native-proof.md); parent task stays open pending the complete support decisions and acceptance evidence. -- [ ] 2.2 Implement plugin-specific confinement, reviewed runtime resolution and descriptor/environment isolation without weakening ordinary tools or protected roots. The content-bound helper copy foundation is documented in [image-preparation.md](image-preparation.md), with packaged macOS entry progress in [packaged-native-entry.md](packaged-native-entry.md) and Linux entry progress in [packaged-linux-entry.md](packaged-linux-entry.md); [Generation-bound final commit](launch-authority.md) adds synchronous revocation/start ordering, and [host bootstrap environment](bootstrap-environment.md) replaces implicit environment inheritance; [Runtime startup isolation](runtime-startup.md) adds package CWD/config selection and prepared workspace resources; [Private-root inventory](private-root-inventory.md) adds bounded metadata-only alias detection; [Native policy composition](native-policy-composition.md) adds the bounded Rust macOS filesystem/process builder and packaged-helper receipts; [Linux protected mount ancestry](linux-mount-protection.md) tests parent binding and regular-file mask primitives before Linux composition; complete owner admission and production native launch integration remain open. +- [ ] 2.2 Implement plugin-specific confinement, reviewed runtime resolution and descriptor/environment isolation without weakening ordinary tools or protected roots. The content-bound helper copy foundation is documented in [image-preparation.md](image-preparation.md), with packaged macOS entry progress in [packaged-native-entry.md](packaged-native-entry.md) and Linux entry progress in [packaged-linux-entry.md](packaged-linux-entry.md); [Generation-bound final commit](launch-authority.md) adds synchronous revocation/start ordering, and [host bootstrap environment](bootstrap-environment.md) replaces implicit environment inheritance; [Runtime startup isolation](runtime-startup.md) adds package CWD/config selection and prepared workspace resources; [Private-root inventory](private-root-inventory.md) adds bounded metadata-only alias detection; [Native policy composition](native-policy-composition.md) adds the bounded Rust macOS filesystem/process builder and packaged-helper receipts; [Linux protected mount ancestry](linux-mount-protection.md) records five passing primitive cases per Linux architecture, including actual ancestor-move counterexamples and regular-file masks, before production Linux composition; complete owner admission and production native launch integration remain open. - [ ] 2.3 Enforce `fs.read`, `fs.write`, `shell`, network and secret intersections; refuse unsupported confinement with actionable plugin diagnostics. - [ ] 2.4 Add real-process positive/negative tests for native reads, home/Location/symlink bypass, spawning a shell/interpreter/native child, inherited restrictions, protected state, revocation and unsupported hosts. Obtain native macOS/Linux evidence and explicit Windows results/limits. diff --git a/scripts/probes/plugin-native-entry/evidence/linux-mounts-d4fcf3ee/linux-arm64.json b/scripts/probes/plugin-native-entry/evidence/linux-mounts-d4fcf3ee/linux-arm64.json new file mode 100644 index 00000000..64a47516 --- /dev/null +++ b/scripts/probes/plugin-native-entry/evidence/linux-mounts-d4fcf3ee/linux-arm64.json @@ -0,0 +1,768 @@ +{ + "platform": "linux", + "architecture": "arm64", + "kernel_release": "7.0.0-1017-azure", + "bubblewrap_version": "bubblewrap 0.9.0", + "python_version": "Python 3.12.3", + "checkout": "d6afa580f51a2a6ec1ad0fec823a8fcc8200e75b", + "run_id": "40943857344", + "outcome": "passed", + "sources": [ + "scripts/probes/plugin-native-entry/linux_mounts.mts", + ".github/workflows/plugin-linux-mounts.yml" + ], + "source_sha256": "fc98524c490a23bc18e373783cdea32b55ceb400dee2d22a9b78c3d3f32660ed", + "rows": [ + { + "write": true, + "anchored": false, + "deviceMask": false, + "observations": { + "started": true, + "mask_info": { + "character": false, + "regular": true, + "mode": 0, + "nodev": true + }, + "renames": [ + { + "root": "package", + "level": 0, + "result": { + "ok": true, + "errno": null + } + }, + { + "root": "package", + "level": 1, + "result": { + "ok": true, + "errno": null + } + }, + { + "root": "runtime_file", + "level": 0, + "result": { + "ok": true, + "errno": null + } + }, + { + "root": "runtime_file", + "level": 1, + "result": { + "ok": true, + "errno": null + } + }, + { + "root": "private_directory", + "level": 0, + "result": { + "ok": true, + "errno": null + } + }, + { + "root": "private_directory", + "level": 1, + "result": { + "ok": true, + "errno": null + } + }, + { + "root": "private_file", + "level": 0, + "result": { + "ok": true, + "errno": null + } + }, + { + "root": "private_file", + "level": 1, + "result": { + "ok": true, + "errno": null + } + } + ], + "siblings": [ + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + } + ], + "immutable_writes": [ + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + } + ], + "private_directory_read": "denied", + "private_file_read": "denied", + "private_writes": [ + { + "ok": false, + "errno": 13 + }, + { + "ok": false, + "errno": 30 + } + ], + "symlink_private_read": "denied", + "workspace_write": { + "ok": true, + "errno": null + }, + "public_rename": { + "ok": true, + "errno": null + }, + "scratch_write": { + "ok": true, + "errno": null + } + }, + "owner_data_unchanged": true + }, + { + "write": true, + "anchored": true, + "deviceMask": false, + "observations": { + "started": true, + "mask_info": { + "character": false, + "regular": true, + "mode": 0, + "nodev": true + }, + "renames": [ + { + "root": "package", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "package", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "runtime_file", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "runtime_file", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_directory", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_directory", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_file", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_file", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + } + ], + "siblings": [ + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + } + ], + "immutable_writes": [ + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + } + ], + "private_directory_read": "denied", + "private_file_read": "denied", + "private_writes": [ + { + "ok": false, + "errno": 13 + }, + { + "ok": false, + "errno": 30 + } + ], + "symlink_private_read": "denied", + "workspace_write": { + "ok": true, + "errno": null + }, + "public_rename": { + "ok": true, + "errno": null + }, + "scratch_write": { + "ok": true, + "errno": null + } + }, + "owner_data_unchanged": true + }, + { + "write": false, + "anchored": false, + "deviceMask": false, + "observations": { + "started": true, + "mask_info": { + "character": false, + "regular": true, + "mode": 0, + "nodev": true + }, + "renames": [ + { + "root": "package", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "package", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "runtime_file", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "runtime_file", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_directory", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_directory", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_file", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_file", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + } + ], + "siblings": [ + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + } + ], + "immutable_writes": [ + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + } + ], + "private_directory_read": "denied", + "private_file_read": "denied", + "private_writes": [ + { + "ok": false, + "errno": 13 + }, + { + "ok": false, + "errno": 30 + } + ], + "symlink_private_read": "denied", + "workspace_write": { + "ok": false, + "errno": 30 + }, + "public_rename": { + "ok": false, + "errno": 30 + }, + "scratch_write": { + "ok": true, + "errno": null + } + }, + "owner_data_unchanged": true + }, + { + "write": false, + "anchored": true, + "deviceMask": false, + "observations": { + "started": true, + "mask_info": { + "character": false, + "regular": true, + "mode": 0, + "nodev": true + }, + "renames": [ + { + "root": "package", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "package", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "runtime_file", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "runtime_file", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_directory", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_directory", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_file", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_file", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + } + ], + "siblings": [ + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + } + ], + "immutable_writes": [ + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + } + ], + "private_directory_read": "denied", + "private_file_read": "denied", + "private_writes": [ + { + "ok": false, + "errno": 13 + }, + { + "ok": false, + "errno": 30 + } + ], + "symlink_private_read": "denied", + "workspace_write": { + "ok": false, + "errno": 30 + }, + "public_rename": { + "ok": false, + "errno": 30 + }, + "scratch_write": { + "ok": true, + "errno": null + } + }, + "owner_data_unchanged": true + }, + { + "write": true, + "anchored": true, + "deviceMask": true, + "observations": { + "started": true, + "mask_info": { + "character": true, + "regular": false, + "mode": 438, + "nodev": true + }, + "renames": [ + { + "root": "package", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "package", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "runtime_file", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "runtime_file", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_directory", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_directory", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_file", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_file", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + } + ], + "siblings": [ + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + } + ], + "immutable_writes": [ + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + } + ], + "private_directory_read": "denied", + "private_file_read": "denied", + "private_writes": [ + { + "ok": false, + "errno": 13 + }, + { + "ok": false, + "errno": 13 + } + ], + "symlink_private_read": "denied", + "workspace_write": { + "ok": true, + "errno": null + }, + "public_rename": { + "ok": true, + "errno": null + }, + "scratch_write": { + "ok": true, + "errno": null + } + }, + "owner_data_unchanged": true + } + ], + "scope": "Mount primitive experiment only. No Landlock, native helper/runtime admission, network capability, Windows or production activation proof." +} diff --git a/scripts/probes/plugin-native-entry/evidence/linux-mounts-d4fcf3ee/linux-x64-initial-failed.json b/scripts/probes/plugin-native-entry/evidence/linux-mounts-d4fcf3ee/linux-x64-initial-failed.json new file mode 100644 index 00000000..21341bb4 --- /dev/null +++ b/scripts/probes/plugin-native-entry/evidence/linux-mounts-d4fcf3ee/linux-x64-initial-failed.json @@ -0,0 +1,738 @@ +{ + "platform": "linux", + "architecture": "x64", + "kernel_release": "7.0.0-1017-azure", + "bubblewrap_version": "bubblewrap 0.9.0", + "python_version": "Python 3.12.3", + "checkout": "a2dfc61265c3b8c19a28d928e1724b9f47c5ef72", + "run_id": "40943429793", + "outcome": "failed", + "sources": [ + "scripts/probes/plugin-native-entry/linux_mounts.mts", + ".github/workflows/plugin-linux-mounts.yml" + ], + "source_sha256": "586261b69ef979a45fde591e17716ee312778fcced8705a5243506a922dec9c6", + "rows": [ + { + "write": true, + "anchored": false, + "deviceMask": false, + "observations": { + "started": true, + "renames": [ + { + "root": "package", + "level": 0, + "result": { + "ok": true, + "errno": null + } + }, + { + "root": "package", + "level": 1, + "result": { + "ok": true, + "errno": null + } + }, + { + "root": "runtime_file", + "level": 0, + "result": { + "ok": true, + "errno": null + } + }, + { + "root": "runtime_file", + "level": 1, + "result": { + "ok": true, + "errno": null + } + }, + { + "root": "private_directory", + "level": 0, + "result": { + "ok": true, + "errno": null + } + }, + { + "root": "private_directory", + "level": 1, + "result": { + "ok": true, + "errno": null + } + }, + { + "root": "private_file", + "level": 0, + "result": { + "ok": true, + "errno": null + } + }, + { + "root": "private_file", + "level": 1, + "result": { + "ok": true, + "errno": null + } + } + ], + "siblings": [ + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + } + ], + "immutable_writes": [ + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + } + ], + "private_directory_read": "denied", + "private_file_read": "denied", + "private_writes": [ + { + "ok": false, + "errno": 13 + }, + { + "ok": false, + "errno": 30 + } + ], + "symlink_private_read": "denied", + "workspace_write": { + "ok": true, + "errno": null + }, + "public_rename": { + "ok": true, + "errno": null + }, + "scratch_write": { + "ok": true, + "errno": null + } + }, + "owner_data_unchanged": true + }, + { + "write": true, + "anchored": true, + "deviceMask": false, + "observations": { + "started": true, + "renames": [ + { + "root": "package", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "package", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "runtime_file", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "runtime_file", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_directory", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_directory", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_file", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_file", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + } + ], + "siblings": [ + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + } + ], + "immutable_writes": [ + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + } + ], + "private_directory_read": "denied", + "private_file_read": "denied", + "private_writes": [ + { + "ok": false, + "errno": 13 + }, + { + "ok": false, + "errno": 30 + } + ], + "symlink_private_read": "denied", + "workspace_write": { + "ok": true, + "errno": null + }, + "public_rename": { + "ok": true, + "errno": null + }, + "scratch_write": { + "ok": true, + "errno": null + } + }, + "owner_data_unchanged": true + }, + { + "write": false, + "anchored": false, + "deviceMask": false, + "observations": { + "started": true, + "renames": [ + { + "root": "package", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "package", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "runtime_file", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "runtime_file", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_directory", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_directory", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_file", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_file", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + } + ], + "siblings": [ + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + } + ], + "immutable_writes": [ + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + } + ], + "private_directory_read": "denied", + "private_file_read": "denied", + "private_writes": [ + { + "ok": false, + "errno": 13 + }, + { + "ok": false, + "errno": 30 + } + ], + "symlink_private_read": "denied", + "workspace_write": { + "ok": false, + "errno": 30 + }, + "public_rename": { + "ok": false, + "errno": 30 + }, + "scratch_write": { + "ok": true, + "errno": null + } + }, + "owner_data_unchanged": true + }, + { + "write": false, + "anchored": true, + "deviceMask": false, + "observations": { + "started": true, + "renames": [ + { + "root": "package", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "package", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "runtime_file", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "runtime_file", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_directory", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_directory", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_file", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_file", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + } + ], + "siblings": [ + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + } + ], + "immutable_writes": [ + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + } + ], + "private_directory_read": "denied", + "private_file_read": "denied", + "private_writes": [ + { + "ok": false, + "errno": 13 + }, + { + "ok": false, + "errno": 30 + } + ], + "symlink_private_read": "denied", + "workspace_write": { + "ok": false, + "errno": 30 + }, + "public_rename": { + "ok": false, + "errno": 30 + }, + "scratch_write": { + "ok": true, + "errno": null + } + }, + "owner_data_unchanged": true + }, + { + "write": true, + "anchored": true, + "deviceMask": true, + "observations": { + "started": true, + "renames": [ + { + "root": "package", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "package", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "runtime_file", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "runtime_file", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_directory", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_directory", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_file", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_file", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + } + ], + "siblings": [ + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + } + ], + "immutable_writes": [ + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + } + ], + "private_directory_read": "denied", + "private_file_read": "denied", + "private_writes": [ + { + "ok": false, + "errno": 13 + }, + { + "ok": false, + "errno": 13 + } + ], + "symlink_private_read": "denied", + "workspace_write": { + "ok": true, + "errno": null + }, + "public_rename": { + "ok": true, + "errno": null + }, + "scratch_write": { + "ok": true, + "errno": null + } + }, + "owner_data_unchanged": true + } + ], + "scope": "Mount primitive experiment only. No Landlock, native helper/runtime admission, network capability, Windows or production activation proof." +} diff --git a/scripts/probes/plugin-native-entry/evidence/linux-mounts-d4fcf3ee/linux-x64.json b/scripts/probes/plugin-native-entry/evidence/linux-mounts-d4fcf3ee/linux-x64.json new file mode 100644 index 00000000..526f045c --- /dev/null +++ b/scripts/probes/plugin-native-entry/evidence/linux-mounts-d4fcf3ee/linux-x64.json @@ -0,0 +1,768 @@ +{ + "platform": "linux", + "architecture": "x64", + "kernel_release": "7.0.0-1017-azure", + "bubblewrap_version": "bubblewrap 0.9.0", + "python_version": "Python 3.12.3", + "checkout": "d6afa580f51a2a6ec1ad0fec823a8fcc8200e75b", + "run_id": "40943857344", + "outcome": "passed", + "sources": [ + "scripts/probes/plugin-native-entry/linux_mounts.mts", + ".github/workflows/plugin-linux-mounts.yml" + ], + "source_sha256": "fc98524c490a23bc18e373783cdea32b55ceb400dee2d22a9b78c3d3f32660ed", + "rows": [ + { + "write": true, + "anchored": false, + "deviceMask": false, + "observations": { + "started": true, + "mask_info": { + "character": false, + "regular": true, + "mode": 0, + "nodev": true + }, + "renames": [ + { + "root": "package", + "level": 0, + "result": { + "ok": true, + "errno": null + } + }, + { + "root": "package", + "level": 1, + "result": { + "ok": true, + "errno": null + } + }, + { + "root": "runtime_file", + "level": 0, + "result": { + "ok": true, + "errno": null + } + }, + { + "root": "runtime_file", + "level": 1, + "result": { + "ok": true, + "errno": null + } + }, + { + "root": "private_directory", + "level": 0, + "result": { + "ok": true, + "errno": null + } + }, + { + "root": "private_directory", + "level": 1, + "result": { + "ok": true, + "errno": null + } + }, + { + "root": "private_file", + "level": 0, + "result": { + "ok": true, + "errno": null + } + }, + { + "root": "private_file", + "level": 1, + "result": { + "ok": true, + "errno": null + } + } + ], + "siblings": [ + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + } + ], + "immutable_writes": [ + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + } + ], + "private_directory_read": "denied", + "private_file_read": "denied", + "private_writes": [ + { + "ok": false, + "errno": 13 + }, + { + "ok": false, + "errno": 30 + } + ], + "symlink_private_read": "denied", + "workspace_write": { + "ok": true, + "errno": null + }, + "public_rename": { + "ok": true, + "errno": null + }, + "scratch_write": { + "ok": true, + "errno": null + } + }, + "owner_data_unchanged": true + }, + { + "write": true, + "anchored": true, + "deviceMask": false, + "observations": { + "started": true, + "mask_info": { + "character": false, + "regular": true, + "mode": 0, + "nodev": true + }, + "renames": [ + { + "root": "package", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "package", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "runtime_file", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "runtime_file", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_directory", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_directory", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_file", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_file", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + } + ], + "siblings": [ + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + } + ], + "immutable_writes": [ + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + } + ], + "private_directory_read": "denied", + "private_file_read": "denied", + "private_writes": [ + { + "ok": false, + "errno": 13 + }, + { + "ok": false, + "errno": 30 + } + ], + "symlink_private_read": "denied", + "workspace_write": { + "ok": true, + "errno": null + }, + "public_rename": { + "ok": true, + "errno": null + }, + "scratch_write": { + "ok": true, + "errno": null + } + }, + "owner_data_unchanged": true + }, + { + "write": false, + "anchored": false, + "deviceMask": false, + "observations": { + "started": true, + "mask_info": { + "character": false, + "regular": true, + "mode": 0, + "nodev": true + }, + "renames": [ + { + "root": "package", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "package", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "runtime_file", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "runtime_file", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_directory", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_directory", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_file", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_file", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + } + ], + "siblings": [ + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + } + ], + "immutable_writes": [ + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + } + ], + "private_directory_read": "denied", + "private_file_read": "denied", + "private_writes": [ + { + "ok": false, + "errno": 13 + }, + { + "ok": false, + "errno": 30 + } + ], + "symlink_private_read": "denied", + "workspace_write": { + "ok": false, + "errno": 30 + }, + "public_rename": { + "ok": false, + "errno": 30 + }, + "scratch_write": { + "ok": true, + "errno": null + } + }, + "owner_data_unchanged": true + }, + { + "write": false, + "anchored": true, + "deviceMask": false, + "observations": { + "started": true, + "mask_info": { + "character": false, + "regular": true, + "mode": 0, + "nodev": true + }, + "renames": [ + { + "root": "package", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "package", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "runtime_file", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "runtime_file", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_directory", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_directory", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_file", + "level": 0, + "result": { + "ok": false, + "errno": 30 + } + }, + { + "root": "private_file", + "level": 1, + "result": { + "ok": false, + "errno": 30 + } + } + ], + "siblings": [ + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + } + ], + "immutable_writes": [ + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + } + ], + "private_directory_read": "denied", + "private_file_read": "denied", + "private_writes": [ + { + "ok": false, + "errno": 13 + }, + { + "ok": false, + "errno": 30 + } + ], + "symlink_private_read": "denied", + "workspace_write": { + "ok": false, + "errno": 30 + }, + "public_rename": { + "ok": false, + "errno": 30 + }, + "scratch_write": { + "ok": true, + "errno": null + } + }, + "owner_data_unchanged": true + }, + { + "write": true, + "anchored": true, + "deviceMask": true, + "observations": { + "started": true, + "mask_info": { + "character": true, + "regular": false, + "mode": 438, + "nodev": true + }, + "renames": [ + { + "root": "package", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "package", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "runtime_file", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "runtime_file", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_directory", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_directory", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_file", + "level": 0, + "result": { + "ok": false, + "errno": 16 + } + }, + { + "root": "private_file", + "level": 1, + "result": { + "ok": false, + "errno": 16 + } + } + ], + "siblings": [ + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + }, + { + "ok": true, + "errno": null + } + ], + "immutable_writes": [ + { + "ok": false, + "errno": 30 + }, + { + "ok": false, + "errno": 30 + } + ], + "private_directory_read": "denied", + "private_file_read": "denied", + "private_writes": [ + { + "ok": false, + "errno": 13 + }, + { + "ok": false, + "errno": 13 + } + ], + "symlink_private_read": "denied", + "workspace_write": { + "ok": true, + "errno": null + }, + "public_rename": { + "ok": true, + "errno": null + }, + "scratch_write": { + "ok": true, + "errno": null + } + }, + "owner_data_unchanged": true + } + ], + "scope": "Mount primitive experiment only. No Landlock, native helper/runtime admission, network capability, Windows or production activation proof." +}