@bluelamp/secret-exec 0.2.0 → 0.3.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/README.md +10 -0
- package/dist/handoff.js +125 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -67,3 +67,13 @@ bluelamp-secret put my-db --file ./db-url.txt --url https://db.example.com
|
|
|
67
67
|
| 1 | 未ログイン・金庫に無い・権限なし・ポータルに届かない(子は起こさない) |
|
|
68
68
|
| 2 | 引数の形が違う |
|
|
69
69
|
| 127 | コマンドを起動できなかった |
|
|
70
|
+
|
|
71
|
+
## `@bluelamp/secret-exec/handoff`(0.3.0〜・箱 n=2946)
|
|
72
|
+
|
|
73
|
+
口座の鍵を、起こす子の `CLAUDE_CODE_OAUTH_TOKEN` にだけ渡すコマンドの**組み立て**。monster-orchestrator(配車の席)と scheduler(予約の席)が同じ式で使う。
|
|
74
|
+
|
|
75
|
+
- `buildCredentialHandoff(command, vaultName, bins)` … `command` を「金庫 `vaultName` の鍵を `CLAUDE_CODE_OAUTH_TOKEN` に入れてから exec する」sh の 1 行に包む。値は `bluelamp-secret exec` が子の env に入れ、コマンド置換で受けて仮の名を unset してから exec する=argv・ディスク・ログに出ない。取り出せなければ exit 97(子は起きない)
|
|
76
|
+
- `probeCredential(vaultName, bins)` … 取り出せるかだけを `{ ok, exitCode }` で返す(何も出力しない)
|
|
77
|
+
- `resolveHandoffBins(overrideEnvNames)` … node と bluelamp-secret の実体の絶対パス(launchd の貧しい PATH に依らない)。見つからなければ null
|
|
78
|
+
|
|
79
|
+
⛔ 値を返す口は無い(テスト P10 が export の一覧を縛る)。外側の `env -u CLAUDE_CODE_OAUTH_TOKEN`(親の継承を切る)は呼び手が付ける。
|
package/dist/handoff.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 口座の鍵を、起こす子の `CLAUDE_CODE_OAUTH_TOKEN` にだけ渡す組み立て(箱 n=2946・`@bluelamp/secret-exec/handoff`)。
|
|
3
|
+
*
|
|
4
|
+
* 出所: monster-orchestrator の account-pool.ts(箱 n=2079・n=2696・n=2730)から移した。orchestrator(配車の席)と
|
|
5
|
+
* scheduler(予約の席)が同じ式で渡す=二つ目の実装を作らない。
|
|
6
|
+
*
|
|
7
|
+
* ■ 秘密の扱い(⛔ 1 つも破らない)
|
|
8
|
+
* - ここに値は来ない。組み立てるのは「`bluelamp-secret exec <仮の名>=<金庫の名前> -- …` をどう撃つか」だけ。
|
|
9
|
+
* - 値は bluelamp-secret が子の env(仮の名)に入れ、取り出す口(sh + node -e)がコマンド置換で受けて
|
|
10
|
+
* `CLAUDE_CODE_OAUTH_TOKEN` に入れ、仮の名を unset してから exec する=argv・ディスク・ログに出ない。
|
|
11
|
+
* - ⛔ 値を返す関数を足さない(このパッケージの約束・index.ts の冒頭)。
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from "node:fs";
|
|
14
|
+
import * as path from "node:path";
|
|
15
|
+
import { spawnSync } from "node:child_process";
|
|
16
|
+
/** 子の env に金庫の値を入れる仮の名(exec 前に unset する)。 */
|
|
17
|
+
export const CRED_ENV_NAME = "MO_ACCOUNT_CRED";
|
|
18
|
+
/** 金庫の値の形を読んで accessToken を `t` に置く本文(JSON `{accessToken}`/JSON の文字列/生の token)。 */
|
|
19
|
+
const READ_TOKEN_JS = `const r=(process.env.${CRED_ENV_NAME}||"").trim();let t;` +
|
|
20
|
+
`try{const o=JSON.parse(r);t=typeof o==="string"?o:o&&typeof o==="object"?o.accessToken:undefined}catch{t=/^[^\\s{}"]+$/.test(r)?r:undefined}`;
|
|
21
|
+
/**
|
|
22
|
+
* 金庫の値から accessToken を取り出して stdout に出す(`node -e` に渡す本文)。
|
|
23
|
+
* 🔐 本文に値は無い(値は env から読む)。stdout はコマンド置換(pipe)で受けるのでディスクに落ちない。
|
|
24
|
+
*/
|
|
25
|
+
export const EXTRACT_TOKEN_JS = READ_TOKEN_JS +
|
|
26
|
+
`if(typeof t!=="string"||t.length===0){process.stderr.write("bluelamp: 口座の資格を取り出せない\\n");process.exit(97)}` +
|
|
27
|
+
`process.stdout.write(t)`;
|
|
28
|
+
/** 取り出せるかだけを終了コードで返す本文(0=取り出せる・97=形が違う/空)。⛔ 何も出力しない */
|
|
29
|
+
export const PROBE_TOKEN_JS = READ_TOKEN_JS + `process.exit(typeof t==="string"&&t.length>0?0:97)`;
|
|
30
|
+
/**
|
|
31
|
+
* 値を受けて exec する sh の本文(`$1`=node・`$2`=取り出す本文・`$3`=元の command)。
|
|
32
|
+
* 値は `$(…)` で変数に受けるだけ=argv に出ない。仮の名は exec 前に unset。
|
|
33
|
+
*/
|
|
34
|
+
export const HANDOFF_SH = `T=$("$1" -e "$2") || exit 97; unset ${CRED_ENV_NAME}; ` +
|
|
35
|
+
`CLAUDE_CODE_OAUTH_TOKEN=$T; export CLAUDE_CODE_OAUTH_TOKEN; unset T; exec /bin/sh -c "$3"`;
|
|
36
|
+
/** 金庫の名前に許す文字(シェルの 1 語にそのまま載るので狭く取る) */
|
|
37
|
+
export const VAULT_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
38
|
+
/** POSIX sh の 1 語にクォートする(純関数) */
|
|
39
|
+
export function shellQuote(s) {
|
|
40
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* `command` を「金庫 `vaultName` の鍵を `CLAUDE_CODE_OAUTH_TOKEN` に入れてから exec する」sh の 1 行に包む(純関数)。
|
|
44
|
+
* 外側の `env -u CLAUDE_CODE_OAUTH_TOKEN`(親の継承を切る多重防御)は呼び手が付ける。
|
|
45
|
+
*/
|
|
46
|
+
export function buildCredentialHandoff(command, vaultName, bins) {
|
|
47
|
+
if (!VAULT_NAME_RE.test(vaultName))
|
|
48
|
+
throw new Error(`金庫の名前の形が不正: ${JSON.stringify(vaultName)}`);
|
|
49
|
+
return [
|
|
50
|
+
shellQuote(bins.node),
|
|
51
|
+
shellQuote(bins.secretExec),
|
|
52
|
+
"exec",
|
|
53
|
+
`${CRED_ENV_NAME}=${vaultName}`,
|
|
54
|
+
"--",
|
|
55
|
+
"/bin/sh",
|
|
56
|
+
"-c",
|
|
57
|
+
shellQuote(HANDOFF_SH),
|
|
58
|
+
"mo-account",
|
|
59
|
+
shellQuote(bins.node),
|
|
60
|
+
shellQuote(EXTRACT_TOKEN_JS),
|
|
61
|
+
shellQuote(command),
|
|
62
|
+
].join(" ");
|
|
63
|
+
}
|
|
64
|
+
/** 取り出せるかだけを確かめる argv(純関数・シェルを通さず spawn する)。 */
|
|
65
|
+
export function buildCredentialProbeArgv(vaultName, bins) {
|
|
66
|
+
if (!VAULT_NAME_RE.test(vaultName))
|
|
67
|
+
throw new Error(`金庫の名前の形が不正: ${JSON.stringify(vaultName)}`);
|
|
68
|
+
return [bins.secretExec, "exec", `${CRED_ENV_NAME}=${vaultName}`, "--", bins.node, "-e", PROBE_TOKEN_JS];
|
|
69
|
+
}
|
|
70
|
+
/** 金庫の鍵を取り出せるか(I/O)。⛔ stdout/stderr は捨てる(値は出ないが、bluelamp-secret の案内も呼び手に返さない) */
|
|
71
|
+
export function probeCredential(vaultName, bins, timeoutMs = 15_000) {
|
|
72
|
+
// bluelamp-secret(.mjs)は shebang の `env node` に依らず node で直接起こす
|
|
73
|
+
const r = spawnSync(bins.node, buildCredentialProbeArgv(vaultName, bins), { stdio: "ignore", timeout: timeoutMs });
|
|
74
|
+
return { ok: r.status === 0, exitCode: r.status };
|
|
75
|
+
}
|
|
76
|
+
/* ============================== bluelamp-secret の置き場 ============================== */
|
|
77
|
+
export const SECRET_EXEC_NAME = "bluelamp-secret";
|
|
78
|
+
/**
|
|
79
|
+
* bluelamp-secret を探す先(純関数・箱 n=2730)。順は
|
|
80
|
+
* 呼び手の上書き(env の値・先頭から)→ /opt/homebrew/bin → /usr/local/bin → PATH の各段 → node と同じ bin(npm の既定 prefix)。
|
|
81
|
+
* Mac の 2 か所を PATH より前に置く=launchd の貧しい PATH でも、Mac で選ばれる実体は変わらない。
|
|
82
|
+
*/
|
|
83
|
+
export function secretExecCandidates(env, execPath, overrideEnvNames = []) {
|
|
84
|
+
const dirs = (env.PATH ?? "").split(path.delimiter).filter((d) => path.isAbsolute(d));
|
|
85
|
+
const list = [
|
|
86
|
+
// 上書きは絶対パスだけ(相対は cwd で解決され、起こした場所で別の実体を掴む・CTO H-5 指摘②)
|
|
87
|
+
...overrideEnvNames.map((n) => env[n]).filter((p) => typeof p === "string" && path.isAbsolute(p)),
|
|
88
|
+
`/opt/homebrew/bin/${SECRET_EXEC_NAME}`,
|
|
89
|
+
`/usr/local/bin/${SECRET_EXEC_NAME}`,
|
|
90
|
+
...dirs.map((d) => path.join(d, SECRET_EXEC_NAME)),
|
|
91
|
+
path.join(path.dirname(execPath), SECRET_EXEC_NAME),
|
|
92
|
+
].filter((p) => typeof p === "string" && p.length > 0);
|
|
93
|
+
return [...new Set(list)];
|
|
94
|
+
}
|
|
95
|
+
/** `npm prefix -g`/bin の bluelamp-secret(npm の prefix を ~/.npmrc で変えた機体向け)。npm が無い・遅いときは null。 */
|
|
96
|
+
function npmGlobalSecretExec() {
|
|
97
|
+
const r = spawnSync("npm", ["prefix", "-g"], { encoding: "utf8", timeout: 5_000 });
|
|
98
|
+
const prefix = r.status === 0 ? r.stdout.trim() : "";
|
|
99
|
+
return path.isAbsolute(prefix) ? path.join(prefix, "bin", SECRET_EXEC_NAME) : null;
|
|
100
|
+
}
|
|
101
|
+
/** node / bluelamp-secret の絶対パスを決める。見つからなければ null(呼び手が自分の言葉でエラーにする)。 */
|
|
102
|
+
export function resolveHandoffBins(overrideEnvNames = [], deps) {
|
|
103
|
+
const d = {
|
|
104
|
+
env: process.env,
|
|
105
|
+
execPath: process.execPath,
|
|
106
|
+
realpath: fs.realpathSync,
|
|
107
|
+
npmGlobal: npmGlobalSecretExec,
|
|
108
|
+
...deps,
|
|
109
|
+
};
|
|
110
|
+
const tryOne = (c) => {
|
|
111
|
+
try {
|
|
112
|
+
return { node: d.execPath, secretExec: d.realpath(c) };
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
for (const c of secretExecCandidates(d.env, d.execPath, overrideEnvNames)) {
|
|
119
|
+
const b = tryOne(c);
|
|
120
|
+
if (b)
|
|
121
|
+
return b;
|
|
122
|
+
}
|
|
123
|
+
const g = d.npmGlobal();
|
|
124
|
+
return g ? tryOne(g) : null;
|
|
125
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bluelamp/secret-exec",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "金庫の値を子プロセスの環境変数にだけ入れてコマンドを実行し(exec)、標準入力から金庫へ入れる(put)。値を画面・引数・ログに出さない口",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bluelamp",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"author": "BlueLamp <dev@bluelamp.app>",
|
|
12
12
|
"type": "module",
|
|
13
13
|
"exports": {
|
|
14
|
-
"./package.json": "./package.json"
|
|
14
|
+
"./package.json": "./package.json",
|
|
15
|
+
"./handoff": "./dist/handoff.js"
|
|
15
16
|
},
|
|
16
17
|
"bin": {
|
|
17
18
|
"bluelamp-secret": "./bin/bluelamp-secret.mjs"
|