@codyswann/lisa 2.332.3 → 2.332.4
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 +83 -1
- package/plugins/lisa/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +83 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +83 -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/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +83 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +83 -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/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +83 -1
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.332.
|
|
118
|
+
"version": "2.332.4",
|
|
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
|
@@ -21,11 +21,15 @@
|
|
|
21
21
|
|
|
22
22
|
import {
|
|
23
23
|
chmodSync,
|
|
24
|
+
existsSync,
|
|
24
25
|
mkdirSync,
|
|
26
|
+
readFileSync,
|
|
25
27
|
renameSync,
|
|
26
28
|
rmSync,
|
|
27
29
|
writeFileSync,
|
|
28
30
|
} from "node:fs";
|
|
31
|
+
import { homedir } from "node:os";
|
|
32
|
+
import { join } from "node:path";
|
|
29
33
|
|
|
30
34
|
import { deriveAwsEnvironment } from "./aws-bootstrap.mjs";
|
|
31
35
|
import { renderEnv, renderNotes } from "./envfile.mjs";
|
|
@@ -58,6 +62,70 @@ function writeAtomic(destination, contents) {
|
|
|
58
62
|
* @param {object} [cfg] Resolved configuration.
|
|
59
63
|
* @returns {{count: number, dir: string}} What was written, and where.
|
|
60
64
|
*/
|
|
65
|
+
/** Marks the block this owns, so it is replaced rather than appended twice. */
|
|
66
|
+
const PROFILE_MARKER = "# >>> lisa secrets (managed) >>>";
|
|
67
|
+
|
|
68
|
+
/** Closes the managed block. */
|
|
69
|
+
const PROFILE_END = "# <<< lisa secrets (managed) <<<";
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Make every shell in this container load the materialized secrets.
|
|
73
|
+
*
|
|
74
|
+
* `set -a` so the values are exported rather than merely set as shell
|
|
75
|
+
* variables, and sourced LAST so they win over anything the host injected —
|
|
76
|
+
* environment variables outrank profile files in AWS's credential chain, which
|
|
77
|
+
* is the whole reason a valid credential was being ignored.
|
|
78
|
+
*
|
|
79
|
+
* Guarded on the file existing, so a shell still starts cleanly before the
|
|
80
|
+
* first materialization or if the file is removed. Written to both `.bashrc`
|
|
81
|
+
* and `.profile` because which one a given shell reads depends on whether it is
|
|
82
|
+
* interactive or a login shell, and an agent's tool calls are not reliably
|
|
83
|
+
* either.
|
|
84
|
+
* @param {string} valuesFile Absolute path to the materialized env file.
|
|
85
|
+
* @param {object} [options] Home directory and file seams, for tests.
|
|
86
|
+
* @returns {string[]} The profile files that now source it.
|
|
87
|
+
*/
|
|
88
|
+
export function installProfileSourcing(valuesFile, options = {}) {
|
|
89
|
+
const {
|
|
90
|
+
home = process.env.HOME || homedir(),
|
|
91
|
+
exists = existsSync,
|
|
92
|
+
read = readFileSync,
|
|
93
|
+
write = writeFileSync,
|
|
94
|
+
} = options;
|
|
95
|
+
|
|
96
|
+
const block = [
|
|
97
|
+
PROFILE_MARKER,
|
|
98
|
+
`if [ -f "${valuesFile}" ]; then`,
|
|
99
|
+
` set -a`,
|
|
100
|
+
` . "${valuesFile}"`,
|
|
101
|
+
` set +a`,
|
|
102
|
+
`fi`,
|
|
103
|
+
PROFILE_END,
|
|
104
|
+
].join("\n");
|
|
105
|
+
|
|
106
|
+
const updated = [];
|
|
107
|
+
for (const name of [".bashrc", ".profile"]) {
|
|
108
|
+
const file = join(home, name);
|
|
109
|
+
const current = exists(file) ? String(read(file, "utf8")) : "";
|
|
110
|
+
|
|
111
|
+
// Replace an existing managed block rather than appending another: this
|
|
112
|
+
// runs on every session, and an appended-forever profile is its own bug.
|
|
113
|
+
const start = current.indexOf(PROFILE_MARKER);
|
|
114
|
+
const next =
|
|
115
|
+
start === -1
|
|
116
|
+
? `${current}${current.endsWith("\n") || !current ? "" : "\n"}\n${block}\n`
|
|
117
|
+
: `${current.slice(0, start)}${block}${current.slice(
|
|
118
|
+
current.indexOf(PROFILE_END, start) + PROFILE_END.length
|
|
119
|
+
)}`;
|
|
120
|
+
|
|
121
|
+
if (next !== current) {
|
|
122
|
+
write(file, next, { mode: 0o600 });
|
|
123
|
+
updated.push(file);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return updated;
|
|
127
|
+
}
|
|
128
|
+
|
|
61
129
|
export function materialize(cfg = readConfig()) {
|
|
62
130
|
if (!cfg.capabilities.mayWriteValues) {
|
|
63
131
|
throw new Error(
|
|
@@ -88,7 +156,21 @@ export function materialize(cfg = readConfig()) {
|
|
|
88
156
|
chmodSync(dir, 0o700);
|
|
89
157
|
writeAtomic(valuesFile, renderEnv(selected));
|
|
90
158
|
writeAtomic(notesFile, renderNotes(selected));
|
|
91
|
-
|
|
159
|
+
|
|
160
|
+
// Writing the file is not the same as the values being in effect.
|
|
161
|
+
//
|
|
162
|
+
// A materialized secrets.env that nothing sources changes nothing: the agent's
|
|
163
|
+
// shell starts from the container's own environment, so a host-injected
|
|
164
|
+
// AWS_ACCESS_KEY_ID keeps winning and every call fails with
|
|
165
|
+
// InvalidClientTokenId while the correct credential sits on disk, correct and
|
|
166
|
+
// unused. Deriving the variables (above) only fixes precedence WITHIN the
|
|
167
|
+
// file — something still has to load the file.
|
|
168
|
+
//
|
|
169
|
+
// So the shell profile sources it. That is what makes "the credential is
|
|
170
|
+
// materialized" and "the credential is usable" the same statement.
|
|
171
|
+
const sourced = installProfileSourcing(valuesFile);
|
|
172
|
+
|
|
173
|
+
return { count: selected.size, derived: derived.size, dir, sourced };
|
|
92
174
|
}
|
|
93
175
|
|
|
94
176
|
function main() {
|
|
@@ -21,11 +21,15 @@
|
|
|
21
21
|
|
|
22
22
|
import {
|
|
23
23
|
chmodSync,
|
|
24
|
+
existsSync,
|
|
24
25
|
mkdirSync,
|
|
26
|
+
readFileSync,
|
|
25
27
|
renameSync,
|
|
26
28
|
rmSync,
|
|
27
29
|
writeFileSync,
|
|
28
30
|
} from "node:fs";
|
|
31
|
+
import { homedir } from "node:os";
|
|
32
|
+
import { join } from "node:path";
|
|
29
33
|
|
|
30
34
|
import { deriveAwsEnvironment } from "./aws-bootstrap.mjs";
|
|
31
35
|
import { renderEnv, renderNotes } from "./envfile.mjs";
|
|
@@ -58,6 +62,70 @@ function writeAtomic(destination, contents) {
|
|
|
58
62
|
* @param {object} [cfg] Resolved configuration.
|
|
59
63
|
* @returns {{count: number, dir: string}} What was written, and where.
|
|
60
64
|
*/
|
|
65
|
+
/** Marks the block this owns, so it is replaced rather than appended twice. */
|
|
66
|
+
const PROFILE_MARKER = "# >>> lisa secrets (managed) >>>";
|
|
67
|
+
|
|
68
|
+
/** Closes the managed block. */
|
|
69
|
+
const PROFILE_END = "# <<< lisa secrets (managed) <<<";
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Make every shell in this container load the materialized secrets.
|
|
73
|
+
*
|
|
74
|
+
* `set -a` so the values are exported rather than merely set as shell
|
|
75
|
+
* variables, and sourced LAST so they win over anything the host injected —
|
|
76
|
+
* environment variables outrank profile files in AWS's credential chain, which
|
|
77
|
+
* is the whole reason a valid credential was being ignored.
|
|
78
|
+
*
|
|
79
|
+
* Guarded on the file existing, so a shell still starts cleanly before the
|
|
80
|
+
* first materialization or if the file is removed. Written to both `.bashrc`
|
|
81
|
+
* and `.profile` because which one a given shell reads depends on whether it is
|
|
82
|
+
* interactive or a login shell, and an agent's tool calls are not reliably
|
|
83
|
+
* either.
|
|
84
|
+
* @param {string} valuesFile Absolute path to the materialized env file.
|
|
85
|
+
* @param {object} [options] Home directory and file seams, for tests.
|
|
86
|
+
* @returns {string[]} The profile files that now source it.
|
|
87
|
+
*/
|
|
88
|
+
export function installProfileSourcing(valuesFile, options = {}) {
|
|
89
|
+
const {
|
|
90
|
+
home = process.env.HOME || homedir(),
|
|
91
|
+
exists = existsSync,
|
|
92
|
+
read = readFileSync,
|
|
93
|
+
write = writeFileSync,
|
|
94
|
+
} = options;
|
|
95
|
+
|
|
96
|
+
const block = [
|
|
97
|
+
PROFILE_MARKER,
|
|
98
|
+
`if [ -f "${valuesFile}" ]; then`,
|
|
99
|
+
` set -a`,
|
|
100
|
+
` . "${valuesFile}"`,
|
|
101
|
+
` set +a`,
|
|
102
|
+
`fi`,
|
|
103
|
+
PROFILE_END,
|
|
104
|
+
].join("\n");
|
|
105
|
+
|
|
106
|
+
const updated = [];
|
|
107
|
+
for (const name of [".bashrc", ".profile"]) {
|
|
108
|
+
const file = join(home, name);
|
|
109
|
+
const current = exists(file) ? String(read(file, "utf8")) : "";
|
|
110
|
+
|
|
111
|
+
// Replace an existing managed block rather than appending another: this
|
|
112
|
+
// runs on every session, and an appended-forever profile is its own bug.
|
|
113
|
+
const start = current.indexOf(PROFILE_MARKER);
|
|
114
|
+
const next =
|
|
115
|
+
start === -1
|
|
116
|
+
? `${current}${current.endsWith("\n") || !current ? "" : "\n"}\n${block}\n`
|
|
117
|
+
: `${current.slice(0, start)}${block}${current.slice(
|
|
118
|
+
current.indexOf(PROFILE_END, start) + PROFILE_END.length
|
|
119
|
+
)}`;
|
|
120
|
+
|
|
121
|
+
if (next !== current) {
|
|
122
|
+
write(file, next, { mode: 0o600 });
|
|
123
|
+
updated.push(file);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return updated;
|
|
127
|
+
}
|
|
128
|
+
|
|
61
129
|
export function materialize(cfg = readConfig()) {
|
|
62
130
|
if (!cfg.capabilities.mayWriteValues) {
|
|
63
131
|
throw new Error(
|
|
@@ -88,7 +156,21 @@ export function materialize(cfg = readConfig()) {
|
|
|
88
156
|
chmodSync(dir, 0o700);
|
|
89
157
|
writeAtomic(valuesFile, renderEnv(selected));
|
|
90
158
|
writeAtomic(notesFile, renderNotes(selected));
|
|
91
|
-
|
|
159
|
+
|
|
160
|
+
// Writing the file is not the same as the values being in effect.
|
|
161
|
+
//
|
|
162
|
+
// A materialized secrets.env that nothing sources changes nothing: the agent's
|
|
163
|
+
// shell starts from the container's own environment, so a host-injected
|
|
164
|
+
// AWS_ACCESS_KEY_ID keeps winning and every call fails with
|
|
165
|
+
// InvalidClientTokenId while the correct credential sits on disk, correct and
|
|
166
|
+
// unused. Deriving the variables (above) only fixes precedence WITHIN the
|
|
167
|
+
// file — something still has to load the file.
|
|
168
|
+
//
|
|
169
|
+
// So the shell profile sources it. That is what makes "the credential is
|
|
170
|
+
// materialized" and "the credential is usable" the same statement.
|
|
171
|
+
const sourced = installProfileSourcing(valuesFile);
|
|
172
|
+
|
|
173
|
+
return { count: selected.size, derived: derived.size, dir, sourced };
|
|
92
174
|
}
|
|
93
175
|
|
|
94
176
|
function main() {
|
|
@@ -21,11 +21,15 @@
|
|
|
21
21
|
|
|
22
22
|
import {
|
|
23
23
|
chmodSync,
|
|
24
|
+
existsSync,
|
|
24
25
|
mkdirSync,
|
|
26
|
+
readFileSync,
|
|
25
27
|
renameSync,
|
|
26
28
|
rmSync,
|
|
27
29
|
writeFileSync,
|
|
28
30
|
} from "node:fs";
|
|
31
|
+
import { homedir } from "node:os";
|
|
32
|
+
import { join } from "node:path";
|
|
29
33
|
|
|
30
34
|
import { deriveAwsEnvironment } from "./aws-bootstrap.mjs";
|
|
31
35
|
import { renderEnv, renderNotes } from "./envfile.mjs";
|
|
@@ -58,6 +62,70 @@ function writeAtomic(destination, contents) {
|
|
|
58
62
|
* @param {object} [cfg] Resolved configuration.
|
|
59
63
|
* @returns {{count: number, dir: string}} What was written, and where.
|
|
60
64
|
*/
|
|
65
|
+
/** Marks the block this owns, so it is replaced rather than appended twice. */
|
|
66
|
+
const PROFILE_MARKER = "# >>> lisa secrets (managed) >>>";
|
|
67
|
+
|
|
68
|
+
/** Closes the managed block. */
|
|
69
|
+
const PROFILE_END = "# <<< lisa secrets (managed) <<<";
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Make every shell in this container load the materialized secrets.
|
|
73
|
+
*
|
|
74
|
+
* `set -a` so the values are exported rather than merely set as shell
|
|
75
|
+
* variables, and sourced LAST so they win over anything the host injected —
|
|
76
|
+
* environment variables outrank profile files in AWS's credential chain, which
|
|
77
|
+
* is the whole reason a valid credential was being ignored.
|
|
78
|
+
*
|
|
79
|
+
* Guarded on the file existing, so a shell still starts cleanly before the
|
|
80
|
+
* first materialization or if the file is removed. Written to both `.bashrc`
|
|
81
|
+
* and `.profile` because which one a given shell reads depends on whether it is
|
|
82
|
+
* interactive or a login shell, and an agent's tool calls are not reliably
|
|
83
|
+
* either.
|
|
84
|
+
* @param {string} valuesFile Absolute path to the materialized env file.
|
|
85
|
+
* @param {object} [options] Home directory and file seams, for tests.
|
|
86
|
+
* @returns {string[]} The profile files that now source it.
|
|
87
|
+
*/
|
|
88
|
+
export function installProfileSourcing(valuesFile, options = {}) {
|
|
89
|
+
const {
|
|
90
|
+
home = process.env.HOME || homedir(),
|
|
91
|
+
exists = existsSync,
|
|
92
|
+
read = readFileSync,
|
|
93
|
+
write = writeFileSync,
|
|
94
|
+
} = options;
|
|
95
|
+
|
|
96
|
+
const block = [
|
|
97
|
+
PROFILE_MARKER,
|
|
98
|
+
`if [ -f "${valuesFile}" ]; then`,
|
|
99
|
+
` set -a`,
|
|
100
|
+
` . "${valuesFile}"`,
|
|
101
|
+
` set +a`,
|
|
102
|
+
`fi`,
|
|
103
|
+
PROFILE_END,
|
|
104
|
+
].join("\n");
|
|
105
|
+
|
|
106
|
+
const updated = [];
|
|
107
|
+
for (const name of [".bashrc", ".profile"]) {
|
|
108
|
+
const file = join(home, name);
|
|
109
|
+
const current = exists(file) ? String(read(file, "utf8")) : "";
|
|
110
|
+
|
|
111
|
+
// Replace an existing managed block rather than appending another: this
|
|
112
|
+
// runs on every session, and an appended-forever profile is its own bug.
|
|
113
|
+
const start = current.indexOf(PROFILE_MARKER);
|
|
114
|
+
const next =
|
|
115
|
+
start === -1
|
|
116
|
+
? `${current}${current.endsWith("\n") || !current ? "" : "\n"}\n${block}\n`
|
|
117
|
+
: `${current.slice(0, start)}${block}${current.slice(
|
|
118
|
+
current.indexOf(PROFILE_END, start) + PROFILE_END.length
|
|
119
|
+
)}`;
|
|
120
|
+
|
|
121
|
+
if (next !== current) {
|
|
122
|
+
write(file, next, { mode: 0o600 });
|
|
123
|
+
updated.push(file);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return updated;
|
|
127
|
+
}
|
|
128
|
+
|
|
61
129
|
export function materialize(cfg = readConfig()) {
|
|
62
130
|
if (!cfg.capabilities.mayWriteValues) {
|
|
63
131
|
throw new Error(
|
|
@@ -88,7 +156,21 @@ export function materialize(cfg = readConfig()) {
|
|
|
88
156
|
chmodSync(dir, 0o700);
|
|
89
157
|
writeAtomic(valuesFile, renderEnv(selected));
|
|
90
158
|
writeAtomic(notesFile, renderNotes(selected));
|
|
91
|
-
|
|
159
|
+
|
|
160
|
+
// Writing the file is not the same as the values being in effect.
|
|
161
|
+
//
|
|
162
|
+
// A materialized secrets.env that nothing sources changes nothing: the agent's
|
|
163
|
+
// shell starts from the container's own environment, so a host-injected
|
|
164
|
+
// AWS_ACCESS_KEY_ID keeps winning and every call fails with
|
|
165
|
+
// InvalidClientTokenId while the correct credential sits on disk, correct and
|
|
166
|
+
// unused. Deriving the variables (above) only fixes precedence WITHIN the
|
|
167
|
+
// file — something still has to load the file.
|
|
168
|
+
//
|
|
169
|
+
// So the shell profile sources it. That is what makes "the credential is
|
|
170
|
+
// materialized" and "the credential is usable" the same statement.
|
|
171
|
+
const sourced = installProfileSourcing(valuesFile);
|
|
172
|
+
|
|
173
|
+
return { count: selected.size, derived: derived.size, dir, sourced };
|
|
92
174
|
}
|
|
93
175
|
|
|
94
176
|
function main() {
|
|
@@ -21,11 +21,15 @@
|
|
|
21
21
|
|
|
22
22
|
import {
|
|
23
23
|
chmodSync,
|
|
24
|
+
existsSync,
|
|
24
25
|
mkdirSync,
|
|
26
|
+
readFileSync,
|
|
25
27
|
renameSync,
|
|
26
28
|
rmSync,
|
|
27
29
|
writeFileSync,
|
|
28
30
|
} from "node:fs";
|
|
31
|
+
import { homedir } from "node:os";
|
|
32
|
+
import { join } from "node:path";
|
|
29
33
|
|
|
30
34
|
import { deriveAwsEnvironment } from "./aws-bootstrap.mjs";
|
|
31
35
|
import { renderEnv, renderNotes } from "./envfile.mjs";
|
|
@@ -58,6 +62,70 @@ function writeAtomic(destination, contents) {
|
|
|
58
62
|
* @param {object} [cfg] Resolved configuration.
|
|
59
63
|
* @returns {{count: number, dir: string}} What was written, and where.
|
|
60
64
|
*/
|
|
65
|
+
/** Marks the block this owns, so it is replaced rather than appended twice. */
|
|
66
|
+
const PROFILE_MARKER = "# >>> lisa secrets (managed) >>>";
|
|
67
|
+
|
|
68
|
+
/** Closes the managed block. */
|
|
69
|
+
const PROFILE_END = "# <<< lisa secrets (managed) <<<";
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Make every shell in this container load the materialized secrets.
|
|
73
|
+
*
|
|
74
|
+
* `set -a` so the values are exported rather than merely set as shell
|
|
75
|
+
* variables, and sourced LAST so they win over anything the host injected —
|
|
76
|
+
* environment variables outrank profile files in AWS's credential chain, which
|
|
77
|
+
* is the whole reason a valid credential was being ignored.
|
|
78
|
+
*
|
|
79
|
+
* Guarded on the file existing, so a shell still starts cleanly before the
|
|
80
|
+
* first materialization or if the file is removed. Written to both `.bashrc`
|
|
81
|
+
* and `.profile` because which one a given shell reads depends on whether it is
|
|
82
|
+
* interactive or a login shell, and an agent's tool calls are not reliably
|
|
83
|
+
* either.
|
|
84
|
+
* @param {string} valuesFile Absolute path to the materialized env file.
|
|
85
|
+
* @param {object} [options] Home directory and file seams, for tests.
|
|
86
|
+
* @returns {string[]} The profile files that now source it.
|
|
87
|
+
*/
|
|
88
|
+
export function installProfileSourcing(valuesFile, options = {}) {
|
|
89
|
+
const {
|
|
90
|
+
home = process.env.HOME || homedir(),
|
|
91
|
+
exists = existsSync,
|
|
92
|
+
read = readFileSync,
|
|
93
|
+
write = writeFileSync,
|
|
94
|
+
} = options;
|
|
95
|
+
|
|
96
|
+
const block = [
|
|
97
|
+
PROFILE_MARKER,
|
|
98
|
+
`if [ -f "${valuesFile}" ]; then`,
|
|
99
|
+
` set -a`,
|
|
100
|
+
` . "${valuesFile}"`,
|
|
101
|
+
` set +a`,
|
|
102
|
+
`fi`,
|
|
103
|
+
PROFILE_END,
|
|
104
|
+
].join("\n");
|
|
105
|
+
|
|
106
|
+
const updated = [];
|
|
107
|
+
for (const name of [".bashrc", ".profile"]) {
|
|
108
|
+
const file = join(home, name);
|
|
109
|
+
const current = exists(file) ? String(read(file, "utf8")) : "";
|
|
110
|
+
|
|
111
|
+
// Replace an existing managed block rather than appending another: this
|
|
112
|
+
// runs on every session, and an appended-forever profile is its own bug.
|
|
113
|
+
const start = current.indexOf(PROFILE_MARKER);
|
|
114
|
+
const next =
|
|
115
|
+
start === -1
|
|
116
|
+
? `${current}${current.endsWith("\n") || !current ? "" : "\n"}\n${block}\n`
|
|
117
|
+
: `${current.slice(0, start)}${block}${current.slice(
|
|
118
|
+
current.indexOf(PROFILE_END, start) + PROFILE_END.length
|
|
119
|
+
)}`;
|
|
120
|
+
|
|
121
|
+
if (next !== current) {
|
|
122
|
+
write(file, next, { mode: 0o600 });
|
|
123
|
+
updated.push(file);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return updated;
|
|
127
|
+
}
|
|
128
|
+
|
|
61
129
|
export function materialize(cfg = readConfig()) {
|
|
62
130
|
if (!cfg.capabilities.mayWriteValues) {
|
|
63
131
|
throw new Error(
|
|
@@ -88,7 +156,21 @@ export function materialize(cfg = readConfig()) {
|
|
|
88
156
|
chmodSync(dir, 0o700);
|
|
89
157
|
writeAtomic(valuesFile, renderEnv(selected));
|
|
90
158
|
writeAtomic(notesFile, renderNotes(selected));
|
|
91
|
-
|
|
159
|
+
|
|
160
|
+
// Writing the file is not the same as the values being in effect.
|
|
161
|
+
//
|
|
162
|
+
// A materialized secrets.env that nothing sources changes nothing: the agent's
|
|
163
|
+
// shell starts from the container's own environment, so a host-injected
|
|
164
|
+
// AWS_ACCESS_KEY_ID keeps winning and every call fails with
|
|
165
|
+
// InvalidClientTokenId while the correct credential sits on disk, correct and
|
|
166
|
+
// unused. Deriving the variables (above) only fixes precedence WITHIN the
|
|
167
|
+
// file — something still has to load the file.
|
|
168
|
+
//
|
|
169
|
+
// So the shell profile sources it. That is what makes "the credential is
|
|
170
|
+
// materialized" and "the credential is usable" the same statement.
|
|
171
|
+
const sourced = installProfileSourcing(valuesFile);
|
|
172
|
+
|
|
173
|
+
return { count: selected.size, derived: derived.size, dir, sourced };
|
|
92
174
|
}
|
|
93
175
|
|
|
94
176
|
function main() {
|
|
@@ -21,11 +21,15 @@
|
|
|
21
21
|
|
|
22
22
|
import {
|
|
23
23
|
chmodSync,
|
|
24
|
+
existsSync,
|
|
24
25
|
mkdirSync,
|
|
26
|
+
readFileSync,
|
|
25
27
|
renameSync,
|
|
26
28
|
rmSync,
|
|
27
29
|
writeFileSync,
|
|
28
30
|
} from "node:fs";
|
|
31
|
+
import { homedir } from "node:os";
|
|
32
|
+
import { join } from "node:path";
|
|
29
33
|
|
|
30
34
|
import { deriveAwsEnvironment } from "./aws-bootstrap.mjs";
|
|
31
35
|
import { renderEnv, renderNotes } from "./envfile.mjs";
|
|
@@ -58,6 +62,70 @@ function writeAtomic(destination, contents) {
|
|
|
58
62
|
* @param {object} [cfg] Resolved configuration.
|
|
59
63
|
* @returns {{count: number, dir: string}} What was written, and where.
|
|
60
64
|
*/
|
|
65
|
+
/** Marks the block this owns, so it is replaced rather than appended twice. */
|
|
66
|
+
const PROFILE_MARKER = "# >>> lisa secrets (managed) >>>";
|
|
67
|
+
|
|
68
|
+
/** Closes the managed block. */
|
|
69
|
+
const PROFILE_END = "# <<< lisa secrets (managed) <<<";
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Make every shell in this container load the materialized secrets.
|
|
73
|
+
*
|
|
74
|
+
* `set -a` so the values are exported rather than merely set as shell
|
|
75
|
+
* variables, and sourced LAST so they win over anything the host injected —
|
|
76
|
+
* environment variables outrank profile files in AWS's credential chain, which
|
|
77
|
+
* is the whole reason a valid credential was being ignored.
|
|
78
|
+
*
|
|
79
|
+
* Guarded on the file existing, so a shell still starts cleanly before the
|
|
80
|
+
* first materialization or if the file is removed. Written to both `.bashrc`
|
|
81
|
+
* and `.profile` because which one a given shell reads depends on whether it is
|
|
82
|
+
* interactive or a login shell, and an agent's tool calls are not reliably
|
|
83
|
+
* either.
|
|
84
|
+
* @param {string} valuesFile Absolute path to the materialized env file.
|
|
85
|
+
* @param {object} [options] Home directory and file seams, for tests.
|
|
86
|
+
* @returns {string[]} The profile files that now source it.
|
|
87
|
+
*/
|
|
88
|
+
export function installProfileSourcing(valuesFile, options = {}) {
|
|
89
|
+
const {
|
|
90
|
+
home = process.env.HOME || homedir(),
|
|
91
|
+
exists = existsSync,
|
|
92
|
+
read = readFileSync,
|
|
93
|
+
write = writeFileSync,
|
|
94
|
+
} = options;
|
|
95
|
+
|
|
96
|
+
const block = [
|
|
97
|
+
PROFILE_MARKER,
|
|
98
|
+
`if [ -f "${valuesFile}" ]; then`,
|
|
99
|
+
` set -a`,
|
|
100
|
+
` . "${valuesFile}"`,
|
|
101
|
+
` set +a`,
|
|
102
|
+
`fi`,
|
|
103
|
+
PROFILE_END,
|
|
104
|
+
].join("\n");
|
|
105
|
+
|
|
106
|
+
const updated = [];
|
|
107
|
+
for (const name of [".bashrc", ".profile"]) {
|
|
108
|
+
const file = join(home, name);
|
|
109
|
+
const current = exists(file) ? String(read(file, "utf8")) : "";
|
|
110
|
+
|
|
111
|
+
// Replace an existing managed block rather than appending another: this
|
|
112
|
+
// runs on every session, and an appended-forever profile is its own bug.
|
|
113
|
+
const start = current.indexOf(PROFILE_MARKER);
|
|
114
|
+
const next =
|
|
115
|
+
start === -1
|
|
116
|
+
? `${current}${current.endsWith("\n") || !current ? "" : "\n"}\n${block}\n`
|
|
117
|
+
: `${current.slice(0, start)}${block}${current.slice(
|
|
118
|
+
current.indexOf(PROFILE_END, start) + PROFILE_END.length
|
|
119
|
+
)}`;
|
|
120
|
+
|
|
121
|
+
if (next !== current) {
|
|
122
|
+
write(file, next, { mode: 0o600 });
|
|
123
|
+
updated.push(file);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return updated;
|
|
127
|
+
}
|
|
128
|
+
|
|
61
129
|
export function materialize(cfg = readConfig()) {
|
|
62
130
|
if (!cfg.capabilities.mayWriteValues) {
|
|
63
131
|
throw new Error(
|
|
@@ -88,7 +156,21 @@ export function materialize(cfg = readConfig()) {
|
|
|
88
156
|
chmodSync(dir, 0o700);
|
|
89
157
|
writeAtomic(valuesFile, renderEnv(selected));
|
|
90
158
|
writeAtomic(notesFile, renderNotes(selected));
|
|
91
|
-
|
|
159
|
+
|
|
160
|
+
// Writing the file is not the same as the values being in effect.
|
|
161
|
+
//
|
|
162
|
+
// A materialized secrets.env that nothing sources changes nothing: the agent's
|
|
163
|
+
// shell starts from the container's own environment, so a host-injected
|
|
164
|
+
// AWS_ACCESS_KEY_ID keeps winning and every call fails with
|
|
165
|
+
// InvalidClientTokenId while the correct credential sits on disk, correct and
|
|
166
|
+
// unused. Deriving the variables (above) only fixes precedence WITHIN the
|
|
167
|
+
// file — something still has to load the file.
|
|
168
|
+
//
|
|
169
|
+
// So the shell profile sources it. That is what makes "the credential is
|
|
170
|
+
// materialized" and "the credential is usable" the same statement.
|
|
171
|
+
const sourced = installProfileSourcing(valuesFile);
|
|
172
|
+
|
|
173
|
+
return { count: selected.size, derived: derived.size, dir, sourced };
|
|
92
174
|
}
|
|
93
175
|
|
|
94
176
|
function main() {
|