@sigrun/sigrun 0.1.6 → 0.2.0

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 (126) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +352 -36
  3. package/dist/blocks/schema.js +9 -2
  4. package/dist/blocks/validate.js +57 -8
  5. package/dist/cli.js +26 -2
  6. package/dist/config.js +67 -1
  7. package/dist/extension.d.ts +189 -15
  8. package/dist/harness/ambient.js +39 -185
  9. package/dist/harness/blocks/entries.js +47 -0
  10. package/dist/harness/blocks/state.js +58 -0
  11. package/dist/harness/blocks/tools.js +274 -0
  12. package/dist/harness/boot.js +253 -20
  13. package/dist/harness/builtins.js +15 -11
  14. package/dist/harness/event-bus.js +105 -10
  15. package/dist/harness/identity/accounts.js +186 -0
  16. package/dist/harness/identity/groups.js +14 -0
  17. package/dist/harness/identity/index.js +13 -6
  18. package/dist/harness/identity/json-store.js +15 -4
  19. package/dist/harness/identity/setup-code-check.js +9 -0
  20. package/dist/harness/identity/tokens.js +28 -5
  21. package/dist/harness/identity/users.js +111 -12
  22. package/dist/harness/interactions.js +292 -136
  23. package/dist/harness/loader.js +271 -17
  24. package/dist/harness/packages.js +1 -1
  25. package/dist/harness/reserved-sections.js +21 -0
  26. package/dist/harness/seam-host.js +80 -6
  27. package/dist/harness/settings.js +63 -2
  28. package/dist/harness/stores.js +241 -4
  29. package/dist/harness/user-services.js +391 -0
  30. package/dist/harness/user-tools.js +187 -0
  31. package/dist/harness/workspace.js +61 -57
  32. package/dist/jobs/fire.js +10 -16
  33. package/dist/sandbox/availability.js +162 -0
  34. package/dist/sandbox/command-observer.js +59 -0
  35. package/dist/sandbox/grants.js +159 -0
  36. package/dist/sandbox/helper.js +29 -0
  37. package/dist/sandbox/isolation.js +29 -0
  38. package/dist/sandbox/launch.js +20 -0
  39. package/dist/sandbox/os-account.js +49 -0
  40. package/dist/sandbox/policy.js +253 -0
  41. package/dist/sandbox/pool.js +548 -0
  42. package/dist/sandbox/protocol.js +167 -0
  43. package/dist/sandbox/sandboxed-env.js +245 -0
  44. package/dist/sandbox/unavailable-env.js +49 -0
  45. package/dist/sandbox/worker.js +281 -0
  46. package/dist/serve.js +82 -7
  47. package/dist/server/auth/access-principal.js +16 -0
  48. package/dist/server/auth/pipeline.js +47 -33
  49. package/dist/server/blocks/answers.js +94 -0
  50. package/dist/server/blocks/chat-surfaces.js +393 -0
  51. package/dist/server/blocks/notes.js +216 -0
  52. package/dist/server/blocks/summaries.js +328 -0
  53. package/dist/server/commands.js +5 -0
  54. package/dist/server/content-parts-lazy.js +13 -0
  55. package/dist/server/conversation-handler.js +2 -5
  56. package/dist/server/conversation-ops.js +138 -37
  57. package/dist/server/helpers.js +18 -6
  58. package/dist/server/host-bridge.js +40 -20
  59. package/dist/server/images/cache.js +205 -0
  60. package/dist/server/images/convert.js +13 -0
  61. package/dist/server/images/fetch.js +302 -0
  62. package/dist/server/images/ranges.js +42 -0
  63. package/dist/server/images/rewrite.js +94 -0
  64. package/dist/server/images/sign.js +84 -0
  65. package/dist/server/images/sniff.js +62 -0
  66. package/dist/server/index.js +144 -4
  67. package/dist/server/login/flows.js +126 -0
  68. package/dist/server/login/http.js +31 -0
  69. package/dist/server/login/resolve.js +133 -0
  70. package/dist/server/login/setup-claim.js +55 -0
  71. package/dist/server/login/usernames.js +15 -0
  72. package/dist/server/ownership.js +13 -0
  73. package/dist/server/routes/admin.js +58 -0
  74. package/dist/server/routes/auth.js +88 -2
  75. package/dist/server/routes/conversations.js +3 -6
  76. package/dist/server/routes/events.js +28 -3
  77. package/dist/server/routes/extensions.js +37 -5
  78. package/dist/server/routes/images.js +267 -0
  79. package/dist/server/routes/interactions.js +57 -68
  80. package/dist/server/routes/jobs.js +2 -8
  81. package/dist/server/routes/login.js +223 -0
  82. package/dist/server/routes/me.js +90 -0
  83. package/dist/server/routes/meta.js +15 -0
  84. package/dist/server/routes/onboarding.js +94 -84
  85. package/dist/server/routes/settings-sections.js +190 -0
  86. package/dist/server/routes/settings.js +16 -4
  87. package/dist/server/routes/submissions.js +1 -1
  88. package/dist/server/routes/surfaces.js +175 -23
  89. package/dist/server/routes/users.js +116 -20
  90. package/dist/server/schema-fixture.js +213 -0
  91. package/dist/server/session-meta.js +3 -0
  92. package/dist/server/shutdown.js +11 -4
  93. package/dist/server/static.js +18 -0
  94. package/dist/server/surfaces/dedup.js +9 -2
  95. package/dist/server/surfaces/service.js +181 -13
  96. package/dist/server/surfaces/settings-surfaces.js +597 -0
  97. package/dist/server/turns/submit-turn.js +26 -0
  98. package/dist/server/views.js +16 -0
  99. package/dist/settings/core-sections.js +532 -0
  100. package/dist/settings/pending.js +181 -0
  101. package/dist/settings/registry.js +453 -0
  102. package/dist/skills/index.js +6 -0
  103. package/node_modules/@sigrun/shared/CHANGELOG.md +6 -0
  104. package/node_modules/@sigrun/shared/dist/access.d.ts +4 -2
  105. package/node_modules/@sigrun/shared/dist/access.js +11 -4
  106. package/node_modules/@sigrun/shared/dist/ambient.d.ts +17 -74
  107. package/node_modules/@sigrun/shared/dist/ambient.js +8 -6
  108. package/node_modules/@sigrun/shared/dist/blocks/text.js +16 -2
  109. package/node_modules/@sigrun/shared/dist/blocks/transport.d.ts +4 -0
  110. package/node_modules/@sigrun/shared/dist/blocks/types.d.ts +21 -4
  111. package/node_modules/@sigrun/shared/dist/interactions.d.ts +22 -48
  112. package/node_modules/@sigrun/shared/dist/interactions.js +9 -7
  113. package/node_modules/@sigrun/shared/dist/settings.d.ts +27 -2
  114. package/node_modules/@sigrun/shared/dist/themes.d.ts +2 -3
  115. package/node_modules/@sigrun/shared/dist/themes.js +2 -3
  116. package/node_modules/@sigrun/shared/dist/turns.d.ts +14 -0
  117. package/node_modules/@sigrun/shared/dist/turns.js +53 -3
  118. package/node_modules/@sigrun/shared/dist/views.d.ts +175 -12
  119. package/node_modules/@sigrun/shared/dist/views.js +16 -0
  120. package/node_modules/@sigrun/shared/package.json +1 -1
  121. package/package.json +11 -6
  122. package/webui/dist/assets/index-CxB969qp.js +55 -0
  123. package/webui/dist/assets/index-yqkL6BSS.css +1 -0
  124. package/webui/dist/index.html +2 -2
  125. package/webui/dist/assets/index-CiYInlLx.css +0 -1
  126. package/webui/dist/assets/index-JDfOQV9z.js +0 -52
@@ -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,253 @@
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
+ function validateUserPath(path, record, managed, inheritedFrom) {
93
+ const candidate = canonical(path) ?? resolve(path);
94
+ if (inside(candidate, "/dev/shm") || inside("/dev/shm", candidate)) {
95
+ return { ok: false, reason: "path is or contains /dev/shm" };
96
+ }
97
+ return validateGrantPath(path, {
98
+ managed,
99
+ user: record.user,
100
+ ...(inheritedFrom === undefined ? {} : { inheritedFrom }),
101
+ });
102
+ }
103
+ function failedStoredPath(record, managed) {
104
+ if (record.workspace !== undefined &&
105
+ !validateUserPath(record.workspace, record, managed).ok) {
106
+ return record.workspace;
107
+ }
108
+ return record.grants?.find((grant) => !validateUserPath(grant.path, record, managed, grant.inheritedFrom).ok)?.path;
109
+ }
110
+ function invalidReason(path, reason) {
111
+ return { ok: false, reason: `${path}: ${reason}` };
112
+ }
113
+ function canonicalModuleTrees(trees, managed) {
114
+ const coreRoot = canonical(managed.coreRoot);
115
+ if (coreRoot === undefined) {
116
+ return invalidReason(managed.coreRoot, "core root cannot be resolved");
117
+ }
118
+ const paths = [];
119
+ for (const tree of trees) {
120
+ // Every path source is canonicalized, then checked by its KIND:
121
+ // module trees are STRICT core descendants (never coreRoot itself);
122
+ // runtime libraries exclude protected roots; user paths use the full
123
+ // validator. A tree may neither sit inside nor contain a store root or
124
+ // /dev/shm (defence in depth if coreRoot ever overlaps one); an ancestor
125
+ // such as / is already excluded by the strict-descendant rule.
126
+ const candidate = canonical(tree);
127
+ if (candidate === undefined) {
128
+ return invalidReason(tree, "module tree does not exist or cannot be resolved");
129
+ }
130
+ if (candidate === coreRoot || !inside(candidate, coreRoot)) {
131
+ return invalidReason(tree, "module tree must be a strict descendant of coreRoot");
132
+ }
133
+ if (inside(candidate, "/dev/shm") ||
134
+ inside("/dev/shm", candidate) ||
135
+ Object.values(managed.roots).some((root) => {
136
+ const protectedRoot = canonical(root) ?? resolve(root);
137
+ return inside(candidate, protectedRoot) || inside(protectedRoot, candidate);
138
+ })) {
139
+ return invalidReason(tree, "module tree is inside a protected root or /dev/shm");
140
+ }
141
+ paths.push(candidate);
142
+ }
143
+ return { ok: true, paths };
144
+ }
145
+ /** Build a user's grants and environment, or fail closed with the bad path. */
146
+ export function buildUserPolicy(record, ctx) {
147
+ const validation = revalidate(record, ctx.managed);
148
+ if (!validation.ok) {
149
+ const path = failedStoredPath(record, ctx.managed);
150
+ return {
151
+ ok: false,
152
+ reason: `${path ?? "stored workspace or grant"}: ${validation.reason}`,
153
+ };
154
+ }
155
+ // EVERY per-user path passes the same validator before entering policy:
156
+ // stored workspace/grants via revalidate, and workspace/home again below.
157
+ // An osAccount home must validate BEFORE any directory is created.
158
+ // tmp is the ONE sanctioned stateDir exception: it is derived only from
159
+ // stateDir/sandbox/<user>/tmp and never silently routed around validation.
160
+ const userArea = ctx.managed.userAreas(record.user);
161
+ const requestedWorkspace = record.workspace ?? ctx.osAccount?.home ?? join(userArea, "workspace");
162
+ const requestedHome = ctx.osAccount?.home ?? join(userArea, "home");
163
+ if (ctx.osAccount !== undefined) {
164
+ const validation = validateUserPath(requestedHome, record, ctx.managed);
165
+ if (!validation.ok)
166
+ return invalidReason(requestedHome, validation.reason);
167
+ }
168
+ const tmp = join(ctx.managed.roots.stateDir, "sandbox", record.user, "tmp");
169
+ for (const path of [requestedWorkspace, requestedHome, tmp]) {
170
+ if (ctx.osAccount !== undefined && path === tmp)
171
+ continue;
172
+ mkdirSync(path, { recursive: true, mode: 0o700 });
173
+ }
174
+ const workspaceValidation = validateUserPath(requestedWorkspace, record, ctx.managed);
175
+ if (!workspaceValidation.ok) {
176
+ return invalidReason(requestedWorkspace, workspaceValidation.reason);
177
+ }
178
+ const homeValidation = validateUserPath(requestedHome, record, ctx.managed);
179
+ if (!homeValidation.ok)
180
+ return invalidReason(requestedHome, homeValidation.reason);
181
+ const realTmp = canonical(tmp) ?? (ctx.osAccount === undefined
182
+ ? undefined
183
+ : resolve(tmp));
184
+ if (realTmp === undefined)
185
+ return invalidReason(tmp, "path cannot be resolved");
186
+ const realWorkspace = workspaceValidation.realpath;
187
+ const realHome = homeValidation.realpath;
188
+ const moduleTrees = canonicalModuleTrees(ctx.moduleTrees, ctx.managed);
189
+ if (!moduleTrees.ok)
190
+ return moduleTrees;
191
+ const grants = [];
192
+ for (const grant of record.grants ?? []) {
193
+ const result = validateUserPath(grant.path, record, ctx.managed, grant.inheritedFrom);
194
+ if (!result.ok)
195
+ return invalidReason(grant.path, result.reason);
196
+ grants.push({ ...grant, path: result.realpath });
197
+ }
198
+ const rw = sortedUnique([
199
+ realWorkspace,
200
+ ...grants.flatMap((grant) => (grant.access === "rw" ? [grant.path] : [])),
201
+ realHome,
202
+ realTmp,
203
+ "/dev/null",
204
+ "/dev/zero",
205
+ "/dev/full",
206
+ "/dev/random",
207
+ "/dev/urandom",
208
+ "/dev/tty",
209
+ "/dev/pts",
210
+ ]);
211
+ const ro = sortedUnique([
212
+ "/usr",
213
+ "/bin",
214
+ "/sbin",
215
+ "/lib",
216
+ "/lib32",
217
+ "/lib64",
218
+ "/etc",
219
+ "/opt",
220
+ "/proc",
221
+ "/sys",
222
+ ...nodeRuntimePaths(process.execPath, ctx.managed),
223
+ ...moduleTrees.paths,
224
+ ...grants.flatMap((grant) => (grant.access === "ro" ? [grant.path] : [])),
225
+ ]);
226
+ const env = agentEnv();
227
+ delete env.SSH_AUTH_SOCK;
228
+ env.HOME = realHome;
229
+ env.TMPDIR = realTmp;
230
+ env.USER = record.user;
231
+ const policy = {
232
+ user: record.user,
233
+ workspace: realWorkspace,
234
+ home: realHome,
235
+ tmp: realTmp,
236
+ ro,
237
+ rw,
238
+ ensureDirs: ctx.osAccount === undefined ? [] : [realTmp],
239
+ env,
240
+ };
241
+ if (ctx.osAccount !== undefined) {
242
+ return {
243
+ ok: true,
244
+ policy: {
245
+ ...policy,
246
+ uid: ctx.osAccount.uid,
247
+ gid: ctx.osAccount.gid,
248
+ groups: ctx.osAccount.groups,
249
+ },
250
+ };
251
+ }
252
+ return { ok: true, policy };
253
+ }