owlrunkit 0.25.1 → 0.25.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/LINUX-HELPERS.sha256 +2 -2
- package/README.md +14 -28
- package/SOURCE.md +10 -10
- package/integrations/codex/skills/owlcoda-runkit/SKILL.md +9 -9
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
- package/package.json +1 -1
- package/packages/attest/distribution-manifest-v1.json +10 -0
- package/packages/attest/src/quick.mjs +10 -0
- package/packages/runkit/build-linux-helper.sh +17 -0
- package/registry-release-policy-v2.json +5 -5
- package/sbom.spdx.json +4 -4
- package/scripts/runkit-contract/core-contract.mjs +2 -1
- package/scripts/runkit-contract/peer-linux-arm64 +0 -0
- package/scripts/runkit-contract/peer-linux-bwrap.mjs +204 -0
- package/scripts/runkit-contract/peer-linux-x64 +0 -0
- package/scripts/runkit-contract/peer-linux.c +45 -12
- package/scripts/runkit-contract/peer-recovery.mjs +46 -13
- package/scripts/runkit-contract/project-cli-resolver.mjs +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.25.3
|
|
4
|
+
|
|
5
|
+
- Accepts Bubblewrap on a verified read-only Linux system mount when the
|
|
6
|
+
platform remaps root and the system file owners appear as overflow UIDs.
|
|
7
|
+
RunKit runs the checked executable by descriptor and rechecks its identity
|
|
8
|
+
before starting each worker.
|
|
9
|
+
- Ships the Linux helper build script and checksums with the npm package and
|
|
10
|
+
links to the exact corresponding public source tag from `SOURCE.md`.
|
|
11
|
+
|
|
12
|
+
## 0.25.2
|
|
13
|
+
|
|
14
|
+
- Supports managed Linux worker recovery with system Bubblewrap when Landlock
|
|
15
|
+
is unavailable. RunKit verifies the writable attempt, read-only sandbox
|
|
16
|
+
root, hidden host files, and seccomp process and network restrictions before
|
|
17
|
+
selecting the backend.
|
|
18
|
+
- Keeps Landlock as the first Linux backend. If neither backend passes its
|
|
19
|
+
runtime probe, managed peer commands refuse to start.
|
|
20
|
+
|
|
3
21
|
## 0.25.1
|
|
4
22
|
|
|
5
23
|
- Adds enforced Linux peer isolation for x86-64 and ARM64 with Landlock and
|
package/LINUX-HELPERS.sha256
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
39e177cebac50c87514e19df991266970a5814eb468ec716250d6e731a73a40d peer-linux-x64
|
|
2
|
+
ad5ecf24f6c13233edb43c8ce0b8aa91b15134e710252ae6e82de39630d67e0e peer-linux-arm64
|
package/README.md
CHANGED
|
@@ -134,29 +134,9 @@ automatically promote an authoritative writer. Core successor, release
|
|
|
134
134
|
compatibility, publication, Project Successor, and project adoption remain
|
|
135
135
|
separate authority boundaries.
|
|
136
136
|
|
|
137
|
-
## Current release
|
|
138
|
-
|
|
139
|
-
`owlrunkit@0.25.1` is the version documented here. Its public surfaces
|
|
140
|
-
include:
|
|
141
|
-
|
|
142
|
-
- Project Driver and typed delivery lifecycle;
|
|
143
|
-
- Quick and Formal verification with source-bound receipts;
|
|
144
|
-
- controller/target worktree separation and foreign-candidate verification;
|
|
145
|
-
- Team Delivery read-only recommendations and portable transfer packets;
|
|
146
|
-
- bounded compact-session Hook Recovery;
|
|
147
|
-
- optional native Codex Stop continuation on a bound RunKit file event;
|
|
148
|
-
- managed local peer recovery for isolated macOS and Linux workers;
|
|
149
|
-
- completed-project drift detection and read-only successor scaffolding;
|
|
150
|
-
- fail-closed bootstrap, profile, registry, Core, and shared-Skill diagnostics.
|
|
151
|
-
|
|
152
|
-
This repository is the public corresponding-source, documentation, issue,
|
|
153
|
-
release, and trust surface for OwlRunKit. Day-to-day development remains in a
|
|
154
|
-
private source repository; every GPL-covered npm release must map to an exact
|
|
155
|
-
public source tag.
|
|
156
|
-
|
|
157
137
|
## Install
|
|
158
138
|
|
|
159
|
-
This package is the lifecycle-stable, registry-gated `owlrunkit@0.25.
|
|
139
|
+
This package is the lifecycle-stable, registry-gated `owlrunkit@0.25.3`
|
|
160
140
|
artifact. Registry adoption remains fail-closed until its exact official npm
|
|
161
141
|
version, shasum, integrity, and tarball URL are independently verified. Local
|
|
162
142
|
tarballs remain candidate-test inputs and are never adoption provenance. Until
|
|
@@ -164,7 +144,7 @@ that registry evidence exists, use the package only for isolated candidate
|
|
|
164
144
|
verification; do not treat the command below as available release truth.
|
|
165
145
|
|
|
166
146
|
```bash
|
|
167
|
-
npm install --save-exact owlrunkit@0.25.
|
|
147
|
+
npm install --save-exact owlrunkit@0.25.3
|
|
168
148
|
```
|
|
169
149
|
|
|
170
150
|
Use `npx --no-install owlrunkit` for project commands. The package bootstrap
|
|
@@ -180,9 +160,9 @@ the project Config must independently bind the same Core.
|
|
|
180
160
|
```bash
|
|
181
161
|
npx --no-install owlrunkit --version
|
|
182
162
|
npx --no-install owlrunkit bootstrap --workspace /absolute/path/to/project \
|
|
183
|
-
--exact owlrunkit@0.25.
|
|
163
|
+
--exact owlrunkit@0.25.3 --dry-run
|
|
184
164
|
npx --no-install owlrunkit bootstrap --workspace /absolute/path/to/project \
|
|
185
|
-
--exact owlrunkit@0.25.
|
|
165
|
+
--exact owlrunkit@0.25.3 --apply
|
|
186
166
|
npx --no-install owlrunkit init --workspace /absolute/path/to/project
|
|
187
167
|
npx --no-install owlrunkit doctor --workspace /absolute/path/to/project
|
|
188
168
|
npx --no-install owlrunkit inspect --workspace /absolute/path/to/project --json --compact
|
|
@@ -1060,10 +1040,16 @@ scratch directory is reported for inspection while it remains on disk.
|
|
|
1060
1040
|
|
|
1061
1041
|
The macOS and Linux backends confine writes to the attempt and deny network
|
|
1062
1042
|
connections and new worker processes. Linux supports x86-64 and ARM64 with
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1043
|
+
seccomp filtering and `close_range`. RunKit tries Landlock ABI 3 or later
|
|
1044
|
+
first. If Landlock is unavailable, it can use a trusted system Bubblewrap
|
|
1045
|
+
executable with user and mount namespaces. The Bubblewrap probe verifies
|
|
1046
|
+
that the attempt is writable, host files are hidden, and new processes and
|
|
1047
|
+
network sockets are denied. If the probe fails, the task stays blocked.
|
|
1048
|
+
On Linux environments that remap root, RunKit can trust `/usr/bin/bwrap` on
|
|
1049
|
+
a dedicated read-only `/usr` system mount after verifying its mount identity
|
|
1050
|
+
and checking for writable aliases. It launches the verified file descriptor.
|
|
1051
|
+
On Linux, the worker is terminated if its supervisor is killed. The host
|
|
1052
|
+
caller supplies the worker command and peer identity. Existing Desktop sessions and writers using
|
|
1067
1053
|
legacy shared-directory leases follow their own recovery procedure. An
|
|
1068
1054
|
existing managed task remains pinned to the core that created it.
|
|
1069
1055
|
|
package/SOURCE.md
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
# Corresponding source
|
|
2
2
|
|
|
3
3
|
OwlRunKit is distributed under GPL-3.0-or-later. The corresponding public
|
|
4
|
-
source
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
source is [OwlRunKit v0.25.3](https://github.com/yeemio/owlrunkit/tree/v0.25.3).
|
|
5
|
+
The npm package includes the JavaScript runtime, read-only verifier, C source,
|
|
6
|
+
and [Linux helper build script](packages/runkit/build-linux-helper.sh).
|
|
7
|
+
The distributed helpers statically link musl and include its copyright notice.
|
|
7
8
|
|
|
8
9
|
## Rebuild the Linux helpers
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
package repository. Build on each architecture or use a Docker environment
|
|
13
|
-
that supports both build platforms.
|
|
11
|
+
From the public tag or unpacked npm package, run the build on Linux x86-64
|
|
12
|
+
and ARM64 using Alpine 3.22 with `build-base` and `linux-headers`.
|
|
14
13
|
|
|
15
14
|
```sh
|
|
16
15
|
mkdir -p .build
|
|
@@ -23,6 +22,7 @@ done
|
|
|
23
22
|
(cd .build && sha256sum -c ../LINUX-HELPERS.sha256)
|
|
24
23
|
```
|
|
25
24
|
|
|
26
|
-
The
|
|
27
|
-
|
|
28
|
-
and
|
|
25
|
+
The authoritative release is identified by the npm registry version, shasum,
|
|
26
|
+
integrity, and tarball URL. Local directories, `file:` dependencies, local
|
|
27
|
+
tarballs, Git URLs, workspace links, and mutable dist-tags are not accepted as
|
|
28
|
+
formal cross-project adoption evidence.
|
|
@@ -7,14 +7,14 @@ description: Run receipt-backed, project-owned work with OwlCoda RunKit. Use whe
|
|
|
7
7
|
|
|
8
8
|
Use the bundled deterministic Core to coordinate work through project artifacts rather than chat memory. Require Node.js 20 or later.
|
|
9
9
|
|
|
10
|
-
Bundled registry-gated artifact: standalone `owlrunkit@0.25.
|
|
11
|
-
`0.2` and Core `0.25.
|
|
12
|
-
`sha256:
|
|
10
|
+
Bundled registry-gated artifact: standalone `owlrunkit@0.25.3`, bundling Contract
|
|
11
|
+
`0.2` and Core `0.25.3` with manifest
|
|
12
|
+
`sha256:901f3b5da227555a1a6b65f9deb4efa71f8f3bf9c3f9a34b1a30034da8779d66`.
|
|
13
13
|
Registry adoption remains fail-closed until exact official npm registry
|
|
14
|
-
provenance for `owlrunkit@0.25.
|
|
15
|
-
published rollback baseline for this artifact is `owlrunkit@0.25.
|
|
16
|
-
`
|
|
17
|
-
`https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.25.
|
|
14
|
+
provenance for `owlrunkit@0.25.3` is independently verified. The
|
|
15
|
+
published rollback baseline for this artifact is `owlrunkit@0.25.2`, with SHA-1
|
|
16
|
+
`0b4bc23778549cdd3537f2e78d55302b34237ded` and canonical npm tarball URL
|
|
17
|
+
`https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.25.2.tgz`.
|
|
18
18
|
The root `owlcoda` package has a separate version lifecycle.
|
|
19
19
|
|
|
20
20
|
The session is an execution interface. Project artifacts are the system of record.
|
|
@@ -370,9 +370,9 @@ below has no CLI entry.
|
|
|
370
370
|
|
|
371
371
|
```bash
|
|
372
372
|
npx --no-install owlrunkit bootstrap --workspace "$PROJECT_ROOT" \
|
|
373
|
-
--exact owlrunkit@0.25.
|
|
373
|
+
--exact owlrunkit@0.25.3 --dry-run
|
|
374
374
|
npx --no-install owlrunkit bootstrap --workspace "$PROJECT_ROOT" \
|
|
375
|
-
--exact owlrunkit@0.25.
|
|
375
|
+
--exact owlrunkit@0.25.3 --apply
|
|
376
376
|
npx --no-install owlrunkit init --workspace "$PROJECT_ROOT"
|
|
377
377
|
npx --no-install owlrunkit inspect --json --workspace "$PROJECT_ROOT"
|
|
378
378
|
npx --no-install owlrunkit inspect --json --compact --workspace "$PROJECT_ROOT"
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"schemaVersion": "OwlCodaRunKitConfigV2",
|
|
3
3
|
"core": {
|
|
4
4
|
"contractVersion": "0.2",
|
|
5
|
-
"coreVersion": "0.25.
|
|
6
|
-
"coreManifestSha256": "sha256:
|
|
7
|
-
"coreSourceRef": "artifact:sha256:
|
|
5
|
+
"coreVersion": "0.25.3",
|
|
6
|
+
"coreManifestSha256": "sha256:901f3b5da227555a1a6b65f9deb4efa71f8f3bf9c3f9a34b1a30034da8779d66",
|
|
7
|
+
"coreSourceRef": "artifact:sha256:901f3b5da227555a1a6b65f9deb4efa71f8f3bf9c3f9a34b1a30034da8779d66"
|
|
8
8
|
},
|
|
9
9
|
"authorizationPolicy": "external_explicit_authority_required"
|
|
10
10
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "owlrunkit",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.3",
|
|
4
4
|
"description": "Project Driver for cross-Agent project continuity and progression; execution, evidence, verification, and recovery are the underlying trust substrate.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"author": "yeemio <owlclaw@163.com>",
|
|
@@ -42,6 +42,16 @@
|
|
|
42
42
|
"sha256": "sha256:3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986"
|
|
43
43
|
},
|
|
44
44
|
"supportedProducerCoreIdentities": [
|
|
45
|
+
{
|
|
46
|
+
"contractVersion": "0.2",
|
|
47
|
+
"coreVersion": "0.25.3",
|
|
48
|
+
"coreManifestSha256": "sha256:901f3b5da227555a1a6b65f9deb4efa71f8f3bf9c3f9a34b1a30034da8779d66"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"contractVersion": "0.2",
|
|
52
|
+
"coreVersion": "0.25.2",
|
|
53
|
+
"coreManifestSha256": "sha256:da816d343597b91c30e4f96c0a3d765bf5c00d6013d6bee430f58bc701399acc"
|
|
54
|
+
},
|
|
45
55
|
{
|
|
46
56
|
"contractVersion": "0.2",
|
|
47
57
|
"coreVersion": "0.25.1",
|
|
@@ -23,6 +23,16 @@ import {
|
|
|
23
23
|
export { validQuickReceiptShape } from "./quick-receipt-contract.mjs";
|
|
24
24
|
|
|
25
25
|
export const SUPPORTED_QUICK_CORE_IDENTITIES = Object.freeze([
|
|
26
|
+
{
|
|
27
|
+
contractVersion: "0.2",
|
|
28
|
+
coreVersion: "0.25.3",
|
|
29
|
+
coreManifestSha256: "sha256:901f3b5da227555a1a6b65f9deb4efa71f8f3bf9c3f9a34b1a30034da8779d66",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
contractVersion: "0.2",
|
|
33
|
+
coreVersion: "0.25.2",
|
|
34
|
+
coreManifestSha256: "sha256:da816d343597b91c30e4f96c0a3d765bf5c00d6013d6bee430f58bc701399acc",
|
|
35
|
+
},
|
|
26
36
|
{
|
|
27
37
|
contractVersion: "0.2",
|
|
28
38
|
coreVersion: "0.25.1",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
case "$(uname -s)/$(uname -m)" in
|
|
5
|
+
Linux/x86_64) arch=x64 ;;
|
|
6
|
+
Linux/aarch64) arch=arm64 ;;
|
|
7
|
+
*) echo "Build the peer helper on Linux x86_64 or aarch64." >&2; exit 1 ;;
|
|
8
|
+
esac
|
|
9
|
+
|
|
10
|
+
if [ "$#" -ne 1 ] || [ ! -d "$1" ]; then
|
|
11
|
+
echo "Usage: sh build-linux-helper.sh <existing output directory>" >&2
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
source_dir=$(CDPATH= cd -- "$(dirname -- "$0")/../../scripts/runkit-contract" && pwd)
|
|
16
|
+
"${CC:-cc}" -std=c11 -O2 -Wall -Wextra -Werror -static -s \
|
|
17
|
+
"$source_dir/peer-linux.c" -o "$1/peer-linux-$arch"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": "OwlCodaRunKitRegistryReleasePolicyV2",
|
|
3
3
|
"status": "release_artifact_registry_gated",
|
|
4
4
|
"packageName": "owlrunkit",
|
|
5
|
-
"artifactVersion": "0.25.
|
|
5
|
+
"artifactVersion": "0.25.3",
|
|
6
6
|
"registry": "https://registry.npmjs.org",
|
|
7
7
|
"registryRequiredBeforeAdoption": true,
|
|
8
8
|
"exactVersionRequired": true,
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
],
|
|
24
24
|
"rollbackRelease": {
|
|
25
25
|
"packageName": "owlrunkit",
|
|
26
|
-
"version": "0.25.
|
|
27
|
-
"shasum": "
|
|
28
|
-
"integrity": "sha512-
|
|
29
|
-
"tarballUrl": "https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.25.
|
|
26
|
+
"version": "0.25.2",
|
|
27
|
+
"shasum": "0b4bc23778549cdd3537f2e78d55302b34237ded",
|
|
28
|
+
"integrity": "sha512-a7EJwkR/3o0Pjuz2dxEKkbB6n7WowLloKtYuuyjfXGfZM3D6/IVphdRLBv7oh/lJPk511e/i0XGUfFxt+OhXzA==",
|
|
29
|
+
"tarballUrl": "https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.25.2.tgz"
|
|
30
30
|
},
|
|
31
31
|
"registryEvidenceMustBeExternal": true,
|
|
32
32
|
"registryEvidenceSchemaVersion": "OwlCodaRunKitRegistryReleaseEvidenceV1",
|
package/sbom.spdx.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"spdxVersion": "SPDX-2.3",
|
|
3
3
|
"dataLicense": "CC0-1.0",
|
|
4
4
|
"SPDXID": "SPDXRef-DOCUMENT",
|
|
5
|
-
"name": "owlrunkit-0.25.
|
|
6
|
-
"documentNamespace": "https://owlcoda.com/spdx/owlrunkit/0.25.
|
|
5
|
+
"name": "owlrunkit-0.25.3",
|
|
6
|
+
"documentNamespace": "https://owlcoda.com/spdx/owlrunkit/0.25.3",
|
|
7
7
|
"creationInfo": {
|
|
8
8
|
"created": "2026-11-11T00:00:00Z",
|
|
9
9
|
"creators": [
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
{
|
|
15
15
|
"name": "owlrunkit",
|
|
16
16
|
"SPDXID": "SPDXRef-Package-OwlRunKit",
|
|
17
|
-
"versionInfo": "0.25.
|
|
17
|
+
"versionInfo": "0.25.3",
|
|
18
18
|
"downloadLocation": "NOASSERTION",
|
|
19
19
|
"filesAnalyzed": false,
|
|
20
20
|
"licenseConcluded": "GPL-3.0-or-later",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
{
|
|
25
25
|
"referenceCategory": "PACKAGE-MANAGER",
|
|
26
26
|
"referenceType": "purl",
|
|
27
|
-
"referenceLocator": "pkg:npm/owlrunkit@0.25.
|
|
27
|
+
"referenceLocator": "pkg:npm/owlrunkit@0.25.3"
|
|
28
28
|
}
|
|
29
29
|
]
|
|
30
30
|
}
|
|
@@ -14,7 +14,7 @@ import { fileURLToPath } from "node:url";
|
|
|
14
14
|
import { parseProjectControlState } from "./project-control-parser.mjs";
|
|
15
15
|
|
|
16
16
|
export const CONTRACT_VERSION = "0.2";
|
|
17
|
-
export const CORE_VERSION = "0.25.
|
|
17
|
+
export const CORE_VERSION = "0.25.3";
|
|
18
18
|
export const RUNTIME_ROOT = ".owlcoda/runkit";
|
|
19
19
|
|
|
20
20
|
const CORE_FILES = [
|
|
@@ -44,6 +44,7 @@ const CORE_FILES = [
|
|
|
44
44
|
"linux-runtime.mjs",
|
|
45
45
|
"offline-store.mjs",
|
|
46
46
|
"outcome-presentation.mjs",
|
|
47
|
+
"peer-linux-bwrap.mjs",
|
|
47
48
|
"peer-linux.c",
|
|
48
49
|
"peer-recovery.mjs",
|
|
49
50
|
"onboarding-doctor.mjs",
|
|
Binary file
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import {
|
|
4
|
+
accessSync, closeSync, constants, fstatSync, lstatSync, mkdtempSync, openSync,
|
|
5
|
+
readFileSync, realpathSync, rmSync, statfsSync, writeFileSync,
|
|
6
|
+
} from "node:fs";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
|
|
10
|
+
export const BUBBLEWRAP_PEER_BACKEND = "linux_bubblewrap_seccomp_v1";
|
|
11
|
+
|
|
12
|
+
const SANDBOX_HELPER = "/owlrunkit-helper";
|
|
13
|
+
const SANDBOX_WORKER = "/owlrunkit-worker";
|
|
14
|
+
const WORK = "/work";
|
|
15
|
+
const SYSTEM_BWRAP = "/usr/bin/bwrap";
|
|
16
|
+
const FD_EXECUTABLE = "/proc/self/fd/3";
|
|
17
|
+
const OVERFLOW_UID = 65534;
|
|
18
|
+
const PROC_SUPER_MAGIC = 0x9fa0;
|
|
19
|
+
const CAP_SYS_ADMIN = 1n << 21n;
|
|
20
|
+
const MAX_EXECUTABLE_BYTES = 1024 * 1024;
|
|
21
|
+
|
|
22
|
+
function rootOwnedPath(file, stat) {
|
|
23
|
+
if (stat.uid !== 0) return false;
|
|
24
|
+
let parent = path.dirname(file);
|
|
25
|
+
for (;;) {
|
|
26
|
+
const entry = lstatSync(parent);
|
|
27
|
+
if (!entry.isDirectory() || entry.uid !== 0 || (entry.mode & 0o022)) return false;
|
|
28
|
+
const next = path.dirname(parent);
|
|
29
|
+
if (parent === next) return true;
|
|
30
|
+
parent = next;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function readOnlySystemBubblewrapIssue({ uid, uidMap, root, systemDirectories, mountId, mountInfo }) {
|
|
35
|
+
if (uid === 0 || uid === OVERFLOW_UID || uidMap.trim().split(/\s+/u).join(" ") !== `${uid} 0 1`) {
|
|
36
|
+
return "the caller is not the single mapped parent root UID";
|
|
37
|
+
}
|
|
38
|
+
if (!root.isDirectory() || root.uid !== uid || (root.mode & 0o022)
|
|
39
|
+
|| !systemDirectories.every(entry =>
|
|
40
|
+
entry.isDirectory() && entry.uid === OVERFLOW_UID && !(entry.mode & 0o022))) {
|
|
41
|
+
return "the system directories do not match the remapped ownership boundary";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const mounts = mountInfo.trim().split("\n").map(line => {
|
|
45
|
+
const [description, filesystem] = line.split(" - ");
|
|
46
|
+
const [id, , device, , target, flags] = description.split(" ");
|
|
47
|
+
return { id, device, target, flags: flags?.split(",") ?? [], type: filesystem?.split(" ")[0] };
|
|
48
|
+
});
|
|
49
|
+
const system = mounts.find(mount => mount.id === mountId);
|
|
50
|
+
if (system?.target !== "/usr" || system.type !== "overlay"
|
|
51
|
+
|| !["ro", "nosuid", "nodev"].every(flag => system.flags.includes(flag))) {
|
|
52
|
+
return "the opened executable is not on the protected read-only /usr mount";
|
|
53
|
+
}
|
|
54
|
+
if (mounts.some(mount => mount.device === system.device && mount.flags.includes("rw"))) {
|
|
55
|
+
return "the /usr mount has a writable alias";
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function readOnlySystemPathIssue(file, stat, fd) {
|
|
61
|
+
if (file !== SYSTEM_BWRAP || stat.uid !== OVERFLOW_UID || process.getuid() !== process.geteuid()) {
|
|
62
|
+
return "the executable is outside the remapped system path";
|
|
63
|
+
}
|
|
64
|
+
if (statfsSync("/proc/self").type !== PROC_SUPER_MAGIC) return "the kernel mount information is unavailable";
|
|
65
|
+
const capabilities = /^CapPrm:\s*([0-9a-f]+)$/imu.exec(readFileSync("/proc/self/status", "utf8"))?.[1];
|
|
66
|
+
if (!capabilities || (BigInt(`0x${capabilities}`) & CAP_SYS_ADMIN)) {
|
|
67
|
+
return "the caller can reconfigure its system mounts or that capability could not be checked";
|
|
68
|
+
}
|
|
69
|
+
const mountId = /^mnt_id:\s*(\d+)$/mu.exec(readFileSync(`/proc/self/fdinfo/${fd}`, "utf8"))?.[1];
|
|
70
|
+
if (!mountId) return "the kernel did not identify the executable mount";
|
|
71
|
+
return readOnlySystemBubblewrapIssue({
|
|
72
|
+
uid: process.geteuid(),
|
|
73
|
+
uidMap: readFileSync("/proc/self/uid_map", "utf8"),
|
|
74
|
+
root: lstatSync("/"),
|
|
75
|
+
systemDirectories: [lstatSync("/usr"), lstatSync("/usr/bin")],
|
|
76
|
+
mountId,
|
|
77
|
+
mountInfo: readFileSync("/proc/self/mountinfo", "utf8"),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function openTrustedBubblewrap(expected) {
|
|
82
|
+
const directories = ["/usr/bin", "/bin", "/usr/local/bin", ...(process.env.PATH ?? "").split(":")];
|
|
83
|
+
const candidates = expected ? [expected.executable]
|
|
84
|
+
: [...new Set(directories)].filter(path.isAbsolute).map(dir => path.join(dir, "bwrap"));
|
|
85
|
+
let systemRejection;
|
|
86
|
+
for (const candidate of candidates) {
|
|
87
|
+
let fd;
|
|
88
|
+
let trusted = false;
|
|
89
|
+
try {
|
|
90
|
+
const executable = realpathSync(candidate);
|
|
91
|
+
if (expected && executable !== expected.executable) continue;
|
|
92
|
+
accessSync(executable, constants.X_OK);
|
|
93
|
+
fd = openSync(executable, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
94
|
+
const stat = fstatSync(fd);
|
|
95
|
+
if (!stat.isFile() || (stat.mode & 0o6022) || stat.size > MAX_EXECUTABLE_BYTES) continue;
|
|
96
|
+
let trust = "root_owned";
|
|
97
|
+
if (!rootOwnedPath(executable, stat)) {
|
|
98
|
+
const issue = readOnlySystemPathIssue(executable, stat, fd);
|
|
99
|
+
if (issue) {
|
|
100
|
+
if (executable === SYSTEM_BWRAP) systemRejection = issue;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
trust = "read_only_system_mount";
|
|
104
|
+
}
|
|
105
|
+
const sandboxDigest = `sha256:${createHash("sha256").update(readFileSync(fd)).digest("hex")}`;
|
|
106
|
+
if (expected && (expected.sandboxDigest !== sandboxDigest || expected.trust !== trust)) continue;
|
|
107
|
+
trusted = true;
|
|
108
|
+
return { executable, fd, sandboxDigest, trust };
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (!["ENOENT", "EACCES", "ENOTDIR", "ELOOP"].includes(error.code)) throw error;
|
|
111
|
+
} finally {
|
|
112
|
+
if (fd !== undefined && !trusted) closeSync(fd);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return { reason: ["trusted system Bubblewrap not found", systemRejection].filter(Boolean).join(": ") };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function spawnBubblewrapPeer(backend, args, options) {
|
|
119
|
+
const launcher = openTrustedBubblewrap(backend);
|
|
120
|
+
if (launcher.fd === undefined) {
|
|
121
|
+
throw new Error(`Bubblewrap executable or trust changed after its self-test: ${launcher.reason}`);
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
return spawn(FD_EXECUTABLE, args, { ...options, stdio: [...options.stdio, launcher.fd] });
|
|
125
|
+
} finally {
|
|
126
|
+
closeSync(launcher.fd);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function bubblewrapPeerArgs({ helper, directory, worker, args, environment }) {
|
|
131
|
+
return [
|
|
132
|
+
"--unshare-user", "--new-session", "--die-with-parent", "--clearenv", "--cap-drop", "ALL",
|
|
133
|
+
"--ro-bind-try", "/lib", "/lib",
|
|
134
|
+
"--ro-bind-try", "/lib64", "/lib64",
|
|
135
|
+
"--ro-bind-try", "/usr/lib", "/usr/lib",
|
|
136
|
+
"--ro-bind-try", "/usr/lib64", "/usr/lib64",
|
|
137
|
+
"--ro-bind-try", "/etc/ld.so.cache", "/etc/ld.so.cache",
|
|
138
|
+
"--dev-bind", "/dev/null", "/dev/null",
|
|
139
|
+
"--ro-bind", helper, SANDBOX_HELPER,
|
|
140
|
+
"--ro-bind", worker, SANDBOX_WORKER,
|
|
141
|
+
"--bind", directory, WORK, "--remount-ro", "/", "--chdir", WORK,
|
|
142
|
+
...Object.entries({ ...environment, TMPDIR: WORK, OPENSSL_CONF: "/dev/null" })
|
|
143
|
+
.flatMap(([key, value]) => ["--setenv", key, value]),
|
|
144
|
+
"--", SANDBOX_HELPER, "run-bwrap", SANDBOX_WORKER, ...args,
|
|
145
|
+
];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const probeWorker = `
|
|
149
|
+
const assert = require('node:assert/strict');
|
|
150
|
+
const fs = require('node:fs');
|
|
151
|
+
const net = require('node:net');
|
|
152
|
+
const { spawnSync } = require('node:child_process');
|
|
153
|
+
assert.equal(process.cwd(), '/work');
|
|
154
|
+
assert.equal(fs.readFileSync('probe-input', 'utf8'), 'from host');
|
|
155
|
+
assert.throws(() => fs.readFileSync(process.argv[1]), { code: 'ENOENT' });
|
|
156
|
+
assert.throws(() => fs.writeFileSync('/probe-outside-work', 'no'), { code: 'EROFS' });
|
|
157
|
+
assert.equal(spawnSync(process.execPath, ['--version']).error?.code, 'EPERM');
|
|
158
|
+
const server = net.createServer();
|
|
159
|
+
server.once('error', error => {
|
|
160
|
+
assert.equal(error.code, 'EPERM');
|
|
161
|
+
fs.writeFileSync('probe-result', 'confined');
|
|
162
|
+
});
|
|
163
|
+
server.listen(0, '127.0.0.1', () => {
|
|
164
|
+
server.close();
|
|
165
|
+
throw new Error('socket unexpectedly succeeded');
|
|
166
|
+
});
|
|
167
|
+
`;
|
|
168
|
+
|
|
169
|
+
export function probeBubblewrapPeer(helper) {
|
|
170
|
+
const directory = mkdtempSync(path.join(realpathSync(tmpdir()), "owlrunkit-bwrap-probe-"));
|
|
171
|
+
let launcher;
|
|
172
|
+
try {
|
|
173
|
+
launcher = openTrustedBubblewrap();
|
|
174
|
+
if (launcher.fd === undefined) return { available: false, reason: launcher.reason };
|
|
175
|
+
const outside = path.join(directory, "outside");
|
|
176
|
+
const work = path.join(directory, "work");
|
|
177
|
+
writeFileSync(outside, "outside", { flag: "wx", mode: 0o600 });
|
|
178
|
+
// Keep the sentinel outside the only writable bind.
|
|
179
|
+
const scratch = mkdtempSync(work);
|
|
180
|
+
writeFileSync(path.join(scratch, "probe-input"), "from host", { flag: "wx", mode: 0o600 });
|
|
181
|
+
const result = spawnSync(FD_EXECUTABLE, bubblewrapPeerArgs({
|
|
182
|
+
helper, directory: scratch, worker: realpathSync(process.execPath),
|
|
183
|
+
args: ["-e", probeWorker, outside], environment: {},
|
|
184
|
+
}), {
|
|
185
|
+
cwd: scratch, env: {}, stdio: ["ignore", "ignore", "pipe", launcher.fd],
|
|
186
|
+
encoding: "utf8", timeout: 5000, maxBuffer: 65536,
|
|
187
|
+
});
|
|
188
|
+
if (result.status !== 0) {
|
|
189
|
+
return { available: false, reason: result.stderr?.trim() || result.error?.code
|
|
190
|
+
|| `Bubblewrap self-test exited ${result.status}` };
|
|
191
|
+
}
|
|
192
|
+
const confirmation = path.join(scratch, "probe-result");
|
|
193
|
+
if (readFileSync(confirmation, "utf8") !== "confined") {
|
|
194
|
+
return { available: false, reason: "Bubblewrap self-test did not confirm enforcement" };
|
|
195
|
+
}
|
|
196
|
+
const { executable, sandboxDigest, trust } = launcher;
|
|
197
|
+
return { available: true, id: BUBBLEWRAP_PEER_BACKEND, executable, sandboxDigest, trust, helper };
|
|
198
|
+
} catch (error) {
|
|
199
|
+
return { available: false, reason: `Bubblewrap self-test failed: ${error.code ?? error.message}` };
|
|
200
|
+
} finally {
|
|
201
|
+
if (launcher?.fd !== undefined) closeSync(launcher.fd);
|
|
202
|
+
rmSync(directory, { recursive: true, force: true });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
Binary file
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
#include <sys/prctl.h>
|
|
19
19
|
#include <sys/socket.h>
|
|
20
20
|
#include <sys/stat.h>
|
|
21
|
+
#include <sys/statvfs.h>
|
|
21
22
|
#include <sys/syscall.h>
|
|
22
23
|
#include <sys/xattr.h>
|
|
23
24
|
#include <unistd.h>
|
|
@@ -61,7 +62,7 @@ static void allow_path(int ruleset, const char *path, uint64_t rights, int optio
|
|
|
61
62
|
close(fd);
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
static void restrict_files(const char *directory, const char *worker)
|
|
65
|
+
static void restrict_files(const char *directory, const char *worker, int probe)
|
|
65
66
|
{
|
|
66
67
|
const uint64_t read_tree = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR;
|
|
67
68
|
const uint64_t work = read_tree | LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_TRUNCATE |
|
|
@@ -71,9 +72,18 @@ static void restrict_files(const char *directory, const char *worker)
|
|
|
71
72
|
LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_MAKE_CHAR | LANDLOCK_ACCESS_FS_MAKE_BLOCK |
|
|
72
73
|
LANDLOCK_ACCESS_FS_MAKE_SOCK | LANDLOCK_ACCESS_FS_MAKE_FIFO | LANDLOCK_ACCESS_FS_MAKE_SYM };
|
|
73
74
|
long abi = syscall(SYS_landlock_create_ruleset, NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
|
|
74
|
-
if (abi < 0)
|
|
75
|
+
if (abi < 0) {
|
|
76
|
+
if (probe && (errno == ENOSYS || errno == EOPNOTSUPP)) {
|
|
77
|
+
perror("landlock_create_ruleset version");
|
|
78
|
+
_exit(77);
|
|
79
|
+
}
|
|
75
80
|
fail("landlock_create_ruleset version");
|
|
81
|
+
}
|
|
76
82
|
if (abi < 3) {
|
|
83
|
+
if (probe) {
|
|
84
|
+
fputs("Landlock ABI 3 is required\n", stderr);
|
|
85
|
+
_exit(77);
|
|
86
|
+
}
|
|
77
87
|
errno = EOPNOTSUPP;
|
|
78
88
|
fail("Landlock ABI 3 is required");
|
|
79
89
|
}
|
|
@@ -160,7 +170,7 @@ static void restrict_syscalls(void)
|
|
|
160
170
|
ALLOW_SYSCALL(faccessat), ALLOW_SYSCALL(faccessat2), ALLOW_SYSCALL(getdents64),
|
|
161
171
|
ALLOW_SYSCALL(getcwd), ALLOW_SYSCALL(chdir), ALLOW_SYSCALL(fchdir), ALLOW_SYSCALL(readlinkat),
|
|
162
172
|
ALLOW_SYSCALL(mkdirat), ALLOW_SYSCALL(unlinkat), ALLOW_SYSCALL(renameat),
|
|
163
|
-
ALLOW_SYSCALL(renameat2), ALLOW_SYSCALL(linkat),
|
|
173
|
+
ALLOW_SYSCALL(renameat2), ALLOW_SYSCALL(linkat),
|
|
164
174
|
ALLOW_SYSCALL(truncate), ALLOW_SYSCALL(ftruncate), ALLOW_SYSCALL(fsync), ALLOW_SYSCALL(fdatasync),
|
|
165
175
|
ALLOW_SYSCALL(dup), ALLOW_SYSCALL(dup3), ALLOW_SYSCALL(pipe2),
|
|
166
176
|
ALLOW_SYSCALL(mmap), ALLOW_SYSCALL(munmap), ALLOW_SYSCALL(mprotect), ALLOW_SYSCALL(mremap),
|
|
@@ -181,7 +191,7 @@ static void restrict_syscalls(void)
|
|
|
181
191
|
ALLOW_SYSCALL(arch_prctl), ALLOW_SYSCALL(access), ALLOW_SYSCALL(open),
|
|
182
192
|
ALLOW_SYSCALL(stat), ALLOW_SYSCALL(lstat), ALLOW_SYSCALL(readlink),
|
|
183
193
|
ALLOW_SYSCALL(mkdir), ALLOW_SYSCALL(rmdir), ALLOW_SYSCALL(unlink),
|
|
184
|
-
ALLOW_SYSCALL(rename), ALLOW_SYSCALL(link),
|
|
194
|
+
ALLOW_SYSCALL(rename), ALLOW_SYSCALL(link),
|
|
185
195
|
ALLOW_SYSCALL(dup2), ALLOW_SYSCALL(pipe), ALLOW_SYSCALL(epoll_wait), ALLOW_SYSCALL(poll),
|
|
186
196
|
#endif
|
|
187
197
|
BPF_STMT(BPF_RET | BPF_K, DENIED),
|
|
@@ -193,6 +203,19 @@ static void restrict_syscalls(void)
|
|
|
193
203
|
fail("seccomp filter");
|
|
194
204
|
}
|
|
195
205
|
|
|
206
|
+
static void verify_bubblewrap_root(void)
|
|
207
|
+
{
|
|
208
|
+
char directory[sizeof "/work"];
|
|
209
|
+
struct statvfs root, work;
|
|
210
|
+
if (!getcwd(directory, sizeof directory) || strcmp(directory, "/work") != 0 ||
|
|
211
|
+
statvfs("/", &root) < 0 || statvfs("/work", &work) < 0 ||
|
|
212
|
+
!(root.f_flag & ST_RDONLY) || (work.f_flag & ST_RDONLY) ||
|
|
213
|
+
access("/proc/self", F_OK) == 0 || errno != ENOENT) {
|
|
214
|
+
fputs("Unexpected Bubblewrap filesystem boundary\n", stderr);
|
|
215
|
+
_exit(125);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
196
219
|
int main(int argc, char **argv)
|
|
197
220
|
{
|
|
198
221
|
if (argc == 2 && strcmp(argv[1], "lock") == 0) {
|
|
@@ -203,21 +226,30 @@ int main(int argc, char **argv)
|
|
|
203
226
|
fail("control flock");
|
|
204
227
|
}
|
|
205
228
|
int probe = argc == 2 && strcmp(argv[1], "probe") == 0;
|
|
206
|
-
|
|
229
|
+
int bubblewrap = argc >= 3 && strcmp(argv[1], "run-bwrap") == 0;
|
|
230
|
+
if (!probe && !bubblewrap && (argc < 5 || strcmp(argv[1], "run") != 0))
|
|
207
231
|
return 125;
|
|
232
|
+
char **worker_argv = probe ? NULL : &argv[bubblewrap ? 2 : 4];
|
|
208
233
|
if (!probe) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
234
|
+
long parent = getppid();
|
|
235
|
+
if (!bubblewrap) {
|
|
236
|
+
char *end;
|
|
237
|
+
parent = strtol(argv[2], &end, 10);
|
|
238
|
+
if (*end)
|
|
239
|
+
return 125;
|
|
240
|
+
}
|
|
241
|
+
if (parent <= 0 || prctl(PR_SET_PDEATHSIG, SIGKILL) < 0 || getppid() != parent)
|
|
212
242
|
return 125;
|
|
243
|
+
if (bubblewrap)
|
|
244
|
+
verify_bubblewrap_root();
|
|
213
245
|
struct stat worker;
|
|
214
|
-
if (stat(
|
|
246
|
+
if (stat(worker_argv[0], &worker) < 0)
|
|
215
247
|
fail("stat worker");
|
|
216
248
|
if (worker.st_mode & (S_ISUID | S_ISGID)) {
|
|
217
249
|
errno = EPERM;
|
|
218
250
|
fail("set-id workers are unsupported");
|
|
219
251
|
}
|
|
220
|
-
if (getxattr(
|
|
252
|
+
if (getxattr(worker_argv[0], "security.capability", NULL, 0) >= 0) {
|
|
221
253
|
errno = EPERM;
|
|
222
254
|
fail("file capabilities on workers are unsupported");
|
|
223
255
|
}
|
|
@@ -228,7 +260,8 @@ int main(int argc, char **argv)
|
|
|
228
260
|
fail("close inherited descriptors");
|
|
229
261
|
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0)
|
|
230
262
|
fail("no_new_privs");
|
|
231
|
-
|
|
263
|
+
if (!bubblewrap)
|
|
264
|
+
restrict_files(probe ? NULL : argv[3], probe ? NULL : worker_argv[0], probe);
|
|
232
265
|
restrict_syscalls();
|
|
233
266
|
if (probe) {
|
|
234
267
|
if (open("/etc/passwd", O_RDONLY) >= 0 || errno != EACCES ||
|
|
@@ -238,6 +271,6 @@ int main(int argc, char **argv)
|
|
|
238
271
|
}
|
|
239
272
|
return 0;
|
|
240
273
|
}
|
|
241
|
-
execve(
|
|
274
|
+
execve(worker_argv[0], worker_argv, environ);
|
|
242
275
|
fail("execve worker");
|
|
243
276
|
}
|
|
@@ -10,6 +10,9 @@ import path from "node:path";
|
|
|
10
10
|
|
|
11
11
|
import { RUNTIME_ROOT, currentCoreIdentity } from "./core-contract.mjs";
|
|
12
12
|
import { linuxRuntimeHelper } from "./linux-runtime.mjs";
|
|
13
|
+
import {
|
|
14
|
+
BUBBLEWRAP_PEER_BACKEND, bubblewrapPeerArgs, probeBubblewrapPeer, spawnBubblewrapPeer,
|
|
15
|
+
} from "./peer-linux-bwrap.mjs";
|
|
13
16
|
import { safeIdentifier } from "./provenance-common.mjs";
|
|
14
17
|
import { resolveVerificationEnvelopeBackendV1 } from "./verification-envelope.mjs";
|
|
15
18
|
|
|
@@ -208,7 +211,14 @@ export function resolvePeerRecoveryBackend() {
|
|
|
208
211
|
return { available: true, id: "linux_landlock_seccomp_v1", executable,
|
|
209
212
|
digest: digest(regularBytes(executable, CHECKPOINT_LIMIT)) };
|
|
210
213
|
}
|
|
211
|
-
|
|
214
|
+
const reason = result.stderr?.trim() || result.error?.code;
|
|
215
|
+
if (result.status !== 77) return { available: false, reason };
|
|
216
|
+
const fallback = probeBubblewrapPeer(executable);
|
|
217
|
+
if (!fallback.available) {
|
|
218
|
+
return { available: false, reason: [reason, fallback.reason].filter(Boolean).join("; ") };
|
|
219
|
+
}
|
|
220
|
+
const helperHash = digest(regularBytes(executable, CHECKPOINT_LIMIT));
|
|
221
|
+
return { ...fallback, digest: digest(JSON.stringify([helperHash, fallback.sandboxDigest])) };
|
|
212
222
|
} catch (error) { return { available: false, reason: error.code }; }
|
|
213
223
|
}
|
|
214
224
|
return resolveVerificationEnvelopeBackendV1();
|
|
@@ -409,7 +419,8 @@ export async function runPeerTask({ workspaceRoot, taskId, agent, generation, co
|
|
|
409
419
|
const directory = mkdtempSync(path.join(realpathSync(tmpdir()), "owlrunkit-peer-"));
|
|
410
420
|
materialize(checkpoint(task, state).files, directory);
|
|
411
421
|
const attempt = { id, directory, backend: backend.id,
|
|
412
|
-
...(backend.digest ? { backendSha256: backend.digest } : {})
|
|
422
|
+
...(backend.digest ? { backendSha256: backend.digest } : {}),
|
|
423
|
+
...(backend.trust ? { backendTrust: backend.trust } : {}) };
|
|
413
424
|
const claimed = append(task, state, {
|
|
414
425
|
attempt, phase: "running", processExit: null, event: "peer_attempt_started",
|
|
415
426
|
});
|
|
@@ -420,17 +431,39 @@ export async function runPeerTask({ workspaceRoot, taskId, agent, generation, co
|
|
|
420
431
|
}
|
|
421
432
|
|
|
422
433
|
return new Promise((resolve, reject) => {
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
:
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
+
const environment = {
|
|
435
|
+
PATH: "/usr/bin:/bin", TMPDIR: directory, OWLRUNKIT_PEER_ATTEMPT: id,
|
|
436
|
+
OWLRUNKIT_PEER_PATHS: JSON.stringify(state.paths),
|
|
437
|
+
};
|
|
438
|
+
let args;
|
|
439
|
+
let env = environment;
|
|
440
|
+
if (backend.id === BUBBLEWRAP_PEER_BACKEND) {
|
|
441
|
+
args = bubblewrapPeerArgs({
|
|
442
|
+
helper: backend.helper, directory, worker: executable,
|
|
443
|
+
args: command.slice(1), environment,
|
|
444
|
+
});
|
|
445
|
+
env = {};
|
|
446
|
+
} else if (backend.id === "linux_landlock_seccomp_v1") {
|
|
447
|
+
args = ["run", String(process.pid), directory, executable, ...command.slice(1)];
|
|
448
|
+
// An isolated worker cannot read host OpenSSL configuration.
|
|
449
|
+
env.OPENSSL_CONF = "/dev/null";
|
|
450
|
+
} else {
|
|
451
|
+
args = ["-p", sandboxPolicy(executable, directory), executable, ...command.slice(1)];
|
|
452
|
+
}
|
|
453
|
+
const options = {
|
|
454
|
+
cwd: directory, env, stdio: ["pipe", "pipe", "pipe"],
|
|
455
|
+
};
|
|
456
|
+
let child;
|
|
457
|
+
try {
|
|
458
|
+
child = backend.id === BUBBLEWRAP_PEER_BACKEND
|
|
459
|
+
? spawnBubblewrapPeer(backend, args, options)
|
|
460
|
+
: spawn(backend.executable, args, options);
|
|
461
|
+
} catch (error) {
|
|
462
|
+
try {
|
|
463
|
+
resolve(reportExit(task, id, { code: null, signal: null, protocolError: error.message, diagnostic: null }));
|
|
464
|
+
} catch (reportError) { reject(reportError); }
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
434
467
|
let watcher, pending = Buffer.alloc(0), sentWake, protocolError = null;
|
|
435
468
|
let diagnostic = Buffer.alloc(0);
|
|
436
469
|
const stop = error => {
|
|
@@ -28,10 +28,18 @@ const SELF_NORMALIZED_QUICK_CORE_VERSIONS = new Set([
|
|
|
28
28
|
"0.24.1",
|
|
29
29
|
"0.25.0",
|
|
30
30
|
"0.25.1",
|
|
31
|
+
"0.25.2",
|
|
32
|
+
"0.25.3",
|
|
31
33
|
]);
|
|
32
34
|
const EXACT_VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$/;
|
|
33
35
|
const SHA256_REF = /^sha256:[a-f0-9]{64}$/;
|
|
34
36
|
const TRUSTED_PRIOR_CORE_IDENTITIES = new Map([
|
|
37
|
+
["0.25.2", new Set([
|
|
38
|
+
"sha256:da816d343597b91c30e4f96c0a3d765bf5c00d6013d6bee430f58bc701399acc",
|
|
39
|
+
])],
|
|
40
|
+
["0.25.1", new Set([
|
|
41
|
+
"sha256:0934a076b147847a4623bc89263b677c88088b493495fc74f11485985b7903b5",
|
|
42
|
+
])],
|
|
35
43
|
["0.25.0", new Set([
|
|
36
44
|
"sha256:20e75c386f1408f11e908a6d3ee1a39316d9728d5719fa06f8d6a5aa45abd83e",
|
|
37
45
|
])],
|