@codyswann/lisa 2.316.2 → 2.317.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 +139 -9
- 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-atlassian-access/SKILL.md +17 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-automation-status/SKILL.md +23 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-doctor/SKILL.md +33 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-implement/SKILL.md +17 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-notion-access/SKILL.md +17 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-remote-dispatch/SKILL.md +99 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-remote-dispatch/agents/openai.yaml +4 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/SKILL.md +137 -51
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-automations/SKILL.md +69 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/SKILL.md +150 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/agents/openai.yaml +4 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
- package/plugins/lisa/commands/implement.md +5 -3
- package/plugins/lisa/commands/setup/remote-env.md +7 -0
- package/plugins/lisa/skills/lisa-atlassian-access/SKILL.md +17 -0
- package/plugins/lisa/skills/lisa-automation-status/SKILL.md +23 -0
- package/plugins/lisa/skills/lisa-doctor/SKILL.md +33 -0
- package/plugins/lisa/skills/lisa-implement/SKILL.md +17 -0
- package/plugins/lisa/skills/lisa-notion-access/SKILL.md +17 -0
- package/plugins/lisa/skills/lisa-remote-dispatch/SKILL.md +99 -0
- package/plugins/lisa/skills/lisa-remote-dispatch/agents/openai.yaml +4 -0
- package/plugins/lisa/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
- package/plugins/lisa/skills/lisa-secrets-access/SKILL.md +138 -52
- package/plugins/lisa/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
- package/plugins/lisa/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
- package/plugins/lisa/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
- package/plugins/lisa/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
- package/plugins/lisa/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
- package/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
- package/plugins/lisa/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
- package/plugins/lisa/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
- package/plugins/lisa/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
- package/plugins/lisa/skills/lisa-setup-automations/SKILL.md +69 -0
- package/plugins/lisa/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
- package/plugins/lisa/skills/lisa-setup-remote-env/SKILL.md +150 -0
- package/plugins/lisa/skills/lisa-setup-remote-env/agents/openai.yaml +4 -0
- package/plugins/lisa/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
- package/plugins/lisa/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
- package/plugins/lisa/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
- package/plugins/lisa/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
- package/plugins/lisa-agy/commands/lisa/implement.md +5 -3
- package/plugins/lisa-agy/commands/lisa/setup/remote-env.md +7 -0
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-atlassian-access/SKILL.md +17 -0
- package/plugins/lisa-agy/skills/lisa-automation-status/SKILL.md +23 -0
- package/plugins/lisa-agy/skills/lisa-doctor/SKILL.md +33 -0
- package/plugins/lisa-agy/skills/lisa-implement/SKILL.md +17 -0
- package/plugins/lisa-agy/skills/lisa-notion-access/SKILL.md +17 -0
- package/plugins/lisa-agy/skills/lisa-remote-dispatch/SKILL.md +99 -0
- package/plugins/lisa-agy/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
- package/plugins/lisa-agy/skills/lisa-secrets-access/SKILL.md +138 -52
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
- package/plugins/lisa-agy/skills/lisa-setup-automations/SKILL.md +69 -0
- package/plugins/lisa-agy/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
- package/plugins/lisa-agy/skills/lisa-setup-remote-env/SKILL.md +150 -0
- package/plugins/lisa-agy/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
- package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
- package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
- package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
- 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/commands/lisa/implement.md +5 -3
- package/plugins/lisa-copilot/commands/lisa/setup/remote-env.md +7 -0
- package/plugins/lisa-copilot/skills/lisa-atlassian-access/SKILL.md +17 -0
- package/plugins/lisa-copilot/skills/lisa-automation-status/SKILL.md +23 -0
- package/plugins/lisa-copilot/skills/lisa-doctor/SKILL.md +33 -0
- package/plugins/lisa-copilot/skills/lisa-implement/SKILL.md +17 -0
- package/plugins/lisa-copilot/skills/lisa-notion-access/SKILL.md +17 -0
- package/plugins/lisa-copilot/skills/lisa-remote-dispatch/SKILL.md +99 -0
- package/plugins/lisa-copilot/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
- package/plugins/lisa-copilot/skills/lisa-secrets-access/SKILL.md +138 -52
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
- package/plugins/lisa-copilot/skills/lisa-setup-automations/SKILL.md +69 -0
- package/plugins/lisa-copilot/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
- package/plugins/lisa-copilot/skills/lisa-setup-remote-env/SKILL.md +150 -0
- package/plugins/lisa-copilot/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
- package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
- package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
- package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/commands/lisa/implement.md +5 -3
- package/plugins/lisa-cursor/commands/lisa/setup/remote-env.md +7 -0
- package/plugins/lisa-cursor/skills/lisa-atlassian-access/SKILL.md +17 -0
- package/plugins/lisa-cursor/skills/lisa-automation-status/SKILL.md +23 -0
- package/plugins/lisa-cursor/skills/lisa-doctor/SKILL.md +33 -0
- package/plugins/lisa-cursor/skills/lisa-implement/SKILL.md +17 -0
- package/plugins/lisa-cursor/skills/lisa-notion-access/SKILL.md +17 -0
- package/plugins/lisa-cursor/skills/lisa-remote-dispatch/SKILL.md +99 -0
- package/plugins/lisa-cursor/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
- package/plugins/lisa-cursor/skills/lisa-secrets-access/SKILL.md +138 -52
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
- package/plugins/lisa-cursor/skills/lisa-setup-automations/SKILL.md +69 -0
- package/plugins/lisa-cursor/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
- package/plugins/lisa-cursor/skills/lisa-setup-remote-env/SKILL.md +150 -0
- package/plugins/lisa-cursor/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
- package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
- package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
- package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
- 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/commands/implement.md +5 -3
- package/plugins/src/base/commands/setup/remote-env.md +7 -0
- package/plugins/src/base/skills/lisa-atlassian-access/SKILL.md +17 -0
- package/plugins/src/base/skills/lisa-automation-status/SKILL.md +23 -0
- package/plugins/src/base/skills/lisa-doctor/SKILL.md +33 -0
- package/plugins/src/base/skills/lisa-implement/SKILL.md +17 -0
- package/plugins/src/base/skills/lisa-notion-access/SKILL.md +17 -0
- package/plugins/src/base/skills/lisa-remote-dispatch/SKILL.md +99 -0
- package/plugins/src/base/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
- package/plugins/src/base/skills/lisa-secrets-access/SKILL.md +138 -52
- package/plugins/src/base/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
- package/plugins/src/base/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
- package/plugins/src/base/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
- package/plugins/src/base/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
- package/plugins/src/base/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
- package/plugins/src/base/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
- package/plugins/src/base/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
- package/plugins/src/base/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
- package/plugins/src/base/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
- package/plugins/src/base/skills/lisa-setup-automations/SKILL.md +69 -0
- package/plugins/src/base/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
- package/plugins/src/base/skills/lisa-setup-remote-env/SKILL.md +150 -0
- package/plugins/src/base/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
- package/plugins/src/base/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
- package/plugins/src/base/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
- package/plugins/src/base/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Provider reads and boundary enforcement for `lisa-secrets-access`.
|
|
4
|
+
*
|
|
5
|
+
* Retrieval and selection are separated deliberately. `fetchRaw` is the only
|
|
6
|
+
* function that touches a provider; everything downstream operates on plain
|
|
7
|
+
* rows, which is what makes the boundary rules testable with synthetic data and
|
|
8
|
+
* without granting a test process access to any real secret.
|
|
9
|
+
* @module providers
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { execFileSync } from "node:child_process";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A provider key becomes a shell variable name on materializing surfaces, so
|
|
16
|
+
* only names valid in every POSIX-like shell are accepted. Anything else stays
|
|
17
|
+
* in the provider and is intentionally not exported.
|
|
18
|
+
*/
|
|
19
|
+
export const ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The provider entry holding rotation leases.
|
|
23
|
+
*
|
|
24
|
+
* Leases live in the provider because it is the only substrate every surface
|
|
25
|
+
* shares — a CI concurrency group and a laptop lockfile cannot see each other,
|
|
26
|
+
* but both can see this. It is coordination state rather than a credential, so
|
|
27
|
+
* it is excluded from every selection: nothing should resolve or materialize it.
|
|
28
|
+
*/
|
|
29
|
+
export const LEASE_KEY = "LISA_ROTATION_LEASES";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Obtain the one credential that unlocks the provider.
|
|
33
|
+
*
|
|
34
|
+
* Walks `sources` in order, environment first, so a CI run where the pipeline
|
|
35
|
+
* injects the bootstrap never reaches for a local store. This is the only
|
|
36
|
+
* credential permitted in an OS keychain — it is a bootstrap, not a cached copy
|
|
37
|
+
* of anything downstream.
|
|
38
|
+
* @param {{sources: string[], key: string|null}} bootstrap Bootstrap config.
|
|
39
|
+
* @returns {string|null} The token, or null when the provider needs none.
|
|
40
|
+
*/
|
|
41
|
+
export function bootstrapToken(bootstrap) {
|
|
42
|
+
if (!bootstrap.key) return null;
|
|
43
|
+
for (const source of bootstrap.sources) {
|
|
44
|
+
const found =
|
|
45
|
+
source === "env"
|
|
46
|
+
? (process.env[bootstrap.key] ?? "").trim()
|
|
47
|
+
: source === "keychain"
|
|
48
|
+
? fromKeychain(bootstrap.key)
|
|
49
|
+
: "";
|
|
50
|
+
if (found) return found;
|
|
51
|
+
}
|
|
52
|
+
throw new Error(
|
|
53
|
+
`${bootstrap.key} not found in: ${bootstrap.sources.join(", ")}.\n` +
|
|
54
|
+
`It is the bootstrap credential — without it no other secret can be read.`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Read one value from the macOS keychain, treating absence as empty.
|
|
60
|
+
* @param {string} key Keychain service name.
|
|
61
|
+
* @returns {string} The value, or an empty string when unavailable.
|
|
62
|
+
*/
|
|
63
|
+
function fromKeychain(key) {
|
|
64
|
+
if (process.platform !== "darwin") return "";
|
|
65
|
+
try {
|
|
66
|
+
return execFileSync(
|
|
67
|
+
"security",
|
|
68
|
+
["find-generic-password", "-s", key, "-a", process.env.USER ?? "", "-w"],
|
|
69
|
+
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
|
|
70
|
+
).trim();
|
|
71
|
+
} catch {
|
|
72
|
+
return "";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Read every secret the provider grants, as unfiltered rows.
|
|
78
|
+
*
|
|
79
|
+
* Deliberately reads all rather than one: the provider already scopes what this
|
|
80
|
+
* caller may see, so the set it returns *is* the permitted set. Restating that
|
|
81
|
+
* as a list in config would duplicate a boundary the provider already enforces.
|
|
82
|
+
* @param {object} cfg Resolved configuration.
|
|
83
|
+
* @returns {Array<{key: string, value: string, note: string, projectId: string|null}>} Rows.
|
|
84
|
+
*/
|
|
85
|
+
export function fetchRaw(cfg) {
|
|
86
|
+
const env = { ...process.env };
|
|
87
|
+
const token = bootstrapToken(cfg.bootstrap);
|
|
88
|
+
if (cfg.bootstrap.key && token) env[cfg.bootstrap.key] = token;
|
|
89
|
+
|
|
90
|
+
if (cfg.provider === "env") {
|
|
91
|
+
return Object.entries(process.env)
|
|
92
|
+
.filter(([k]) => /^[A-Z][A-Z0-9_]*$/.test(k))
|
|
93
|
+
.map(([key, value]) => ({
|
|
94
|
+
key,
|
|
95
|
+
value: value ?? "",
|
|
96
|
+
note: "",
|
|
97
|
+
projectId: null,
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (cfg.provider === "bitwarden") {
|
|
102
|
+
const raw = run("bws", ["secret", "list", "--output", "json"], env);
|
|
103
|
+
return JSON.parse(raw || "[]").map(s => ({
|
|
104
|
+
key: s.key,
|
|
105
|
+
value: s.value,
|
|
106
|
+
note: s.note ?? "",
|
|
107
|
+
projectId: s.projectId ?? null,
|
|
108
|
+
id: s.id ?? null,
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (cfg.provider === "doppler") {
|
|
113
|
+
const args = ["secrets", "download", "--no-file", "--format", "json"];
|
|
114
|
+
const raw = run("doppler", args, env);
|
|
115
|
+
return Object.entries(JSON.parse(raw || "{}")).map(([key, value]) => ({
|
|
116
|
+
key,
|
|
117
|
+
value: String(value),
|
|
118
|
+
note: "",
|
|
119
|
+
projectId: null,
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
throw new Error(
|
|
124
|
+
`provider "${cfg.provider}" has no bulk read implemented yet.\n` +
|
|
125
|
+
`Add one in providers.mjs — see the dispatch table in SKILL.md.`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Run a provider CLI, keeping its output off any shared stream.
|
|
131
|
+
* @param {string} bin Executable name.
|
|
132
|
+
* @param {string[]} args Arguments.
|
|
133
|
+
* @param {NodeJS.ProcessEnv} env Environment for the child.
|
|
134
|
+
* @returns {string} Captured stdout.
|
|
135
|
+
*/
|
|
136
|
+
function run(bin, args, env) {
|
|
137
|
+
return execFileSync(bin, args, {
|
|
138
|
+
encoding: "utf8",
|
|
139
|
+
env,
|
|
140
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Apply the exposure boundary and reject an ambiguous set.
|
|
146
|
+
*
|
|
147
|
+
* The provider's own scoping is the default allowlist. The optional controls
|
|
148
|
+
* may only *narrow* it — there is intentionally no way to widen access from
|
|
149
|
+
* config, because that boundary belongs to the provider grant.
|
|
150
|
+
*
|
|
151
|
+
* A duplicate exact-name key is fatal rather than last-wins. Silently choosing
|
|
152
|
+
* one would make which credential gets used depend on provider response order,
|
|
153
|
+
* which is neither stable nor visible at the call site.
|
|
154
|
+
* @param {Array<object>} rows Raw provider rows.
|
|
155
|
+
* @param {{projectIds: string[], excludeKeys: string[]}} narrow Narrowing controls.
|
|
156
|
+
* @returns {Map<string, {value: string, note: string, id: string|null}>} Selected secrets by name.
|
|
157
|
+
*/
|
|
158
|
+
export function normalizeRows(
|
|
159
|
+
rows,
|
|
160
|
+
narrow = { projectIds: [], excludeKeys: [] }
|
|
161
|
+
) {
|
|
162
|
+
const projects = new Set(narrow.projectIds ?? []);
|
|
163
|
+
const excluded = new Set([...(narrow.excludeKeys ?? []), LEASE_KEY]);
|
|
164
|
+
const out = new Map();
|
|
165
|
+
|
|
166
|
+
for (const row of rows) {
|
|
167
|
+
if (projects.size && !projects.has(row.projectId)) continue;
|
|
168
|
+
if (typeof row.key !== "string" || !ENV_KEY.test(row.key)) continue;
|
|
169
|
+
if (excluded.has(row.key)) continue;
|
|
170
|
+
if (out.has(row.key)) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
`duplicate secret key across visible projects: ${row.key}.\n` +
|
|
173
|
+
`Resolve it at the provider — choosing one here would make credential ` +
|
|
174
|
+
`use depend on response order.`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
if (typeof row.value !== "string") {
|
|
178
|
+
throw new Error(`secret value is not a string: ${row.key}`);
|
|
179
|
+
}
|
|
180
|
+
out.set(row.key, {
|
|
181
|
+
value: row.value,
|
|
182
|
+
note: row.note ?? "",
|
|
183
|
+
id: row.id ?? null,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Read and select in one step — the normal entry point for consumers.
|
|
191
|
+
* @param {object} cfg Resolved configuration.
|
|
192
|
+
* @returns {Map<string, {value: string, note: string, id: string|null}>} Selected secrets.
|
|
193
|
+
*/
|
|
194
|
+
export function fetchAll(cfg) {
|
|
195
|
+
return normalizeRows(fetchRaw(cfg), cfg.narrow);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Find one raw row by exact key, bypassing the exposure boundary.
|
|
200
|
+
*
|
|
201
|
+
* Only the rotation path uses this, and only to reach the lease record, which
|
|
202
|
+
* is deliberately excluded from every normal selection. Reading a *credential*
|
|
203
|
+
* must always go through {@link fetchAll} so the boundary applies.
|
|
204
|
+
* @param {object} cfg Resolved configuration.
|
|
205
|
+
* @param {string} key Exact key name.
|
|
206
|
+
* @returns {object|null} The raw row, or null when absent.
|
|
207
|
+
*/
|
|
208
|
+
export function rawByKey(cfg, key) {
|
|
209
|
+
const matches = fetchRaw(cfg).filter(row => row.key === key);
|
|
210
|
+
if (matches.length > 1) throw new Error(`duplicate secret key: ${key}`);
|
|
211
|
+
return matches[0] ?? null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Write a replacement value back to the provider.
|
|
216
|
+
*
|
|
217
|
+
* Passing a value as an argument is normally forbidden, because process
|
|
218
|
+
* arguments are visible to anyone who can run `ps` on the same host. The
|
|
219
|
+
* Bitwarden CLI exposes no stdin path for an edit, so this is a documented
|
|
220
|
+
* exception rather than an oversight: it is confined to the one operation that
|
|
221
|
+
* cannot be expressed otherwise, and `execFileSync` at least keeps the value
|
|
222
|
+
* out of a shell and therefore out of shell history.
|
|
223
|
+
* @param {object} cfg Resolved configuration.
|
|
224
|
+
* @param {string} id Provider-side identifier for the secret.
|
|
225
|
+
* @param {string} value Replacement value.
|
|
226
|
+
*/
|
|
227
|
+
export function writeSecret(cfg, id, value) {
|
|
228
|
+
const env = { ...process.env };
|
|
229
|
+
const token = bootstrapToken(cfg.bootstrap);
|
|
230
|
+
if (cfg.bootstrap.key && token) env[cfg.bootstrap.key] = token;
|
|
231
|
+
|
|
232
|
+
if (cfg.provider === "bitwarden") {
|
|
233
|
+
run("bws", ["secret", "edit", id, "--value", value], env);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
throw new Error(
|
|
237
|
+
`provider "${cfg.provider}" has no write implemented.\n` +
|
|
238
|
+
`Rotation requires a proven write path; add one in providers.mjs.`
|
|
239
|
+
);
|
|
240
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Print one credential's usage note without opening the values file.
|
|
4
|
+
*
|
|
5
|
+
* This is a separate program from the resolver on purpose. An agent about to
|
|
6
|
+
* use a credential needs to know its blast radius — scope, owner, rotation,
|
|
7
|
+
* cautions — and should be able to learn that without any code path that could
|
|
8
|
+
* serialize a secret value. Values never enter this process, so its output
|
|
9
|
+
* cannot leak one even if it is logged.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* read-secret-note.mjs NAME
|
|
13
|
+
* @module read-secret-note
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
17
|
+
|
|
18
|
+
import { materializedPaths, readConfig } from "./surfaces.mjs";
|
|
19
|
+
|
|
20
|
+
/** A key must be an exact environment-variable name; see the naming rule. */
|
|
21
|
+
const ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Load the notes manifest, rejecting a shape this version does not understand.
|
|
25
|
+
* @param {string} notesFile Path to `secret-notes.json`.
|
|
26
|
+
* @returns {Record<string, string>} Notes by exact key name.
|
|
27
|
+
*/
|
|
28
|
+
export function loadNotes(notesFile) {
|
|
29
|
+
if (!existsSync(notesFile)) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`secret notes are unavailable at ${notesFile}.\n` +
|
|
32
|
+
`Run the remote environment bootstrap before using a credential.`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
const payload = JSON.parse(readFileSync(notesFile, "utf8"));
|
|
36
|
+
if (payload.schemaVersion !== 1 || typeof payload.secrets !== "object") {
|
|
37
|
+
throw new Error("unsupported or malformed secret-notes manifest");
|
|
38
|
+
}
|
|
39
|
+
return payload.secrets;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Resolve one note, treating absence as a stop condition rather than a default.
|
|
44
|
+
*
|
|
45
|
+
* A missing note is deliberately fatal for the credential that lacks it. The
|
|
46
|
+
* alternative — carrying on with an inferred purpose — is how a token scoped to
|
|
47
|
+
* one repository gets used against another, and the failure is silent.
|
|
48
|
+
* @param {string} name Exact environment-variable name.
|
|
49
|
+
* @param {Record<string, string>} notes Loaded manifest entries.
|
|
50
|
+
* @returns {string} The note text.
|
|
51
|
+
*/
|
|
52
|
+
export function noteFor(name, notes) {
|
|
53
|
+
if (!ENV_KEY.test(name)) {
|
|
54
|
+
throw new Error("secret key must be an exact environment-variable name");
|
|
55
|
+
}
|
|
56
|
+
const note = notes[name];
|
|
57
|
+
if (typeof note !== "string" || !note.trim()) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`no usable note exists for ${name}.\n` +
|
|
60
|
+
`A missing or ambiguous note is a stop condition for that credential — ` +
|
|
61
|
+
`do not infer its purpose from its name.`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return note;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function main() {
|
|
68
|
+
const [name] = process.argv.slice(2);
|
|
69
|
+
if (!name) throw new Error("usage: read-secret-note.mjs NAME");
|
|
70
|
+
const cfg = readConfig();
|
|
71
|
+
const { notesFile } = materializedPaths(cfg.namespace);
|
|
72
|
+
console.log(noteFor(name, loadNotes(notesFile)));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
76
|
+
try {
|
|
77
|
+
main();
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.error(err.message);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
}
|