@codyswann/lisa 4.19.0 → 4.20.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/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +9 -1
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +2 -2
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +349 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-aws/SKILL.md +23 -5
- package/plugins/lisa/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +349 -0
- package/plugins/lisa/skills/lisa-setup-remote-aws/SKILL.md +23 -5
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +349 -0
- package/plugins/lisa-agy/skills/lisa-setup-remote-aws/SKILL.md +23 -5
- 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/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +349 -0
- package/plugins/lisa-copilot/skills/lisa-setup-remote-aws/SKILL.md +23 -5
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +349 -0
- package/plugins/lisa-cursor/skills/lisa-setup-remote-aws/SKILL.md +23 -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/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +349 -0
- package/plugins/src/base/skills/lisa-setup-remote-aws/SKILL.md +23 -5
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Publish a newly emitted remote-agent AWS bootstrap bundle to the configured
|
|
4
|
+
* secrets provider without creating a silent split brain.
|
|
5
|
+
*
|
|
6
|
+
* Infrastructure owns the IAM access key and usually emits the complete bundle
|
|
7
|
+
* to AWS Secrets Manager. Remote coding environments cannot use that store as
|
|
8
|
+
* their bootstrap provider: reading it already requires AWS credentials. The
|
|
9
|
+
* provider configured by lisa-secrets-access therefore needs the same bundle,
|
|
10
|
+
* and a manual copy leaves two ways for a rotation to stop halfway.
|
|
11
|
+
*
|
|
12
|
+
* This command makes that handoff one fail-closed operation:
|
|
13
|
+
*
|
|
14
|
+
* 1. prove the existing provider record is writable with a no-op write;
|
|
15
|
+
* 2. validate the candidate and assume every declared role with it;
|
|
16
|
+
* 3. replace the provider value;
|
|
17
|
+
* 4. read the value back exactly and assume every role again; and
|
|
18
|
+
* 5. restore the previous provider value if a post-write check fails.
|
|
19
|
+
*
|
|
20
|
+
* The candidate arrives on stdin so it never appears in a shell command or a
|
|
21
|
+
* temporary file. No credential value is written to stdout or stderr.
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* aws secretsmanager get-secret-value ... --query SecretString --output text \
|
|
25
|
+
* | publish-aws-bootstrap.mjs publish
|
|
26
|
+
* publish-aws-bootstrap.mjs verify
|
|
27
|
+
* publish-aws-bootstrap.mjs preflight
|
|
28
|
+
* @module publish-aws-bootstrap
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
32
|
+
import { fileURLToPath } from "node:url";
|
|
33
|
+
|
|
34
|
+
import {
|
|
35
|
+
BOOTSTRAP_KEY,
|
|
36
|
+
parseBootstrap,
|
|
37
|
+
readProfiles,
|
|
38
|
+
} from "./aws-bootstrap.mjs";
|
|
39
|
+
import { fetchAll, writeSecret } from "./providers.mjs";
|
|
40
|
+
import { readConfig } from "./surfaces.mjs";
|
|
41
|
+
|
|
42
|
+
import { boundedChildOutput } from "../../lisa-setup-workstation/scripts/bounded-child.mjs";
|
|
43
|
+
|
|
44
|
+
const PROFILE_NAME = /^[\w.@-]+$/;
|
|
45
|
+
const ROLE_ARN =
|
|
46
|
+
/^arn:(?:aws|aws-us-gov|aws-cn):iam::\d{12}:role\/[\w+=,.@/-]+$/;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Validate and normalize one complete bundle without changing its serialized
|
|
50
|
+
* representation.
|
|
51
|
+
* @param {string} raw Candidate JSON from stdin or the provider.
|
|
52
|
+
* @returns {{raw: string, bundle: object, profiles: Array<{name: string, roleArn: string, region: string}>}}
|
|
53
|
+
* Validated candidate and its role entries.
|
|
54
|
+
*/
|
|
55
|
+
export function validateAwsBootstrap(raw) {
|
|
56
|
+
if (typeof raw !== "string" || !raw.trim()) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`${BOOTSTRAP_KEY} is empty; refusing to replace a working credential with nothing`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// AWS CLI's text output ends with one newline. It is transport framing, not
|
|
63
|
+
// part of the SecretString, so do not make every publication differ by one
|
|
64
|
+
// invisible byte from the value AWS emitted.
|
|
65
|
+
const normalized = raw.trim();
|
|
66
|
+
const bundle = parseBootstrap(normalized);
|
|
67
|
+
if (!bundle || Array.isArray(bundle)) {
|
|
68
|
+
throw new Error(`${BOOTSTRAP_KEY} is not a JSON object`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const accessKeyId = bundle.accessKeyId ?? bundle.aws_access_key_id;
|
|
72
|
+
const secretAccessKey =
|
|
73
|
+
bundle.secretAccessKey ?? bundle.aws_secret_access_key;
|
|
74
|
+
for (const [field, value] of [
|
|
75
|
+
["accessKeyId", accessKeyId],
|
|
76
|
+
["secretAccessKey", secretAccessKey],
|
|
77
|
+
["externalId", bundle.externalId],
|
|
78
|
+
["roleName", bundle.roleName],
|
|
79
|
+
]) {
|
|
80
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
81
|
+
throw new Error(`${BOOTSTRAP_KEY}.${field} must be a non-empty string`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const entries = Object.entries(readProfiles(bundle));
|
|
86
|
+
if (entries.length === 0) {
|
|
87
|
+
throw new Error(`${BOOTSTRAP_KEY}.profiles must declare at least one role`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const profiles = entries.map(([name, entry]) => {
|
|
91
|
+
if (!PROFILE_NAME.test(name)) {
|
|
92
|
+
throw new Error(`AWS bootstrap profile name is not safe: ${name}`);
|
|
93
|
+
}
|
|
94
|
+
const roleArn = entry?.roleArn ?? entry?.role_arn;
|
|
95
|
+
if (typeof roleArn !== "string" || !ROLE_ARN.test(roleArn)) {
|
|
96
|
+
throw new Error(`AWS bootstrap profile ${name} has an invalid roleArn`);
|
|
97
|
+
}
|
|
98
|
+
if (typeof entry?.region !== "string" || !entry.region.trim()) {
|
|
99
|
+
throw new Error(`AWS bootstrap profile ${name} has no region`);
|
|
100
|
+
}
|
|
101
|
+
return { name, roleArn, region: entry.region };
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return { raw: normalized, bundle, profiles };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Build the isolated environment used to prove one candidate. Ambient AWS
|
|
109
|
+
* variables are removed so a green result cannot come from the host identity.
|
|
110
|
+
* @param {object} bundle Validated bundle.
|
|
111
|
+
* @returns {NodeJS.ProcessEnv} Child environment containing only this identity.
|
|
112
|
+
*/
|
|
113
|
+
export function candidateAwsEnvironment(bundle) {
|
|
114
|
+
const env = { ...process.env };
|
|
115
|
+
for (const key of [
|
|
116
|
+
"AWS_PROFILE",
|
|
117
|
+
"AWS_DEFAULT_PROFILE",
|
|
118
|
+
"AWS_SESSION_TOKEN",
|
|
119
|
+
"AWS_SECURITY_TOKEN",
|
|
120
|
+
]) {
|
|
121
|
+
delete env[key];
|
|
122
|
+
}
|
|
123
|
+
env.AWS_ACCESS_KEY_ID = String(
|
|
124
|
+
bundle.accessKeyId ?? bundle.aws_access_key_id
|
|
125
|
+
);
|
|
126
|
+
env.AWS_SECRET_ACCESS_KEY = String(
|
|
127
|
+
bundle.secretAccessKey ?? bundle.aws_secret_access_key
|
|
128
|
+
);
|
|
129
|
+
env.AWS_PAGER = "";
|
|
130
|
+
return env;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Prove that the candidate authenticates and can assume every declared role.
|
|
135
|
+
* Captures all child output so account ids and credential metadata never reach
|
|
136
|
+
* a shared log.
|
|
137
|
+
* @param {{bundle: object, profiles: Array<object>}} candidate Validated bundle.
|
|
138
|
+
* @param {(bin: string, args: string[], options: object) => string} [run]
|
|
139
|
+
* Injectable child runner for tests.
|
|
140
|
+
*/
|
|
141
|
+
export function verifyAwsBootstrap(candidate, run = boundedChildOutput) {
|
|
142
|
+
const env = candidateAwsEnvironment(candidate.bundle);
|
|
143
|
+
for (const profile of candidate.profiles) {
|
|
144
|
+
const sessionName = `lisa-publish-${profile.name}`
|
|
145
|
+
.replace(/[^A-Za-z0-9_+=,.@-]/g, "-")
|
|
146
|
+
.slice(0, 64);
|
|
147
|
+
try {
|
|
148
|
+
run(
|
|
149
|
+
"aws",
|
|
150
|
+
[
|
|
151
|
+
"sts",
|
|
152
|
+
"assume-role",
|
|
153
|
+
"--role-arn",
|
|
154
|
+
profile.roleArn,
|
|
155
|
+
"--role-session-name",
|
|
156
|
+
sessionName,
|
|
157
|
+
"--external-id",
|
|
158
|
+
String(candidate.bundle.externalId),
|
|
159
|
+
"--region",
|
|
160
|
+
profile.region,
|
|
161
|
+
"--query",
|
|
162
|
+
"Credentials.Expiration",
|
|
163
|
+
"--output",
|
|
164
|
+
"text",
|
|
165
|
+
],
|
|
166
|
+
{ encoding: "utf8", env, stdio: ["ignore", "pipe", "pipe"] }
|
|
167
|
+
);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
const reason =
|
|
170
|
+
error instanceof Error ? error.message.split("\n")[0] : String(error);
|
|
171
|
+
throw new Error(
|
|
172
|
+
`AWS bootstrap profile ${profile.name} failed STS verification: ${reason}`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return candidate.profiles.map(profile => profile.name);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Locate the provider record that owns the bundle.
|
|
181
|
+
* @param {object} cfg Resolved lisa-secrets-access configuration.
|
|
182
|
+
* @param {(cfg: object) => Map<string, object>} fetch Provider reader.
|
|
183
|
+
* @returns {{id: string, value: string}} Writable provider entry.
|
|
184
|
+
*/
|
|
185
|
+
function currentEntry(cfg, fetch) {
|
|
186
|
+
const entry = fetch(cfg).get(BOOTSTRAP_KEY);
|
|
187
|
+
if (!entry) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
`${BOOTSTRAP_KEY} is not available through the configured provider boundary`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
if (!entry.id) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
`${BOOTSTRAP_KEY} has no provider identifier, so it cannot be published`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
return { id: entry.id, value: entry.value };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Exercise the exact provider write permission without changing its value.
|
|
202
|
+
* @param {object} cfg Resolved configuration.
|
|
203
|
+
* @param {object} [operations] Injectable provider operations for tests.
|
|
204
|
+
* @returns {{id: string, value: string}} Current provider entry.
|
|
205
|
+
*/
|
|
206
|
+
export function preflightAwsBootstrap(cfg, operations = {}) {
|
|
207
|
+
const fetch = operations.fetch ?? fetchAll;
|
|
208
|
+
const write = operations.write ?? writeSecret;
|
|
209
|
+
const entry = currentEntry(cfg, fetch);
|
|
210
|
+
try {
|
|
211
|
+
write(cfg, entry.id, entry.value);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
const reason =
|
|
214
|
+
error instanceof Error ? error.message.split("\n")[0] : String(error);
|
|
215
|
+
throw new Error(
|
|
216
|
+
`no write access to ${BOOTSTRAP_KEY}: ${reason}\n` +
|
|
217
|
+
`Refusing to continue because a rotated key that cannot be published would strand every remote session.`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
return entry;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Restore the previous provider value and prove the restoration.
|
|
225
|
+
* @param {object} cfg Resolved configuration.
|
|
226
|
+
* @param {{id: string, value: string}} previous Previous entry.
|
|
227
|
+
* @param {Function} fetch Provider reader.
|
|
228
|
+
* @param {Function} write Provider writer.
|
|
229
|
+
* @returns {string} Human-readable rollback outcome with no secret material.
|
|
230
|
+
*/
|
|
231
|
+
function rollback(cfg, previous, fetch, write) {
|
|
232
|
+
try {
|
|
233
|
+
write(cfg, previous.id, previous.value);
|
|
234
|
+
const restored = currentEntry(cfg, fetch);
|
|
235
|
+
if (restored.value !== previous.value) {
|
|
236
|
+
return "the previous provider value could not be verified after rollback";
|
|
237
|
+
}
|
|
238
|
+
return "the previous provider value was restored";
|
|
239
|
+
} catch (error) {
|
|
240
|
+
const reason =
|
|
241
|
+
error instanceof Error ? error.message.split("\n")[0] : String(error);
|
|
242
|
+
return `rollback failed: ${reason}`;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Publish, read back, and verify a candidate. Any failure after the candidate
|
|
248
|
+
* write triggers a restoration of the previous provider value.
|
|
249
|
+
* @param {string} raw Candidate JSON.
|
|
250
|
+
* @param {object} cfg Resolved configuration.
|
|
251
|
+
* @param {object} [operations] Injectable provider/AWS operations for tests.
|
|
252
|
+
* @returns {{changed: boolean, profiles: string[]}} Publication outcome.
|
|
253
|
+
*/
|
|
254
|
+
export function publishAwsBootstrap(raw, cfg, operations = {}) {
|
|
255
|
+
const fetch = operations.fetch ?? fetchAll;
|
|
256
|
+
const write = operations.write ?? writeSecret;
|
|
257
|
+
const verify = operations.verify ?? verifyAwsBootstrap;
|
|
258
|
+
const previous = preflightAwsBootstrap(cfg, { fetch, write });
|
|
259
|
+
const candidate = validateAwsBootstrap(raw);
|
|
260
|
+
|
|
261
|
+
// Verify before the write. A candidate that cannot assume one of its roles
|
|
262
|
+
// never becomes the value every new remote session receives.
|
|
263
|
+
verify(candidate);
|
|
264
|
+
|
|
265
|
+
if (candidate.raw === previous.value) {
|
|
266
|
+
return {
|
|
267
|
+
changed: false,
|
|
268
|
+
profiles: candidate.profiles.map(profile => profile.name),
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
write(cfg, previous.id, candidate.raw);
|
|
274
|
+
const stored = currentEntry(cfg, fetch);
|
|
275
|
+
if (stored.value !== candidate.raw) {
|
|
276
|
+
throw new Error("provider read-back did not match the candidate exactly");
|
|
277
|
+
}
|
|
278
|
+
const storedCandidate = validateAwsBootstrap(stored.value);
|
|
279
|
+
verify(storedCandidate);
|
|
280
|
+
return {
|
|
281
|
+
changed: true,
|
|
282
|
+
profiles: storedCandidate.profiles.map(profile => profile.name),
|
|
283
|
+
};
|
|
284
|
+
} catch (error) {
|
|
285
|
+
const reason =
|
|
286
|
+
error instanceof Error ? error.message.split("\n")[0] : String(error);
|
|
287
|
+
const rollbackOutcome = rollback(cfg, previous, fetch, write);
|
|
288
|
+
throw new Error(
|
|
289
|
+
`${BOOTSTRAP_KEY} publication failed: ${reason}; ${rollbackOutcome}`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Read a candidate from stdin without ever placing it in argv. */
|
|
295
|
+
function readCandidate() {
|
|
296
|
+
return readFileSync(0, "utf8");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function main() {
|
|
300
|
+
const [operation] = process.argv.slice(2);
|
|
301
|
+
const cfg = readConfig();
|
|
302
|
+
|
|
303
|
+
if (operation === "preflight") {
|
|
304
|
+
const current = preflightAwsBootstrap(cfg);
|
|
305
|
+
const candidate = validateAwsBootstrap(current.value);
|
|
306
|
+
const profiles = verifyAwsBootstrap(candidate);
|
|
307
|
+
console.log(
|
|
308
|
+
`${BOOTSTRAP_KEY}: provider write path and ${profiles.length} AWS profile(s) verified; no value changed`
|
|
309
|
+
);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (operation === "verify") {
|
|
314
|
+
const current = currentEntry(cfg, fetchAll);
|
|
315
|
+
const candidate = validateAwsBootstrap(current.value);
|
|
316
|
+
const profiles = verifyAwsBootstrap(candidate);
|
|
317
|
+
console.log(`${BOOTSTRAP_KEY}: ${profiles.length} AWS profile(s) verified`);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (operation === "publish") {
|
|
322
|
+
const result = publishAwsBootstrap(readCandidate(), cfg);
|
|
323
|
+
console.log(
|
|
324
|
+
`${BOOTSTRAP_KEY}: ${result.changed ? "published" : "already current"}; ` +
|
|
325
|
+
`${result.profiles.length} AWS profile(s) verified`
|
|
326
|
+
);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
throw new Error("usage: publish-aws-bootstrap.mjs preflight|verify|publish");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function invokedAsScript(moduleUrl, argv1 = process.argv[1]) {
|
|
334
|
+
if (!argv1) return false;
|
|
335
|
+
try {
|
|
336
|
+
return realpathSync(argv1) === realpathSync(fileURLToPath(moduleUrl));
|
|
337
|
+
} catch {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (invokedAsScript(import.meta.url)) {
|
|
343
|
+
try {
|
|
344
|
+
main();
|
|
345
|
+
} catch (error) {
|
|
346
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
347
|
+
process.exit(1);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
@@ -49,11 +49,29 @@ it to AWS Secrets Manager as `remote-agent-credentials`, but that is where it is
|
|
|
49
49
|
*emitted*, not where it must *live*. Copy it once into the project's configured
|
|
50
50
|
provider and read it from there.
|
|
51
51
|
|
|
52
|
-
**Do not
|
|
53
|
-
the platform's secret store is two live copies of one credential: rotate
|
|
54
|
-
IAM key and whichever copy you forget
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
**Do not maintain two stores by hand.** A bundle sitting in Secrets Manager
|
|
53
|
+
and in the platform's secret store is two live copies of one credential: rotate
|
|
54
|
+
the IAM key and whichever copy you forget becomes stale, with nothing to say
|
|
55
|
+
which is current. Treat the infrastructure secret as a short-lived deployment
|
|
56
|
+
emission and the configured provider as the consumer-facing source of truth.
|
|
57
|
+
|
|
58
|
+
Publish the emission as part of the same rotation automation. The publisher
|
|
59
|
+
reads the candidate on stdin, proves every role before writing, reads the
|
|
60
|
+
provider value back exactly, proves every stored role again, and restores the
|
|
61
|
+
previous provider value if a post-write check fails:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
aws --profile shared secretsmanager get-secret-value \
|
|
65
|
+
--secret-id remote-agent-credentials \
|
|
66
|
+
--query SecretString \
|
|
67
|
+
--output text \
|
|
68
|
+
| node .claude/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs publish
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Resolve the script from the installed runtime or `node_modules` when the
|
|
72
|
+
`.claude/skills` copy is not present. Never print or place the candidate in an
|
|
73
|
+
argument or temporary file. A CDK deployment followed later by a human copy is
|
|
74
|
+
not a completed rotation.
|
|
57
75
|
|
|
58
76
|
**One provider cannot serve this particular secret.** If `secrets.provider` is
|
|
59
77
|
`aws`, reading the bundle requires AWS credentials — and the bundle *is* the AWS
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.20.0",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.20.0",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.20.0",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.20.0",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.20.0",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|