@codyswann/lisa 4.70.4 → 4.71.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 +174 -0
- package/all/copy-overwrite/scripts/lib/automation-provenance-local.mjs +279 -0
- package/all/copy-overwrite/scripts/lib/gate-failure-diagnosis.mjs +17 -7
- package/all/copy-overwrite/scripts/lib/github-attestation-provider.mjs +241 -0
- package/all/copy-overwrite/scripts/lib/github-attestation-recovery.mjs +283 -0
- package/all/copy-overwrite/scripts/lib/github-attestation-verifier.mjs +244 -0
- package/all/copy-overwrite/scripts/lib/npm-update-allocate.mjs +289 -0
- package/all/copy-overwrite/scripts/lib/npm-update-authorization.mjs +235 -0
- package/all/copy-overwrite/scripts/lib/npm-update-broker-client.mjs +92 -0
- package/all/copy-overwrite/scripts/lib/npm-update-checkpoint.mjs +241 -0
- package/all/copy-overwrite/scripts/lib/npm-update-contract.mjs +278 -0
- package/all/copy-overwrite/scripts/lib/npm-update-controller-broker.mjs +234 -0
- package/all/copy-overwrite/scripts/lib/npm-update-controller-factory.mjs +278 -0
- package/all/copy-overwrite/scripts/lib/npm-update-controller-recipe.mjs +135 -0
- package/all/copy-overwrite/scripts/lib/npm-update-execution-adapter.mjs +292 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate-hooks.mjs +214 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate-install.mjs +267 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate-proof.mjs +99 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gate.mjs +300 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gh-dispatch.mjs +182 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gh-grants.mjs +156 -0
- package/all/copy-overwrite/scripts/lib/npm-update-gh-requests.mjs +221 -0
- package/all/copy-overwrite/scripts/lib/npm-update-github.mjs +214 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper-graph.mjs +238 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper-inventory.mjs +126 -0
- package/all/copy-overwrite/scripts/lib/npm-update-helper.mjs +292 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-installation.mjs +116 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-preload.mjs +153 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-provider.mjs +207 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hook-read-client.mjs +164 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hosted-gate.mjs +272 -0
- package/all/copy-overwrite/scripts/lib/npm-update-hosted-hook.mjs +157 -0
- package/all/copy-overwrite/scripts/lib/npm-update-invariants.mjs +28 -0
- package/all/copy-overwrite/scripts/lib/npm-update-isolation.mjs +11 -0
- package/all/copy-overwrite/scripts/lib/npm-update-leaf-contract.mjs +171 -0
- package/all/copy-overwrite/scripts/lib/npm-update-leaf.mjs +194 -0
- package/all/copy-overwrite/scripts/lib/npm-update-native-process.mjs +155 -0
- package/all/copy-overwrite/scripts/lib/npm-update-npm.mjs +190 -0
- package/all/copy-overwrite/scripts/lib/npm-update-object.mjs +112 -0
- package/all/copy-overwrite/scripts/lib/npm-update-orchestrator.mjs +273 -0
- package/all/copy-overwrite/scripts/lib/npm-update-owner.mjs +292 -0
- package/all/copy-overwrite/scripts/lib/npm-update-prepare.mjs +263 -0
- package/all/copy-overwrite/scripts/lib/npm-update-process-core.mjs +179 -0
- package/all/copy-overwrite/scripts/lib/npm-update-process.mjs +16 -0
- package/all/copy-overwrite/scripts/lib/npm-update-publication.mjs +258 -0
- package/all/copy-overwrite/scripts/lib/npm-update-publish.mjs +258 -0
- package/all/copy-overwrite/scripts/lib/npm-update-quality.mjs +153 -0
- package/all/copy-overwrite/scripts/lib/npm-update-recovery.mjs +264 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime-archive.mjs +204 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime-graph.mjs +185 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime-transport.mjs +221 -0
- package/all/copy-overwrite/scripts/lib/npm-update-runtime.mjs +284 -0
- package/all/copy-overwrite/scripts/lib/npm-update-tool-launcher.mjs +287 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-environment.mjs +161 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-inspection.mjs +142 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-lifecycle.mjs +256 -0
- package/all/copy-overwrite/scripts/lib/npm-update-worker-policy.mjs +266 -0
- package/all/copy-overwrite/scripts/lib/process-tree-runner.mjs +230 -36
- package/all/copy-overwrite/scripts/lisa-automation-provenance.mjs +161 -0
- package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +89 -5
- package/all/copy-overwrite/scripts/lisa-npm-updater.mjs +204 -0
- package/all/copy-overwrite/scripts/lisa-run-gates.mjs +48 -123
- package/all/copy-overwrite/scripts/lisa-work-item.mjs +107 -41
- package/all/copy-overwrite/scripts/npm-updater-gate-runtime.json +1 -0
- package/all/copy-overwrite/scripts/npm-updater-gate-supervisor.c +121 -0
- package/all/copy-overwrite/scripts/npm-updater-gate.Dockerfile +49 -0
- package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +1085 -0
- package/dist/codex/scripts/block-no-verify.sh +89 -5
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +598 -0
- package/dist/core/lisa-owned-hash-ledger.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 +157 -11
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/block-no-verify.sh +89 -5
- 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/block-no-verify.agy.sh +89 -5
- package/plugins/lisa/hooks/block-no-verify.sh +89 -5
- package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +89 -5
- 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/block-no-verify.sh +89 -5
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-no-verify.sh +89 -5
- 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/block-no-verify.agy.sh +89 -5
- package/plugins/src/base/hooks/block-no-verify.sh +89 -5
- package/scripts/check-shipped-surface-removals.mjs +96 -24
- package/scripts/generate-npm-updater-helper-graph.mjs +184 -0
- package/scripts/two-channel-couplings.json +31 -7
- package/typescript/copy-contents/.husky/commit-msg +18 -0
- package/typescript/copy-overwrite/.prettierignore +2 -0
|
@@ -0,0 +1,207 @@
|
|
|
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
|
+
/** A disposable CI gate exposes only a fixed read capability; publisher credentials never enter this job. */
|
|
5
|
+
import { createServer, createConnection } from "node:net";
|
|
6
|
+
import { chmodSync, lstatSync, realpathSync, unlinkSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
import { required, keys } from "./npm-update-contract.mjs";
|
|
10
|
+
import { createGhDispatcher } from "./npm-update-gh-dispatch.mjs";
|
|
11
|
+
|
|
12
|
+
export const HOOK_READ_SLOTS = [
|
|
13
|
+
"commit-prepare",
|
|
14
|
+
"commit-work-item",
|
|
15
|
+
"commit-provenance",
|
|
16
|
+
"audit-work-item",
|
|
17
|
+
"audit-provenance",
|
|
18
|
+
"destination-work-item",
|
|
19
|
+
"destination-provenance",
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
/** Slots separate actual hook reads; they are bounded state labels, not claimed caller authentication. */
|
|
23
|
+
function readScopes(profile, token) {
|
|
24
|
+
required(
|
|
25
|
+
profile.subject.phase === "hook-read",
|
|
26
|
+
"hook broker cannot carry writer authority"
|
|
27
|
+
);
|
|
28
|
+
return new Map(
|
|
29
|
+
HOOK_READ_SLOTS.map(slot => [slot, createGhDispatcher(profile, token)])
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function nativeResponse(result) {
|
|
34
|
+
return {
|
|
35
|
+
status: result.status,
|
|
36
|
+
signal: result.signal,
|
|
37
|
+
stdout: Buffer.from(result.stdout ?? "").toString("base64"),
|
|
38
|
+
stderr: Buffer.from(result.stderr ?? "").toString("base64"),
|
|
39
|
+
error: result.error
|
|
40
|
+
? { code: result.error.code ?? null, name: result.error.name }
|
|
41
|
+
: null,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Node normally unlinks its listener on close; an existing pathname still requires the original inode. */
|
|
46
|
+
function removeOwnedSocket(path, identity) {
|
|
47
|
+
try {
|
|
48
|
+
const current = lstatSync(path);
|
|
49
|
+
required(
|
|
50
|
+
current.dev === identity.dev &&
|
|
51
|
+
current.ino === identity.ino &&
|
|
52
|
+
current.isSocket(),
|
|
53
|
+
"hook read cleanup ownership changed"
|
|
54
|
+
);
|
|
55
|
+
unlinkSync(path);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (error.code !== "ENOENT") throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** No request can supply a native path, token, environment or new provider subject. */
|
|
62
|
+
function dispatchRead(scopes, request, cwd) {
|
|
63
|
+
keys(request, ["slot", "args", "options"]);
|
|
64
|
+
const execute = scopes.get(request.slot);
|
|
65
|
+
required(execute, "unknown hook read slot");
|
|
66
|
+
keys(request.options, ["encoding", "timeout", "maxBuffer", "killSignal"]);
|
|
67
|
+
return nativeResponse(
|
|
68
|
+
execute(
|
|
69
|
+
"spawnSync",
|
|
70
|
+
"gh",
|
|
71
|
+
request.args,
|
|
72
|
+
{ ...request.options, cwd, env: {} },
|
|
73
|
+
spawnSync
|
|
74
|
+
)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Each connection has a finite byte budget and consumes the shared phase request bound. */
|
|
79
|
+
function readServer(profile, scopes) {
|
|
80
|
+
const connections = new Set();
|
|
81
|
+
const state = { count: 0, closing: false };
|
|
82
|
+
const server = createServer(socket => {
|
|
83
|
+
if (state.closing || connections.size >= 32) {
|
|
84
|
+
socket.destroy();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
connections.add(socket);
|
|
88
|
+
socket.on("error", () => {});
|
|
89
|
+
socket.once("close", () => connections.delete(socket));
|
|
90
|
+
const chunks = [];
|
|
91
|
+
let bytes = 0;
|
|
92
|
+
socket.on("data", chunk => {
|
|
93
|
+
bytes += chunk.length;
|
|
94
|
+
if (bytes > 262_144) socket.destroy();
|
|
95
|
+
else chunks.push(chunk);
|
|
96
|
+
});
|
|
97
|
+
socket.on("end", () => {
|
|
98
|
+
try {
|
|
99
|
+
required(
|
|
100
|
+
!state.closing &&
|
|
101
|
+
Date.now() < profile.deadline &&
|
|
102
|
+
++state.count <= 1024,
|
|
103
|
+
"hook read phase exhausted"
|
|
104
|
+
);
|
|
105
|
+
const text = new TextDecoder("utf8", { fatal: true }).decode(
|
|
106
|
+
Buffer.concat(chunks)
|
|
107
|
+
);
|
|
108
|
+
const request = JSON.parse(text);
|
|
109
|
+
required(
|
|
110
|
+
text === `${JSON.stringify(request)}\n`,
|
|
111
|
+
"ambiguous hook read request"
|
|
112
|
+
);
|
|
113
|
+
socket.end(
|
|
114
|
+
`${JSON.stringify({ ok: true, result: dispatchRead(scopes, request, profile.cwd) })}\n`
|
|
115
|
+
);
|
|
116
|
+
} catch {
|
|
117
|
+
socket.end('{"ok":false}\n');
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
return { server, connections, state };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Private pathname ownership and finite requests bound transport; this is not same-user confidentiality. */
|
|
125
|
+
export async function startHookReadBroker(profile, token) {
|
|
126
|
+
const scopes = readScopes(profile, token);
|
|
127
|
+
const path = join(profile.home, "hook-reader.sock");
|
|
128
|
+
required(
|
|
129
|
+
Buffer.byteLength(path) <= 103 &&
|
|
130
|
+
realpathSync(profile.home) === profile.home,
|
|
131
|
+
"hook read pathname is unsupported"
|
|
132
|
+
);
|
|
133
|
+
const { server, connections, state } = readServer(profile, scopes);
|
|
134
|
+
let identity;
|
|
135
|
+
try {
|
|
136
|
+
await new Promise((resolve, reject) => {
|
|
137
|
+
server.once("error", reject);
|
|
138
|
+
server.listen(path, resolve);
|
|
139
|
+
});
|
|
140
|
+
identity = lstatSync(path);
|
|
141
|
+
chmodSync(path, 0o600);
|
|
142
|
+
required(
|
|
143
|
+
identity.isSocket() &&
|
|
144
|
+
identity.uid === process.getuid() &&
|
|
145
|
+
(lstatSync(path).mode & 0o777) === 0o600,
|
|
146
|
+
"hook read socket ownership differs"
|
|
147
|
+
);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
await new Promise(resolve => server.close(resolve));
|
|
150
|
+
if (identity) removeOwnedSocket(path, identity);
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
let completion;
|
|
154
|
+
const close = () =>
|
|
155
|
+
(completion ??= (async () => {
|
|
156
|
+
state.closing = true;
|
|
157
|
+
clearTimeout(timer);
|
|
158
|
+
for (const socket of connections) socket.destroy();
|
|
159
|
+
await new Promise(resolve => server.close(resolve));
|
|
160
|
+
removeOwnedSocket(path, identity);
|
|
161
|
+
})());
|
|
162
|
+
const timer = setTimeout(() => {
|
|
163
|
+
close().catch(() => {});
|
|
164
|
+
}, profile.deadline - Date.now());
|
|
165
|
+
timer.unref();
|
|
166
|
+
return { path, close };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Literal public request data crosses IPC; the parent supplies all credentialed execution authority. */
|
|
170
|
+
export async function requestHookRead(context, request) {
|
|
171
|
+
const identity = lstatSync(context.path);
|
|
172
|
+
required(
|
|
173
|
+
identity.isSocket() &&
|
|
174
|
+
identity.uid === process.getuid() &&
|
|
175
|
+
(identity.mode & 0o077) === 0 &&
|
|
176
|
+
context.path === join(context.root, "hook-reader.sock") &&
|
|
177
|
+
context.deadline > Date.now(),
|
|
178
|
+
"hook read endpoint is unavailable"
|
|
179
|
+
);
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
const socket = createConnection(context.path);
|
|
182
|
+
const chunks = [];
|
|
183
|
+
let bytes = 0;
|
|
184
|
+
const timer = setTimeout(
|
|
185
|
+
() => socket.destroy(new Error("hook read phase expired")),
|
|
186
|
+
context.deadline - Date.now()
|
|
187
|
+
);
|
|
188
|
+
socket.on("connect", () => socket.end(`${JSON.stringify(request)}\n`));
|
|
189
|
+
socket.on("data", chunk => {
|
|
190
|
+
bytes += chunk.length;
|
|
191
|
+
if (bytes > 11_185_000)
|
|
192
|
+
socket.destroy(new Error("hook read capture exceeded"));
|
|
193
|
+
else chunks.push(chunk);
|
|
194
|
+
});
|
|
195
|
+
socket.once("error", reject);
|
|
196
|
+
socket.once("close", () => clearTimeout(timer));
|
|
197
|
+
socket.once("end", () => {
|
|
198
|
+
try {
|
|
199
|
+
const response = JSON.parse(Buffer.concat(chunks).toString());
|
|
200
|
+
required(response.ok === true, "hook read request refused");
|
|
201
|
+
resolve(response.result);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
reject(error);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
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
|
+
/** Canonical synchronous callers receive the actual broker-owned native result, without a credential. */
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
import { invokedAsScript } from "./invoked-as-script.mjs";
|
|
8
|
+
import { isProviderTransport } from "./npm-update-worker-environment.mjs";
|
|
9
|
+
import { readJson } from "./npm-update-process.mjs";
|
|
10
|
+
import { keys, required } from "./npm-update-contract.mjs";
|
|
11
|
+
import { requestHookRead } from "./npm-update-hook-provider.mjs";
|
|
12
|
+
|
|
13
|
+
const ENTRY = fileURLToPath(import.meta.url);
|
|
14
|
+
const OPTIONS = new Set([
|
|
15
|
+
"cwd",
|
|
16
|
+
"encoding",
|
|
17
|
+
"env",
|
|
18
|
+
"input",
|
|
19
|
+
"timeout",
|
|
20
|
+
"maxBuffer",
|
|
21
|
+
"killSignal",
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
/** Only public native bounds cross IPC; ambient environment, loaders and tokens never cross. */
|
|
25
|
+
function requestOptions(options, context) {
|
|
26
|
+
required(
|
|
27
|
+
options &&
|
|
28
|
+
typeof options === "object" &&
|
|
29
|
+
!Array.isArray(options) &&
|
|
30
|
+
Object.keys(options).every(key => OPTIONS.has(key)),
|
|
31
|
+
"unsupported canonical hook GH option"
|
|
32
|
+
);
|
|
33
|
+
required(
|
|
34
|
+
options.encoding === "utf8" &&
|
|
35
|
+
options.input === undefined &&
|
|
36
|
+
(options.cwd === undefined || resolve(options.cwd) === context.cwd) &&
|
|
37
|
+
(options.killSignal === undefined || options.killSignal === "SIGKILL"),
|
|
38
|
+
"unsupported canonical hook GH options"
|
|
39
|
+
);
|
|
40
|
+
const env = options.env ?? process.env;
|
|
41
|
+
required(
|
|
42
|
+
env &&
|
|
43
|
+
typeof env === "object" &&
|
|
44
|
+
!Array.isArray(env) &&
|
|
45
|
+
Object.values(env).every(
|
|
46
|
+
value =>
|
|
47
|
+
value === undefined ||
|
|
48
|
+
(typeof value === "string" && !value.includes("\0"))
|
|
49
|
+
) &&
|
|
50
|
+
!env.GH_TOKEN &&
|
|
51
|
+
!env.GITHUB_TOKEN &&
|
|
52
|
+
Object.entries(env).every(
|
|
53
|
+
([key, value]) => !value || !isProviderTransport(key)
|
|
54
|
+
) &&
|
|
55
|
+
(!env.GH_HOST || env.GH_HOST === "github.com") &&
|
|
56
|
+
(!env.GITHUB_API_URL ||
|
|
57
|
+
env.GITHUB_API_URL === "https://api.github.com") &&
|
|
58
|
+
(!env.GITHUB_SERVER_URL ||
|
|
59
|
+
env.GITHUB_SERVER_URL === "https://github.com"),
|
|
60
|
+
"hook caller contains provider credentials or transport overrides"
|
|
61
|
+
);
|
|
62
|
+
return {
|
|
63
|
+
encoding: "utf8",
|
|
64
|
+
timeout: options.timeout ?? 30_000,
|
|
65
|
+
maxBuffer: options.maxBuffer ?? 1_048_576,
|
|
66
|
+
killSignal: "SIGKILL",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Native error/status distinctions survive the internal successful IPC process. */
|
|
71
|
+
export function hookNativeResult(value) {
|
|
72
|
+
keys(value, ["status", "signal", "stdout", "stderr", "error"]);
|
|
73
|
+
if (value.error !== null) keys(value.error, ["name", "code"]);
|
|
74
|
+
required(
|
|
75
|
+
(value.status === null ||
|
|
76
|
+
(Number.isInteger(value.status) &&
|
|
77
|
+
value.status >= 0 &&
|
|
78
|
+
value.status <= 255)) &&
|
|
79
|
+
(value.signal === null || /^SIG[A-Z0-9]+$/.test(value.signal)) &&
|
|
80
|
+
(value.error === null ||
|
|
81
|
+
(typeof value.error.name === "string" &&
|
|
82
|
+
value.error.name.length <= 64 &&
|
|
83
|
+
(value.error.code === null ||
|
|
84
|
+
/^E[A-Z0-9]+$/.test(value.error.code)))),
|
|
85
|
+
"invalid hook native status"
|
|
86
|
+
);
|
|
87
|
+
const bytes = [value.stdout, value.stderr].map(encoded => {
|
|
88
|
+
required(
|
|
89
|
+
typeof encoded === "string" && /^[A-Za-z0-9+/]*={0,2}$/.test(encoded),
|
|
90
|
+
"invalid hook native capture"
|
|
91
|
+
);
|
|
92
|
+
const decoded = Buffer.from(encoded, "base64");
|
|
93
|
+
required(
|
|
94
|
+
decoded.toString("base64") === encoded,
|
|
95
|
+
"ambiguous hook native capture"
|
|
96
|
+
);
|
|
97
|
+
return decoded;
|
|
98
|
+
});
|
|
99
|
+
required(
|
|
100
|
+
bytes[0].length + bytes[1].length <= 8_388_608,
|
|
101
|
+
"hook native capture exceeded"
|
|
102
|
+
);
|
|
103
|
+
return {
|
|
104
|
+
status: value.status,
|
|
105
|
+
signal: value.signal,
|
|
106
|
+
stdout: bytes[0].toString(),
|
|
107
|
+
stderr: bytes[1].toString(),
|
|
108
|
+
...(value.error
|
|
109
|
+
? {
|
|
110
|
+
error: Object.assign(
|
|
111
|
+
new Error("native hook provider did not complete"),
|
|
112
|
+
value.error
|
|
113
|
+
),
|
|
114
|
+
}
|
|
115
|
+
: {}),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The selected source entry supplies saved native spawnSync; client subprocess cannot select GH authority. */
|
|
120
|
+
export function synchronousHookRead(
|
|
121
|
+
context,
|
|
122
|
+
slot,
|
|
123
|
+
args,
|
|
124
|
+
options,
|
|
125
|
+
execute,
|
|
126
|
+
node
|
|
127
|
+
) {
|
|
128
|
+
const native = requestOptions(options, context);
|
|
129
|
+
const timeout = Math.min(native.timeout, context.deadline - Date.now());
|
|
130
|
+
required(timeout > 0, "hook provider phase expired");
|
|
131
|
+
const result = execute(node, [ENTRY, context.file], {
|
|
132
|
+
cwd: context.cwd,
|
|
133
|
+
encoding: "utf8",
|
|
134
|
+
timeout,
|
|
135
|
+
maxBuffer: 11_185_000,
|
|
136
|
+
killSignal: "SIGKILL",
|
|
137
|
+
input: `${JSON.stringify({ slot, args, options: native })}\n`,
|
|
138
|
+
env: { PATH: "/usr/bin:/bin", HOME: context.root, LANG: "C.UTF-8" },
|
|
139
|
+
});
|
|
140
|
+
if (result.error || result.signal || result.status !== 0)
|
|
141
|
+
return { ...result, stdout: "", stderr: "" };
|
|
142
|
+
return hookNativeResult(JSON.parse(result.stdout));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function main() {
|
|
146
|
+
required(process.argv.length === 3, "invalid hook read client arguments");
|
|
147
|
+
const context = readJson(process.argv[2], 1_048_576);
|
|
148
|
+
const chunks = [];
|
|
149
|
+
let length = 0;
|
|
150
|
+
for await (const chunk of process.stdin) {
|
|
151
|
+
length += chunk.length;
|
|
152
|
+
required(length <= 262_144, "hook request exceeded");
|
|
153
|
+
chunks.push(chunk);
|
|
154
|
+
}
|
|
155
|
+
const request = JSON.parse(Buffer.concat(chunks).toString());
|
|
156
|
+
const result = await requestHookRead(context.reader, request);
|
|
157
|
+
process.stdout.write(JSON.stringify(result));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (invokedAsScript(import.meta.url))
|
|
161
|
+
main().catch(() => {
|
|
162
|
+
process.stderr.write("hook provider request refused\n");
|
|
163
|
+
process.exitCode = 1;
|
|
164
|
+
});
|
|
@@ -0,0 +1,272 @@
|
|
|
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
|
+
/** Supported disposable Actions Linux gates use original native tools; publisher authority stays in another job. */
|
|
5
|
+
import {
|
|
6
|
+
existsSync,
|
|
7
|
+
lstatSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
mkdirSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
} from "node:fs";
|
|
12
|
+
import { join, resolve } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { required } from "./npm-update-contract.mjs";
|
|
15
|
+
import { runProcess, writeJson } from "./npm-update-process.mjs";
|
|
16
|
+
import { qualifiedControllerGraph } from "./npm-update-helper.mjs";
|
|
17
|
+
import {
|
|
18
|
+
controllerTools,
|
|
19
|
+
controllerSubject,
|
|
20
|
+
} from "./npm-update-controller-factory.mjs";
|
|
21
|
+
import { startHookReadBroker } from "./npm-update-hook-provider.mjs";
|
|
22
|
+
import { originalHookInstallation } from "./npm-update-hook-installation.mjs";
|
|
23
|
+
import {
|
|
24
|
+
sha256,
|
|
25
|
+
PROPOSAL_PREDICATE,
|
|
26
|
+
RECOVERY_PREDICATE,
|
|
27
|
+
} from "./github-attestation-verifier.mjs";
|
|
28
|
+
|
|
29
|
+
/** This diagnoses the supported caller; attestation/provider validation independently establishes actual authority. */
|
|
30
|
+
export function assertHostedGate(
|
|
31
|
+
context,
|
|
32
|
+
env = process.env,
|
|
33
|
+
platform = process.platform,
|
|
34
|
+
arch = process.arch
|
|
35
|
+
) {
|
|
36
|
+
required(
|
|
37
|
+
platform === "linux" &&
|
|
38
|
+
arch === "x64" &&
|
|
39
|
+
env.GITHUB_ACTIONS === "true" &&
|
|
40
|
+
env.RUNNER_ENVIRONMENT === "github-hosted" &&
|
|
41
|
+
env.GITHUB_REPOSITORY === context.policy.repository &&
|
|
42
|
+
env.GITHUB_SHA === context.proposal.parent &&
|
|
43
|
+
env.GITHUB_REF === "refs/heads/main" &&
|
|
44
|
+
context.config.automationProvenance.allowedTriggers.includes(
|
|
45
|
+
env.GITHUB_EVENT_NAME
|
|
46
|
+
),
|
|
47
|
+
"gate requires the opted-in genuine hosted Linux AMD64 caller"
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Every native step consumes the same original thirty-minute phase budget. */
|
|
52
|
+
export function remainingGateTime(deadline, now = Date.now()) {
|
|
53
|
+
required(
|
|
54
|
+
Number.isSafeInteger(deadline) &&
|
|
55
|
+
deadline > now &&
|
|
56
|
+
deadline <= now + 1_800_000,
|
|
57
|
+
"original gate phase deadline is absent or expired"
|
|
58
|
+
);
|
|
59
|
+
return deadline - now;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function nativeStep(context, env, command, args, maximum = 3_145_728) {
|
|
63
|
+
return runProcess(command, args, {
|
|
64
|
+
cwd: context.cwd,
|
|
65
|
+
env,
|
|
66
|
+
timeout: remainingGateTime(context.deadline),
|
|
67
|
+
maximum,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Lifecycle execution is disabled; Bundler is an explicit original tool install in this credential-free job. */
|
|
72
|
+
async function installHostedDependencies(context, root, env) {
|
|
73
|
+
const ruby = existsSync(join(context.cwd, "Gemfile"));
|
|
74
|
+
const installed = { ...env };
|
|
75
|
+
if (ruby) {
|
|
76
|
+
required(
|
|
77
|
+
existsSync(join(context.cwd, "Gemfile.lock")),
|
|
78
|
+
"frozen Ruby lock is missing"
|
|
79
|
+
);
|
|
80
|
+
installed.BUNDLE_PATH = join(root, "ruby-dependencies");
|
|
81
|
+
installed.BUNDLE_APP_CONFIG = join(root, "bundle");
|
|
82
|
+
installed.BUNDLE_FROZEN = "true";
|
|
83
|
+
}
|
|
84
|
+
const before = [
|
|
85
|
+
"package.json",
|
|
86
|
+
"package-lock.json",
|
|
87
|
+
...(ruby ? ["Gemfile", "Gemfile.lock"] : []),
|
|
88
|
+
].map(name => [name, readFileSync(join(context.cwd, name))]);
|
|
89
|
+
await nativeStep(context, installed, "npm", [
|
|
90
|
+
"ci",
|
|
91
|
+
"--ignore-scripts",
|
|
92
|
+
"--no-audit",
|
|
93
|
+
"--no-fund",
|
|
94
|
+
]);
|
|
95
|
+
if (ruby)
|
|
96
|
+
await nativeStep(context, installed, "bundle", [
|
|
97
|
+
"install",
|
|
98
|
+
"--jobs",
|
|
99
|
+
"2",
|
|
100
|
+
"--retry",
|
|
101
|
+
"0",
|
|
102
|
+
]);
|
|
103
|
+
required(
|
|
104
|
+
before.every(([name, bytes]) =>
|
|
105
|
+
readFileSync(join(context.cwd, name)).equals(bytes)
|
|
106
|
+
),
|
|
107
|
+
"ordinary dependency install changed manifest/lock baseline"
|
|
108
|
+
);
|
|
109
|
+
return installed;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The original supported manager installs its own wrappers; no synthetic replacement hook is written. */
|
|
113
|
+
async function installOriginalManager(context, env) {
|
|
114
|
+
const lefthook = existsSync(join(context.cwd, "lefthook.yml"));
|
|
115
|
+
if (lefthook)
|
|
116
|
+
await nativeStep(context, env, "bundle", ["exec", "lefthook", "install"]);
|
|
117
|
+
else {
|
|
118
|
+
const husky = join(context.cwd, "node_modules/husky/bin.js");
|
|
119
|
+
required(
|
|
120
|
+
existsSync(husky) && existsSync(join(context.cwd, ".husky/pre-push")),
|
|
121
|
+
"original hook manager installation is unavailable"
|
|
122
|
+
);
|
|
123
|
+
await nativeStep(context, env, process.execPath, [husky]);
|
|
124
|
+
}
|
|
125
|
+
const git = args => nativeStep(context, env, "/usr/bin/git", args);
|
|
126
|
+
const result = await git(["rev-parse", "--git-path", "hooks"]);
|
|
127
|
+
const text = result.stdout.toString();
|
|
128
|
+
required(
|
|
129
|
+
text.endsWith("\n") && !/[\n\r\0]/.test(text.slice(0, -1)),
|
|
130
|
+
"original hook path is unsupported"
|
|
131
|
+
);
|
|
132
|
+
const directory = resolve(context.cwd, text.slice(0, -1));
|
|
133
|
+
const wrappers = {};
|
|
134
|
+
for (const name of [
|
|
135
|
+
"pre-commit",
|
|
136
|
+
"prepare-commit-msg",
|
|
137
|
+
"commit-msg",
|
|
138
|
+
"pre-push",
|
|
139
|
+
]) {
|
|
140
|
+
const path = join(directory, name);
|
|
141
|
+
required(
|
|
142
|
+
lstatSync(path).isFile() && !lstatSync(path).isSymbolicLink(),
|
|
143
|
+
"original hook wrapper is missing or aliased"
|
|
144
|
+
);
|
|
145
|
+
wrappers[name] = sha256(readFileSync(path));
|
|
146
|
+
}
|
|
147
|
+
const installation = { manager: lefthook ? "lefthook" : "husky", wrappers };
|
|
148
|
+
await originalHookInstallation(context.cwd, git, installation);
|
|
149
|
+
return installation;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Proof paths are the actual fixed slots installed for the unchanged canonical verifier. */
|
|
153
|
+
async function proofScope(context, env) {
|
|
154
|
+
const result = await nativeStep(context, env, "/usr/bin/git", [
|
|
155
|
+
"rev-parse",
|
|
156
|
+
"--git-common-dir",
|
|
157
|
+
]);
|
|
158
|
+
const text = result.stdout.toString();
|
|
159
|
+
required(
|
|
160
|
+
text.endsWith("\n") && !/[\n\r\0]/.test(text.slice(0, -1)),
|
|
161
|
+
"original Git control path is unsupported"
|
|
162
|
+
);
|
|
163
|
+
const control = resolve(
|
|
164
|
+
context.cwd,
|
|
165
|
+
text.slice(0, -1),
|
|
166
|
+
"lisa/automation-provenance"
|
|
167
|
+
);
|
|
168
|
+
const policy = context.config.automationProvenance;
|
|
169
|
+
const record = (file, bundle, predicate) => ({
|
|
170
|
+
file: join(control, file),
|
|
171
|
+
bundle: join(control, bundle),
|
|
172
|
+
predicate,
|
|
173
|
+
signerWorkflow: policy.signerWorkflow,
|
|
174
|
+
signerDigest: policy.signerDigest,
|
|
175
|
+
});
|
|
176
|
+
const proofs = [record("descriptor.json", "bundle.json", PROPOSAL_PREDICATE)];
|
|
177
|
+
if (context.recovery !== undefined)
|
|
178
|
+
proofs.push(
|
|
179
|
+
record("recovery.json", "recovery-bundle.json", RECOVERY_PREDICATE)
|
|
180
|
+
);
|
|
181
|
+
const recoveryDescriptor =
|
|
182
|
+
context.recovery === undefined
|
|
183
|
+
? undefined
|
|
184
|
+
: JSON.parse(context.recovery.toString());
|
|
185
|
+
return { proofs, recoveryDescriptor };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** PATH preserves original native commands; the sole Node gateway carries no provider token. */
|
|
189
|
+
function nodeGateway(root, context) {
|
|
190
|
+
const bin = join(root, "hook-bin");
|
|
191
|
+
mkdirSync(bin, { mode: 0o700 });
|
|
192
|
+
const entry = fileURLToPath(
|
|
193
|
+
new URL("./npm-update-hosted-hook.mjs", import.meta.url)
|
|
194
|
+
);
|
|
195
|
+
const quote = text => {
|
|
196
|
+
required(!/[\0\n\r]/.test(text), "unsupported hosted gateway path");
|
|
197
|
+
return `'${text.replaceAll("'", "'\\''")}'`;
|
|
198
|
+
};
|
|
199
|
+
const file = join(root, "hosted-hooks.json");
|
|
200
|
+
writeJson(file, context);
|
|
201
|
+
writeFileSync(
|
|
202
|
+
join(bin, "node"),
|
|
203
|
+
`#!/bin/sh\nexec ${quote(context.native.node.path)} ${quote(entry)} --context ${quote(file)} -- "$@"\n`,
|
|
204
|
+
{ flag: "wx", mode: 0o700 }
|
|
205
|
+
);
|
|
206
|
+
return bin;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** A separate read-only capability is available during original gates; it cannot acquire publisher/issuer permissions. */
|
|
210
|
+
export async function createHostedGate(context, root, env) {
|
|
211
|
+
assertHostedGate(context);
|
|
212
|
+
const native = controllerTools(context.config.automationProvenance);
|
|
213
|
+
const graph = await qualifiedControllerGraph(context.cwd, context.config, [
|
|
214
|
+
"lisa-work-item.mjs",
|
|
215
|
+
"lisa-automation-provenance.mjs",
|
|
216
|
+
"lisa-rails-prepush.mjs",
|
|
217
|
+
"lib/npm-update-hosted-hook.mjs",
|
|
218
|
+
"lib/npm-update-hook-preload.mjs",
|
|
219
|
+
]);
|
|
220
|
+
const installed = await installHostedDependencies(context, root, env);
|
|
221
|
+
const installation = await installOriginalManager(context, installed);
|
|
222
|
+
const scope = await proofScope(context, installed);
|
|
223
|
+
mkdirSync(join(root, "gh-config"), { mode: 0o700 });
|
|
224
|
+
const profile = {
|
|
225
|
+
version: 1,
|
|
226
|
+
deadline: context.deadline,
|
|
227
|
+
cwd: context.cwd,
|
|
228
|
+
home: root,
|
|
229
|
+
nativeGh: native.gh,
|
|
230
|
+
invocation: {
|
|
231
|
+
entry: join(context.cwd, "scripts/lisa-npm-updater.mjs"),
|
|
232
|
+
args: [join(context.cwd, "scripts/lisa-npm-updater.mjs"), "gate"],
|
|
233
|
+
cwd: context.cwd,
|
|
234
|
+
},
|
|
235
|
+
subject: controllerSubject(
|
|
236
|
+
{ ...context, recoveryDescriptor: scope.recoveryDescriptor },
|
|
237
|
+
"hook-read",
|
|
238
|
+
null,
|
|
239
|
+
scope.proofs
|
|
240
|
+
),
|
|
241
|
+
};
|
|
242
|
+
const broker = await startHookReadBroker(profile, context.token);
|
|
243
|
+
try {
|
|
244
|
+
const bin = nodeGateway(root, {
|
|
245
|
+
version: 1,
|
|
246
|
+
root,
|
|
247
|
+
cwd: context.cwd,
|
|
248
|
+
graph,
|
|
249
|
+
native,
|
|
250
|
+
reader: { root, path: broker.path, deadline: context.deadline },
|
|
251
|
+
});
|
|
252
|
+
return {
|
|
253
|
+
installation,
|
|
254
|
+
env: {
|
|
255
|
+
...installed,
|
|
256
|
+
PATH: `${bin}:${installed.PATH}`,
|
|
257
|
+
CI: "true",
|
|
258
|
+
GITHUB_ACTIONS: "true",
|
|
259
|
+
GITHUB_REPOSITORY: context.policy.repository,
|
|
260
|
+
GITHUB_RUN_ID: process.env.GITHUB_RUN_ID,
|
|
261
|
+
GITHUB_RUN_ATTEMPT: process.env.GITHUB_RUN_ATTEMPT,
|
|
262
|
+
GITHUB_SHA: context.proposal.parent,
|
|
263
|
+
GITHUB_REF: "refs/heads/main",
|
|
264
|
+
GITHUB_EVENT_NAME: process.env.GITHUB_EVENT_NAME,
|
|
265
|
+
},
|
|
266
|
+
close: broker.close,
|
|
267
|
+
};
|
|
268
|
+
} catch (error) {
|
|
269
|
+
await broker.close();
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
}
|