@sigrun/sigrun 0.1.6 → 0.2.1

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.
Files changed (137) hide show
  1. package/CHANGELOG.md +192 -0
  2. package/README.md +445 -42
  3. package/dist/blocks/build-stamp.json +13 -0
  4. package/dist/blocks/builders.d.ts +57 -0
  5. package/dist/blocks/images.d.ts +26 -0
  6. package/dist/blocks/index.d.ts +20 -0
  7. package/dist/blocks/limits.d.ts +25 -0
  8. package/dist/blocks/placements.d.ts +26 -0
  9. package/dist/blocks/schema.d.ts +38 -0
  10. package/dist/blocks/schema.js +9 -2
  11. package/dist/blocks/validate.d.ts +73 -0
  12. package/dist/blocks/validate.js +57 -8
  13. package/dist/cli.js +26 -2
  14. package/dist/config.js +67 -1
  15. package/dist/extension.d.ts +230 -22
  16. package/dist/harness/ambient.js +39 -185
  17. package/dist/harness/blocks/entries.js +47 -0
  18. package/dist/harness/blocks/state.js +58 -0
  19. package/dist/harness/blocks/tools.js +274 -0
  20. package/dist/harness/boot.js +259 -20
  21. package/dist/harness/builtins.js +15 -11
  22. package/dist/harness/event-bus.js +105 -10
  23. package/dist/harness/identity/accounts.js +186 -0
  24. package/dist/harness/identity/groups.js +14 -0
  25. package/dist/harness/identity/index.js +13 -6
  26. package/dist/harness/identity/json-store.js +15 -4
  27. package/dist/harness/identity/setup-code-check.js +9 -0
  28. package/dist/harness/identity/tokens.js +28 -5
  29. package/dist/harness/identity/users.js +111 -12
  30. package/dist/harness/interactions.js +292 -136
  31. package/dist/harness/loader.js +271 -17
  32. package/dist/harness/packages.js +1 -1
  33. package/dist/harness/reserved-sections.js +21 -0
  34. package/dist/harness/seam-host.js +92 -6
  35. package/dist/harness/sections/context-files.js +1 -1
  36. package/dist/harness/settings.js +63 -2
  37. package/dist/harness/stores.js +241 -4
  38. package/dist/harness/user-services.js +391 -0
  39. package/dist/harness/user-tools.js +187 -0
  40. package/dist/harness/workspace.js +61 -57
  41. package/dist/jobs/fire.js +10 -16
  42. package/dist/sandbox/availability.js +211 -0
  43. package/dist/sandbox/command-observer.js +59 -0
  44. package/dist/sandbox/grants.js +159 -0
  45. package/dist/sandbox/helper.js +29 -0
  46. package/dist/sandbox/isolation.js +29 -0
  47. package/dist/sandbox/launch.js +31 -0
  48. package/dist/sandbox/os-account.js +49 -0
  49. package/dist/sandbox/policy.js +274 -0
  50. package/dist/sandbox/pool.js +548 -0
  51. package/dist/sandbox/protocol.js +167 -0
  52. package/dist/sandbox/sandboxed-env.js +245 -0
  53. package/dist/sandbox/unavailable-env.js +49 -0
  54. package/dist/sandbox/worker.js +261 -0
  55. package/dist/serve.js +213 -25
  56. package/dist/server/auth/access-principal.js +16 -0
  57. package/dist/server/auth/pipeline.js +99 -36
  58. package/dist/server/blocks/answers.js +94 -0
  59. package/dist/server/blocks/chat-surfaces.js +393 -0
  60. package/dist/server/blocks/notes.js +216 -0
  61. package/dist/server/blocks/summaries.js +328 -0
  62. package/dist/server/commands.js +5 -0
  63. package/dist/server/content-parts-lazy.js +13 -0
  64. package/dist/server/conversation-handler.js +2 -5
  65. package/dist/server/conversation-ops.js +138 -37
  66. package/dist/server/helpers.js +18 -6
  67. package/dist/server/host-bridge.js +40 -20
  68. package/dist/server/images/cache.js +213 -0
  69. package/dist/server/images/convert.js +13 -0
  70. package/dist/server/images/fetch.js +302 -0
  71. package/dist/server/images/ranges.js +42 -0
  72. package/dist/server/images/rewrite.js +94 -0
  73. package/dist/server/images/sign.js +84 -0
  74. package/dist/server/images/sniff.js +62 -0
  75. package/dist/server/index.js +152 -4
  76. package/dist/server/login/flows.js +126 -0
  77. package/dist/server/login/http.js +31 -0
  78. package/dist/server/login/resolve.js +133 -0
  79. package/dist/server/login/setup-claim.js +55 -0
  80. package/dist/server/login/usernames.js +15 -0
  81. package/dist/server/notifications.js +21 -4
  82. package/dist/server/ownership.js +13 -0
  83. package/dist/server/routes/admin.js +58 -0
  84. package/dist/server/routes/auth.js +88 -2
  85. package/dist/server/routes/conversations.js +3 -6
  86. package/dist/server/routes/events.js +28 -3
  87. package/dist/server/routes/extensions.js +37 -5
  88. package/dist/server/routes/images.js +274 -0
  89. package/dist/server/routes/interactions.js +57 -68
  90. package/dist/server/routes/jobs.js +2 -8
  91. package/dist/server/routes/login.js +223 -0
  92. package/dist/server/routes/me.js +90 -0
  93. package/dist/server/routes/meta.js +19 -0
  94. package/dist/server/routes/onboarding.js +94 -84
  95. package/dist/server/routes/settings-sections.js +190 -0
  96. package/dist/server/routes/settings.js +16 -4
  97. package/dist/server/routes/submissions.js +1 -1
  98. package/dist/server/routes/surfaces.js +175 -23
  99. package/dist/server/routes/users.js +116 -20
  100. package/dist/server/schema-fixture.js +213 -0
  101. package/dist/server/session-meta.js +3 -0
  102. package/dist/server/shutdown.js +11 -4
  103. package/dist/server/static.js +18 -0
  104. package/dist/server/surfaces/dedup.js +9 -2
  105. package/dist/server/surfaces/service.js +181 -13
  106. package/dist/server/surfaces/settings-surfaces.js +597 -0
  107. package/dist/server/turns/submit-turn.js +26 -0
  108. package/dist/server/views.js +16 -0
  109. package/dist/settings/core-sections.js +532 -0
  110. package/dist/settings/pending.js +181 -0
  111. package/dist/settings/registry.js +453 -0
  112. package/dist/skills/index.js +6 -0
  113. package/node_modules/@sigrun/shared/CHANGELOG.md +11 -0
  114. package/node_modules/@sigrun/shared/dist/access.d.ts +4 -2
  115. package/node_modules/@sigrun/shared/dist/access.js +11 -4
  116. package/node_modules/@sigrun/shared/dist/ambient.d.ts +17 -74
  117. package/node_modules/@sigrun/shared/dist/ambient.js +8 -6
  118. package/node_modules/@sigrun/shared/dist/blocks/text.js +16 -2
  119. package/node_modules/@sigrun/shared/dist/blocks/transport.d.ts +4 -0
  120. package/node_modules/@sigrun/shared/dist/blocks/types.d.ts +21 -4
  121. package/node_modules/@sigrun/shared/dist/interactions.d.ts +22 -48
  122. package/node_modules/@sigrun/shared/dist/interactions.js +9 -7
  123. package/node_modules/@sigrun/shared/dist/settings.d.ts +27 -2
  124. package/node_modules/@sigrun/shared/dist/themes.d.ts +2 -3
  125. package/node_modules/@sigrun/shared/dist/themes.js +2 -3
  126. package/node_modules/@sigrun/shared/dist/turns.d.ts +14 -0
  127. package/node_modules/@sigrun/shared/dist/turns.js +53 -3
  128. package/node_modules/@sigrun/shared/dist/views.d.ts +202 -12
  129. package/node_modules/@sigrun/shared/dist/views.js +16 -0
  130. package/node_modules/@sigrun/shared/package.json +1 -1
  131. package/package.json +13 -9
  132. package/webui/dist/assets/index-SO2W4jjk.js +55 -0
  133. package/webui/dist/assets/index-yqkL6BSS.css +1 -0
  134. package/webui/dist/index.html +2 -2
  135. package/CONTEXT.md +0 -163
  136. package/webui/dist/assets/index-CiYInlLx.css +0 -1
  137. package/webui/dist/assets/index-JDfOQV9z.js +0 -52
@@ -0,0 +1,29 @@
1
+ /** The process-lifetime isolation decision, including auto's irreversible latch. */
2
+ export class IsolationState {
3
+ opts;
4
+ current;
5
+ latched = false;
6
+ constructor(opts) {
7
+ this.opts = opts;
8
+ }
9
+ active() {
10
+ if (this.opts.mode === "auto" && this.opts.availability.available && this.opts.userCount() > 1) {
11
+ this.latched = true;
12
+ }
13
+ const active = this.opts.mode === "off"
14
+ ? false
15
+ : this.opts.mode === "always"
16
+ ? this.opts.availability.available
17
+ : this.latched;
18
+ if (active !== this.current) {
19
+ this.current = active;
20
+ this.opts.log(`sandbox isolation: ${active ? "active" : "inactive"} (mode ${this.opts.mode}${this.opts.availability.available ? "" : `, unavailable: ${this.reason() ?? "unknown reason"}`})`);
21
+ }
22
+ return active;
23
+ }
24
+ reason() {
25
+ if (this.opts.mode === "off")
26
+ return "isolation mode is off";
27
+ return this.opts.availability.reason;
28
+ }
29
+ }
@@ -0,0 +1,31 @@
1
+ /** The shared helper launch shape for worker and per-user service processes. */
2
+ /** Apply a complete policy in the helper, then exec the requested sibling process. */
3
+ export function launchInJail(helper, policy, command, args, env) {
4
+ return {
5
+ command: helper,
6
+ args: ["run", "--", command, ...args],
7
+ env: {
8
+ ...env,
9
+ // Node compiled with --openssl-shared-config auto-loads
10
+ // $OPENSSLDIR/openssl.cnf at startup. On a package-managed
11
+ // Node (Homebrew) that directory lives OUTSIDE every policy
12
+ // prefix, and an unreadable config is a FATAL startup error —
13
+ // the jailed worker cannot start node at all. The worker
14
+ // policy grants tmp rw, so point the loader there; node does
15
+ // not require the file to exist. This deliberately overrides
16
+ // any operator-supplied OPENSSL_CONF: an operator config path
17
+ // is not granted inside the jail, so honoring it would be the
18
+ // same fatal startup error.
19
+ OPENSSL_CONF: `${policy.tmp}/openssl.cnf`,
20
+ SIGRUN_SANDBOX_POLICY: JSON.stringify({
21
+ ro: policy.ro,
22
+ rw: policy.rw,
23
+ ensureDirs: policy.ensureDirs,
24
+ minAbi: 6,
25
+ ...(policy.uid === undefined ? {} : { uid: policy.uid }),
26
+ ...(policy.gid === undefined ? {} : { gid: policy.gid }),
27
+ ...(policy.groups === undefined ? {} : { groups: policy.groups }),
28
+ }),
29
+ },
30
+ };
31
+ }
@@ -0,0 +1,49 @@
1
+ /** Resolve local OS accounts from text files, without native account modules. */
2
+ import { readFileSync } from "node:fs";
3
+ function fields(line) {
4
+ if (line === "" || line.startsWith("#"))
5
+ return undefined;
6
+ return line.split(":");
7
+ }
8
+ function id(value) {
9
+ if (value === undefined || !/^\d+$/.test(value))
10
+ return undefined;
11
+ const parsed = Number(value);
12
+ return Number.isSafeInteger(parsed) ? parsed : undefined;
13
+ }
14
+ /**
15
+ * Find one login in passwd and collect its primary plus supplementary gids.
16
+ * Missing, malformed, and root-linked records fail closed as `undefined`.
17
+ */
18
+ export function lookupOsAccount(login, read = (path) => readFileSync(path, "utf8")) {
19
+ const passwd = read("/etc/passwd");
20
+ let account;
21
+ for (const line of passwd.split(/\r?\n/)) {
22
+ const parts = fields(line);
23
+ if (parts === undefined || parts[0] !== login || parts.length < 7)
24
+ continue;
25
+ const uid = id(parts[2]);
26
+ const gid = id(parts[3]);
27
+ const home = parts[5];
28
+ if (uid === undefined || gid === undefined || uid === 0 || gid === 0 || home === "") {
29
+ return undefined;
30
+ }
31
+ account = { uid, gid, home };
32
+ break;
33
+ }
34
+ if (account === undefined)
35
+ return undefined;
36
+ const groups = new Set([account.gid]);
37
+ for (const line of read("/etc/group").split(/\r?\n/)) {
38
+ const parts = fields(line);
39
+ if (parts === undefined || parts.length < 4)
40
+ continue;
41
+ const gid = id(parts[2]);
42
+ if (gid === undefined || gid === 0)
43
+ continue;
44
+ const members = parts[3].split(",").map((member) => member.trim());
45
+ if (members.includes(login))
46
+ groups.add(gid);
47
+ }
48
+ return { ...account, groups: [...groups] };
49
+ }
@@ -0,0 +1,274 @@
1
+ /** Build the complete, deterministic Landlock policy for one directory user. */
2
+ import { execFileSync } from "node:child_process";
3
+ import { existsSync, mkdirSync, realpathSync } from "node:fs";
4
+ import { dirname, isAbsolute, join, resolve, sep } from "node:path";
5
+ import { agentEnv } from "../harness/agent-env.js";
6
+ import { revalidate, validateGrantPath } from "./grants.js";
7
+ function canonical(path) {
8
+ try {
9
+ return realpathSync(path);
10
+ }
11
+ catch {
12
+ return undefined;
13
+ }
14
+ }
15
+ function sortedUnique(paths) {
16
+ return [...new Set(paths)].sort((left, right) => left.localeCompare(right));
17
+ }
18
+ function sortedUniqueCanonical(paths) {
19
+ return sortedUnique(paths.flatMap((path) => {
20
+ const real = canonical(path);
21
+ return real === undefined ? [] : [real];
22
+ }));
23
+ }
24
+ function inside(path, root) {
25
+ return path === root || path.startsWith(root.endsWith(sep) ? root : root + sep);
26
+ }
27
+ function protectedRuntimePath(path, managed) {
28
+ const protectedPaths = [
29
+ ...Object.values(managed.roots),
30
+ managed.storagePath,
31
+ managed.coreRoot,
32
+ ];
33
+ return protectedPaths.some((protectedPath) => {
34
+ const real = canonical(protectedPath) ?? resolve(protectedPath);
35
+ return inside(path, real) || inside(real, path);
36
+ });
37
+ }
38
+ function filteredRuntimePaths(paths, managed) {
39
+ return paths.filter((path) => !inside("/dev/shm", path) &&
40
+ (managed === undefined || !protectedRuntimePath(path, managed)));
41
+ }
42
+ /**
43
+ * The node binary directory plus every existing shared-library directory
44
+ * reported by a trusted absolute ldd (/usr/bin/ldd, then /bin/ldd). Static
45
+ * builds and loader failures fall back to the binary directory alone. Only
46
+ * absolute ldd targets are read; virtual entries have no such target. All
47
+ * candidates are realpath-canonicalized before deduplication and sorting.
48
+ * The raw cache is keyed by execPath; protected-root filtering is per call,
49
+ * so one managed layout can never reuse another layout's filtered result.
50
+ */
51
+ export function nodeRuntimePaths(execPath = process.execPath, managed, readLdd) {
52
+ const binaryDirectory = canonical(dirname(resolve(execPath)));
53
+ // Injected loader output is a test seam, never a production cache identity.
54
+ const cached = readLdd === undefined ? runtimePaths.get(execPath) : undefined;
55
+ if (cached !== undefined) {
56
+ return filteredRuntimePaths(cached, managed);
57
+ }
58
+ const paths = binaryDirectory === undefined ? [] : [binaryDirectory];
59
+ const loader = ["/usr/bin/ldd", "/bin/ldd"].find((path) => existsSync(path));
60
+ if (loader !== undefined) {
61
+ try {
62
+ const output = readLdd === undefined
63
+ ? execFileSync(loader, [execPath], {
64
+ encoding: "utf8",
65
+ stdio: ["ignore", "pipe", "ignore"],
66
+ })
67
+ : readLdd(loader, execPath);
68
+ for (const match of output.matchAll(/=>\s+(\/[^\s]+)/g)) {
69
+ const target = match[1];
70
+ if (target !== undefined && isAbsolute(target)) {
71
+ paths.push(dirname(target));
72
+ }
73
+ }
74
+ }
75
+ catch {
76
+ // A static executable, a missing executable, or ldd failure adds nothing.
77
+ }
78
+ }
79
+ const result = sortedUniqueCanonical(paths);
80
+ if (readLdd === undefined)
81
+ runtimePaths.set(execPath, result);
82
+ return filteredRuntimePaths(result, managed);
83
+ }
84
+ const runtimePaths = new Map();
85
+ /** Only the core module trees, never the core installation root itself. */
86
+ export function defaultModuleTrees(coreRoot) {
87
+ const code = existsSync(join(coreRoot, "dist"))
88
+ ? join(coreRoot, "dist")
89
+ : join(coreRoot, "src");
90
+ return [code, join(coreRoot, "shared"), join(coreRoot, "node_modules")];
91
+ }
92
+ /**
93
+ * The worker's ESM scope resolution reads coreRoot/package.json even when
94
+ * the entry lives in a granted module tree; an unreadable package config
95
+ * is a fatal startup error, so the worker policy grants this one file.
96
+ */
97
+ export function defaultPackageConfig(coreRoot) {
98
+ return join(coreRoot, "package.json");
99
+ }
100
+ function validateUserPath(path, record, managed, inheritedFrom) {
101
+ const candidate = canonical(path) ?? resolve(path);
102
+ if (inside(candidate, "/dev/shm") || inside("/dev/shm", candidate)) {
103
+ return { ok: false, reason: "path is or contains /dev/shm" };
104
+ }
105
+ return validateGrantPath(path, {
106
+ managed,
107
+ user: record.user,
108
+ ...(inheritedFrom === undefined ? {} : { inheritedFrom }),
109
+ });
110
+ }
111
+ function failedStoredPath(record, managed) {
112
+ if (record.workspace !== undefined &&
113
+ !validateUserPath(record.workspace, record, managed).ok) {
114
+ return record.workspace;
115
+ }
116
+ return record.grants?.find((grant) => !validateUserPath(grant.path, record, managed, grant.inheritedFrom).ok)?.path;
117
+ }
118
+ function invalidReason(path, reason) {
119
+ return { ok: false, reason: `${path}: ${reason}` };
120
+ }
121
+ function canonicalModuleTrees(trees, managed) {
122
+ const coreRoot = canonical(managed.coreRoot);
123
+ if (coreRoot === undefined) {
124
+ return invalidReason(managed.coreRoot, "core root cannot be resolved");
125
+ }
126
+ const paths = [];
127
+ for (const tree of trees) {
128
+ // Every path source is canonicalized, then checked by its KIND:
129
+ // module trees are STRICT core descendants (never coreRoot itself);
130
+ // runtime libraries exclude protected roots; user paths use the full
131
+ // validator. A tree may neither sit inside nor contain a store root or
132
+ // /dev/shm (defence in depth if coreRoot ever overlaps one); an ancestor
133
+ // such as / is already excluded by the strict-descendant rule.
134
+ const candidate = canonical(tree);
135
+ if (candidate === undefined) {
136
+ return invalidReason(tree, "module tree does not exist or cannot be resolved");
137
+ }
138
+ if (candidate === coreRoot || !inside(candidate, coreRoot)) {
139
+ return invalidReason(tree, "module tree must be a strict descendant of coreRoot");
140
+ }
141
+ if (inside(candidate, "/dev/shm") ||
142
+ inside("/dev/shm", candidate) ||
143
+ Object.values(managed.roots).some((root) => {
144
+ const protectedRoot = canonical(root) ?? resolve(root);
145
+ return inside(candidate, protectedRoot) || inside(protectedRoot, candidate);
146
+ })) {
147
+ return invalidReason(tree, "module tree is inside a protected root or /dev/shm");
148
+ }
149
+ paths.push(candidate);
150
+ }
151
+ return { ok: true, paths };
152
+ }
153
+ /** Build a user's grants and environment, or fail closed with the bad path. */
154
+ export function buildUserPolicy(record, ctx) {
155
+ const validation = revalidate(record, ctx.managed);
156
+ if (!validation.ok) {
157
+ const path = failedStoredPath(record, ctx.managed);
158
+ return {
159
+ ok: false,
160
+ reason: `${path ?? "stored workspace or grant"}: ${validation.reason}`,
161
+ };
162
+ }
163
+ // EVERY per-user path passes the same validator before entering policy:
164
+ // stored workspace/grants via revalidate, and workspace/home again below.
165
+ // An osAccount home must validate BEFORE any directory is created.
166
+ // tmp is the ONE sanctioned stateDir exception: it is derived only from
167
+ // stateDir/sandbox/<user>/tmp and never silently routed around validation.
168
+ const userArea = ctx.managed.userAreas(record.user);
169
+ const requestedWorkspace = record.workspace ?? ctx.osAccount?.home ?? join(userArea, "workspace");
170
+ const requestedHome = ctx.osAccount?.home ?? join(userArea, "home");
171
+ if (ctx.osAccount !== undefined) {
172
+ const validation = validateUserPath(requestedHome, record, ctx.managed);
173
+ if (!validation.ok)
174
+ return invalidReason(requestedHome, validation.reason);
175
+ }
176
+ const tmp = join(ctx.managed.roots.stateDir, "sandbox", record.user, "tmp");
177
+ for (const path of [requestedWorkspace, requestedHome, tmp]) {
178
+ if (ctx.osAccount !== undefined && path === tmp)
179
+ continue;
180
+ mkdirSync(path, { recursive: true, mode: 0o700 });
181
+ }
182
+ const workspaceValidation = validateUserPath(requestedWorkspace, record, ctx.managed);
183
+ if (!workspaceValidation.ok) {
184
+ return invalidReason(requestedWorkspace, workspaceValidation.reason);
185
+ }
186
+ const homeValidation = validateUserPath(requestedHome, record, ctx.managed);
187
+ if (!homeValidation.ok)
188
+ return invalidReason(requestedHome, homeValidation.reason);
189
+ const realTmp = canonical(tmp) ?? (ctx.osAccount === undefined
190
+ ? undefined
191
+ : resolve(tmp));
192
+ if (realTmp === undefined)
193
+ return invalidReason(tmp, "path cannot be resolved");
194
+ const realWorkspace = workspaceValidation.realpath;
195
+ const realHome = homeValidation.realpath;
196
+ // A source checkout that has ever been built has BOTH src/ and dist/;
197
+ // defaultModuleTrees picks one while the worker entry may use the other
198
+ // (dist worker.js when present, src worker.ts via tsx otherwise).
199
+ // Grant whichever sibling code tree exists so both shapes stay readable.
200
+ const siblingTree = join(ctx.managed.coreRoot, "src");
201
+ const extraTrees = existsSync(siblingTree) && !ctx.moduleTrees.includes(siblingTree)
202
+ ? [...ctx.moduleTrees, siblingTree]
203
+ : ctx.moduleTrees;
204
+ const moduleTrees = canonicalModuleTrees(extraTrees, ctx.managed);
205
+ if (!moduleTrees.ok)
206
+ return moduleTrees;
207
+ const grants = [];
208
+ for (const grant of record.grants ?? []) {
209
+ const result = validateUserPath(grant.path, record, ctx.managed, grant.inheritedFrom);
210
+ if (!result.ok)
211
+ return invalidReason(grant.path, result.reason);
212
+ grants.push({ ...grant, path: result.realpath });
213
+ }
214
+ const rw = sortedUnique([
215
+ realWorkspace,
216
+ ...grants.flatMap((grant) => (grant.access === "rw" ? [grant.path] : [])),
217
+ realHome,
218
+ realTmp,
219
+ // Device creation belongs to rw, but the selected devices' ordinary
220
+ // read/write I/O survives a read-only Landlock rule. rw stays empty
221
+ // except for workspace/home/tmp/grants so /dev/shm remains denied.
222
+ ]);
223
+ const ro = sortedUnique([
224
+ "/dev",
225
+ "/dev/null",
226
+ "/dev/zero",
227
+ "/dev/full",
228
+ "/dev/random",
229
+ "/dev/urandom",
230
+ "/dev/tty",
231
+ "/dev/pts",
232
+ "/usr",
233
+ "/bin",
234
+ "/sbin",
235
+ "/lib",
236
+ "/lib32",
237
+ "/lib64",
238
+ "/etc",
239
+ "/opt",
240
+ "/proc",
241
+ "/sys",
242
+ ...nodeRuntimePaths(process.execPath, ctx.managed),
243
+ ...moduleTrees.paths,
244
+ defaultPackageConfig(ctx.managed.coreRoot),
245
+ ...grants.flatMap((grant) => (grant.access === "ro" ? [grant.path] : [])),
246
+ ]);
247
+ const env = agentEnv();
248
+ delete env.SSH_AUTH_SOCK;
249
+ env.HOME = realHome;
250
+ env.TMPDIR = realTmp;
251
+ env.USER = record.user;
252
+ const policy = {
253
+ user: record.user,
254
+ workspace: realWorkspace,
255
+ home: realHome,
256
+ tmp: realTmp,
257
+ ro,
258
+ rw,
259
+ ensureDirs: ctx.osAccount === undefined ? [] : [realTmp],
260
+ env,
261
+ };
262
+ if (ctx.osAccount !== undefined) {
263
+ return {
264
+ ok: true,
265
+ policy: {
266
+ ...policy,
267
+ uid: ctx.osAccount.uid,
268
+ gid: ctx.osAccount.gid,
269
+ groups: ctx.osAccount.groups,
270
+ },
271
+ };
272
+ }
273
+ return { ok: true, policy };
274
+ }