@codyswann/lisa 4.72.6 → 4.73.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.
- package/all/copy-overwrite/scripts/lib/automation-provenance-contract.mjs +19 -0
- package/all/copy-overwrite/scripts/lib/automation-provenance-local.mjs +4 -2
- package/all/copy-overwrite/scripts/lib/history-secret-evidence-shape.mjs +398 -6
- package/all/copy-overwrite/scripts/lib/history-secret-evidence.mjs +175 -7
- package/all/copy-overwrite/scripts/lib/npm-update-cancel-origin.mjs +7 -1
- package/all/copy-overwrite/scripts/lib/npm-update-contract.mjs +11 -66
- package/all/copy-overwrite/scripts/lib/npm-update-gate.mjs +3 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper-inventory.mjs +10 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hosted-gate.mjs +162 -121
- package/all/copy-overwrite/scripts/lib/npm-update-hosted-hook.mjs +8 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hosted-scope.mjs +177 -0
- package/all/copy-overwrite/scripts/lib/npm-update-policy.mjs +73 -0
- package/all/copy-overwrite/scripts/lib/npm-update-publish.mjs +1 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-mysql-daemon.mjs +227 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-mysql-resource.mjs +176 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-mysql-storage.mjs +201 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-mysql.mjs +220 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-runtime-contract.mjs +75 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-tool-downloads.mjs +187 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-tool-identity.mjs +288 -0
- package/all/copy-overwrite/scripts/lib/npm-update-rails-tools.mjs +292 -0
- package/all/copy-overwrite/scripts/lisa-automation-provenance.mjs +3 -0
- package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +1 -1
- package/all/copy-overwrite/scripts/lisa-scratch-run.sh +7 -4
- package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +148 -20
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +126 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/lisa.d.ts.map +1 -1
- package/dist/core/lisa.js +2 -1
- package/dist/core/lisa.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +79 -17
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/parity-safety-net.sh +1 -1
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/hooks/parity-safety-net.sh +1 -1
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/hooks/parity-safety-net.sh +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/hooks/parity-safety-net.sh +1 -1
- package/scripts/two-channel-couplings.json +1 -1
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
// This file is managed by Lisa and IS replaced on each `lisa` run.
|
|
2
|
+
// Do not edit directly — durable changes belong upstream in Lisa.
|
|
3
|
+
|
|
4
|
+
/** Fixed native runtime identities are rechecked before each Docker observation. */
|
|
5
|
+
import {
|
|
6
|
+
constants,
|
|
7
|
+
existsSync,
|
|
8
|
+
openSync,
|
|
9
|
+
closeSync,
|
|
10
|
+
fstatSync,
|
|
11
|
+
readFileSync,
|
|
12
|
+
realpathSync,
|
|
13
|
+
lstatSync,
|
|
14
|
+
} from "node:fs";
|
|
15
|
+
import { join, isAbsolute, dirname } from "node:path";
|
|
16
|
+
import { createHash } from "node:crypto";
|
|
17
|
+
import { required } from "./npm-update-contract.mjs";
|
|
18
|
+
import { runProcess } from "./npm-update-process.mjs";
|
|
19
|
+
|
|
20
|
+
/** Runtime calls consume the original absolute phase, including every identity readback. */
|
|
21
|
+
export function runtimeTime(deadline, maximum = 10000) {
|
|
22
|
+
const remaining = deadline - Date.now();
|
|
23
|
+
required(
|
|
24
|
+
Number.isSafeInteger(deadline) && remaining > 0 && remaining <= 1800000,
|
|
25
|
+
"original runtime deadline is absent or expired"
|
|
26
|
+
);
|
|
27
|
+
return Math.min(remaining, maximum);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Initial qualification reads only an already bounded no-follow executable descriptor. */
|
|
31
|
+
function toolSnapshot(file) {
|
|
32
|
+
required(
|
|
33
|
+
isAbsolute(file) && realpathSync(file) === file,
|
|
34
|
+
"native tool identity is aliased or absent"
|
|
35
|
+
);
|
|
36
|
+
const fd = openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
37
|
+
try {
|
|
38
|
+
const st = fstatSync(fd);
|
|
39
|
+
required(
|
|
40
|
+
st.isFile() &&
|
|
41
|
+
st.nlink === 1 &&
|
|
42
|
+
(st.mode & 0o111) !== 0 &&
|
|
43
|
+
st.size <= 536870912,
|
|
44
|
+
"native tool identity is not a bounded executable"
|
|
45
|
+
);
|
|
46
|
+
const hash = createHash("sha256").update(readFileSync(fd)).digest("hex");
|
|
47
|
+
const current = lstatSync(file);
|
|
48
|
+
required(
|
|
49
|
+
current.dev === st.dev &&
|
|
50
|
+
current.ino === st.ino &&
|
|
51
|
+
!current.isSymbolicLink(),
|
|
52
|
+
"native tool identity changed"
|
|
53
|
+
);
|
|
54
|
+
return { dev: st.dev, ino: st.ino, uid: st.uid, sha256: hash };
|
|
55
|
+
} finally {
|
|
56
|
+
closeSync(fd);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** An owned descriptor cannot authorize a symlink, shared inode, nonexecutable or substituted bytes. */
|
|
61
|
+
export function toolIdentity(file, expected) {
|
|
62
|
+
required(
|
|
63
|
+
/^[a-f0-9]{64}$/.test(expected),
|
|
64
|
+
"native tool identity is aliased or absent"
|
|
65
|
+
);
|
|
66
|
+
const identity = toolSnapshot(file);
|
|
67
|
+
required(identity.sha256 === expected, "native tool identity changed");
|
|
68
|
+
return identity;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Private config is generated data only; no original auths, helper or credential store is inherited. */
|
|
72
|
+
function dockerEnvironment(docker) {
|
|
73
|
+
const env = docker.env;
|
|
74
|
+
required(
|
|
75
|
+
env &&
|
|
76
|
+
Object.keys(env).sort().join(",") === "DOCKER_CONFIG,HOME,PATH" &&
|
|
77
|
+
isAbsolute(env.HOME) &&
|
|
78
|
+
env.PATH.split(":").every(isAbsolute) &&
|
|
79
|
+
env.DOCKER_CONFIG === join(env.HOME, "docker-config"),
|
|
80
|
+
"Docker environment differs"
|
|
81
|
+
);
|
|
82
|
+
for (const dir of [env.HOME, env.DOCKER_CONFIG]) {
|
|
83
|
+
const st = lstatSync(dir);
|
|
84
|
+
required(
|
|
85
|
+
realpathSync(dir) === dir &&
|
|
86
|
+
st.isDirectory() &&
|
|
87
|
+
st.uid === process.getuid() &&
|
|
88
|
+
(st.mode & 0o077) === 0,
|
|
89
|
+
"Docker configuration directory is not private owned storage"
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
const file = join(env.DOCKER_CONFIG, "config.json");
|
|
93
|
+
const fd = openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
94
|
+
try {
|
|
95
|
+
const st = fstatSync(fd);
|
|
96
|
+
required(
|
|
97
|
+
st.isFile() &&
|
|
98
|
+
st.nlink === 1 &&
|
|
99
|
+
st.uid === process.getuid() &&
|
|
100
|
+
(st.mode & 0o077) === 0 &&
|
|
101
|
+
st.size <= 4096,
|
|
102
|
+
"Docker configuration is not private data"
|
|
103
|
+
);
|
|
104
|
+
const bytes = readFileSync(fd);
|
|
105
|
+
const config = JSON.parse(bytes.toString());
|
|
106
|
+
const dirs = docker.plugins ? [dirname(docker.plugins.compose.path)] : [];
|
|
107
|
+
required(
|
|
108
|
+
Object.keys(config).join(",") ===
|
|
109
|
+
(dirs.length ? "cliPluginsExtraDirs" : "") &&
|
|
110
|
+
JSON.stringify(config) ===
|
|
111
|
+
JSON.stringify(dirs.length ? { cliPluginsExtraDirs: dirs } : {}) &&
|
|
112
|
+
bytes.toString() === `${JSON.stringify(config)}\n`,
|
|
113
|
+
"Docker configuration differs"
|
|
114
|
+
);
|
|
115
|
+
if (docker.plugins) {
|
|
116
|
+
required(
|
|
117
|
+
Object.keys(docker.plugins).sort().join(",") === "buildx,compose" &&
|
|
118
|
+
dirname(docker.plugins.buildx.path) === dirs[0] &&
|
|
119
|
+
dirs[0] === join(env.HOME, "docker-plugins"),
|
|
120
|
+
"Docker plugin identity differs"
|
|
121
|
+
);
|
|
122
|
+
for (const plugin of Object.values(docker.plugins))
|
|
123
|
+
toolIdentity(plugin.path, plugin.sha256);
|
|
124
|
+
}
|
|
125
|
+
} finally {
|
|
126
|
+
closeSync(fd);
|
|
127
|
+
}
|
|
128
|
+
return env;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Native query errors never imply absence; daemon architecture is separate from the service platform. */
|
|
132
|
+
export async function assertDockerQualified(docker, deadline) {
|
|
133
|
+
runtimeTime(deadline);
|
|
134
|
+
const env = dockerEnvironment(docker);
|
|
135
|
+
required(docker.version === "29.8.2", "unsupported native Docker identity");
|
|
136
|
+
const identity = toolIdentity(docker.path, docker.sha256);
|
|
137
|
+
const version = await runProcess(docker.path, ["--version"], {
|
|
138
|
+
env,
|
|
139
|
+
timeout: runtimeTime(deadline),
|
|
140
|
+
maximum: 4096,
|
|
141
|
+
});
|
|
142
|
+
required(
|
|
143
|
+
/^Docker version 29\.8\.2, build [a-z0-9]+\n$/.test(
|
|
144
|
+
version.stdout.toString()
|
|
145
|
+
),
|
|
146
|
+
"native Docker version identity differs"
|
|
147
|
+
);
|
|
148
|
+
const result = await runProcess(
|
|
149
|
+
docker.path,
|
|
150
|
+
["info", "--format", '{"ID":{{json .ID}},"OSType":{{json .OSType}}}'],
|
|
151
|
+
{ env, timeout: runtimeTime(deadline), maximum: 4096 }
|
|
152
|
+
);
|
|
153
|
+
const value = JSON.parse(result.stdout.toString());
|
|
154
|
+
required(
|
|
155
|
+
Object.keys(value).sort().join(",") === "ID,OSType" &&
|
|
156
|
+
value.OSType === "linux" &&
|
|
157
|
+
typeof value.ID === "string" &&
|
|
158
|
+
value.ID.length > 0 &&
|
|
159
|
+
value.ID === docker.engineId,
|
|
160
|
+
"native Docker daemon identity differs"
|
|
161
|
+
);
|
|
162
|
+
const after = toolIdentity(docker.path, docker.sha256);
|
|
163
|
+
required(
|
|
164
|
+
identity.dev === after.dev &&
|
|
165
|
+
identity.ino === after.ino &&
|
|
166
|
+
identity.uid === after.uid,
|
|
167
|
+
"native Docker identity changed during observation"
|
|
168
|
+
);
|
|
169
|
+
return value;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export const SUPPORTED_RAILS_CLIENT = "1:10.11.14-0ubuntu0.24.04.1";
|
|
173
|
+
/** Fixed Ubuntu archive versions supply browser libraries without ambient runner assumptions. */
|
|
174
|
+
export const SUPPORTED_RAILS_BROWSER = Object.freeze([
|
|
175
|
+
"fonts-liberation=1:2.1.5-3",
|
|
176
|
+
"libasound2t64=1.2.11-1ubuntu0.3",
|
|
177
|
+
"libatk-bridge2.0-0t64=2.52.0-1build1",
|
|
178
|
+
"libatk1.0-0t64=2.52.0-1build1",
|
|
179
|
+
"libcairo2=1.18.0-3build1",
|
|
180
|
+
"libcups2t64=2.4.7-1.2ubuntu7.14",
|
|
181
|
+
"libdbus-1-3=1.14.10-4ubuntu4.1",
|
|
182
|
+
"libdrm2=2.4.125-1ubuntu0.1~24.04.2",
|
|
183
|
+
"libgbm1=25.2.8-0ubuntu0.24.04.4",
|
|
184
|
+
"libglib2.0-0t64=2.80.0-6ubuntu3.9",
|
|
185
|
+
"libnss3=2:3.98-1ubuntu0.2",
|
|
186
|
+
"libpango-1.0-0=1.52.1+ds-1build1",
|
|
187
|
+
"libx11-xcb1=2:1.8.7-1build1",
|
|
188
|
+
"libxcomposite1=1:0.4.5-1build3",
|
|
189
|
+
"libxdamage1=1:1.1.6-1build1",
|
|
190
|
+
"libxext6=2:1.3.4-1build2",
|
|
191
|
+
"libxfixes3=1:6.0.0-2build1",
|
|
192
|
+
"libxkbcommon0=1.6.0-1build1",
|
|
193
|
+
"libxrandr2=2:1.5.2-2build1",
|
|
194
|
+
]);
|
|
195
|
+
|
|
196
|
+
/** Original trusted PATH selects regular Ruby tools; their bytes are pinned around native version calls. */
|
|
197
|
+
export async function qualifyRailsRuby(env, deadline) {
|
|
198
|
+
const output = {};
|
|
199
|
+
required(
|
|
200
|
+
typeof env.PATH === "string" && env.PATH.split(":").every(isAbsolute),
|
|
201
|
+
"Ruby tool environment path differs"
|
|
202
|
+
);
|
|
203
|
+
for (const [name, version] of [
|
|
204
|
+
["ruby", "3.4.11"],
|
|
205
|
+
["bundle", "2.4.10"],
|
|
206
|
+
]) {
|
|
207
|
+
const path = env.PATH.split(":")
|
|
208
|
+
.map(dir => join(dir, name))
|
|
209
|
+
.find(existsSync);
|
|
210
|
+
required(typeof path === "string", "supported native Ruby tool is absent");
|
|
211
|
+
const before = toolSnapshot(path);
|
|
212
|
+
const sha256 = before.sha256;
|
|
213
|
+
const result = await runProcess(path, ["--version"], {
|
|
214
|
+
env,
|
|
215
|
+
timeout: runtimeTime(deadline),
|
|
216
|
+
maximum: 4096,
|
|
217
|
+
});
|
|
218
|
+
const text = result.stdout.toString();
|
|
219
|
+
required(
|
|
220
|
+
name === "ruby"
|
|
221
|
+
? text.startsWith("ruby 3.4.11 ")
|
|
222
|
+
: text === "Bundler version 2.4.10\n",
|
|
223
|
+
"supported native Ruby/Bundler identity differs"
|
|
224
|
+
);
|
|
225
|
+
const after = toolIdentity(path, sha256);
|
|
226
|
+
required(
|
|
227
|
+
before.dev === after.dev &&
|
|
228
|
+
before.ino === after.ino &&
|
|
229
|
+
before.uid === after.uid,
|
|
230
|
+
"native Ruby tool changed during qualification"
|
|
231
|
+
);
|
|
232
|
+
output[name] = { path, sha256, version };
|
|
233
|
+
}
|
|
234
|
+
return output;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Signed Ubuntu packages qualify both native client and bounded header identities. */
|
|
238
|
+
export async function qualifyRailsClient(step) {
|
|
239
|
+
const versions = await step("/usr/bin/dpkg-query", [
|
|
240
|
+
"-W",
|
|
241
|
+
"-f=${Package} ${Version}\n",
|
|
242
|
+
"libmariadb-dev",
|
|
243
|
+
"libmariadb-dev-compat",
|
|
244
|
+
]);
|
|
245
|
+
required(
|
|
246
|
+
versions.stdout.toString() ===
|
|
247
|
+
`libmariadb-dev ${SUPPORTED_RAILS_CLIENT}\nlibmariadb-dev-compat ${SUPPORTED_RAILS_CLIENT}\n`,
|
|
248
|
+
"supported native client package version differs"
|
|
249
|
+
);
|
|
250
|
+
const verified = await step("/usr/bin/dpkg", [
|
|
251
|
+
"--verify",
|
|
252
|
+
"libmariadb-dev",
|
|
253
|
+
"libmariadb-dev-compat",
|
|
254
|
+
]);
|
|
255
|
+
required(verified.stdout.length === 0, "native client package bytes differ");
|
|
256
|
+
const path = "/usr/bin/mariadb_config";
|
|
257
|
+
const sha256 = toolSnapshot(path).sha256;
|
|
258
|
+
const version = await step(path, ["--version"]);
|
|
259
|
+
required(
|
|
260
|
+
version.stdout.toString() === "10.11.14\n",
|
|
261
|
+
"supported native client identity differs"
|
|
262
|
+
);
|
|
263
|
+
const header = "/usr/include/mariadb/mysql.h";
|
|
264
|
+
const fd = openSync(header, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
265
|
+
try {
|
|
266
|
+
const st = fstatSync(fd);
|
|
267
|
+
required(
|
|
268
|
+
st.isFile() &&
|
|
269
|
+
st.nlink === 1 &&
|
|
270
|
+
st.uid === 0 &&
|
|
271
|
+
st.size <= 1048576 &&
|
|
272
|
+
realpathSync(header) === header,
|
|
273
|
+
"native client header identity differs"
|
|
274
|
+
);
|
|
275
|
+
const headerSha256 = createHash("sha256")
|
|
276
|
+
.update(readFileSync(fd))
|
|
277
|
+
.digest("hex");
|
|
278
|
+
return {
|
|
279
|
+
path,
|
|
280
|
+
sha256,
|
|
281
|
+
version: SUPPORTED_RAILS_CLIENT,
|
|
282
|
+
header,
|
|
283
|
+
headerSha256,
|
|
284
|
+
};
|
|
285
|
+
} finally {
|
|
286
|
+
closeSync(fd);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// This file is managed by Lisa and IS replaced on each `lisa` run.
|
|
2
|
+
// Do not edit directly — durable changes belong upstream in Lisa.
|
|
3
|
+
|
|
4
|
+
/** Authenticated finite Rails profiles install only the fixed supported native prerequisites. */
|
|
5
|
+
import {
|
|
6
|
+
chmodSync,
|
|
7
|
+
closeSync,
|
|
8
|
+
constants,
|
|
9
|
+
fstatSync,
|
|
10
|
+
mkdirSync,
|
|
11
|
+
openSync,
|
|
12
|
+
readFileSync,
|
|
13
|
+
realpathSync,
|
|
14
|
+
writeFileSync,
|
|
15
|
+
} from "node:fs";
|
|
16
|
+
import { join, dirname } from "node:path";
|
|
17
|
+
import { required } from "./npm-update-contract.mjs";
|
|
18
|
+
import { canonicalJson } from "./automation-provenance-contract.mjs";
|
|
19
|
+
import {
|
|
20
|
+
assertRuntimeBinding,
|
|
21
|
+
railsRuntime,
|
|
22
|
+
} from "./npm-update-rails-runtime-contract.mjs";
|
|
23
|
+
import {
|
|
24
|
+
downloadRailsTool,
|
|
25
|
+
extractRailsBrowser,
|
|
26
|
+
assertRailsArchive,
|
|
27
|
+
railsBrowserEnvironment,
|
|
28
|
+
} from "./npm-update-rails-tool-downloads.mjs";
|
|
29
|
+
import {
|
|
30
|
+
assertDockerQualified,
|
|
31
|
+
runtimeTime,
|
|
32
|
+
toolIdentity,
|
|
33
|
+
qualifyRailsClient,
|
|
34
|
+
qualifyRailsRuby,
|
|
35
|
+
SUPPORTED_RAILS_CLIENT,
|
|
36
|
+
SUPPORTED_RAILS_BROWSER,
|
|
37
|
+
} from "./npm-update-rails-tool-identity.mjs";
|
|
38
|
+
import { runProcess } from "./npm-update-process.mjs";
|
|
39
|
+
|
|
40
|
+
export { assertDockerQualified, runtimeTime, toolIdentity };
|
|
41
|
+
const CLIENT = SUPPORTED_RAILS_CLIENT;
|
|
42
|
+
const BROWSER = "154.0.8037.92";
|
|
43
|
+
const SUDO = "/usr/bin/sudo";
|
|
44
|
+
const SNAPSHOT = "--snapshot=20261008T000000Z";
|
|
45
|
+
|
|
46
|
+
/** Observe the actual supported OS; no caller-supplied platform can authorize installation. */
|
|
47
|
+
function hostedPlatform() {
|
|
48
|
+
required(
|
|
49
|
+
process.platform === "linux" && process.arch === "x64",
|
|
50
|
+
"Rails tools require native Linux AMD64"
|
|
51
|
+
);
|
|
52
|
+
const os = readFileSync("/etc/os-release", "utf8");
|
|
53
|
+
required(
|
|
54
|
+
/^ID=ubuntu$/m.test(os) && /^VERSION_ID="24\.04"$/m.test(os),
|
|
55
|
+
"Rails tools require supported Ubuntu 24.04"
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Fixed privileged package operations inherit only the original credential-free candidate keys. */
|
|
60
|
+
function toolEnvironment(root, env) {
|
|
61
|
+
const allowed = new Set([
|
|
62
|
+
"PATH",
|
|
63
|
+
"HOME",
|
|
64
|
+
"LANG",
|
|
65
|
+
"LC_ALL",
|
|
66
|
+
"TZ",
|
|
67
|
+
"TMPDIR",
|
|
68
|
+
"NPM_CONFIG_USERCONFIG",
|
|
69
|
+
"NPM_CONFIG_GLOBALCONFIG",
|
|
70
|
+
"NPM_CONFIG_CACHE",
|
|
71
|
+
"NPM_CONFIG_REGISTRY",
|
|
72
|
+
"NPM_CONFIG_AUDIT",
|
|
73
|
+
"NPM_CONFIG_FUND",
|
|
74
|
+
"NPM_CONFIG_UPDATE_NOTIFIER",
|
|
75
|
+
"GIT_TERMINAL_PROMPT",
|
|
76
|
+
"GIT_AUTHOR_NAME",
|
|
77
|
+
"GIT_AUTHOR_EMAIL",
|
|
78
|
+
"GIT_COMMITTER_NAME",
|
|
79
|
+
"GIT_COMMITTER_EMAIL",
|
|
80
|
+
"GIT_AUTHOR_DATE",
|
|
81
|
+
"GIT_COMMITTER_DATE",
|
|
82
|
+
]);
|
|
83
|
+
required(
|
|
84
|
+
Object.keys(env).every(name => allowed.has(name)) &&
|
|
85
|
+
env.HOME === root &&
|
|
86
|
+
typeof env.PATH === "string" &&
|
|
87
|
+
realpathSync(root) === root,
|
|
88
|
+
"Rails tool environment differs"
|
|
89
|
+
);
|
|
90
|
+
const fd = openSync(
|
|
91
|
+
root,
|
|
92
|
+
constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW
|
|
93
|
+
);
|
|
94
|
+
try {
|
|
95
|
+
const st = fstatSync(fd);
|
|
96
|
+
required(
|
|
97
|
+
st.isDirectory() &&
|
|
98
|
+
st.uid === process.getuid() &&
|
|
99
|
+
(st.mode & 0o077) === 0,
|
|
100
|
+
"Rails tool root is not private owned storage"
|
|
101
|
+
);
|
|
102
|
+
} finally {
|
|
103
|
+
closeSync(fd);
|
|
104
|
+
}
|
|
105
|
+
return { ...env, BUNDLER_VERSION: "2.4.10" };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function plugins(root, step, deadline) {
|
|
109
|
+
const directory = join(root, "docker-plugins");
|
|
110
|
+
mkdirSync(directory, { mode: 0o700 });
|
|
111
|
+
const value = {};
|
|
112
|
+
for (const [name, version] of [
|
|
113
|
+
["compose", "5.6.0"],
|
|
114
|
+
["buildx", "0.38.0"],
|
|
115
|
+
]) {
|
|
116
|
+
const archive = await downloadRailsTool(name, root, deadline);
|
|
117
|
+
const path = join(directory, `docker-${name}`);
|
|
118
|
+
writeFileSync(path, readFileSync(archive.file), {
|
|
119
|
+
flag: "wx",
|
|
120
|
+
mode: 0o700,
|
|
121
|
+
});
|
|
122
|
+
toolIdentity(path, archive.sha256);
|
|
123
|
+
const result = await step(path, ["version"]);
|
|
124
|
+
required(
|
|
125
|
+
result.stdout.toString().includes(`v${version}`),
|
|
126
|
+
"fixed Docker plugin version differs"
|
|
127
|
+
);
|
|
128
|
+
value[name] = { path, sha256: archive.sha256, version };
|
|
129
|
+
}
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Pin the vendor SUID helper descriptor; sandbox and AppArmor policy remain enabled. */
|
|
134
|
+
async function browser(root, step, deadline) {
|
|
135
|
+
const paths = {};
|
|
136
|
+
let sandboxHash;
|
|
137
|
+
for (const name of ["chrome", "chromedriver"]) {
|
|
138
|
+
const archive = await downloadRailsTool(name, root, deadline);
|
|
139
|
+
paths[name] = await extractRailsBrowser(name, archive.file, root, step);
|
|
140
|
+
toolIdentity(paths[name], archive.binary);
|
|
141
|
+
const version = await step(paths[name], ["--version"]);
|
|
142
|
+
required(
|
|
143
|
+
version.stdout.toString().includes(BROWSER),
|
|
144
|
+
"fixed native browser version differs"
|
|
145
|
+
);
|
|
146
|
+
if (name === "chrome") sandboxHash = archive.sandbox;
|
|
147
|
+
}
|
|
148
|
+
const sandbox = join(root, "chrome/chrome-linux64/chrome_sandbox");
|
|
149
|
+
toolIdentity(sandbox, sandboxHash);
|
|
150
|
+
const fd = openSync(sandbox, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
151
|
+
try {
|
|
152
|
+
const reference = `/proc/${process.pid}/fd/${fd}`;
|
|
153
|
+
await step(SUDO, ["-n", "/usr/bin/chown", "root:root", reference]);
|
|
154
|
+
await step(SUDO, ["-n", "/usr/bin/chmod", "4755", reference]);
|
|
155
|
+
const st = fstatSync(fd);
|
|
156
|
+
required(
|
|
157
|
+
st.uid === 0 && st.gid === 0 && (st.mode & 0o7777) === 0o4755,
|
|
158
|
+
"fixed native sandbox ownership differs"
|
|
159
|
+
);
|
|
160
|
+
toolIdentity(sandbox, sandboxHash);
|
|
161
|
+
} finally {
|
|
162
|
+
closeSync(fd);
|
|
163
|
+
}
|
|
164
|
+
return railsBrowserEnvironment(paths.chrome, paths.chromedriver, sandbox);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Fixed Docker bytes and private plugin-only config bind the actual Linux daemon. */
|
|
168
|
+
async function prepareDocker(root, profile, step, deadline) {
|
|
169
|
+
const directory = join(root, "runtime-tools");
|
|
170
|
+
mkdirSync(directory, { mode: 0o700 });
|
|
171
|
+
const archive = await downloadRailsTool("docker", directory, deadline);
|
|
172
|
+
assertRailsArchive("docker", archive.file);
|
|
173
|
+
await step("/usr/bin/tar", [
|
|
174
|
+
"-xzf",
|
|
175
|
+
archive.file,
|
|
176
|
+
"--no-same-owner",
|
|
177
|
+
"--no-same-permissions",
|
|
178
|
+
"-C",
|
|
179
|
+
directory,
|
|
180
|
+
"docker/docker",
|
|
181
|
+
]);
|
|
182
|
+
const path = join(directory, "docker/docker");
|
|
183
|
+
chmodSync(path, 0o700);
|
|
184
|
+
toolIdentity(path, archive.binary);
|
|
185
|
+
const pluginMap = profile.dockerFixtures
|
|
186
|
+
? await plugins(root, step, deadline)
|
|
187
|
+
: undefined;
|
|
188
|
+
const config = join(root, "docker-config");
|
|
189
|
+
mkdirSync(config, { mode: 0o700 });
|
|
190
|
+
writeFileSync(
|
|
191
|
+
join(config, "config.json"),
|
|
192
|
+
`${JSON.stringify(pluginMap ? { cliPluginsExtraDirs: [join(root, "docker-plugins")] } : {})}\n`,
|
|
193
|
+
{ flag: "wx", mode: 0o600 }
|
|
194
|
+
);
|
|
195
|
+
const dockerEnv = {
|
|
196
|
+
HOME: root,
|
|
197
|
+
PATH: "/usr/bin:/bin",
|
|
198
|
+
DOCKER_CONFIG: config,
|
|
199
|
+
};
|
|
200
|
+
const info = await runProcess(path, ["info", "--format", "{{json .ID}}"], {
|
|
201
|
+
env: dockerEnv,
|
|
202
|
+
timeout: runtimeTime(deadline),
|
|
203
|
+
maximum: 4096,
|
|
204
|
+
});
|
|
205
|
+
const docker = {
|
|
206
|
+
path,
|
|
207
|
+
sha256: archive.binary,
|
|
208
|
+
version: "29.8.2",
|
|
209
|
+
engineId: JSON.parse(info.stdout.toString()),
|
|
210
|
+
env: dockerEnv,
|
|
211
|
+
...(pluginMap ? { plugins: pluginMap } : {}),
|
|
212
|
+
};
|
|
213
|
+
await assertDockerQualified(docker, deadline);
|
|
214
|
+
return docker;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** One immutable signed Ubuntu snapshot retains the fixed roster when moving mirrors retire pins. */
|
|
218
|
+
export async function installRailsPackages(step, browser) {
|
|
219
|
+
const libraries = browser ? SUPPORTED_RAILS_BROWSER : [];
|
|
220
|
+
await step(SUDO, ["-n", "/usr/bin/apt-get", SNAPSHOT, "update"]);
|
|
221
|
+
await step(SUDO, [
|
|
222
|
+
"-n",
|
|
223
|
+
"/usr/bin/apt-get",
|
|
224
|
+
SNAPSHOT,
|
|
225
|
+
"install",
|
|
226
|
+
"--yes",
|
|
227
|
+
"--no-install-recommends",
|
|
228
|
+
`libmariadb-dev=${CLIENT}`,
|
|
229
|
+
`libmariadb-dev-compat=${CLIENT}`,
|
|
230
|
+
...libraries,
|
|
231
|
+
]);
|
|
232
|
+
return libraries;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Caller authentication precedes this fixed route; signed policy is rechecked before all side effects. */
|
|
236
|
+
export async function prepareRailsTools(context, root, env, profile) {
|
|
237
|
+
assertRuntimeBinding(context.proposal, context.policy);
|
|
238
|
+
required(
|
|
239
|
+
canonicalJson(railsRuntime(context.policy)) === canonicalJson(profile),
|
|
240
|
+
"original runtime profile differs"
|
|
241
|
+
);
|
|
242
|
+
hostedPlatform();
|
|
243
|
+
const environment = toolEnvironment(root, env);
|
|
244
|
+
const step = (command, args, maximum = 3145728) =>
|
|
245
|
+
runProcess(command, args, {
|
|
246
|
+
cwd: context.cwd,
|
|
247
|
+
env: environment,
|
|
248
|
+
timeout: runtimeTime(context.deadline, 1800000),
|
|
249
|
+
maximum,
|
|
250
|
+
});
|
|
251
|
+
const ruby = await qualifyRailsRuby(environment, context.deadline);
|
|
252
|
+
const libraries = await installRailsPackages(step, profile.browser);
|
|
253
|
+
const client = await qualifyRailsClient(step);
|
|
254
|
+
for (const library of libraries) {
|
|
255
|
+
const [name, expected] = library.split("=");
|
|
256
|
+
const version = await step("/usr/bin/dpkg-query", [
|
|
257
|
+
"-W",
|
|
258
|
+
"-f=${Version}\n",
|
|
259
|
+
name,
|
|
260
|
+
]);
|
|
261
|
+
required(
|
|
262
|
+
version.stdout.toString() === `${expected}\n`,
|
|
263
|
+
"native browser library version differs"
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
if (libraries.length) {
|
|
267
|
+
const verified = await step("/usr/bin/dpkg", [
|
|
268
|
+
"--verify",
|
|
269
|
+
...libraries.map(value => value.split("=")[0]),
|
|
270
|
+
]);
|
|
271
|
+
required(
|
|
272
|
+
verified.stdout.length === 0,
|
|
273
|
+
"native browser library bytes differ"
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
const docker = await prepareDocker(root, profile, step, context.deadline);
|
|
277
|
+
const chrome = profile.browser
|
|
278
|
+
? await browser(root, step, context.deadline)
|
|
279
|
+
: {};
|
|
280
|
+
return {
|
|
281
|
+
docker,
|
|
282
|
+
client,
|
|
283
|
+
ruby,
|
|
284
|
+
env: {
|
|
285
|
+
...environment,
|
|
286
|
+
...chrome,
|
|
287
|
+
PATH: `${dirname(docker.path)}:${dirname(ruby.ruby.path)}:${dirname(ruby.bundle.path)}:${environment.PATH}`,
|
|
288
|
+
DOCKER_CONFIG: docker.env.DOCKER_CONFIG,
|
|
289
|
+
BUNDLE_BUILD__MYSQL2: "--with-mysql-config=/usr/bin/mariadb_config",
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
canonicalJson,
|
|
19
19
|
trustedConfiguration,
|
|
20
20
|
npmProposal,
|
|
21
|
+
signedProposalFields,
|
|
21
22
|
} from "./lib/automation-provenance-contract.mjs";
|
|
22
23
|
import {
|
|
23
24
|
git,
|
|
@@ -72,6 +73,7 @@ function recoveryPermission(
|
|
|
72
73
|
parent: descriptor.parent,
|
|
73
74
|
policySha256: npmPolicySha256,
|
|
74
75
|
updates: descriptor.updates,
|
|
76
|
+
...signedProposalFields(descriptor),
|
|
75
77
|
})
|
|
76
78
|
),
|
|
77
79
|
leafBodySha256: sha256(context.issue.body),
|
|
@@ -103,6 +105,7 @@ export function verifyAutomationProvenance(messageFile) {
|
|
|
103
105
|
repository: policy.repository,
|
|
104
106
|
parent: descriptor.parent,
|
|
105
107
|
updates: descriptor.updates,
|
|
108
|
+
...signedProposalFields(descriptor),
|
|
106
109
|
})
|
|
107
110
|
),
|
|
108
111
|
"deterministic proposal key differs"
|
|
@@ -534,7 +534,7 @@ followed_text=""
|
|
|
534
534
|
followed_rm_text=""
|
|
535
535
|
# Generated from the canonical reviewed supervisor, never from a host manifest.
|
|
536
536
|
# Regenerate through scripts/generate-scratch-supervisor-profile.mjs.
|
|
537
|
-
readonly SCRATCH_SUPERVISOR_SHA256='
|
|
537
|
+
readonly SCRATCH_SUPERVISOR_SHA256='76ccf8ca6f98b214361f02ebf6ad58041129b76ed29128455e810095db6c292f'
|
|
538
538
|
readonly SCRATCH_SUPERVISOR_CLEANUP_COUNT='7'
|
|
539
539
|
|
|
540
540
|
# An authenticated public supervisor owns its fixed cleanup sites. Its original
|
|
@@ -223,14 +223,17 @@ lisa_marker_get() {
|
|
|
223
223
|
_key="$2"
|
|
224
224
|
[ -f "$_file" ] || return 1
|
|
225
225
|
if [ -L "$_file" ]; then return 1; fi
|
|
226
|
-
|
|
227
|
-
|
|
226
|
+
# Cleanup may remove this invocation's marker between native reads. A
|
|
227
|
+
# failed open remains a refusal, without leaking shell pathname diagnostics.
|
|
228
|
+
_bytes="$(wc -c 2>/dev/null <"$_file" | tr -d ' ')" || return 1
|
|
229
|
+
case "$_bytes" in "" | *[!0-9]*) return 1 ;; esac
|
|
228
230
|
[ "$_bytes" -le "$LISA_SCRATCH_MARKER_MAX_BYTES" ] || return 1
|
|
229
|
-
_lines="$(wc -l <"$_file"
|
|
231
|
+
_lines="$(wc -l 2>/dev/null <"$_file" | tr -d ' ')" || return 1
|
|
232
|
+
case "$_lines" in "" | *[!0-9]*) return 1 ;; esac
|
|
230
233
|
[ "$_lines" -le "$LISA_SCRATCH_MARKER_MAX_LINES" ] || return 1
|
|
231
234
|
_hits="$(grep -c "^${_key}=" "$_file" 2>/dev/null || true)"
|
|
232
235
|
[ "$_hits" = "1" ] || return 1
|
|
233
|
-
sed -n "s/^${_key}=//p" "$_file"
|
|
236
|
+
sed -n "s/^${_key}=//p" "$_file" 2>/dev/null
|
|
234
237
|
}
|
|
235
238
|
|
|
236
239
|
# --- path safety -------------------------------------------------------------
|