You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by "jsiu93 (via GitHub)" <gi...@apache.org> on 2026/08/30 02:19:36 UTC
[PR] fix(runtime): resolve macOS sandbox temp roots to canonical paths [maka]
jsiu93 opened a new pull request, #4234:
URL: https://github.com/apache/maka/pull/4234
## Summary
Resolve every macOS Seatbelt root to its canonical filesystem path before emitting policy definitions, with a fallback to the supplied path when resolution fails. Policy generation is the narrowest correct layer because Seatbelt matches kernel-resolved paths; Linux and Windows backends retain their current behavior. Denied roots and protected writable roots now share the same canonical representation.
Adds policy coverage for a real symlinked temp root and a `sandbox-exec` smoke test that runs `mktemp` under `$TMPDIR` and `touch` under `/tmp` from an uncanonicalized workspace path.
Fixes #4233
## Verification
```text
mise exec node@24.19.0 -- node --test <macos-seatbelt unit and smoke tests>
Before fix: 18 pass, 3 fail, exit 1
After fix: 21 pass, 0 fail, exit 0
```
The failing run reproduced both lexical `/tmp` definitions and temp writes returning `Operation not permitted`.
```text
npm --workspace @maka/runtime run build: exit 0
npm run format:check: exit 0
npm run lint: exit 0
npm run typecheck: exit 0
```
## AI use
Select exactly one:
- [ ] No generative tool made a substantive contribution
- [x] Generative tooling made a substantive contribution
Tool(s) and scope: pi (gpt-5.6-sol), human-reviewed
## Checklist
- [x] Tests cover the change and fail without it
- [x] Lint, format, typecheck and the affected suites pass locally
Does this PR entail a change in behavior?
- [x] Yes — described under Summary above
- [ ] No
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org
Re: [PR] fix(runtime): resolve macOS sandbox temp roots to canonical paths [maka]
Posted by "jsiu93 (via GitHub)" <gi...@apache.org>.
jsiu93 commented on code in PR #4234:
URL: https://github.com/apache/maka/pull/4234#discussion_r3889286069
##########
packages/runtime/src/sandbox/macos-seatbelt.ts:
##########
@@ -375,23 +383,74 @@ function rootsForEntry(
pathContext: SandboxPathContext,
): readonly ResolvedRoot[] {
if (entry.kind === 'path') {
- return [{ path: entry.path, match: entry.match ?? 'subtree' }];
+ return [{ path: resolveRootPath(entry.path), match: entry.match ?? 'subtree' }];
}
switch (entry.special) {
case ':root':
- return [{ path: '/', match: 'subtree' }];
+ return [{ path: resolveRootPath('/'), match: 'subtree' }];
case ':workspace_roots':
- return pathContext.workspaceRoots.map((path) => ({ path, match: 'subtree' as const }));
+ return pathContext.workspaceRoots.map((path) => ({
+ path: resolveRootPath(path),
+ match: 'subtree' as const,
+ }));
case ':tmpdir':
- return pathContext.tmpdir ? [{ path: pathContext.tmpdir, match: 'subtree' }] : [];
+ return pathContext.tmpdir
+ ? [{ path: resolveRootPath(pathContext.tmpdir), match: 'subtree' }]
+ : [];
case ':slash_tmp':
- return [{ path: pathContext.slashTmp ?? '/tmp', match: 'subtree' }];
+ return [{ path: resolveRootPath(pathContext.slashTmp ?? '/tmp'), match: 'subtree' }];
case ':minimal':
- return (pathContext.minimalRoots ?? []).map((path) => ({ path, match: 'subtree' as const }));
+ return (pathContext.minimalRoots ?? []).map((path) => ({
+ path: resolveRootPath(path),
+ match: 'subtree' as const,
+ }));
}
}
+const MAX_DANGLING_SYMLINK_HOPS = 40;
+
+/**
+ * Seatbelt evaluates kernel-resolved paths, so every root must be emitted in
+ * canonical form. A root may not exist yet (a deny for a file that has not
+ * been created), so canonicalize the deepest existing ancestor and re-append
+ * the missing tail, mirroring `realpathAllowMissing`; an allow and its deny
+ * then stay in the same path space. Fall back to the lexical path only when
+ * even the filesystem root cannot be resolved.
+ */
+function resolveRootPath(path: string): string {
+ let cursor = resolve(path);
+ const missing: string[] = [];
+ let hops = 0;
+ while (true) {
+ try {
+ return resolve(realpathSync(cursor), ...missing.reverse());
+ } catch (error) {
+ if (!isMissingPathError(error)) return path;
Review Comment:
Agreed — the early return on non-missing errors contradicted the stated contract. `resolveRootPath` now propagates any non-ENOENT/ENOTDIR failure (and throws on the dangling-symlink hop cap), so policy construction fails instead of emitting a root in lexical path space; the only fallback left is re-appending genuinely missing tail components under their canonicalized existing ancestor. Added the EACCES regression you described: a symlinked writable root with a mode-000 directory and an exact deny beneath it now makes `buildSeatbeltPolicy` throw (skipped when running as root).
Also rebased onto current `main` to pick up #4237, so the branch now carries both seatbelt suites together: 25/25 unit + macOS smoke tests, full `build`, `format:check`, `lint`, `typecheck` all exit 0 after `npm ci`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org
Re: [PR] fix(runtime): resolve macOS sandbox temp roots to canonical paths [maka]
Posted by "hqhq1025 (via GitHub)" <gi...@apache.org>.
hqhq1025 commented on code in PR #4234:
URL: https://github.com/apache/maka/pull/4234#discussion_r3888373597
##########
packages/runtime/src/sandbox/macos-seatbelt.ts:
##########
@@ -375,23 +383,74 @@ function rootsForEntry(
pathContext: SandboxPathContext,
): readonly ResolvedRoot[] {
if (entry.kind === 'path') {
- return [{ path: entry.path, match: entry.match ?? 'subtree' }];
+ return [{ path: resolveRootPath(entry.path), match: entry.match ?? 'subtree' }];
}
switch (entry.special) {
case ':root':
- return [{ path: '/', match: 'subtree' }];
+ return [{ path: resolveRootPath('/'), match: 'subtree' }];
case ':workspace_roots':
- return pathContext.workspaceRoots.map((path) => ({ path, match: 'subtree' as const }));
+ return pathContext.workspaceRoots.map((path) => ({
+ path: resolveRootPath(path),
+ match: 'subtree' as const,
+ }));
case ':tmpdir':
- return pathContext.tmpdir ? [{ path: pathContext.tmpdir, match: 'subtree' }] : [];
+ return pathContext.tmpdir
+ ? [{ path: resolveRootPath(pathContext.tmpdir), match: 'subtree' }]
+ : [];
case ':slash_tmp':
- return [{ path: pathContext.slashTmp ?? '/tmp', match: 'subtree' }];
+ return [{ path: resolveRootPath(pathContext.slashTmp ?? '/tmp'), match: 'subtree' }];
case ':minimal':
- return (pathContext.minimalRoots ?? []).map((path) => ({ path, match: 'subtree' as const }));
+ return (pathContext.minimalRoots ?? []).map((path) => ({
+ path: resolveRootPath(path),
+ match: 'subtree' as const,
+ }));
}
}
+const MAX_DANGLING_SYMLINK_HOPS = 40;
+
+/**
+ * Seatbelt evaluates kernel-resolved paths, so every root must be emitted in
+ * canonical form. A root may not exist yet (a deny for a file that has not
+ * been created), so canonicalize the deepest existing ancestor and re-append
+ * the missing tail, mirroring `realpathAllowMissing`; an allow and its deny
+ * then stay in the same path space. Fall back to the lexical path only when
+ * even the filesystem root cannot be resolved.
+ */
+function resolveRootPath(path: string): string {
+ let cursor = resolve(path);
+ const missing: string[] = [];
+ let hops = 0;
+ while (true) {
+ try {
+ return resolve(realpathSync(cursor), ...missing.reverse());
+ } catch (error) {
+ if (!isMissingPathError(error)) return path;
Review Comment:
[P1] Fail closed on non-missing canonicalization errors
This branch contradicts the comment above it: any `EACCES`, `ELOOP`, or other non-missing error immediately returns the original lexical path, rather than reserving that fallback for an unresolvable filesystem root. That can recreate the deny bypass under review. I reproduced it with a symlinked writable root containing a mode-000 directory: the allow is emitted for the canonical target, while an exact deny below the inaccessible directory remains under the symlink alias. Because the child runs as the same owner, it can first restore the directory mode and then create the canonical denied file, which no longer matches the lexical `require-not`. The existing `realpathAllowMissing` contract deliberately propagates non-missing errors for this reason. Please fail policy construction on these errors (or otherwise preserve a single canonical namespace) and add an `EACCES` regression.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org
Re: [PR] fix(runtime): resolve macOS sandbox temp roots to canonical paths [maka]
Posted by "hqhq1025 (via GitHub)" <gi...@apache.org>.
hqhq1025 commented on code in PR #4234:
URL: https://github.com/apache/maka/pull/4234#discussion_r3888284827
##########
packages/runtime/src/sandbox/macos-seatbelt.ts:
##########
@@ -375,20 +382,36 @@ function rootsForEntry(
pathContext: SandboxPathContext,
): readonly ResolvedRoot[] {
if (entry.kind === 'path') {
- return [{ path: entry.path, match: entry.match ?? 'subtree' }];
+ return [{ path: resolveRootPath(entry.path), match: entry.match ?? 'subtree' }];
}
switch (entry.special) {
case ':root':
- return [{ path: '/', match: 'subtree' }];
+ return [{ path: resolveRootPath('/'), match: 'subtree' }];
case ':workspace_roots':
- return pathContext.workspaceRoots.map((path) => ({ path, match: 'subtree' as const }));
+ return pathContext.workspaceRoots.map((path) => ({
+ path: resolveRootPath(path),
+ match: 'subtree' as const,
+ }));
case ':tmpdir':
- return pathContext.tmpdir ? [{ path: pathContext.tmpdir, match: 'subtree' }] : [];
+ return pathContext.tmpdir
+ ? [{ path: resolveRootPath(pathContext.tmpdir), match: 'subtree' }]
+ : [];
case ':slash_tmp':
- return [{ path: pathContext.slashTmp ?? '/tmp', match: 'subtree' }];
+ return [{ path: resolveRootPath(pathContext.slashTmp ?? '/tmp'), match: 'subtree' }];
case ':minimal':
- return (pathContext.minimalRoots ?? []).map((path) => ({ path, match: 'subtree' as const }));
+ return (pathContext.minimalRoots ?? []).map((path) => ({
+ path: resolveRootPath(path),
+ match: 'subtree' as const,
+ }));
+ }
+}
+
+function resolveRootPath(path: string): string {
+ try {
+ return realpathSync(path);
+ } catch {
+ return path;
Review Comment:
[P1] Canonicalize missing descendants through their existing ancestor
When the leaf does not exist, this fallback leaves it in the caller’s lexical path space. After this patch that can split an allow and its deny across aliases: on macOS a custom managed profile that writes `:slash_tmp` but denies an exact, not-yet-created `/tmp/blocked.txt` gets a writable root of `/private/tmp`, while the deny remains the literal `/tmp/blocked.txt`. Seatbelt evaluates the write at the canonical `/private/tmp/blocked.txt`, so the `require-not` no longer excludes it and an explicitly denied file can be created. I reproduced the generated mismatch with a symlinked temp root and missing denied leaf. The existing `realpathAllowMissing` helper already canonicalizes the deepest existing ancestor; please use equivalent semantics here and cover the missing-leaf deny case.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org
Re: [PR] fix(runtime): resolve macOS sandbox temp roots to canonical paths [maka]
Posted by "jsiu93 (via GitHub)" <gi...@apache.org>.
jsiu93 commented on code in PR #4234:
URL: https://github.com/apache/maka/pull/4234#discussion_r3888296133
##########
packages/runtime/src/sandbox/macos-seatbelt.ts:
##########
@@ -375,20 +382,36 @@ function rootsForEntry(
pathContext: SandboxPathContext,
): readonly ResolvedRoot[] {
if (entry.kind === 'path') {
- return [{ path: entry.path, match: entry.match ?? 'subtree' }];
+ return [{ path: resolveRootPath(entry.path), match: entry.match ?? 'subtree' }];
}
switch (entry.special) {
case ':root':
- return [{ path: '/', match: 'subtree' }];
+ return [{ path: resolveRootPath('/'), match: 'subtree' }];
case ':workspace_roots':
- return pathContext.workspaceRoots.map((path) => ({ path, match: 'subtree' as const }));
+ return pathContext.workspaceRoots.map((path) => ({
+ path: resolveRootPath(path),
+ match: 'subtree' as const,
+ }));
case ':tmpdir':
- return pathContext.tmpdir ? [{ path: pathContext.tmpdir, match: 'subtree' }] : [];
+ return pathContext.tmpdir
+ ? [{ path: resolveRootPath(pathContext.tmpdir), match: 'subtree' }]
+ : [];
case ':slash_tmp':
- return [{ path: pathContext.slashTmp ?? '/tmp', match: 'subtree' }];
+ return [{ path: resolveRootPath(pathContext.slashTmp ?? '/tmp'), match: 'subtree' }];
case ':minimal':
- return (pathContext.minimalRoots ?? []).map((path) => ({ path, match: 'subtree' as const }));
+ return (pathContext.minimalRoots ?? []).map((path) => ({
+ path: resolveRootPath(path),
+ match: 'subtree' as const,
+ }));
+ }
+}
+
+function resolveRootPath(path: string): string {
+ try {
+ return realpathSync(path);
+ } catch {
+ return path;
Review Comment:
Thanks, that split is real and I reproduced it the same way (symlinked temp root + a denied leaf that does not exist yet).
Updated in the amended commit: `resolveRootPath` now walks up to the deepest existing ancestor, canonicalizes it, and re-appends the missing tail (mirroring `realpathAllowMissing`, including the dangling-symlink hop cap), so the writable root and the `require-not` deny for a not-yet-created file land in the same path space. The lexical path is kept only when even `/` cannot be resolved. Added a unit test that builds a managed profile with a symlinked write root and an exact deny on a missing leaf, and asserts the deny literal is emitted under the canonical root and the lexical alias no longer appears. The pre-existing `/tmp/repo.(test)+[x]` escaping test now expects the canonical `/tmp` ancestor.
Local run after `npm ci` on current `main`: 22/22 seatbelt unit + macOS smoke tests, `format:check`, `lint`, `typecheck`, full `build` all exit 0.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org
Re: [PR] fix(runtime): resolve macOS sandbox temp roots to canonical paths [maka]
Posted by "Astro-Han (via GitHub)" <gi...@apache.org>.
Astro-Han merged PR #4234:
URL: https://github.com/apache/maka/pull/4234
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org