@codyswann/lisa 2.333.1 → 2.333.2
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 +2 -1
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +1 -1
- 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/materialize-secrets.mjs +57 -26
- package/plugins/lisa/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +57 -26
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +57 -26
- 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/materialize-secrets.mjs +57 -26
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +57 -26
- 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/materialize-secrets.mjs +57 -26
package/package.json
CHANGED
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"brace-expansion": ">=5.0.9"
|
|
116
116
|
},
|
|
117
117
|
"name": "@codyswann/lisa",
|
|
118
|
-
"version": "2.333.
|
|
118
|
+
"version": "2.333.2",
|
|
119
119
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
120
120
|
"main": "dist/index.js",
|
|
121
121
|
"exports": {
|
package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/materialize-secrets.mjs
CHANGED
|
@@ -80,7 +80,41 @@ const PROFILE_END = "# <<< lisa secrets (managed) <<<";
|
|
|
80
80
|
* consumer while still being the thing that distinguishes "our file, refresh
|
|
81
81
|
* it" from "someone else's file, leave it alone".
|
|
82
82
|
*/
|
|
83
|
-
const MANAGED_MARKER = "# managed by lisa-secrets-access";
|
|
83
|
+
const MANAGED_MARKER = "# >>> managed by lisa-secrets-access >>>";
|
|
84
|
+
|
|
85
|
+
/** Closes the managed region of an `~/.aws` file. */
|
|
86
|
+
const MANAGED_END = "# <<< managed by lisa-secrets-access <<<";
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Replace this module's delimited region in a file, preserving everything else.
|
|
90
|
+
*
|
|
91
|
+
* Written as a merge rather than a whole-file write because both `~/.aws` files
|
|
92
|
+
* routinely hold sections nobody here knows about — an operator's own profiles,
|
|
93
|
+
* or a container's bare `[default]`. Refusing on their account wrote nothing at
|
|
94
|
+
* all; overwriting would delete them. This does neither.
|
|
95
|
+
* @param {string} current Existing file contents, or "".
|
|
96
|
+
* @param {string} body The region this module owns.
|
|
97
|
+
* @returns {string} The merged file.
|
|
98
|
+
*/
|
|
99
|
+
export function upsertManagedBlock(current, body) {
|
|
100
|
+
const block = `${MANAGED_MARKER}\n${body.trimEnd()}\n${MANAGED_END}`;
|
|
101
|
+
const start = current.indexOf(MANAGED_MARKER);
|
|
102
|
+
|
|
103
|
+
if (start === -1) {
|
|
104
|
+
const prefix =
|
|
105
|
+
current && !current.endsWith("\n") ? `${current}\n` : current;
|
|
106
|
+
return `${prefix}${prefix ? "\n" : ""}${block}\n`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const endAt = current.indexOf(MANAGED_END, start);
|
|
110
|
+
// A truncated block (marker opened, never closed) would otherwise swallow the
|
|
111
|
+
// rest of the file on every subsequent write.
|
|
112
|
+
const after =
|
|
113
|
+
endAt === -1
|
|
114
|
+
? ""
|
|
115
|
+
: current.slice(endAt + MANAGED_END.length).replace(/^\n/, "");
|
|
116
|
+
return `${current.slice(0, start)}${block}\n${after}`;
|
|
117
|
+
}
|
|
84
118
|
|
|
85
119
|
/**
|
|
86
120
|
* Make every shell in this container load the materialized secrets.
|
|
@@ -161,7 +195,7 @@ export function installAwsProfiles(bundle, options = {}) {
|
|
|
161
195
|
const {
|
|
162
196
|
home = process.env.HOME || homedir(),
|
|
163
197
|
mkdir = mkdirSync,
|
|
164
|
-
write =
|
|
198
|
+
write = writeAtomic,
|
|
165
199
|
read = readFileSync,
|
|
166
200
|
exists = existsSync,
|
|
167
201
|
chmod = chmodSync,
|
|
@@ -171,35 +205,32 @@ export function installAwsProfiles(bundle, options = {}) {
|
|
|
171
205
|
if (!rendered) return [];
|
|
172
206
|
|
|
173
207
|
const dir = join(home, ".aws");
|
|
208
|
+
mkdir(dir, { recursive: true, mode: 0o700 });
|
|
209
|
+
chmod(dir, 0o700);
|
|
174
210
|
|
|
175
|
-
//
|
|
211
|
+
// Merge into a delimited block; never replace the file.
|
|
212
|
+
//
|
|
213
|
+
// Refusing whole files whenever one already existed sounded safe and was
|
|
214
|
+
// worse than useless: a container ships `~/.aws/config` containing a bare
|
|
215
|
+
// `[default]`, so the guard fired every time, wrote nothing, and returned
|
|
216
|
+
// silently — while AWS_PROFILE was still derived from the bundle. The session
|
|
217
|
+
// got a pointer to a profile that did not exist:
|
|
218
|
+
//
|
|
219
|
+
// The config profile (agent-dev) could not be found
|
|
176
220
|
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
221
|
+
// Merging keeps the operator's own sections intact AND writes ours, which is
|
|
222
|
+
// what the guard was actually for. Same delimited-block approach as the shell
|
|
223
|
+
// profile, and `#` is a comment in the shared-config format so the markers are
|
|
224
|
+
// inert to every consumer.
|
|
225
|
+
for (const [name, body] of [
|
|
226
|
+
["credentials", rendered.credentials],
|
|
227
|
+
["config", rendered.config],
|
|
228
|
+
]) {
|
|
184
229
|
const file = join(dir, name);
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
230
|
+
const current = exists(file) ? String(read(file, "utf8")) : "";
|
|
231
|
+
write(file, upsertManagedBlock(current, body));
|
|
188
232
|
}
|
|
189
233
|
|
|
190
|
-
mkdir(dir, { recursive: true, mode: 0o700 });
|
|
191
|
-
chmod(dir, 0o700);
|
|
192
|
-
write(
|
|
193
|
-
join(dir, "credentials"),
|
|
194
|
-
`${MANAGED_MARKER}\n${rendered.credentials}`,
|
|
195
|
-
{
|
|
196
|
-
mode: 0o600,
|
|
197
|
-
}
|
|
198
|
-
);
|
|
199
|
-
write(join(dir, "config"), `${MANAGED_MARKER}\n${rendered.config}`, {
|
|
200
|
-
mode: 0o600,
|
|
201
|
-
});
|
|
202
|
-
|
|
203
234
|
return rendered.profiles;
|
|
204
235
|
}
|
|
205
236
|
|
|
@@ -80,7 +80,41 @@ const PROFILE_END = "# <<< lisa secrets (managed) <<<";
|
|
|
80
80
|
* consumer while still being the thing that distinguishes "our file, refresh
|
|
81
81
|
* it" from "someone else's file, leave it alone".
|
|
82
82
|
*/
|
|
83
|
-
const MANAGED_MARKER = "# managed by lisa-secrets-access";
|
|
83
|
+
const MANAGED_MARKER = "# >>> managed by lisa-secrets-access >>>";
|
|
84
|
+
|
|
85
|
+
/** Closes the managed region of an `~/.aws` file. */
|
|
86
|
+
const MANAGED_END = "# <<< managed by lisa-secrets-access <<<";
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Replace this module's delimited region in a file, preserving everything else.
|
|
90
|
+
*
|
|
91
|
+
* Written as a merge rather than a whole-file write because both `~/.aws` files
|
|
92
|
+
* routinely hold sections nobody here knows about — an operator's own profiles,
|
|
93
|
+
* or a container's bare `[default]`. Refusing on their account wrote nothing at
|
|
94
|
+
* all; overwriting would delete them. This does neither.
|
|
95
|
+
* @param {string} current Existing file contents, or "".
|
|
96
|
+
* @param {string} body The region this module owns.
|
|
97
|
+
* @returns {string} The merged file.
|
|
98
|
+
*/
|
|
99
|
+
export function upsertManagedBlock(current, body) {
|
|
100
|
+
const block = `${MANAGED_MARKER}\n${body.trimEnd()}\n${MANAGED_END}`;
|
|
101
|
+
const start = current.indexOf(MANAGED_MARKER);
|
|
102
|
+
|
|
103
|
+
if (start === -1) {
|
|
104
|
+
const prefix =
|
|
105
|
+
current && !current.endsWith("\n") ? `${current}\n` : current;
|
|
106
|
+
return `${prefix}${prefix ? "\n" : ""}${block}\n`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const endAt = current.indexOf(MANAGED_END, start);
|
|
110
|
+
// A truncated block (marker opened, never closed) would otherwise swallow the
|
|
111
|
+
// rest of the file on every subsequent write.
|
|
112
|
+
const after =
|
|
113
|
+
endAt === -1
|
|
114
|
+
? ""
|
|
115
|
+
: current.slice(endAt + MANAGED_END.length).replace(/^\n/, "");
|
|
116
|
+
return `${current.slice(0, start)}${block}\n${after}`;
|
|
117
|
+
}
|
|
84
118
|
|
|
85
119
|
/**
|
|
86
120
|
* Make every shell in this container load the materialized secrets.
|
|
@@ -161,7 +195,7 @@ export function installAwsProfiles(bundle, options = {}) {
|
|
|
161
195
|
const {
|
|
162
196
|
home = process.env.HOME || homedir(),
|
|
163
197
|
mkdir = mkdirSync,
|
|
164
|
-
write =
|
|
198
|
+
write = writeAtomic,
|
|
165
199
|
read = readFileSync,
|
|
166
200
|
exists = existsSync,
|
|
167
201
|
chmod = chmodSync,
|
|
@@ -171,35 +205,32 @@ export function installAwsProfiles(bundle, options = {}) {
|
|
|
171
205
|
if (!rendered) return [];
|
|
172
206
|
|
|
173
207
|
const dir = join(home, ".aws");
|
|
208
|
+
mkdir(dir, { recursive: true, mode: 0o700 });
|
|
209
|
+
chmod(dir, 0o700);
|
|
174
210
|
|
|
175
|
-
//
|
|
211
|
+
// Merge into a delimited block; never replace the file.
|
|
212
|
+
//
|
|
213
|
+
// Refusing whole files whenever one already existed sounded safe and was
|
|
214
|
+
// worse than useless: a container ships `~/.aws/config` containing a bare
|
|
215
|
+
// `[default]`, so the guard fired every time, wrote nothing, and returned
|
|
216
|
+
// silently — while AWS_PROFILE was still derived from the bundle. The session
|
|
217
|
+
// got a pointer to a profile that did not exist:
|
|
218
|
+
//
|
|
219
|
+
// The config profile (agent-dev) could not be found
|
|
176
220
|
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
221
|
+
// Merging keeps the operator's own sections intact AND writes ours, which is
|
|
222
|
+
// what the guard was actually for. Same delimited-block approach as the shell
|
|
223
|
+
// profile, and `#` is a comment in the shared-config format so the markers are
|
|
224
|
+
// inert to every consumer.
|
|
225
|
+
for (const [name, body] of [
|
|
226
|
+
["credentials", rendered.credentials],
|
|
227
|
+
["config", rendered.config],
|
|
228
|
+
]) {
|
|
184
229
|
const file = join(dir, name);
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
230
|
+
const current = exists(file) ? String(read(file, "utf8")) : "";
|
|
231
|
+
write(file, upsertManagedBlock(current, body));
|
|
188
232
|
}
|
|
189
233
|
|
|
190
|
-
mkdir(dir, { recursive: true, mode: 0o700 });
|
|
191
|
-
chmod(dir, 0o700);
|
|
192
|
-
write(
|
|
193
|
-
join(dir, "credentials"),
|
|
194
|
-
`${MANAGED_MARKER}\n${rendered.credentials}`,
|
|
195
|
-
{
|
|
196
|
-
mode: 0o600,
|
|
197
|
-
}
|
|
198
|
-
);
|
|
199
|
-
write(join(dir, "config"), `${MANAGED_MARKER}\n${rendered.config}`, {
|
|
200
|
-
mode: 0o600,
|
|
201
|
-
});
|
|
202
|
-
|
|
203
234
|
return rendered.profiles;
|
|
204
235
|
}
|
|
205
236
|
|
|
@@ -80,7 +80,41 @@ const PROFILE_END = "# <<< lisa secrets (managed) <<<";
|
|
|
80
80
|
* consumer while still being the thing that distinguishes "our file, refresh
|
|
81
81
|
* it" from "someone else's file, leave it alone".
|
|
82
82
|
*/
|
|
83
|
-
const MANAGED_MARKER = "# managed by lisa-secrets-access";
|
|
83
|
+
const MANAGED_MARKER = "# >>> managed by lisa-secrets-access >>>";
|
|
84
|
+
|
|
85
|
+
/** Closes the managed region of an `~/.aws` file. */
|
|
86
|
+
const MANAGED_END = "# <<< managed by lisa-secrets-access <<<";
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Replace this module's delimited region in a file, preserving everything else.
|
|
90
|
+
*
|
|
91
|
+
* Written as a merge rather than a whole-file write because both `~/.aws` files
|
|
92
|
+
* routinely hold sections nobody here knows about — an operator's own profiles,
|
|
93
|
+
* or a container's bare `[default]`. Refusing on their account wrote nothing at
|
|
94
|
+
* all; overwriting would delete them. This does neither.
|
|
95
|
+
* @param {string} current Existing file contents, or "".
|
|
96
|
+
* @param {string} body The region this module owns.
|
|
97
|
+
* @returns {string} The merged file.
|
|
98
|
+
*/
|
|
99
|
+
export function upsertManagedBlock(current, body) {
|
|
100
|
+
const block = `${MANAGED_MARKER}\n${body.trimEnd()}\n${MANAGED_END}`;
|
|
101
|
+
const start = current.indexOf(MANAGED_MARKER);
|
|
102
|
+
|
|
103
|
+
if (start === -1) {
|
|
104
|
+
const prefix =
|
|
105
|
+
current && !current.endsWith("\n") ? `${current}\n` : current;
|
|
106
|
+
return `${prefix}${prefix ? "\n" : ""}${block}\n`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const endAt = current.indexOf(MANAGED_END, start);
|
|
110
|
+
// A truncated block (marker opened, never closed) would otherwise swallow the
|
|
111
|
+
// rest of the file on every subsequent write.
|
|
112
|
+
const after =
|
|
113
|
+
endAt === -1
|
|
114
|
+
? ""
|
|
115
|
+
: current.slice(endAt + MANAGED_END.length).replace(/^\n/, "");
|
|
116
|
+
return `${current.slice(0, start)}${block}\n${after}`;
|
|
117
|
+
}
|
|
84
118
|
|
|
85
119
|
/**
|
|
86
120
|
* Make every shell in this container load the materialized secrets.
|
|
@@ -161,7 +195,7 @@ export function installAwsProfiles(bundle, options = {}) {
|
|
|
161
195
|
const {
|
|
162
196
|
home = process.env.HOME || homedir(),
|
|
163
197
|
mkdir = mkdirSync,
|
|
164
|
-
write =
|
|
198
|
+
write = writeAtomic,
|
|
165
199
|
read = readFileSync,
|
|
166
200
|
exists = existsSync,
|
|
167
201
|
chmod = chmodSync,
|
|
@@ -171,35 +205,32 @@ export function installAwsProfiles(bundle, options = {}) {
|
|
|
171
205
|
if (!rendered) return [];
|
|
172
206
|
|
|
173
207
|
const dir = join(home, ".aws");
|
|
208
|
+
mkdir(dir, { recursive: true, mode: 0o700 });
|
|
209
|
+
chmod(dir, 0o700);
|
|
174
210
|
|
|
175
|
-
//
|
|
211
|
+
// Merge into a delimited block; never replace the file.
|
|
212
|
+
//
|
|
213
|
+
// Refusing whole files whenever one already existed sounded safe and was
|
|
214
|
+
// worse than useless: a container ships `~/.aws/config` containing a bare
|
|
215
|
+
// `[default]`, so the guard fired every time, wrote nothing, and returned
|
|
216
|
+
// silently — while AWS_PROFILE was still derived from the bundle. The session
|
|
217
|
+
// got a pointer to a profile that did not exist:
|
|
218
|
+
//
|
|
219
|
+
// The config profile (agent-dev) could not be found
|
|
176
220
|
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
221
|
+
// Merging keeps the operator's own sections intact AND writes ours, which is
|
|
222
|
+
// what the guard was actually for. Same delimited-block approach as the shell
|
|
223
|
+
// profile, and `#` is a comment in the shared-config format so the markers are
|
|
224
|
+
// inert to every consumer.
|
|
225
|
+
for (const [name, body] of [
|
|
226
|
+
["credentials", rendered.credentials],
|
|
227
|
+
["config", rendered.config],
|
|
228
|
+
]) {
|
|
184
229
|
const file = join(dir, name);
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
230
|
+
const current = exists(file) ? String(read(file, "utf8")) : "";
|
|
231
|
+
write(file, upsertManagedBlock(current, body));
|
|
188
232
|
}
|
|
189
233
|
|
|
190
|
-
mkdir(dir, { recursive: true, mode: 0o700 });
|
|
191
|
-
chmod(dir, 0o700);
|
|
192
|
-
write(
|
|
193
|
-
join(dir, "credentials"),
|
|
194
|
-
`${MANAGED_MARKER}\n${rendered.credentials}`,
|
|
195
|
-
{
|
|
196
|
-
mode: 0o600,
|
|
197
|
-
}
|
|
198
|
-
);
|
|
199
|
-
write(join(dir, "config"), `${MANAGED_MARKER}\n${rendered.config}`, {
|
|
200
|
-
mode: 0o600,
|
|
201
|
-
});
|
|
202
|
-
|
|
203
234
|
return rendered.profiles;
|
|
204
235
|
}
|
|
205
236
|
|
|
@@ -80,7 +80,41 @@ const PROFILE_END = "# <<< lisa secrets (managed) <<<";
|
|
|
80
80
|
* consumer while still being the thing that distinguishes "our file, refresh
|
|
81
81
|
* it" from "someone else's file, leave it alone".
|
|
82
82
|
*/
|
|
83
|
-
const MANAGED_MARKER = "# managed by lisa-secrets-access";
|
|
83
|
+
const MANAGED_MARKER = "# >>> managed by lisa-secrets-access >>>";
|
|
84
|
+
|
|
85
|
+
/** Closes the managed region of an `~/.aws` file. */
|
|
86
|
+
const MANAGED_END = "# <<< managed by lisa-secrets-access <<<";
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Replace this module's delimited region in a file, preserving everything else.
|
|
90
|
+
*
|
|
91
|
+
* Written as a merge rather than a whole-file write because both `~/.aws` files
|
|
92
|
+
* routinely hold sections nobody here knows about — an operator's own profiles,
|
|
93
|
+
* or a container's bare `[default]`. Refusing on their account wrote nothing at
|
|
94
|
+
* all; overwriting would delete them. This does neither.
|
|
95
|
+
* @param {string} current Existing file contents, or "".
|
|
96
|
+
* @param {string} body The region this module owns.
|
|
97
|
+
* @returns {string} The merged file.
|
|
98
|
+
*/
|
|
99
|
+
export function upsertManagedBlock(current, body) {
|
|
100
|
+
const block = `${MANAGED_MARKER}\n${body.trimEnd()}\n${MANAGED_END}`;
|
|
101
|
+
const start = current.indexOf(MANAGED_MARKER);
|
|
102
|
+
|
|
103
|
+
if (start === -1) {
|
|
104
|
+
const prefix =
|
|
105
|
+
current && !current.endsWith("\n") ? `${current}\n` : current;
|
|
106
|
+
return `${prefix}${prefix ? "\n" : ""}${block}\n`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const endAt = current.indexOf(MANAGED_END, start);
|
|
110
|
+
// A truncated block (marker opened, never closed) would otherwise swallow the
|
|
111
|
+
// rest of the file on every subsequent write.
|
|
112
|
+
const after =
|
|
113
|
+
endAt === -1
|
|
114
|
+
? ""
|
|
115
|
+
: current.slice(endAt + MANAGED_END.length).replace(/^\n/, "");
|
|
116
|
+
return `${current.slice(0, start)}${block}\n${after}`;
|
|
117
|
+
}
|
|
84
118
|
|
|
85
119
|
/**
|
|
86
120
|
* Make every shell in this container load the materialized secrets.
|
|
@@ -161,7 +195,7 @@ export function installAwsProfiles(bundle, options = {}) {
|
|
|
161
195
|
const {
|
|
162
196
|
home = process.env.HOME || homedir(),
|
|
163
197
|
mkdir = mkdirSync,
|
|
164
|
-
write =
|
|
198
|
+
write = writeAtomic,
|
|
165
199
|
read = readFileSync,
|
|
166
200
|
exists = existsSync,
|
|
167
201
|
chmod = chmodSync,
|
|
@@ -171,35 +205,32 @@ export function installAwsProfiles(bundle, options = {}) {
|
|
|
171
205
|
if (!rendered) return [];
|
|
172
206
|
|
|
173
207
|
const dir = join(home, ".aws");
|
|
208
|
+
mkdir(dir, { recursive: true, mode: 0o700 });
|
|
209
|
+
chmod(dir, 0o700);
|
|
174
210
|
|
|
175
|
-
//
|
|
211
|
+
// Merge into a delimited block; never replace the file.
|
|
212
|
+
//
|
|
213
|
+
// Refusing whole files whenever one already existed sounded safe and was
|
|
214
|
+
// worse than useless: a container ships `~/.aws/config` containing a bare
|
|
215
|
+
// `[default]`, so the guard fired every time, wrote nothing, and returned
|
|
216
|
+
// silently — while AWS_PROFILE was still derived from the bundle. The session
|
|
217
|
+
// got a pointer to a profile that did not exist:
|
|
218
|
+
//
|
|
219
|
+
// The config profile (agent-dev) could not be found
|
|
176
220
|
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
221
|
+
// Merging keeps the operator's own sections intact AND writes ours, which is
|
|
222
|
+
// what the guard was actually for. Same delimited-block approach as the shell
|
|
223
|
+
// profile, and `#` is a comment in the shared-config format so the markers are
|
|
224
|
+
// inert to every consumer.
|
|
225
|
+
for (const [name, body] of [
|
|
226
|
+
["credentials", rendered.credentials],
|
|
227
|
+
["config", rendered.config],
|
|
228
|
+
]) {
|
|
184
229
|
const file = join(dir, name);
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
230
|
+
const current = exists(file) ? String(read(file, "utf8")) : "";
|
|
231
|
+
write(file, upsertManagedBlock(current, body));
|
|
188
232
|
}
|
|
189
233
|
|
|
190
|
-
mkdir(dir, { recursive: true, mode: 0o700 });
|
|
191
|
-
chmod(dir, 0o700);
|
|
192
|
-
write(
|
|
193
|
-
join(dir, "credentials"),
|
|
194
|
-
`${MANAGED_MARKER}\n${rendered.credentials}`,
|
|
195
|
-
{
|
|
196
|
-
mode: 0o600,
|
|
197
|
-
}
|
|
198
|
-
);
|
|
199
|
-
write(join(dir, "config"), `${MANAGED_MARKER}\n${rendered.config}`, {
|
|
200
|
-
mode: 0o600,
|
|
201
|
-
});
|
|
202
|
-
|
|
203
234
|
return rendered.profiles;
|
|
204
235
|
}
|
|
205
236
|
|
|
@@ -80,7 +80,41 @@ const PROFILE_END = "# <<< lisa secrets (managed) <<<";
|
|
|
80
80
|
* consumer while still being the thing that distinguishes "our file, refresh
|
|
81
81
|
* it" from "someone else's file, leave it alone".
|
|
82
82
|
*/
|
|
83
|
-
const MANAGED_MARKER = "# managed by lisa-secrets-access";
|
|
83
|
+
const MANAGED_MARKER = "# >>> managed by lisa-secrets-access >>>";
|
|
84
|
+
|
|
85
|
+
/** Closes the managed region of an `~/.aws` file. */
|
|
86
|
+
const MANAGED_END = "# <<< managed by lisa-secrets-access <<<";
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Replace this module's delimited region in a file, preserving everything else.
|
|
90
|
+
*
|
|
91
|
+
* Written as a merge rather than a whole-file write because both `~/.aws` files
|
|
92
|
+
* routinely hold sections nobody here knows about — an operator's own profiles,
|
|
93
|
+
* or a container's bare `[default]`. Refusing on their account wrote nothing at
|
|
94
|
+
* all; overwriting would delete them. This does neither.
|
|
95
|
+
* @param {string} current Existing file contents, or "".
|
|
96
|
+
* @param {string} body The region this module owns.
|
|
97
|
+
* @returns {string} The merged file.
|
|
98
|
+
*/
|
|
99
|
+
export function upsertManagedBlock(current, body) {
|
|
100
|
+
const block = `${MANAGED_MARKER}\n${body.trimEnd()}\n${MANAGED_END}`;
|
|
101
|
+
const start = current.indexOf(MANAGED_MARKER);
|
|
102
|
+
|
|
103
|
+
if (start === -1) {
|
|
104
|
+
const prefix =
|
|
105
|
+
current && !current.endsWith("\n") ? `${current}\n` : current;
|
|
106
|
+
return `${prefix}${prefix ? "\n" : ""}${block}\n`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const endAt = current.indexOf(MANAGED_END, start);
|
|
110
|
+
// A truncated block (marker opened, never closed) would otherwise swallow the
|
|
111
|
+
// rest of the file on every subsequent write.
|
|
112
|
+
const after =
|
|
113
|
+
endAt === -1
|
|
114
|
+
? ""
|
|
115
|
+
: current.slice(endAt + MANAGED_END.length).replace(/^\n/, "");
|
|
116
|
+
return `${current.slice(0, start)}${block}\n${after}`;
|
|
117
|
+
}
|
|
84
118
|
|
|
85
119
|
/**
|
|
86
120
|
* Make every shell in this container load the materialized secrets.
|
|
@@ -161,7 +195,7 @@ export function installAwsProfiles(bundle, options = {}) {
|
|
|
161
195
|
const {
|
|
162
196
|
home = process.env.HOME || homedir(),
|
|
163
197
|
mkdir = mkdirSync,
|
|
164
|
-
write =
|
|
198
|
+
write = writeAtomic,
|
|
165
199
|
read = readFileSync,
|
|
166
200
|
exists = existsSync,
|
|
167
201
|
chmod = chmodSync,
|
|
@@ -171,35 +205,32 @@ export function installAwsProfiles(bundle, options = {}) {
|
|
|
171
205
|
if (!rendered) return [];
|
|
172
206
|
|
|
173
207
|
const dir = join(home, ".aws");
|
|
208
|
+
mkdir(dir, { recursive: true, mode: 0o700 });
|
|
209
|
+
chmod(dir, 0o700);
|
|
174
210
|
|
|
175
|
-
//
|
|
211
|
+
// Merge into a delimited block; never replace the file.
|
|
212
|
+
//
|
|
213
|
+
// Refusing whole files whenever one already existed sounded safe and was
|
|
214
|
+
// worse than useless: a container ships `~/.aws/config` containing a bare
|
|
215
|
+
// `[default]`, so the guard fired every time, wrote nothing, and returned
|
|
216
|
+
// silently — while AWS_PROFILE was still derived from the bundle. The session
|
|
217
|
+
// got a pointer to a profile that did not exist:
|
|
218
|
+
//
|
|
219
|
+
// The config profile (agent-dev) could not be found
|
|
176
220
|
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
221
|
+
// Merging keeps the operator's own sections intact AND writes ours, which is
|
|
222
|
+
// what the guard was actually for. Same delimited-block approach as the shell
|
|
223
|
+
// profile, and `#` is a comment in the shared-config format so the markers are
|
|
224
|
+
// inert to every consumer.
|
|
225
|
+
for (const [name, body] of [
|
|
226
|
+
["credentials", rendered.credentials],
|
|
227
|
+
["config", rendered.config],
|
|
228
|
+
]) {
|
|
184
229
|
const file = join(dir, name);
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
230
|
+
const current = exists(file) ? String(read(file, "utf8")) : "";
|
|
231
|
+
write(file, upsertManagedBlock(current, body));
|
|
188
232
|
}
|
|
189
233
|
|
|
190
|
-
mkdir(dir, { recursive: true, mode: 0o700 });
|
|
191
|
-
chmod(dir, 0o700);
|
|
192
|
-
write(
|
|
193
|
-
join(dir, "credentials"),
|
|
194
|
-
`${MANAGED_MARKER}\n${rendered.credentials}`,
|
|
195
|
-
{
|
|
196
|
-
mode: 0o600,
|
|
197
|
-
}
|
|
198
|
-
);
|
|
199
|
-
write(join(dir, "config"), `${MANAGED_MARKER}\n${rendered.config}`, {
|
|
200
|
-
mode: 0o600,
|
|
201
|
-
});
|
|
202
|
-
|
|
203
234
|
return rendered.profiles;
|
|
204
235
|
}
|
|
205
236
|
|