Files
openclaw/test/scripts/mac-node-worker.test.ts
Peter Steinberger 35f17c7f5d test(core,plugins,scripts): remove low-value tests (batch d012) (#158802)
* test(agents): deslop s047 tests

* test(codex): deslop s045 tests

* test(gateway): deslop s049 tests

* test(commands): deslop s048 tests

* test(scripts): deslop s012 tests

* test(scripts): deslop s046 tests

* test(workboard): deslop s053 tests

* test(plugins): deslop s051 tests

* test(slack): deslop s054 tests

* test: preserve current fixture contracts after replay

* test: retain distinct cleanup contracts and fixture scopes

* test(gateway): retain idle suspension coverage for local claims
2026-09-26 03:46:53 -07:00

813 lines
32 KiB
TypeScript

// Exercise publication and provisioning boundaries without signing, service control, or operator state.
import { spawnSync } from "node:child_process";
import {
chmodSync,
existsSync,
mkdirSync,
readFileSync,
symlinkSync,
writeFileSync,
} from "node:fs";
import path from "node:path";
import { afterEach, describe, expect, it as baseIt } from "vitest";
import { resolveTestNodeExecPath } from "../../src/test-utils/node-process.js";
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
import { registerMacWorkerMaterializationTests } from "./mac-node-worker-materialization.test-support.js";
import { createMacScriptTest } from "./mac-script-fixture.test-support.js";
registerMacWorkerMaterializationTests();
const temps = useAutoCleanupTempDirTracker(afterEach);
const testNodeExecPath = resolveTestNodeExecPath();
const quote = (value: string) => `'${value.replaceAll("'", "'\\''")}'`;
describe("Mac app worker publication", () => {
baseIt.each([undefined, "", "26.8.2"])(
"preserves the requested installer Node version in the isolated worker (%s)",
(version) => {
const root = temps.make("openclaw-worker-version-");
const scripts = path.join(root, "scripts");
const bin = path.join(root, "bin");
mkdirSync(scripts);
mkdirSync(bin);
symlinkSync(process.execPath, path.join(bin, "node"));
writeFileSync(
path.join(scripts, "stage-mac-node-worker.sh"),
readFileSync("scripts/stage-mac-node-worker.sh"),
);
writeFileSync(
path.join(scripts, "package-openclaw-for-docker.mjs"),
`import fs from "node:fs";
const file = process.argv[process.argv.indexOf("--output-dir") + 1] + "/openclaw.tgz";
fs.writeFileSync(file, "fixture");
console.log(file);
`,
);
// Stop at the download boundary after the real installer resolves its
// version. No downloads, app signing, service control, or native build.
writeFileSync(
path.join(scripts, "install-cli.sh"),
`${readFileSync("scripts/install-cli.sh", "utf8")}
install_node() {
printf '%s\\n' "$NODE_VERSION" "$DEFAULT_NODE_VERSION" "$NODE_VERSION_REQUESTED" "\${OPENCLAW_CONFIG_PATH:-}" "\${OPENCLAW_GATEWAY_TOKEN:-}"
exit 77
}
`,
);
const result = spawnSync(
"/bin/bash",
[path.join(scripts, "stage-mac-node-worker.sh"), path.join(root, "worker"), "arm64"],
{
encoding: "utf8",
env: {
HOME: root,
TMPDIR: root,
PATH: `${bin}:/usr/bin:/bin`,
...(version === undefined ? {} : { OPENCLAW_NODE_VERSION: version }),
OPENCLAW_CONFIG_PATH: "/fixture/operator-config",
OPENCLAW_GATEWAY_TOKEN: "synthetic-isolation-sentinel",
},
},
);
expect(result.status, result.stderr).toBe(77);
const [selected, defaultVersion, requested, config, token] = result.stdout.split("\n");
expect(selected).toBe(version || defaultVersion);
expect(requested).toBe(version ? "1" : "0");
expect(config).toBe("");
expect(token).toBe("");
expect(existsSync(path.join(root, "worker"))).toBe(false);
},
);
baseIt.each(["sign", "worker", "seal", "stage", "success"])(
"publishes only a verified replacement (%s)",
(failure) => {
const root = temps.make("openclaw-worker-publication-");
const target = path.join(root, "OpenClaw.app");
const staged = path.join(root, "candidate.app");
mkdirSync(target);
mkdirSync(staged);
writeFileSync(path.join(target, "worker"), "old signed worker");
writeFileSync(path.join(staged, "worker"), "new signed worker");
const packageScript = readFileSync("scripts/package-mac-app.sh", "utf8");
const publication = packageScript.slice(
packageScript.indexOf('if [[ -n "${SIGN_IDENTITY:-}" ]]'),
);
const worker = path.join(staged, "Contents/Resources/node-worker/arm64/bin/node");
mkdirSync(path.dirname(worker), { recursive: true });
writeFileSync(worker, `#!/bin/bash\nexit ${failure === "worker" ? 6 : 0}\n`);
chmodSync(worker, 0o755);
const scripts = path.join(root, "scripts");
mkdirSync(scripts);
writeFileSync(
path.join(scripts, "codesign-mac-app.sh"),
`#!/bin/bash\nexit ${failure === "sign" ? 9 : 0}\n`,
);
chmodSync(path.join(scripts, "codesign-mac-app.sh"), 0o755);
const result = spawnSync(
"/bin/bash",
[
"-c",
`
set -euo pipefail
source scripts/lib/mac-app-bundle.sh
ROOT_DIR=${quote(root)}
APP_ROOT=${quote(staged)}
APP_STAGE_DIR=${quote(root)}
BUILD_ARCHS=(arm64)
APP_DESTINATION=${quote(target)}
codesign_calls=0
codesign() {
codesign_calls=$((codesign_calls + 1))
if [[ ${quote(failure)} == seal && "$codesign_calls" -eq 2 ]]; then return 1; fi
return 0
}
stop_packaged_app_if_running() { :; }
mv() {
if [[ "$1" == "$APP_ROOT" && ${quote(failure)} == stage ]]; then return 7; fi
command mv "$@"
}
${publication}
`,
],
{ encoding: "utf8", env: { HOME: root, PATH: "/usr/bin:/bin" } },
);
expect(result.status, result.stderr).toBe(
failure === "success" ? 0 : failure === "worker" ? 6 : failure === "sign" ? 9 : 1,
);
expect(readFileSync(path.join(target, "worker"), "utf8")).toBe(
failure === "success" ? "new signed worker" : "old signed worker",
);
},
);
baseIt(
"provisions packages without invoking the service owner or changing operator state",
() => {
const root = temps.make("openclaw-worker-provision-");
const home = path.join(root, "home");
const prefix = path.join(root, "private");
const sentinel = path.join(root, "operator", ".openclaw", "state", "sentinel");
mkdirSync(path.dirname(sentinel), { recursive: true });
mkdirSync(home);
writeFileSync(sentinel, "operator-owned");
const nodeDir = path.join(prefix, "tools", "node-v24.21.0");
mkdirSync(path.join(nodeDir, "bin"), { recursive: true });
// Only npm/network is replaced. The real install_openclaw implementation
// must remain a provision-only seam even when a loaded Gateway is reported.
symlinkSync(testNodeExecPath, path.join(nodeDir, "bin", "node"));
const npm = path.join(nodeDir, "bin", "npm");
writeFileSync(
npm,
`#!/bin/bash
case "$1" in
--version) echo 11.15.0 ;;
config) echo null ;;
install)
mkdir -p "$HOME/../private/tools/node-v24.21.0/lib/node_modules/openclaw/dist"
touch "$HOME/../private/tools/node-v24.21.0/lib/node_modules/openclaw/dist/entry.js"
;;
*) exit 4 ;;
esac
`,
);
chmodSync(npm, 0o755);
const calls = path.join(root, "service-calls");
const result = spawnSync(
"/bin/bash",
[
"-c",
`
set -euo pipefail
source scripts/install-cli.sh
PREFIX=${quote(prefix)}
OPENCLAW_VERSION=/fixture/openclaw.tgz
is_gateway_daemon_loaded() { echo loaded >> ${quote(calls)}; return 0; }
refresh_gateway_service_if_loaded() { echo refresh >> ${quote(calls)}; }
install_openclaw
test -f "$(node_dir)/lib/node_modules/openclaw/dist/entry.js"
`,
],
{
encoding: "utf8",
env: {
HOME: home,
PATH: `${path.join(nodeDir, "bin")}:/usr/bin:/bin`,
OPENCLAW_INSTALL_CLI_SH_NO_RUN: "1",
},
},
);
expect(result.status, result.stderr).toBe(0);
expect(existsSync(calls)).toBe(false);
expect(readFileSync(sentinel, "utf8")).toBe("operator-owned");
},
);
});
describe.runIf(process.platform === "darwin")("Mac worker portability inventory", () => {
const it = createMacScriptTest();
it("audits supported thin and fat formats through real otool", async () => {
const { auditMacWorkerPortability } =
await import("../../scripts/lib/mac-worker-portability.mjs");
const { machoFixture } = await import("../helpers/mac-native.js");
const root = temps.make("openclaw-portability-native-");
const node = path.join(root, "node");
writeFileSync(node, machoFixture());
for (const bits of [32, 64]) {
for (const little of [false, true]) {
for (const fat of [false, true]) {
if (fat && little) {
continue;
}
const filename = path.join(root, `${bits}-${little}-${fat} addon`);
writeFileSync(filename, machoFixture(bits, little, fat, 6));
}
}
}
symlinkSync("node", path.join(root, "internal-link"));
expect(auditMacWorkerPortability(root, node)).toBe(7);
});
it.for(["Java", "fat32 archive", "fat64 archive", "thin object", "fat32 object", "fat64 object"])(
"preserves %s resources without treating them as loadable images",
(kind, { mac }) =>
mac.lifetime.run(async () => {
const { auditMacWorkerPortability } =
await import("../../scripts/lib/mac-worker-portability.mjs");
const { machoFixture, nativeObjectFixture, universalArchiveFixture } =
await import("../helpers/mac-native.js");
const parent = mac.createTempDir("openclaw-portability-resource-");
const root = path.join(parent, "runtime");
mkdirSync(root);
const node = path.join(root, "node");
writeFileSync(node, machoFixture());
const filename = path.join(root, "opaque-resource");
const format = kind.startsWith("fat32")
? "fat32"
: kind.startsWith("fat64")
? "fat64"
: "thin";
const inputs = path.join(parent, "resource-inputs");
const bytes =
kind === "Java"
? Buffer.from("cafebabe0000003d0001", "hex")
: kind.endsWith("archive")
? await universalArchiveFixture(inputs, format === "fat64", false, mac)
: await nativeObjectFixture(inputs, format, mac);
writeFileSync(filename, bytes);
expect(auditMacWorkerPortability(root, node)).toBe(1);
expect(readFileSync(filename)).toEqual(bytes);
}),
);
it.each([false, true])(
"binds captured symlink targets before publication (replacement: %s)",
(replace) => {
const root = temps.make("openclaw-native-link-binding-");
const result = spawnSync(
"/usr/bin/python3",
[
"-B",
"-c",
`
import os, pathlib, runpy, shutil, sys
api = runpy.run_path('scripts/lib/mac-native-inventory.py')
root = pathlib.Path(sys.argv[1])
source, output = root / 'source', root / 'output'
source.mkdir(); output.mkdir()
(source / 'a.txt').write_text('alpha')
(source / 'b.txt').write_text('bravo')
(source / 'alias').symlink_to('a.txt')
before = os.lstat(source / 'alias')
readlink = os.readlink
swapped = False
def replace_link(name, *, dir_fd=None):
global swapped
if sys.argv[2] == 'true' and name == 'alias' and dir_fd is not None and not swapped:
swapped = True
os.rename('alias', 'held', src_dir_fd=dir_fd, dst_dir_fd=dir_fd)
os.symlink('b.txt', 'alias', dir_fd=dir_fd)
target = readlink(name, dir_fd=dir_fd)
os.unlink('alias', dir_fd=dir_fd)
os.rename('held', 'alias', src_dir_fd=dir_fd, dst_dir_fd=dir_fd)
return target
return readlink(name, dir_fd=dir_fd)
os.readlink = replace_link
accepted = False
try:
with api['open_native_inventory_tree'](source) as tree:
for entry in tree.entries():
destination = output.joinpath(*entry.parts)
if isinstance(entry, api['NativeInventoryFile']):
with destination.open('wb') as target: shutil.copyfileobj(entry.stream, target)
elif isinstance(entry, api['NativeInventorySymlink']):
destination.symlink_to(entry.target)
tree.validate()
accepted = True
except ValueError as error:
assert 'changed' in str(error), str(error)
assert readlink(source / 'alias') == 'a.txt'
assert os.lstat(source / 'alias').st_ino == before.st_ino
if sys.argv[2] == 'true':
assert swapped
assert not accepted, 'Captured replacement target passed binding validation'
else:
assert accepted
assert readlink(output / 'alias') == 'a.txt'
assert (output / 'alias').read_text() == 'alpha'
print('symlink-binding-ok')
`,
root,
String(replace),
],
{ encoding: "utf8" },
);
expect(result.status, result.stderr).toBe(0);
expect(result.stdout.trim()).toBe("symlink-binding-ok");
},
);
it("looks up actual filesystem child names without following links or leaking handles", () => {
const root = temps.make("openclaw-native-child-lookup-");
const result = spawnSync(
"/usr/bin/python3",
[
"-B",
"-c",
`
import os, pathlib, runpy, sys, unicodedata
api = runpy.run_path('scripts/lib/mac-native-inventory.py')
root = pathlib.Path(sys.argv[1])
source = root / 'source'
(source / 'Library').mkdir(parents=True)
(source / 'Library/AddOn.node').write_text('resource')
(source / 'Café.txt').write_text('unicode resource')
special = 'slash' + chr(92) + chr(10) + 'name'
(source / special).write_text('literal name')
(root / 'outside').mkdir()
(root / 'outside/probe').write_text('outside')
(source / 'redirect').symlink_to('../outside')
initial_fds = set(os.listdir('/dev/fd'))
with api['open_native_inventory_tree'](source) as tree:
entries = {entry.parts: entry for entry in tree.entries()}
queries = [((), 'Library', ('Library',)), ((), 'library', ('Library',)),
(('Library',), 'addon.node', ('Library', 'AddOn.node')),
((), unicodedata.normalize('NFD', 'Café.txt'), ('Café.txt',)),
((), special, (special,)), ((), 'redirect', ('redirect',))]
for parent, name, canonical in queries:
try: os.stat(source.joinpath(*parent, name), follow_symlinks=False)
except FileNotFoundError:
try: tree.lstat_child(parent, name)
except FileNotFoundError: pass
else: raise AssertionError('Invented filesystem name equivalence')
else:
actual = tree.lstat_child(parent, name)
assert api['content_identity'](actual) == api['content_identity'](entries[canonical].info)
for name in ('', '.', '..', '/outside', 'redirect/probe', 'x' + chr(0)):
try: tree.lstat_child((), name)
except ValueError: pass
else: raise AssertionError('Lookup escaped its single-child boundary')
try: tree.lstat_child(('redirect',), 'probe')
except ValueError: pass
else: raise AssertionError('Lookup followed an untraversed symlink parent')
tree.validate()
try: tree.lstat_child((), 'Library')
except ValueError as error: assert 'closed' in str(error)
else: raise AssertionError('Lookup survived tree closure')
assert set(os.listdir('/dev/fd')) == initial_fds
print('child-lookup-ok')
`,
root,
],
{ encoding: "utf8" },
);
expect(result.status, result.stderr).toBe(0);
expect(result.stdout.trim()).toBe("child-lookup-ok");
});
it.each([
"missing",
"leaf-replacement",
"parent-link",
"parent-directory",
"root",
"new-hardlink",
])("rejects changed child lookup bindings and closes handles (%s)", (mutation) => {
const root = temps.make("openclaw-native-child-replacement-");
const result = spawnSync(
"/usr/bin/python3",
[
"-B",
"-c",
`
import os, pathlib, runpy, sys
api = runpy.run_path('scripts/lib/mac-native-inventory.py')
root = pathlib.Path(sys.argv[1])
source, outside = root / 'source', root / 'outside'
(source / 'Library').mkdir(parents=True)
(source / 'Library/addon').write_text('original')
outside.mkdir(); (outside / 'addon').write_text('outside')
initial_fds = set(os.listdir('/dev/fd'))
with api['open_native_inventory_tree'](source) as tree:
entries = list(tree.entries())
parent, name = ('Library',), 'addon'
mode = sys.argv[2]
if mode == 'missing': (source / 'Library/addon').unlink()
elif mode == 'leaf-replacement':
(source / 'Library/addon').unlink()
(source / 'Library/addon').symlink_to('../../outside/addon')
elif mode in ('parent-link', 'parent-directory'):
(source / 'Library').rename(root / 'held')
if mode == 'parent-link': (source / 'Library').symlink_to(outside)
else:
(source / 'Library').mkdir()
(source / 'Library/addon').write_text('replacement')
elif mode == 'root':
source.rename(root / 'held')
source.mkdir()
else:
os.link(source / 'Library/addon', source / 'new-alias')
parent, name = (), 'new-alias'
try: tree.lstat_child(parent, name)
except (OSError, ValueError): pass
else: raise AssertionError('Changed namespace lookup was accepted')
if mode == 'root':
try: tree.validate()
except (OSError, ValueError): pass
else: raise AssertionError('Final validation skipped root replacement')
assert (outside / 'addon').read_text() == 'outside'
assert set(os.listdir('/dev/fd')) == initial_fds
print('child-binding-rejected')
`,
root,
mutation,
],
{ encoding: "utf8" },
);
expect(result.status, result.stderr).toBe(0);
expect(result.stdout.trim()).toBe("child-binding-rejected");
});
it.each(["namespace-root", "namespace-nested", "discarded-resource", "incomplete"])(
"rejects incomplete or changed observed input even when not retained (%s)",
async (mode) => {
const { machoFixture } = await import("../helpers/mac-native.js");
const root = temps.make("openclaw-native-complete-audit-");
const directory = path.join(
root,
"source",
...(mode === "namespace-nested" ? ["child"] : []),
);
mkdirSync(directory, { recursive: true });
writeFileSync(
path.join(directory, "resource"),
mode === "discarded-resource" ? machoFixture() : "resource",
);
const result = spawnSync(
"/usr/bin/python3",
[
"-B",
"-c",
`
import os, pathlib, runpy, sys
api = runpy.run_path('scripts/lib/mac-native-inventory.py')
root, mode = pathlib.Path(sys.argv[1]), sys.argv[2]
source = root / 'source'
directory = source / 'child' if mode == 'namespace-nested' else source
resource = directory / 'resource'
original, original_info = resource.read_bytes(), resource.stat()
initial_fds = set(os.listdir('/dev/fd'))
held = None
try:
with api['open_native_inventory_tree'](source) as tree:
iterator = tree.entries()
records = [next(iterator)]
if mode.startswith('namespace-'):
if mode == 'namespace-nested': records.append(next(iterator))
before = directory.stat()
resource.rename(root / 'hidden')
records.extend(iterator)
assert not any(entry.parts and entry.parts[-1] == 'resource' for entry in records)
(root / 'hidden').rename(resource)
assert api['content_identity'](directory.stat()) != api['content_identity'](before)
elif mode == 'discarded-resource':
for entry in iterator:
records.append(entry)
if isinstance(entry, api['NativeInventoryFile']):
held = os.fdopen(os.dup(entry.stream.fileno()), 'rb', buffering=0)
resource.write_bytes(bytes.fromhex('cafebabe0000003d0001'))
assert api['classify_macho_candidates']([(held, 'resource')]) == [None]
resource.write_bytes(original)
os.utime(resource, ns=(original_info.st_atime_ns, original_info.st_mtime_ns))
assert resource.stat().st_ino == original_info.st_ino
assert resource.stat().st_ctime_ns != original_info.st_ctime_ns
try: tree.validate()
except ValueError as error:
assert ('complete' if mode == 'incomplete' else 'changed') in str(error), str(error)
else: raise AssertionError('Incomplete or changed observed input was accepted')
finally:
if held is not None: held.close()
assert resource.read_bytes() == original
assert set(os.listdir('/dev/fd')) == initial_fds
print('complete-audit-rejected')
`,
root,
mode,
],
{ encoding: "utf8" },
);
expect(result.status, result.stderr).toBe(0);
expect(result.stdout.trim()).toBe("complete-audit-rejected");
},
);
it.each(["stable", "ancestor-symlink", "ancestor-directory", "root-swap"])(
"audits deep trees with linear opens and bounded handles (%s)",
(mode) => {
const root = temps.make("openclaw-native-audit-depth-");
const result = spawnSync(
"/usr/bin/python3",
[
"-B",
"-c",
`
import os, pathlib, runpy, sys
api = runpy.run_path('scripts/lib/mac-native-inventory.py')
root, mode = pathlib.Path(sys.argv[1]), sys.argv[2]
source, outside = root / 'source', root / 'outside'
source.mkdir(); outside.mkdir()
(outside / 'a-resource').write_text('outside')
folder = source
for _ in range(12):
folder = folder / 'branch'
folder.mkdir()
(folder / 'a-resource').write_text('resource')
(folder / 'z-empty').mkdir()
initial_fds = set(os.listdir('/dev/fd'))
original_open, original_stat = os.open, os.stat
opens, peak, swapped = 0, len(initial_fds), False
def opened(name, flags, *args, **kwargs):
global opens, peak
fd = original_open(name, flags, *args, **kwargs)
opens += bool(flags & os.O_DIRECTORY)
peak = max(peak, len(os.listdir('/dev/fd')))
assert api['identity'](os.fstat(fd)) != api['identity'](original_stat(outside)), 'Opened external directory'
return fd
def stated(name, *args, **kwargs):
global swapped
info = original_stat(name, *args, **kwargs)
if mode != 'stable' and name == 'a-resource' and not swapped:
swapped = True
replaced = source if mode == 'root-swap' else source / 'branch'
replaced.rename(root / 'held')
if mode == 'ancestor-directory': replaced.mkdir()
else: replaced.symlink_to(outside)
return info
with api['open_native_inventory_tree'](source) as tree:
entries = list(tree.entries())
directories = sum(isinstance(entry, api['NativeInventoryDirectory']) for entry in entries)
depth = max(len(entry.parts) for entry in entries)
os.open, os.stat = opened, stated
try:
try: tree.validate()
except (OSError, ValueError): assert mode != 'stable'
else:
assert mode == 'stable', 'Audit accepted a replaced ancestor'
assert opens <= directories * 3, (opens, directories)
assert peak <= len(initial_fds) + depth + 3, (peak, depth)
finally:
os.open, os.stat = original_open, original_stat
assert swapped == (mode != 'stable')
assert (outside / 'a-resource').read_text() == 'outside'
assert set(os.listdir('/dev/fd')) == initial_fds
print('bounded-audit-ok')
`,
root,
mode,
],
{ encoding: "utf8" },
);
expect(result.status, result.stderr).toBe(0);
expect(result.stdout.trim()).toBe("bounded-audit-ok");
},
);
it.each(["finish", "abort"])(
"binds classify-and-copy to borrowed files and closes them on %s",
async (mode) => {
const { machoFixture } = await import("../helpers/mac-native.js");
const root = temps.make("openclaw-native-borrow-");
const source = path.join(root, "source");
const outside = path.join(root, "outside");
mkdirSync(path.join(source, "dir"), { recursive: true });
mkdirSync(path.join(source, "empty"));
mkdirSync(outside);
writeFileSync(path.join(source, "dir", "native"), machoFixture());
writeFileSync(path.join(outside, "native"), machoFixture(64, true, false, 6));
writeFileSync(path.join(source, "resource.txt"), "ordinary resource");
writeFileSync(
path.join(source, "resource.class"),
Buffer.from("cafebabe0000003d0001", "hex"),
);
writeFileSync(path.join(source, "invalid"), machoFixture(32, true, true));
symlinkSync("../outside", path.join(source, "link"));
expect(spawnSync("/usr/bin/mkfifo", [path.join(source, "fifo")]).status).toBe(0);
const result = spawnSync(
"/usr/bin/python3",
[
"-c",
`
import os, pathlib, runpy, shutil, sys
api = runpy.run_path('scripts/lib/mac-native-inventory.py')
root = pathlib.Path(sys.argv[1])
source = root / 'source'
original = (source / 'dir/native').read_bytes()
resources = {('resource.txt',): b'ordinary resource', ('resource.class',): bytes.fromhex('cafebabe0000003d0001')}
initial_fds = set(os.listdir('/dev/fd'))
class CopyAborted(Exception): pass
borrowed = None
seen = []
try:
with api['open_native_inventory_tree'](source) as tree:
iterator = tree.entries()
for entry in iterator:
seen.append((type(entry).__name__, entry.parts))
if isinstance(entry, api['NativeInventorySymlink']):
assert entry.target == '../outside'
if not isinstance(entry, api['NativeInventoryFile']): continue
assert entry.stream.tell() == 0
if entry.parts == ('dir', 'native'):
borrowed = entry.stream
os.rename(source / 'dir', root / 'retained')
os.symlink(root / 'outside', source / 'dir')
assert api['classify_macho_candidates']([(borrowed, 'native')]) == [b'executable']
assert borrowed.tell() == 0
with (root / 'copied-native').open('wb') as output:
shutil.copyfileobj(borrowed, output)
assert (root / 'copied-native').read_bytes() == original
if sys.argv[2] == 'abort': raise CopyAborted()
else:
assert borrowed.closed
if entry.parts == ('invalid',):
entry.stream.seek(7)
try: api['classify_macho_candidates']([(entry.stream, 'invalid')])
except ValueError: pass
else: raise AssertionError('Invalid native container was accepted')
assert entry.stream.tell() == 7
continue
if entry.header in api['MACHO_MAGICS']:
assert api['classify_macho_candidates']([(entry.stream, 'resource')]) == [None]
assert entry.stream.tell() == 0
assert entry.stream.read() == resources[entry.parts]
assert ('NativeInventoryDirectory', ('empty',)) in seen
assert ('NativeInventorySpecial', ('fifo',)) in seen
assert ('NativeInventorySymlink', ('link',)) in seen
try: tree.validate()
except NotADirectoryError as error: assert error.filename == 'dir'
except ValueError as error: assert 'changed' in str(error)
else: raise AssertionError('Replaced pathname was accepted')
except CopyAborted:
assert sys.argv[2] == 'abort'
assert borrowed.closed
try: borrowed.fileno()
except ValueError: pass
else: raise AssertionError('Borrowed descriptor survived its context')
assert next(iterator, None) is None
try: tree.validate()
except ValueError: pass
else: raise AssertionError('Closed tree remained usable')
assert set(os.listdir('/dev/fd')) == initial_fds
print('held-file-copy-ok')
`,
root,
mode,
],
{ encoding: "utf8" },
);
expect(result.status, result.stderr).toBe(0);
expect(result.stdout.trim()).toBe("held-file-copy-ok");
},
);
it.each(["file", "directory", "dangling"])(
"rejects %s symlinks outside the worker",
async (kind) => {
const { auditMacWorkerPortability } =
await import("../../scripts/lib/mac-worker-portability.mjs");
const { machoFixture } = await import("../helpers/mac-native.js");
const parent = temps.make("openclaw-portability-link-");
const root = path.join(parent, "runtime");
mkdirSync(root);
const node = path.join(root, "node");
writeFileSync(node, machoFixture());
const external = path.join(parent, "external");
if (kind === "directory") {
mkdirSync(external);
}
if (kind === "file") {
writeFileSync(external, "outside");
}
symlinkSync(external, path.join(root, "link"));
expect(() => auditMacWorkerPortability(root, node)).toThrow(
kind === "dangling" ? /ENOENT/ : /symlink escapes/,
);
},
);
it("does not borrow loader paths from a different architecture", async () => {
const { auditMacWorkerPortability } =
await import("../../scripts/lib/mac-worker-portability.mjs");
const { machoFixture } = await import("../helpers/mac-native.js");
const root = temps.make("openclaw-portability-slices-");
const node = path.join(root, "node");
writeFileSync(node, machoFixture());
const command = (id: number, value: string, offset: number) => {
const name = Buffer.from(value + "\0");
const bytes = Buffer.alloc(Math.ceil((offset + name.length) / 8) * 8);
bytes.writeUInt32LE(id, 0);
bytes.writeUInt32LE(bytes.length, 4);
bytes.writeUInt32LE(offset, 8);
name.copy(bytes, offset);
return bytes;
};
const slice = (cpu: number, subtype: number, folder: string) => {
const header = machoFixture(64, true, false, 6);
const commands = Buffer.concat([
command(0x8000001c, `@loader_path/${folder}`, 12),
command(0xc, "@rpath/libdemo.dylib", 24),
]);
header.writeUInt32LE(cpu, 4);
header.writeUInt32LE(subtype, 8);
header.writeUInt32LE(2, 16);
header.writeUInt32LE(commands.length, 20);
return Buffer.concat([header, commands]);
};
const slices = [slice(0x01000007, 3, "empty"), slice(0x0100000c, 0x80000002, "valid")] as const;
const payload = Buffer.alloc(8192 + slices[1].length);
payload.writeUInt32BE(0xcafebabf, 0);
payload.writeUInt32BE(2, 4);
for (const [index, bytes] of slices.entries()) {
const record = 8 + index * 32;
const offset = 4096 * (index + 1);
payload.writeUInt32BE(bytes.readUInt32LE(4), record);
payload.writeUInt32BE(bytes.readUInt32LE(8), record + 4);
payload.writeBigUInt64BE(BigInt(offset), record + 8);
payload.writeBigUInt64BE(BigInt(bytes.length), record + 16);
payload.writeUInt32BE(12, record + 24);
bytes.copy(payload, offset);
}
const addon = path.join(root, "addon");
writeFileSync(addon, payload);
mkdirSync(path.join(root, "empty"));
mkdirSync(path.join(root, "valid"));
writeFileSync(path.join(root, "valid/libdemo.dylib"), machoFixture(64, true, false, 6));
expect(spawnSync("/usr/bin/lipo", ["-archs", addon]).status).toBe(0);
expect(() => auditMacWorkerPortability(root, node)).toThrow(/Nonportable LC_LOAD_DYLIB/);
});
it.each([
["thin", "/usr/lib/libSystem.B.dylib"],
["thin", "/opt/homebrew/lib/nonportable.dylib"],
["thin", "/usr/lib/../../opt/homebrew/lib/nonportable.dylib"],
["thin", "/System/Library/../../opt/homebrew/lib/nonportable.dylib"],
["thin", "@loader_path/../../outside.dylib"],
["fat64", "/usr/lib/libSystem.B.dylib"],
["fat64", "/opt/homebrew/lib/nonportable.dylib"],
] as const)("audits load dependencies after inventory (%s, %s)", async (format, library) => {
const { auditMacWorkerPortability } =
await import("../../scripts/lib/mac-worker-portability.mjs");
const { machoFixture } = await import("../helpers/mac-native.js");
const root = temps.make("openclaw-portability-load-");
const node = path.join(root, "node");
writeFileSync(node, machoFixture());
const addon = path.join(root, "addon");
const header = machoFixture(64, true, false, 6);
const name = Buffer.from(library + "\0");
const command = Buffer.alloc(Math.ceil((24 + name.length) / 8) * 8);
command.writeUInt32LE(0xc, 0); // LC_LOAD_DYLIB
command.writeUInt32LE(command.length, 4);
command.writeUInt32LE(24, 8);
name.copy(command, 24);
header.writeUInt32LE(1, 16);
header.writeUInt32LE(command.length, 20);
const thin = Buffer.concat([header, command]);
// On-disk fat headers are big endian; the arm64 slice is little endian.
const payload =
format === "fat64"
? Buffer.concat([machoFixture(64, false, true, 6).subarray(0, 4096), thin])
: thin;
if (format === "fat64") {
payload.writeBigUInt64BE(BigInt(thin.length), 24);
}
writeFileSync(addon, payload);
const load = spawnSync("/usr/bin/otool", ["-l", addon], { encoding: "utf8" });
expect(load.status, load.stderr).toBe(0);
expect(load.stdout).toContain(`name ${library} (offset 24)`);
if (library === "/usr/lib/libSystem.B.dylib") {
expect(auditMacWorkerPortability(root, node)).toBe(2);
} else {
expect(() => auditMacWorkerPortability(root, node)).toThrow(/Nonportable LC_LOAD_DYLIB/);
}
});
});