@kitsy/cnos 1.2.0 → 1.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 +3 -3
- package/dist/build/index.cjs +902 -113
- package/dist/build/index.d.cts +1 -1
- package/dist/build/index.d.ts +1 -1
- package/dist/build/index.js +22 -10
- package/dist/{chunk-WHUGFPE4.js → chunk-CDXJISGB.js} +1 -1
- package/dist/{chunk-APCTXRUN.js → chunk-DRKDNY4I.js} +998 -191
- package/dist/chunk-E7SE6N26.js +189 -0
- package/dist/{chunk-SO5XREEU.js → chunk-EDCLLCNL.js} +32 -11
- package/dist/{chunk-SXTMTACL.js → chunk-FC3IV6A7.js} +1 -31
- package/dist/{chunk-MLQGYCO7.js → chunk-JDII6O72.js} +1 -1
- package/dist/chunk-K6QYI2T4.js +105 -0
- package/dist/{chunk-EIN55XXA.js → chunk-OOKFRWTN.js} +1 -1
- package/dist/{chunk-ZA74BO47.js → chunk-OWUZQ4OH.js} +1 -1
- package/dist/{chunk-RD5WMHPM.js → chunk-QTKXPY3N.js} +1 -1
- package/dist/configure/index.cjs +2928 -0
- package/dist/configure/index.d.cts +12 -0
- package/dist/configure/index.d.ts +12 -0
- package/dist/configure/index.js +24 -0
- package/dist/{envNaming-BTJpH93W.d.cts → envNaming-D6k66myh.d.cts} +1 -1
- package/dist/{envNaming-CcsqAel3.d.ts → envNaming-Dy3WYiGK.d.ts} +1 -1
- package/dist/index.cjs +1142 -178
- package/dist/index.d.cts +2 -13
- package/dist/index.d.ts +2 -13
- package/dist/index.js +13 -25
- package/dist/internal.cjs +1512 -80
- package/dist/internal.d.cts +170 -14
- package/dist/internal.d.ts +170 -14
- package/dist/internal.js +645 -5
- package/dist/plugin/basic-schema.cjs +29 -2
- package/dist/plugin/basic-schema.d.cts +1 -1
- package/dist/plugin/basic-schema.d.ts +1 -1
- package/dist/plugin/basic-schema.js +2 -2
- package/dist/plugin/cli-args.cjs +29 -2
- package/dist/plugin/cli-args.d.cts +1 -1
- package/dist/plugin/cli-args.d.ts +1 -1
- package/dist/plugin/cli-args.js +2 -2
- package/dist/plugin/dotenv.cjs +36 -9
- package/dist/plugin/dotenv.d.cts +2 -2
- package/dist/plugin/dotenv.d.ts +2 -2
- package/dist/plugin/dotenv.js +2 -2
- package/dist/plugin/env-export.cjs +31 -2
- package/dist/plugin/env-export.d.cts +2 -2
- package/dist/plugin/env-export.d.ts +2 -2
- package/dist/plugin/env-export.js +2 -2
- package/dist/plugin/filesystem.cjs +46 -91
- package/dist/plugin/filesystem.d.cts +1 -1
- package/dist/plugin/filesystem.d.ts +1 -1
- package/dist/plugin/filesystem.js +2 -2
- package/dist/plugin/process-env.cjs +31 -4
- package/dist/plugin/process-env.d.cts +2 -2
- package/dist/plugin/process-env.d.ts +2 -2
- package/dist/plugin/process-env.js +2 -2
- package/dist/{plugin-DkOIT5uI.d.cts → plugin-CyNkf7Dm.d.cts} +14 -2
- package/dist/{plugin-DkOIT5uI.d.ts → plugin-CyNkf7Dm.d.ts} +14 -2
- package/dist/runtime/index.cjs +956 -128
- package/dist/runtime/index.d.cts +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +11 -186
- package/dist/{toPublicEnv-DvFeV3qG.d.cts → toPublicEnv-Cz72m6y0.d.cts} +1 -1
- package/dist/{toPublicEnv-C9clvXLo.d.ts → toPublicEnv-D2PZkaN-.d.ts} +1 -1
- package/package.json +11 -1
- package/dist/chunk-JUHPBAEH.js +0 -20
- package/dist/chunk-PQ4KSV76.js +0 -50
package/dist/runtime/index.cjs
CHANGED
|
@@ -53,6 +53,11 @@ var CnosSecurityError = class extends CnosError {
|
|
|
53
53
|
super(message);
|
|
54
54
|
}
|
|
55
55
|
};
|
|
56
|
+
var CnosAuthenticationError = class extends CnosError {
|
|
57
|
+
constructor(message) {
|
|
58
|
+
super(message);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
56
61
|
var CnosKeyNotFoundError = class extends CnosError {
|
|
57
62
|
constructor(key) {
|
|
58
63
|
super(`Missing required CNOS config key: ${key}`);
|
|
@@ -105,6 +110,68 @@ function createProvenanceInspector() {
|
|
|
105
110
|
};
|
|
106
111
|
}
|
|
107
112
|
|
|
113
|
+
// ../core/src/keychain/linux.ts
|
|
114
|
+
var import_node_child_process = require("child_process");
|
|
115
|
+
var import_node_util = require("util");
|
|
116
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
117
|
+
async function readLinuxKeychain(entry) {
|
|
118
|
+
try {
|
|
119
|
+
const { stdout } = await execFileAsync("secret-tool", ["lookup", "service", "cnos", "account", entry]);
|
|
120
|
+
const value = stdout.trim();
|
|
121
|
+
return value.length > 0 ? value : void 0;
|
|
122
|
+
} catch {
|
|
123
|
+
return void 0;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ../core/src/keychain/macos.ts
|
|
128
|
+
var import_node_child_process2 = require("child_process");
|
|
129
|
+
var import_node_util2 = require("util");
|
|
130
|
+
var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
|
|
131
|
+
async function readMacosKeychain(entry) {
|
|
132
|
+
try {
|
|
133
|
+
const { stdout } = await execFileAsync2("security", ["find-generic-password", "-a", "cnos", "-s", entry, "-w"]);
|
|
134
|
+
const value = stdout.trim();
|
|
135
|
+
return value.length > 0 ? value : void 0;
|
|
136
|
+
} catch {
|
|
137
|
+
return void 0;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ../core/src/keychain/windows.ts
|
|
142
|
+
var import_node_child_process3 = require("child_process");
|
|
143
|
+
var import_node_util3 = require("util");
|
|
144
|
+
var execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process3.execFile);
|
|
145
|
+
function wrap(script) {
|
|
146
|
+
return ["-NoProfile", "-Command", script];
|
|
147
|
+
}
|
|
148
|
+
async function readWindowsKeychain(entry) {
|
|
149
|
+
try {
|
|
150
|
+
const { stdout } = await execFileAsync3(
|
|
151
|
+
"powershell",
|
|
152
|
+
wrap(`Add-Type -AssemblyName System.Runtime.WindowsRuntime; [Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime] > $null; $vault = New-Object Windows.Security.Credentials.PasswordVault; $credential = $vault.Retrieve('cnos','${entry}'); $credential.RetrievePassword(); Write-Output $credential.Password`)
|
|
153
|
+
);
|
|
154
|
+
const value = stdout.trim();
|
|
155
|
+
return value.length > 0 ? value : void 0;
|
|
156
|
+
} catch {
|
|
157
|
+
return void 0;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ../core/src/keychain/index.ts
|
|
162
|
+
async function readKeychain(entry) {
|
|
163
|
+
if (process.platform === "win32") {
|
|
164
|
+
return readWindowsKeychain(entry);
|
|
165
|
+
}
|
|
166
|
+
if (process.platform === "darwin") {
|
|
167
|
+
return readMacosKeychain(entry);
|
|
168
|
+
}
|
|
169
|
+
if (process.platform === "linux") {
|
|
170
|
+
return readLinuxKeychain(entry);
|
|
171
|
+
}
|
|
172
|
+
return void 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
108
175
|
// ../core/src/manifest/loadManifest.ts
|
|
109
176
|
var import_promises2 = require("fs/promises");
|
|
110
177
|
var import_node_path2 = __toESM(require("path"), 1);
|
|
@@ -191,6 +258,9 @@ var import_yaml = require("yaml");
|
|
|
191
258
|
function parseYaml(source) {
|
|
192
259
|
return (0, import_yaml.parse)(source);
|
|
193
260
|
}
|
|
261
|
+
function stringifyYaml(value) {
|
|
262
|
+
return (0, import_yaml.stringify)(value);
|
|
263
|
+
}
|
|
194
264
|
|
|
195
265
|
// ../core/src/manifest/normalizeManifest.ts
|
|
196
266
|
var DEFAULT_RESOLVE_FROM = ["cli.profile", "env.CNOS_PROFILE", "default"];
|
|
@@ -278,22 +348,82 @@ function normalizeNamespaces(namespaces) {
|
|
|
278
348
|
function normalizeVaults(vaults) {
|
|
279
349
|
return Object.fromEntries(
|
|
280
350
|
Object.entries(vaults ?? {}).map(([name, definition]) => {
|
|
351
|
+
const legacyPassphrase = definition.passphrase;
|
|
352
|
+
if (legacyPassphrase !== void 0) {
|
|
353
|
+
throw new CnosManifestError(
|
|
354
|
+
`Vault "${name}" uses legacy passphrase configuration. Use vaults.${name}.auth instead.`
|
|
355
|
+
);
|
|
356
|
+
}
|
|
281
357
|
const provider = definition.provider?.trim();
|
|
282
358
|
if (!provider) {
|
|
283
359
|
throw new CnosManifestError(`Vault "${name}" requires a provider`);
|
|
284
360
|
}
|
|
361
|
+
const normalizedAuth = normalizeVaultAuth(name, provider, definition.auth);
|
|
362
|
+
const normalizedMapping = Object.fromEntries(
|
|
363
|
+
Object.entries(definition.mapping ?? {}).filter(
|
|
364
|
+
(entry) => typeof entry[0] === "string" && typeof entry[1] === "string"
|
|
365
|
+
).map(([envVar, logicalRef]) => [envVar.trim(), logicalRef.trim()]).filter(([envVar, logicalRef]) => envVar.length > 0 && logicalRef.length > 0)
|
|
366
|
+
);
|
|
285
367
|
return [
|
|
286
368
|
name,
|
|
287
369
|
{
|
|
288
370
|
provider,
|
|
289
|
-
|
|
290
|
-
|
|
371
|
+
auth: normalizedAuth,
|
|
372
|
+
...Object.keys(normalizedMapping).length > 0 ? {
|
|
373
|
+
mapping: normalizedMapping
|
|
291
374
|
} : {}
|
|
292
375
|
}
|
|
293
376
|
];
|
|
294
377
|
})
|
|
295
378
|
);
|
|
296
379
|
}
|
|
380
|
+
function normalizeAuthSources(value) {
|
|
381
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
382
|
+
return void 0;
|
|
383
|
+
}
|
|
384
|
+
const sources = Array.isArray(value.from) ? value.from : void 0;
|
|
385
|
+
const normalized = (sources ?? []).map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
|
|
386
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
387
|
+
}
|
|
388
|
+
function normalizeVaultAuth(vaultName, provider, auth) {
|
|
389
|
+
if (provider === "local") {
|
|
390
|
+
const passphraseSources = normalizeAuthSources(auth?.passphrase);
|
|
391
|
+
const defaultToken = vaultName.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
392
|
+
const defaultSources = [
|
|
393
|
+
...defaultToken ? [`env:CNOS_SECRET_PASSPHRASE_${defaultToken}`] : [],
|
|
394
|
+
"env:CNOS_SECRET_PASSPHRASE",
|
|
395
|
+
`keychain:cnos/${vaultName}`,
|
|
396
|
+
"prompt"
|
|
397
|
+
];
|
|
398
|
+
return {
|
|
399
|
+
method: auth?.method ?? "passphrase",
|
|
400
|
+
passphrase: {
|
|
401
|
+
from: passphraseSources ?? defaultSources
|
|
402
|
+
},
|
|
403
|
+
...auth?.config ? { config: auth.config } : {}
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
if (provider === "github-secrets") {
|
|
407
|
+
return {
|
|
408
|
+
method: auth?.method ?? "environment",
|
|
409
|
+
...auth?.config ? { config: auth.config } : {}
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
...auth?.method ? { method: auth.method } : {},
|
|
414
|
+
...normalizeAuthSources(auth?.passphrase) ? {
|
|
415
|
+
passphrase: {
|
|
416
|
+
from: normalizeAuthSources(auth?.passphrase) ?? []
|
|
417
|
+
}
|
|
418
|
+
} : {},
|
|
419
|
+
...normalizeAuthSources(auth?.token) ? {
|
|
420
|
+
token: {
|
|
421
|
+
from: normalizeAuthSources(auth?.token) ?? []
|
|
422
|
+
}
|
|
423
|
+
} : {},
|
|
424
|
+
...auth?.config ? { config: auth.config } : {}
|
|
425
|
+
};
|
|
426
|
+
}
|
|
297
427
|
function normalizeManifest(manifest) {
|
|
298
428
|
const version = manifest.version ?? 1;
|
|
299
429
|
if (version !== 1) {
|
|
@@ -1097,6 +1227,712 @@ async function runPipeline(options) {
|
|
|
1097
1227
|
return collectedEntries.flat();
|
|
1098
1228
|
}
|
|
1099
1229
|
|
|
1230
|
+
// ../core/src/secrets/auditLog.ts
|
|
1231
|
+
var import_promises8 = require("fs/promises");
|
|
1232
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
1233
|
+
|
|
1234
|
+
// ../core/src/utils/secretStore.ts
|
|
1235
|
+
var import_node_crypto = require("crypto");
|
|
1236
|
+
var import_promises7 = require("fs/promises");
|
|
1237
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
1238
|
+
|
|
1239
|
+
// ../core/src/secrets/sessionStore.ts
|
|
1240
|
+
var import_promises6 = require("fs/promises");
|
|
1241
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
1242
|
+
function buildSessionRoot(processEnv = process.env) {
|
|
1243
|
+
return import_node_path6.default.join(import_node_path6.default.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets")), "sessions");
|
|
1244
|
+
}
|
|
1245
|
+
function buildSessionPath(vault, processEnv) {
|
|
1246
|
+
return import_node_path6.default.join(buildSessionRoot(processEnv), `${vault}.json`);
|
|
1247
|
+
}
|
|
1248
|
+
async function readVaultSessionKey(vault, processEnv) {
|
|
1249
|
+
try {
|
|
1250
|
+
const source = await (0, import_promises6.readFile)(buildSessionPath(vault, processEnv), "utf8");
|
|
1251
|
+
const document = JSON.parse(source);
|
|
1252
|
+
if (document.version !== 1 || typeof document.derivedKey !== "string") {
|
|
1253
|
+
return void 0;
|
|
1254
|
+
}
|
|
1255
|
+
const key = Buffer.from(document.derivedKey, "hex");
|
|
1256
|
+
return key.length > 0 ? key : void 0;
|
|
1257
|
+
} catch {
|
|
1258
|
+
return void 0;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
// ../core/src/utils/secretStore.ts
|
|
1263
|
+
var KEY_LENGTH = 32;
|
|
1264
|
+
var SALT_LENGTH = 32;
|
|
1265
|
+
var IV_LENGTH = 12;
|
|
1266
|
+
var AUTH_TAG_LENGTH = 16;
|
|
1267
|
+
var PBKDF2_ITERATIONS = 6e5;
|
|
1268
|
+
var KEYSTORE_VERSION = 1;
|
|
1269
|
+
var METADATA_VERSION = 1;
|
|
1270
|
+
var META_FILENAME = "meta.yml";
|
|
1271
|
+
var KEYSTORE_FILENAME = "keystore.enc";
|
|
1272
|
+
function isObject(value) {
|
|
1273
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1274
|
+
}
|
|
1275
|
+
function isSecretReference(value) {
|
|
1276
|
+
return isObject(value) && typeof value.provider === "string" && value.provider.trim().length > 0 && typeof value.ref === "string" && value.ref.trim().length > 0 && (value.vault === void 0 && true || typeof value.vault === "string" && value.vault.trim().length > 0) && Object.keys(value).every((key) => ["provider", "ref", "vault"].includes(key));
|
|
1277
|
+
}
|
|
1278
|
+
function resolveSecretStoreRoot(processEnv = process.env) {
|
|
1279
|
+
return import_node_path7.default.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets"));
|
|
1280
|
+
}
|
|
1281
|
+
function normalizeVaultToken(vault = "default") {
|
|
1282
|
+
return vault.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
1283
|
+
}
|
|
1284
|
+
function getVaultPassphraseEnvVar(vault = "default") {
|
|
1285
|
+
const vaultToken = normalizeVaultToken(vault);
|
|
1286
|
+
return vaultToken && vaultToken !== "DEFAULT" ? `CNOS_SECRET_PASSPHRASE_${vaultToken}` : "CNOS_SECRET_PASSPHRASE";
|
|
1287
|
+
}
|
|
1288
|
+
function getVaultSessionKeyEnvVar(vault = "default") {
|
|
1289
|
+
const vaultToken = normalizeVaultToken(vault);
|
|
1290
|
+
return `__CNOS_VAULT_KEY_${vaultToken || "DEFAULT"}__`;
|
|
1291
|
+
}
|
|
1292
|
+
function resolveSecretPassphrase(vault = "default", processEnv = process.env) {
|
|
1293
|
+
return processEnv[getVaultPassphraseEnvVar(vault)] ?? processEnv.CNOS_SECRET_PASSPHRASE;
|
|
1294
|
+
}
|
|
1295
|
+
function resolveVaultSessionKey(vault = "default", processEnv = process.env) {
|
|
1296
|
+
const encoded = processEnv[getVaultSessionKeyEnvVar(vault)];
|
|
1297
|
+
if (!encoded) {
|
|
1298
|
+
return readVaultSessionKey(vault, processEnv);
|
|
1299
|
+
}
|
|
1300
|
+
try {
|
|
1301
|
+
const key = Buffer.from(encoded, "hex");
|
|
1302
|
+
return key.length === KEY_LENGTH ? key : void 0;
|
|
1303
|
+
} catch {
|
|
1304
|
+
return void 0;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
function deriveVaultKey(passphrase, salt, iterations = PBKDF2_ITERATIONS) {
|
|
1308
|
+
return (0, import_node_crypto.pbkdf2Sync)(passphrase, salt, iterations, KEY_LENGTH, "sha512");
|
|
1309
|
+
}
|
|
1310
|
+
function buildMetaPath(storeRoot, vault = "default") {
|
|
1311
|
+
return import_node_path7.default.join(storeRoot, "vaults", vault, META_FILENAME);
|
|
1312
|
+
}
|
|
1313
|
+
function buildKeystorePath(storeRoot, vault = "default") {
|
|
1314
|
+
return import_node_path7.default.join(storeRoot, "vaults", vault, KEYSTORE_FILENAME);
|
|
1315
|
+
}
|
|
1316
|
+
function buildLegacyVaultFile(storeRoot, vault = "default") {
|
|
1317
|
+
return import_node_path7.default.join(storeRoot, "vaults", `${vault}.json`);
|
|
1318
|
+
}
|
|
1319
|
+
function buildLegacyVaultStoreRoot(storeRoot, vault = "default") {
|
|
1320
|
+
return import_node_path7.default.join(storeRoot, "vaults", vault, "store");
|
|
1321
|
+
}
|
|
1322
|
+
function assertVaultMetadata(value, filePath) {
|
|
1323
|
+
if (!isObject(value)) {
|
|
1324
|
+
throw new CnosManifestError("Invalid CNOS vault metadata", filePath);
|
|
1325
|
+
}
|
|
1326
|
+
if (value.version !== METADATA_VERSION || value.algorithm !== "aes-256-gcm" || value.kdf !== "pbkdf2-sha512" || typeof value.iterations !== "number" || typeof value.salt !== "string" || typeof value.createdAt !== "string" || typeof value.secretCount !== "number") {
|
|
1327
|
+
throw new CnosManifestError("Invalid CNOS vault metadata", filePath);
|
|
1328
|
+
}
|
|
1329
|
+
return value;
|
|
1330
|
+
}
|
|
1331
|
+
async function exists3(targetPath) {
|
|
1332
|
+
try {
|
|
1333
|
+
await (0, import_promises7.stat)(targetPath);
|
|
1334
|
+
return true;
|
|
1335
|
+
} catch {
|
|
1336
|
+
return false;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
async function detectLegacyVaultFormat(storeRoot, vault = "default") {
|
|
1340
|
+
const legacyFile = buildLegacyVaultFile(storeRoot, vault);
|
|
1341
|
+
const legacyStore = buildLegacyVaultStoreRoot(storeRoot, vault);
|
|
1342
|
+
if (await exists3(legacyFile)) {
|
|
1343
|
+
return legacyFile;
|
|
1344
|
+
}
|
|
1345
|
+
if (await exists3(legacyStore)) {
|
|
1346
|
+
return legacyStore;
|
|
1347
|
+
}
|
|
1348
|
+
return void 0;
|
|
1349
|
+
}
|
|
1350
|
+
async function assertNoLegacyVaultFormat(storeRoot, vault = "default") {
|
|
1351
|
+
const legacyPath = await detectLegacyVaultFormat(storeRoot, vault);
|
|
1352
|
+
if (!legacyPath) {
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
throw new CnosSecurityError(
|
|
1356
|
+
`Legacy CNOS local vault format detected for vault "${vault}" at ${legacyPath}. CNOS 1.4 requires the new keystore format. Remove and recreate the vault.`
|
|
1357
|
+
);
|
|
1358
|
+
}
|
|
1359
|
+
function encryptPayload(payload, key) {
|
|
1360
|
+
const iv = (0, import_node_crypto.randomBytes)(IV_LENGTH);
|
|
1361
|
+
const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", key, iv);
|
|
1362
|
+
const plaintext = Buffer.from(JSON.stringify(payload), "utf8");
|
|
1363
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
1364
|
+
const tag = cipher.getAuthTag();
|
|
1365
|
+
return Buffer.concat([
|
|
1366
|
+
Buffer.from(Uint32Array.of(KEYSTORE_VERSION).buffer),
|
|
1367
|
+
iv,
|
|
1368
|
+
tag,
|
|
1369
|
+
ciphertext
|
|
1370
|
+
]);
|
|
1371
|
+
}
|
|
1372
|
+
function decryptPayload(buffer, key) {
|
|
1373
|
+
if (buffer.length < 4 + IV_LENGTH + AUTH_TAG_LENGTH) {
|
|
1374
|
+
throw new CnosSecurityError("Invalid CNOS local vault keystore");
|
|
1375
|
+
}
|
|
1376
|
+
const version = buffer.readUInt32LE(0);
|
|
1377
|
+
if (version !== KEYSTORE_VERSION) {
|
|
1378
|
+
throw new CnosSecurityError(`Unsupported CNOS local vault keystore version: ${version}`);
|
|
1379
|
+
}
|
|
1380
|
+
const ivOffset = 4;
|
|
1381
|
+
const tagOffset = ivOffset + IV_LENGTH;
|
|
1382
|
+
const cipherOffset = tagOffset + AUTH_TAG_LENGTH;
|
|
1383
|
+
const iv = buffer.subarray(ivOffset, tagOffset);
|
|
1384
|
+
const tag = buffer.subarray(tagOffset, cipherOffset);
|
|
1385
|
+
const ciphertext = buffer.subarray(cipherOffset);
|
|
1386
|
+
const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", key, iv);
|
|
1387
|
+
decipher.setAuthTag(tag);
|
|
1388
|
+
try {
|
|
1389
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
|
|
1390
|
+
const payload = JSON.parse(plaintext);
|
|
1391
|
+
if (!payload || !isObject(payload.secrets) || !isObject(payload.metadata)) {
|
|
1392
|
+
throw new Error("invalid");
|
|
1393
|
+
}
|
|
1394
|
+
return {
|
|
1395
|
+
secrets: Object.fromEntries(
|
|
1396
|
+
Object.entries(payload.secrets).filter((entry) => typeof entry[1] === "string")
|
|
1397
|
+
),
|
|
1398
|
+
metadata: Object.fromEntries(
|
|
1399
|
+
Object.entries(payload.metadata).filter(
|
|
1400
|
+
(entry) => isObject(entry[1]) && typeof entry[1].createdAt === "string" && typeof entry[1].updatedAt === "string"
|
|
1401
|
+
)
|
|
1402
|
+
)
|
|
1403
|
+
};
|
|
1404
|
+
} catch {
|
|
1405
|
+
throw new CnosAuthenticationError("Failed to decrypt CNOS local vault. Check vault authentication.");
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
function buildInitialPayload() {
|
|
1409
|
+
return {
|
|
1410
|
+
secrets: {},
|
|
1411
|
+
metadata: {}
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
async function writeVaultFiles(storeRoot, vault, meta, payload, key) {
|
|
1415
|
+
const metaPath = buildMetaPath(storeRoot, vault);
|
|
1416
|
+
const keystorePath = buildKeystorePath(storeRoot, vault);
|
|
1417
|
+
await (0, import_promises7.mkdir)(import_node_path7.default.dirname(metaPath), { recursive: true });
|
|
1418
|
+
await (0, import_promises7.writeFile)(metaPath, stringifyYaml(meta), "utf8");
|
|
1419
|
+
await (0, import_promises7.writeFile)(keystorePath, encryptPayload(payload, key));
|
|
1420
|
+
}
|
|
1421
|
+
async function readVaultMetadata(storeRoot, vault = "default") {
|
|
1422
|
+
await assertNoLegacyVaultFormat(storeRoot, vault);
|
|
1423
|
+
const metaPath = buildMetaPath(storeRoot, vault);
|
|
1424
|
+
try {
|
|
1425
|
+
const source = await (0, import_promises7.readFile)(metaPath, "utf8");
|
|
1426
|
+
return assertVaultMetadata(parseYaml(source), metaPath);
|
|
1427
|
+
} catch (error) {
|
|
1428
|
+
if (error.code === "ENOENT") {
|
|
1429
|
+
return void 0;
|
|
1430
|
+
}
|
|
1431
|
+
throw error;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
async function createSecretVault(storeRoot, vault, passphrase) {
|
|
1435
|
+
const normalizedVault = vault.trim() || "default";
|
|
1436
|
+
await assertNoLegacyVaultFormat(storeRoot, normalizedVault);
|
|
1437
|
+
const salt = (0, import_node_crypto.randomBytes)(SALT_LENGTH);
|
|
1438
|
+
const key = deriveVaultKey(passphrase, salt, PBKDF2_ITERATIONS);
|
|
1439
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1440
|
+
const meta = {
|
|
1441
|
+
version: METADATA_VERSION,
|
|
1442
|
+
algorithm: "aes-256-gcm",
|
|
1443
|
+
kdf: "pbkdf2-sha512",
|
|
1444
|
+
iterations: PBKDF2_ITERATIONS,
|
|
1445
|
+
salt: salt.toString("base64"),
|
|
1446
|
+
createdAt,
|
|
1447
|
+
secretCount: 0
|
|
1448
|
+
};
|
|
1449
|
+
await writeVaultFiles(storeRoot, normalizedVault, meta, buildInitialPayload(), key);
|
|
1450
|
+
return buildMetaPath(storeRoot, normalizedVault);
|
|
1451
|
+
}
|
|
1452
|
+
async function ensureSecretVault(storeRoot, vault, passphrase) {
|
|
1453
|
+
const normalizedVault = vault.trim() || "default";
|
|
1454
|
+
const meta = await readVaultMetadata(storeRoot, normalizedVault);
|
|
1455
|
+
if (meta) {
|
|
1456
|
+
return buildMetaPath(storeRoot, normalizedVault);
|
|
1457
|
+
}
|
|
1458
|
+
return createSecretVault(storeRoot, normalizedVault, passphrase);
|
|
1459
|
+
}
|
|
1460
|
+
function resolveConfiguredVaultPassphrase(definition, vault = "default", processEnv = process.env) {
|
|
1461
|
+
if (definition?.provider !== "local") {
|
|
1462
|
+
return void 0;
|
|
1463
|
+
}
|
|
1464
|
+
const configuredSources = definition.auth?.passphrase?.from ?? [];
|
|
1465
|
+
for (const source of configuredSources) {
|
|
1466
|
+
if (source.startsWith("env:")) {
|
|
1467
|
+
const value = processEnv[source.slice(4)];
|
|
1468
|
+
if (value) {
|
|
1469
|
+
return value;
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
return resolveSecretPassphrase(vault, processEnv);
|
|
1474
|
+
}
|
|
1475
|
+
async function resolveVaultAccessKey(storeRoot, definition, vault = "default", processEnv = process.env) {
|
|
1476
|
+
if (definition?.provider !== "local") {
|
|
1477
|
+
return definition?.provider === "github-secrets" ? {
|
|
1478
|
+
method: definition.auth?.method ?? "environment",
|
|
1479
|
+
...definition?.auth?.config ? { config: definition.auth.config } : {}
|
|
1480
|
+
} : void 0;
|
|
1481
|
+
}
|
|
1482
|
+
const sessionKey = await resolveVaultSessionKey(vault, processEnv);
|
|
1483
|
+
if (sessionKey) {
|
|
1484
|
+
return {
|
|
1485
|
+
derivedKey: sessionKey,
|
|
1486
|
+
method: "keychain",
|
|
1487
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
const passphrase = resolveConfiguredVaultPassphrase(definition, vault, processEnv);
|
|
1491
|
+
if (passphrase) {
|
|
1492
|
+
return {
|
|
1493
|
+
passphrase,
|
|
1494
|
+
method: "passphrase",
|
|
1495
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
const metadata = await readVaultMetadata(storeRoot, vault);
|
|
1499
|
+
if (!metadata) {
|
|
1500
|
+
return void 0;
|
|
1501
|
+
}
|
|
1502
|
+
throw new CnosAuthenticationError(
|
|
1503
|
+
`Cannot authenticate to vault "${vault}". Set ${getVaultPassphraseEnvVar(vault)} or run cnos vault auth ${vault}.`
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1506
|
+
async function loadVaultPayload(storeRoot, vault, auth) {
|
|
1507
|
+
const meta = await readVaultMetadata(storeRoot, vault);
|
|
1508
|
+
if (!meta) {
|
|
1509
|
+
throw new CnosManifestError(`Missing CNOS vault metadata for "${vault}"`);
|
|
1510
|
+
}
|
|
1511
|
+
const salt = Buffer.from(meta.salt, "base64");
|
|
1512
|
+
const key = auth.derivedKey ?? (auth.passphrase ? deriveVaultKey(auth.passphrase, salt, meta.iterations) : void 0);
|
|
1513
|
+
if (!key) {
|
|
1514
|
+
throw new CnosAuthenticationError(`Vault "${vault}" requires authentication before access.`);
|
|
1515
|
+
}
|
|
1516
|
+
const buffer = await (0, import_promises7.readFile)(buildKeystorePath(storeRoot, vault));
|
|
1517
|
+
return {
|
|
1518
|
+
meta,
|
|
1519
|
+
payload: decryptPayload(buffer, key),
|
|
1520
|
+
key
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
async function writeLocalSecret(storeRoot, ref, value, authOrPassphrase, vault = "default") {
|
|
1524
|
+
const auth = typeof authOrPassphrase === "string" ? {
|
|
1525
|
+
passphrase: authOrPassphrase,
|
|
1526
|
+
method: "passphrase"
|
|
1527
|
+
} : authOrPassphrase;
|
|
1528
|
+
if (auth.passphrase) {
|
|
1529
|
+
await ensureSecretVault(storeRoot, vault, auth.passphrase);
|
|
1530
|
+
} else {
|
|
1531
|
+
const meta2 = await readVaultMetadata(storeRoot, vault);
|
|
1532
|
+
if (!meta2) {
|
|
1533
|
+
throw new CnosAuthenticationError(`Vault "${vault}" requires passphrase-based authentication for initial creation.`);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
const { meta, payload, key } = await loadVaultPayload(storeRoot, vault, auth);
|
|
1537
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1538
|
+
const existing = payload.metadata[ref];
|
|
1539
|
+
payload.secrets[ref] = value;
|
|
1540
|
+
payload.metadata[ref] = {
|
|
1541
|
+
createdAt: existing?.createdAt ?? now,
|
|
1542
|
+
updatedAt: now
|
|
1543
|
+
};
|
|
1544
|
+
const nextMeta = {
|
|
1545
|
+
...meta,
|
|
1546
|
+
secretCount: Object.keys(payload.secrets).length
|
|
1547
|
+
};
|
|
1548
|
+
await writeVaultFiles(storeRoot, vault, nextMeta, payload, key);
|
|
1549
|
+
return buildKeystorePath(storeRoot, vault);
|
|
1550
|
+
}
|
|
1551
|
+
async function deleteLocalSecret(storeRoot, ref, auth, vault = "default") {
|
|
1552
|
+
const { meta, payload, key } = await loadVaultPayload(storeRoot, vault, auth);
|
|
1553
|
+
if (!(ref in payload.secrets)) {
|
|
1554
|
+
return false;
|
|
1555
|
+
}
|
|
1556
|
+
delete payload.secrets[ref];
|
|
1557
|
+
delete payload.metadata[ref];
|
|
1558
|
+
const nextMeta = {
|
|
1559
|
+
...meta,
|
|
1560
|
+
secretCount: Object.keys(payload.secrets).length
|
|
1561
|
+
};
|
|
1562
|
+
await writeVaultFiles(storeRoot, vault, nextMeta, payload, key);
|
|
1563
|
+
return true;
|
|
1564
|
+
}
|
|
1565
|
+
async function readLocalSecret(storeRoot, ref, auth, vault = "default") {
|
|
1566
|
+
const { payload } = await loadVaultPayload(storeRoot, vault, auth);
|
|
1567
|
+
const value = payload.secrets[ref];
|
|
1568
|
+
if (value === void 0) {
|
|
1569
|
+
throw new CnosManifestError(`Missing local secret ref "${ref}" in vault "${vault}"`);
|
|
1570
|
+
}
|
|
1571
|
+
return value;
|
|
1572
|
+
}
|
|
1573
|
+
async function listLocalSecrets(storeRoot, auth, vault = "default") {
|
|
1574
|
+
const { payload } = await loadVaultPayload(storeRoot, vault, auth);
|
|
1575
|
+
return Object.keys(payload.secrets).sort((left, right) => left.localeCompare(right));
|
|
1576
|
+
}
|
|
1577
|
+
function resolveVaultDefinition(vaults, vault = "default") {
|
|
1578
|
+
const definition = vaults?.[vault];
|
|
1579
|
+
const provider = definition?.provider ?? "local";
|
|
1580
|
+
return {
|
|
1581
|
+
name: vault,
|
|
1582
|
+
provider,
|
|
1583
|
+
...definition?.auth ? { auth: definition.auth } : {},
|
|
1584
|
+
...definition?.mapping ? { mapping: definition.mapping } : {},
|
|
1585
|
+
requiresAuthentication: provider === "local"
|
|
1586
|
+
};
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
// ../core/src/secrets/auditLog.ts
|
|
1590
|
+
async function appendAuditEvent(event, processEnv = process.env) {
|
|
1591
|
+
const auditFile = processEnv.CNOS_AUDIT_FILE ?? import_node_path8.default.join(resolveSecretStoreRoot(processEnv), "audit", "access.log");
|
|
1592
|
+
await (0, import_promises8.mkdir)(import_node_path8.default.dirname(auditFile), { recursive: true });
|
|
1593
|
+
await (0, import_promises8.appendFile)(
|
|
1594
|
+
auditFile,
|
|
1595
|
+
`${JSON.stringify({
|
|
1596
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1597
|
+
...event
|
|
1598
|
+
})}
|
|
1599
|
+
`,
|
|
1600
|
+
"utf8"
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
// ../core/src/secrets/secretCache.ts
|
|
1605
|
+
var SecretCache = class {
|
|
1606
|
+
cache = /* @__PURE__ */ new Map();
|
|
1607
|
+
authenticated = /* @__PURE__ */ new Set();
|
|
1608
|
+
load(vaultId, secrets) {
|
|
1609
|
+
this.authenticated.add(vaultId);
|
|
1610
|
+
for (const [ref, value] of secrets) {
|
|
1611
|
+
this.cache.set(`${vaultId}:${ref}`, value);
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
isVaultAuthenticated(vaultId) {
|
|
1615
|
+
return this.authenticated.has(vaultId);
|
|
1616
|
+
}
|
|
1617
|
+
get(vaultId, ref) {
|
|
1618
|
+
return this.cache.get(`${vaultId}:${ref}`);
|
|
1619
|
+
}
|
|
1620
|
+
clear(vaultId) {
|
|
1621
|
+
if (!vaultId) {
|
|
1622
|
+
this.cache.clear();
|
|
1623
|
+
this.authenticated.clear();
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
this.authenticated.delete(vaultId);
|
|
1627
|
+
for (const key of Array.from(this.cache.keys())) {
|
|
1628
|
+
if (key.startsWith(`${vaultId}:`)) {
|
|
1629
|
+
this.cache.delete(key);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
};
|
|
1634
|
+
|
|
1635
|
+
// ../core/src/secrets/providers/github.ts
|
|
1636
|
+
var GithubSecretsVaultProvider = class {
|
|
1637
|
+
constructor(vaultId, definition, processEnv = process.env) {
|
|
1638
|
+
this.vaultId = vaultId;
|
|
1639
|
+
this.definition = definition;
|
|
1640
|
+
this.processEnv = processEnv;
|
|
1641
|
+
}
|
|
1642
|
+
vaultId;
|
|
1643
|
+
definition;
|
|
1644
|
+
processEnv;
|
|
1645
|
+
authenticated = false;
|
|
1646
|
+
async authenticate(_authConfig) {
|
|
1647
|
+
void _authConfig;
|
|
1648
|
+
this.authenticated = true;
|
|
1649
|
+
}
|
|
1650
|
+
isAuthenticated() {
|
|
1651
|
+
return this.authenticated;
|
|
1652
|
+
}
|
|
1653
|
+
resolveEnvVar(ref) {
|
|
1654
|
+
if (this.processEnv[ref] !== void 0) {
|
|
1655
|
+
return ref;
|
|
1656
|
+
}
|
|
1657
|
+
return Object.entries(this.definition.mapping ?? {}).find(([, logicalRef]) => logicalRef === ref)?.[0];
|
|
1658
|
+
}
|
|
1659
|
+
async batchGet(refs) {
|
|
1660
|
+
this.authenticated = true;
|
|
1661
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
1662
|
+
for (const ref of Array.from(new Set(refs)).sort((left, right) => left.localeCompare(right))) {
|
|
1663
|
+
const envVar = this.resolveEnvVar(ref);
|
|
1664
|
+
const value = envVar ? this.processEnv[envVar] : void 0;
|
|
1665
|
+
if (value !== void 0) {
|
|
1666
|
+
resolved.set(ref, value);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
return resolved;
|
|
1670
|
+
}
|
|
1671
|
+
async get(ref) {
|
|
1672
|
+
const envVar = this.resolveEnvVar(ref);
|
|
1673
|
+
this.authenticated = true;
|
|
1674
|
+
return envVar ? this.processEnv[envVar] : void 0;
|
|
1675
|
+
}
|
|
1676
|
+
async set(ref, value) {
|
|
1677
|
+
void ref;
|
|
1678
|
+
void value;
|
|
1679
|
+
throw new Error(`Vault "${this.vaultId}" is environment-backed and cannot be written by CNOS.`);
|
|
1680
|
+
}
|
|
1681
|
+
async delete(ref) {
|
|
1682
|
+
void ref;
|
|
1683
|
+
throw new Error(`Vault "${this.vaultId}" is environment-backed and cannot be mutated by CNOS.`);
|
|
1684
|
+
}
|
|
1685
|
+
async list() {
|
|
1686
|
+
return Object.values(this.definition.mapping ?? {}).sort((left, right) => left.localeCompare(right));
|
|
1687
|
+
}
|
|
1688
|
+
};
|
|
1689
|
+
|
|
1690
|
+
// ../core/src/secrets/providers/local.ts
|
|
1691
|
+
var LocalSecretVaultProvider = class _LocalSecretVaultProvider {
|
|
1692
|
+
constructor(vaultId, definition, processEnv = process.env, storeRoot = resolveSecretStoreRoot(processEnv)) {
|
|
1693
|
+
this.vaultId = vaultId;
|
|
1694
|
+
this.processEnv = processEnv;
|
|
1695
|
+
this.storeRoot = storeRoot;
|
|
1696
|
+
this.definition = definition;
|
|
1697
|
+
}
|
|
1698
|
+
vaultId;
|
|
1699
|
+
processEnv;
|
|
1700
|
+
storeRoot;
|
|
1701
|
+
authConfig;
|
|
1702
|
+
definition;
|
|
1703
|
+
static fromVaults(vaults, vaultId, processEnv) {
|
|
1704
|
+
const definition = resolveVaultDefinition(vaults, vaultId);
|
|
1705
|
+
return new _LocalSecretVaultProvider(vaultId, definition, processEnv);
|
|
1706
|
+
}
|
|
1707
|
+
async authenticate(authConfig) {
|
|
1708
|
+
this.authConfig = authConfig;
|
|
1709
|
+
await this.list();
|
|
1710
|
+
}
|
|
1711
|
+
isAuthenticated() {
|
|
1712
|
+
return Boolean(this.authConfig);
|
|
1713
|
+
}
|
|
1714
|
+
async requireAuth() {
|
|
1715
|
+
if (this.authConfig) {
|
|
1716
|
+
return this.authConfig;
|
|
1717
|
+
}
|
|
1718
|
+
const resolved = await resolveVaultAccessKey(this.storeRoot, this.definition, this.vaultId, this.processEnv);
|
|
1719
|
+
if (!resolved) {
|
|
1720
|
+
throw new CnosAuthenticationError(
|
|
1721
|
+
`Cannot authenticate to vault "${this.vaultId}". Set the configured passphrase env var or run cnos vault auth ${this.vaultId}.`
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1724
|
+
this.authConfig = resolved;
|
|
1725
|
+
return resolved;
|
|
1726
|
+
}
|
|
1727
|
+
async batchGet(refs) {
|
|
1728
|
+
const auth = await this.requireAuth();
|
|
1729
|
+
const entries = await Promise.all(
|
|
1730
|
+
Array.from(new Set(refs)).sort((left, right) => left.localeCompare(right)).map(async (ref) => [ref, await readLocalSecret(this.storeRoot, ref, auth, this.vaultId)])
|
|
1731
|
+
);
|
|
1732
|
+
return new Map(entries);
|
|
1733
|
+
}
|
|
1734
|
+
async get(ref) {
|
|
1735
|
+
const auth = await this.requireAuth();
|
|
1736
|
+
try {
|
|
1737
|
+
return await readLocalSecret(this.storeRoot, ref, auth, this.vaultId);
|
|
1738
|
+
} catch {
|
|
1739
|
+
return void 0;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
async set(ref, value) {
|
|
1743
|
+
const auth = await this.requireAuth();
|
|
1744
|
+
await writeLocalSecret(this.storeRoot, ref, value, auth, this.vaultId);
|
|
1745
|
+
await appendAuditEvent(
|
|
1746
|
+
{
|
|
1747
|
+
action: "write",
|
|
1748
|
+
vault: this.vaultId,
|
|
1749
|
+
ref,
|
|
1750
|
+
caller: "cli"
|
|
1751
|
+
},
|
|
1752
|
+
this.processEnv
|
|
1753
|
+
);
|
|
1754
|
+
}
|
|
1755
|
+
async delete(ref) {
|
|
1756
|
+
const auth = await this.requireAuth();
|
|
1757
|
+
await deleteLocalSecret(this.storeRoot, ref, auth, this.vaultId);
|
|
1758
|
+
await appendAuditEvent(
|
|
1759
|
+
{
|
|
1760
|
+
action: "delete",
|
|
1761
|
+
vault: this.vaultId,
|
|
1762
|
+
ref,
|
|
1763
|
+
caller: "cli"
|
|
1764
|
+
},
|
|
1765
|
+
this.processEnv
|
|
1766
|
+
);
|
|
1767
|
+
}
|
|
1768
|
+
async list() {
|
|
1769
|
+
const auth = this.authConfig ?? await resolveVaultAccessKey(this.storeRoot, this.definition, this.vaultId, this.processEnv);
|
|
1770
|
+
if (!auth) {
|
|
1771
|
+
throw new CnosAuthenticationError(
|
|
1772
|
+
`Cannot authenticate to vault "${this.vaultId}". Set the configured passphrase env var or run cnos vault auth ${this.vaultId}.`
|
|
1773
|
+
);
|
|
1774
|
+
}
|
|
1775
|
+
this.authConfig = auth;
|
|
1776
|
+
return listLocalSecrets(this.storeRoot, auth, this.vaultId);
|
|
1777
|
+
}
|
|
1778
|
+
};
|
|
1779
|
+
|
|
1780
|
+
// ../core/src/secrets/providers/registry.ts
|
|
1781
|
+
function createSecretVaultProvider(vaultId, definition, processEnv) {
|
|
1782
|
+
if (definition.provider === "local") {
|
|
1783
|
+
return new LocalSecretVaultProvider(vaultId, definition, processEnv);
|
|
1784
|
+
}
|
|
1785
|
+
if (definition.provider === "github-secrets") {
|
|
1786
|
+
return new GithubSecretsVaultProvider(vaultId, definition, processEnv);
|
|
1787
|
+
}
|
|
1788
|
+
throw new CnosManifestError(`Unsupported vault provider: ${definition.provider}`);
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
// ../core/src/secrets/prompt.ts
|
|
1792
|
+
var import_node_readline = __toESM(require("readline"), 1);
|
|
1793
|
+
var import_node_stream = require("stream");
|
|
1794
|
+
async function promptHidden(message) {
|
|
1795
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1796
|
+
return void 0;
|
|
1797
|
+
}
|
|
1798
|
+
const mutableStdout = new WritableMask();
|
|
1799
|
+
const rl = import_node_readline.default.createInterface({
|
|
1800
|
+
input: process.stdin,
|
|
1801
|
+
output: mutableStdout,
|
|
1802
|
+
terminal: true
|
|
1803
|
+
});
|
|
1804
|
+
try {
|
|
1805
|
+
mutableStdout.muted = true;
|
|
1806
|
+
const value = await new Promise((resolve) => {
|
|
1807
|
+
rl.question(message, resolve);
|
|
1808
|
+
});
|
|
1809
|
+
process.stdout.write("\n");
|
|
1810
|
+
return value;
|
|
1811
|
+
} finally {
|
|
1812
|
+
rl.close();
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
var WritableMask = class extends import_node_stream.Writable {
|
|
1816
|
+
muted = false;
|
|
1817
|
+
_write(chunk, _encoding, callback) {
|
|
1818
|
+
if (!this.muted) {
|
|
1819
|
+
process.stdout.write(chunk);
|
|
1820
|
+
}
|
|
1821
|
+
callback();
|
|
1822
|
+
}
|
|
1823
|
+
};
|
|
1824
|
+
|
|
1825
|
+
// ../core/src/secrets/resolveAuth.ts
|
|
1826
|
+
function toAuthError(vaultId, sources) {
|
|
1827
|
+
return new CnosAuthenticationError(
|
|
1828
|
+
`Cannot authenticate to vault "${vaultId}". Tried: ${sources.join(", ")}. Set ${getVaultPassphraseEnvVar(vaultId)} or run cnos vault auth ${vaultId}.`
|
|
1829
|
+
);
|
|
1830
|
+
}
|
|
1831
|
+
async function resolveVaultAuth(vaultId, definition, processEnv = process.env) {
|
|
1832
|
+
const sessionKey = await resolveVaultSessionKey(vaultId, processEnv);
|
|
1833
|
+
if (sessionKey) {
|
|
1834
|
+
return {
|
|
1835
|
+
derivedKey: sessionKey,
|
|
1836
|
+
method: "keychain",
|
|
1837
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
if (definition.provider === "github-secrets") {
|
|
1841
|
+
return {
|
|
1842
|
+
method: definition.auth?.method ?? "environment",
|
|
1843
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
const sources = definition.auth?.passphrase?.from ?? [getVaultPassphraseEnvVar(vaultId)];
|
|
1847
|
+
for (const source of sources) {
|
|
1848
|
+
if (source.startsWith("env:")) {
|
|
1849
|
+
const value = processEnv[source.slice(4)];
|
|
1850
|
+
if (value) {
|
|
1851
|
+
return {
|
|
1852
|
+
passphrase: value,
|
|
1853
|
+
method: "passphrase",
|
|
1854
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
if (source.startsWith("keychain:")) {
|
|
1859
|
+
const value = await readKeychain(source.slice("keychain:".length));
|
|
1860
|
+
if (value) {
|
|
1861
|
+
return {
|
|
1862
|
+
derivedKey: Buffer.from(value, "hex"),
|
|
1863
|
+
method: "keychain",
|
|
1864
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
if (source === "prompt") {
|
|
1869
|
+
const value = await promptHidden(`Enter passphrase for vault "${vaultId}": `);
|
|
1870
|
+
if (value) {
|
|
1871
|
+
return {
|
|
1872
|
+
passphrase: value,
|
|
1873
|
+
method: "passphrase",
|
|
1874
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1875
|
+
};
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
const fallback = resolveSecretPassphrase(vaultId, processEnv);
|
|
1880
|
+
if (fallback) {
|
|
1881
|
+
return {
|
|
1882
|
+
passphrase: fallback,
|
|
1883
|
+
method: "passphrase",
|
|
1884
|
+
...definition.auth?.config ? { config: definition.auth.config } : {}
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
throw toAuthError(vaultId, [getVaultSessionKeyEnvVar(vaultId), ...sources]);
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
// ../core/src/secrets/batchResolve.ts
|
|
1891
|
+
function collectSecretDescriptors(graph) {
|
|
1892
|
+
return Array.from(graph.entries.values()).filter((entry) => entry.namespace === "secret" && isSecretReference(entry.value)).map((entry) => ({
|
|
1893
|
+
logicalKey: entry.key,
|
|
1894
|
+
ref: entry.value
|
|
1895
|
+
}));
|
|
1896
|
+
}
|
|
1897
|
+
async function batchResolveSecrets(graph, manifest, processEnv = process.env) {
|
|
1898
|
+
const cache = new SecretCache();
|
|
1899
|
+
const descriptors = collectSecretDescriptors(graph);
|
|
1900
|
+
const grouped = descriptors.reduce((accumulator, descriptor) => {
|
|
1901
|
+
const vaultId = descriptor.ref.vault ?? "default";
|
|
1902
|
+
const bucket = accumulator.get(vaultId) ?? [];
|
|
1903
|
+
bucket.push(descriptor);
|
|
1904
|
+
accumulator.set(vaultId, bucket);
|
|
1905
|
+
return accumulator;
|
|
1906
|
+
}, /* @__PURE__ */ new Map());
|
|
1907
|
+
for (const [vaultId, refs] of grouped) {
|
|
1908
|
+
const definition = manifest.vaults[vaultId] ?? { provider: "local", auth: { passphrase: { from: [] } } };
|
|
1909
|
+
const provider = createSecretVaultProvider(vaultId, definition, processEnv);
|
|
1910
|
+
const auth = await resolveVaultAuth(vaultId, definition, processEnv);
|
|
1911
|
+
await provider.authenticate(auth);
|
|
1912
|
+
const resolved = await provider.batchGet(refs.map((entry) => entry.ref.ref));
|
|
1913
|
+
cache.load(vaultId, resolved);
|
|
1914
|
+
await appendAuditEvent(
|
|
1915
|
+
{
|
|
1916
|
+
action: "batch_read",
|
|
1917
|
+
vault: vaultId,
|
|
1918
|
+
refs: Array.from(resolved.keys()).sort((left, right) => left.localeCompare(right)),
|
|
1919
|
+
caller: "runtime",
|
|
1920
|
+
workspace: graph.workspace.workspaceId,
|
|
1921
|
+
profile: graph.profile
|
|
1922
|
+
},
|
|
1923
|
+
processEnv
|
|
1924
|
+
);
|
|
1925
|
+
}
|
|
1926
|
+
return cache;
|
|
1927
|
+
}
|
|
1928
|
+
function resolveSecretEntryValue(key, value, cache) {
|
|
1929
|
+
if (!key.startsWith("secret.") || !isSecretReference(value)) {
|
|
1930
|
+
return value;
|
|
1931
|
+
}
|
|
1932
|
+
const vaultId = value.vault ?? "default";
|
|
1933
|
+
return cache.get(vaultId, value.ref) ?? value;
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1100
1936
|
// ../core/src/runtime/projection.ts
|
|
1101
1937
|
function setNestedValue(target, pathSegments, value) {
|
|
1102
1938
|
const [head, ...tail] = pathSegments;
|
|
@@ -1147,55 +1983,6 @@ function requireValue(graph, key) {
|
|
|
1147
1983
|
return value;
|
|
1148
1984
|
}
|
|
1149
1985
|
|
|
1150
|
-
// ../core/src/utils/secretStore.ts
|
|
1151
|
-
var import_node_crypto = require("crypto");
|
|
1152
|
-
var import_promises6 = require("fs/promises");
|
|
1153
|
-
var import_node_path6 = __toESM(require("path"), 1);
|
|
1154
|
-
function isObject(value) {
|
|
1155
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1156
|
-
}
|
|
1157
|
-
function isSecretReference(value) {
|
|
1158
|
-
return isObject(value) && typeof value.provider === "string" && value.provider.trim().length > 0 && typeof value.ref === "string" && value.ref.trim().length > 0 && (value.vault === void 0 && true || typeof value.vault === "string" && value.vault.trim().length > 0) && Object.keys(value).every((key) => ["provider", "ref", "vault"].includes(key));
|
|
1159
|
-
}
|
|
1160
|
-
function resolveSecretStoreRoot(processEnv = process.env) {
|
|
1161
|
-
return import_node_path6.default.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets"));
|
|
1162
|
-
}
|
|
1163
|
-
function resolveSecretStoreFile(storeRoot, ref, vault = "default") {
|
|
1164
|
-
return import_node_path6.default.join(storeRoot, "vaults", vault, "store", ...ref.split("/")).concat(".json");
|
|
1165
|
-
}
|
|
1166
|
-
function deriveKey(passphrase, salt) {
|
|
1167
|
-
return (0, import_node_crypto.scryptSync)(passphrase, salt, 32);
|
|
1168
|
-
}
|
|
1169
|
-
function resolveSecretPassphrase(vault = "default", processEnv = process.env) {
|
|
1170
|
-
const vaultToken = vault.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
1171
|
-
return processEnv[`CNOS_SECRET_PASSPHRASE_${vaultToken}`] ?? processEnv.CNOS_SECRET_PASSPHRASE;
|
|
1172
|
-
}
|
|
1173
|
-
function decryptDocument(document, passphrase) {
|
|
1174
|
-
const salt = Buffer.from(document.salt, "base64");
|
|
1175
|
-
const iv = Buffer.from(document.iv, "base64");
|
|
1176
|
-
const tag = Buffer.from(document.tag, "base64");
|
|
1177
|
-
const ciphertext = Buffer.from(document.ciphertext, "base64");
|
|
1178
|
-
const key = deriveKey(passphrase, salt);
|
|
1179
|
-
const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", key, iv);
|
|
1180
|
-
decipher.setAuthTag(tag);
|
|
1181
|
-
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
1182
|
-
return plaintext.toString("utf8");
|
|
1183
|
-
}
|
|
1184
|
-
async function readLocalSecret(storeRoot, ref, passphrase, vault = "default") {
|
|
1185
|
-
if (!passphrase) {
|
|
1186
|
-
throw new CnosManifestError(
|
|
1187
|
-
`Missing CNOS secret passphrase for local secret ref "${ref}". Set CNOS_SECRET_PASSPHRASE or pass processEnv explicitly.`
|
|
1188
|
-
);
|
|
1189
|
-
}
|
|
1190
|
-
const filePath = resolveSecretStoreFile(storeRoot, ref, vault);
|
|
1191
|
-
const source = await (0, import_promises6.readFile)(filePath, "utf8");
|
|
1192
|
-
const document = JSON.parse(source);
|
|
1193
|
-
if (document.version !== 1 || document.algorithm !== "aes-256-gcm" || typeof document.salt !== "string" || typeof document.iv !== "string" || typeof document.tag !== "string" || typeof document.ciphertext !== "string") {
|
|
1194
|
-
throw new CnosManifestError("Invalid local secret document", filePath);
|
|
1195
|
-
}
|
|
1196
|
-
return decryptDocument(document, passphrase);
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
1986
|
// ../core/src/runtime/toEnv.ts
|
|
1200
1987
|
function normalizeEnvValue(value) {
|
|
1201
1988
|
if (value === void 0 || value === null) {
|
|
@@ -1277,28 +2064,42 @@ function toPublicEnv(graph, manifest, options = {}) {
|
|
|
1277
2064
|
}
|
|
1278
2065
|
|
|
1279
2066
|
// ../core/src/orchestrator/runtime.ts
|
|
1280
|
-
function createRuntime(manifest, graph, plugins = []) {
|
|
2067
|
+
function createRuntime(manifest, graph, plugins = [], secretCache) {
|
|
2068
|
+
function readLogicalKey(key) {
|
|
2069
|
+
const entry = graph.entries.get(key);
|
|
2070
|
+
if (!entry) {
|
|
2071
|
+
return void 0;
|
|
2072
|
+
}
|
|
2073
|
+
if (!secretCache) {
|
|
2074
|
+
return entry.value;
|
|
2075
|
+
}
|
|
2076
|
+
return resolveSecretEntryValue(key, entry.value, secretCache);
|
|
2077
|
+
}
|
|
1281
2078
|
return {
|
|
1282
2079
|
manifest,
|
|
1283
2080
|
plugins,
|
|
1284
2081
|
graph,
|
|
1285
2082
|
read(key) {
|
|
1286
|
-
return
|
|
2083
|
+
return readLogicalKey(key);
|
|
1287
2084
|
},
|
|
1288
2085
|
require(key) {
|
|
1289
|
-
|
|
2086
|
+
const value = readLogicalKey(key);
|
|
2087
|
+
if (value === void 0) {
|
|
2088
|
+
return requireValue(graph, key);
|
|
2089
|
+
}
|
|
2090
|
+
return value;
|
|
1290
2091
|
},
|
|
1291
2092
|
readOr(key, fallback) {
|
|
1292
2093
|
return readOrValue(graph, key, fallback);
|
|
1293
2094
|
},
|
|
1294
|
-
value(
|
|
1295
|
-
return
|
|
2095
|
+
value(path12) {
|
|
2096
|
+
return readLogicalKey(toLogicalKey("value", path12));
|
|
1296
2097
|
},
|
|
1297
|
-
secret(
|
|
1298
|
-
return
|
|
2098
|
+
secret(path12) {
|
|
2099
|
+
return readLogicalKey(toLogicalKey("secret", path12));
|
|
1299
2100
|
},
|
|
1300
|
-
meta(
|
|
1301
|
-
return
|
|
2101
|
+
meta(path12) {
|
|
2102
|
+
return readLogicalKey(toLogicalKey("meta", path12));
|
|
1302
2103
|
},
|
|
1303
2104
|
inspect(key) {
|
|
1304
2105
|
return inspectValue(graph, key);
|
|
@@ -1456,19 +2257,21 @@ async function createCnos(options = {}) {
|
|
|
1456
2257
|
});
|
|
1457
2258
|
const schemaApplied = applySchemaRules(graph, loadedManifest.manifest.schema);
|
|
1458
2259
|
const promotedGraph = promoteToPublic(schemaApplied.graph, loadedManifest.manifest);
|
|
2260
|
+
const secretCache = options.secretResolution === "lazy" ? void 0 : await batchResolveSecrets(promotedGraph, loadedManifest.manifest, options.processEnv);
|
|
1459
2261
|
return createRuntime(
|
|
1460
2262
|
loadedManifest.manifest,
|
|
1461
2263
|
appendMetaEntries({
|
|
1462
2264
|
...promotedGraph,
|
|
1463
2265
|
profileSource: activeProfile.source
|
|
1464
2266
|
}, options.cnosVersion),
|
|
1465
|
-
plugins
|
|
2267
|
+
plugins,
|
|
2268
|
+
secretCache
|
|
1466
2269
|
);
|
|
1467
2270
|
}
|
|
1468
2271
|
|
|
1469
2272
|
// ../core/src/runtime/dump.ts
|
|
1470
|
-
var
|
|
1471
|
-
var
|
|
2273
|
+
var import_promises9 = require("fs/promises");
|
|
2274
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
1472
2275
|
|
|
1473
2276
|
// ../core/src/utils/envNaming.ts
|
|
1474
2277
|
function normalizeMappingConfig(config = {}) {
|
|
@@ -1477,8 +2280,8 @@ function normalizeMappingConfig(config = {}) {
|
|
|
1477
2280
|
explicit: config.explicit ?? {}
|
|
1478
2281
|
};
|
|
1479
2282
|
}
|
|
1480
|
-
function fromScreamingSnake(
|
|
1481
|
-
return
|
|
2283
|
+
function fromScreamingSnake(path12) {
|
|
2284
|
+
return path12.split("_").map((segment) => segment.trim().toLowerCase()).filter(Boolean).join(".");
|
|
1482
2285
|
}
|
|
1483
2286
|
function envVarToLogicalKey(envVar, config = {}) {
|
|
1484
2287
|
const normalized = normalizeMappingConfig(config);
|
|
@@ -1505,7 +2308,7 @@ function envVarToLogicalKey(envVar, config = {}) {
|
|
|
1505
2308
|
// package.json
|
|
1506
2309
|
var package_default = {
|
|
1507
2310
|
name: "@kitsy/cnos",
|
|
1508
|
-
version: "1.
|
|
2311
|
+
version: "1.3.0",
|
|
1509
2312
|
description: "Batteries-included CNOS runtime package wired with the official plugins.",
|
|
1510
2313
|
type: "module",
|
|
1511
2314
|
main: "./dist/index.cjs",
|
|
@@ -1517,6 +2320,16 @@ var package_default = {
|
|
|
1517
2320
|
import: "./dist/index.js",
|
|
1518
2321
|
require: "./dist/index.cjs"
|
|
1519
2322
|
},
|
|
2323
|
+
"./configure": {
|
|
2324
|
+
types: "./dist/configure/index.d.ts",
|
|
2325
|
+
import: "./dist/configure/index.js",
|
|
2326
|
+
require: "./dist/configure/index.cjs"
|
|
2327
|
+
},
|
|
2328
|
+
"./create": {
|
|
2329
|
+
types: "./dist/configure/index.d.ts",
|
|
2330
|
+
import: "./dist/configure/index.js",
|
|
2331
|
+
require: "./dist/configure/index.cjs"
|
|
2332
|
+
},
|
|
1520
2333
|
"./internal": {
|
|
1521
2334
|
types: "./dist/internal.d.ts",
|
|
1522
2335
|
import: "./dist/internal.js",
|
|
@@ -1694,8 +2507,8 @@ function createCliArgsPlugin() {
|
|
|
1694
2507
|
}
|
|
1695
2508
|
|
|
1696
2509
|
// ../../plugins/dotenv/src/index.ts
|
|
1697
|
-
var
|
|
1698
|
-
var
|
|
2510
|
+
var import_promises10 = require("fs/promises");
|
|
2511
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
1699
2512
|
var DOTENV_PLUGIN_ID = "@kitsy/cnos/plugins/dotenv";
|
|
1700
2513
|
function parseDoubleQuoted(value) {
|
|
1701
2514
|
return value.replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
@@ -1752,7 +2565,7 @@ function dotenvEntriesFromObject(values, mapping = {}, originFile, workspaceId =
|
|
|
1752
2565
|
}
|
|
1753
2566
|
async function readIfPresent(filePath) {
|
|
1754
2567
|
try {
|
|
1755
|
-
return await (0,
|
|
2568
|
+
return await (0, import_promises10.readFile)(filePath, "utf8");
|
|
1756
2569
|
} catch {
|
|
1757
2570
|
return void 0;
|
|
1758
2571
|
}
|
|
@@ -1771,7 +2584,7 @@ function createDotenvPlugin() {
|
|
|
1771
2584
|
workspace: workspaceRoot.workspaceId
|
|
1772
2585
|
});
|
|
1773
2586
|
for (const fileName of fileNames) {
|
|
1774
|
-
const absolutePath =
|
|
2587
|
+
const absolutePath = import_node_path10.default.join(envRoot, fileName);
|
|
1775
2588
|
const document = await readIfPresent(absolutePath);
|
|
1776
2589
|
if (!document) {
|
|
1777
2590
|
continue;
|
|
@@ -1780,7 +2593,7 @@ function createDotenvPlugin() {
|
|
|
1780
2593
|
...dotenvEntriesFromObject(
|
|
1781
2594
|
parseDotenv(document),
|
|
1782
2595
|
config.envMapping,
|
|
1783
|
-
toPortablePath(
|
|
2596
|
+
toPortablePath(import_node_path10.default.relative(import_node_path10.default.dirname(context.manifestRoot), absolutePath)),
|
|
1784
2597
|
workspaceRoot.workspaceId
|
|
1785
2598
|
)
|
|
1786
2599
|
);
|
|
@@ -1818,32 +2631,32 @@ function createPublicEnvExportPlugin() {
|
|
|
1818
2631
|
}
|
|
1819
2632
|
|
|
1820
2633
|
// ../../plugins/filesystem/src/filesystemSecretsReader.ts
|
|
1821
|
-
var
|
|
2634
|
+
var import_promises12 = require("fs/promises");
|
|
1822
2635
|
|
|
1823
2636
|
// ../../plugins/filesystem/src/helpers.ts
|
|
1824
|
-
var
|
|
1825
|
-
var
|
|
2637
|
+
var import_promises11 = require("fs/promises");
|
|
2638
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
1826
2639
|
var YAML_EXTENSIONS = /* @__PURE__ */ new Set([".yml", ".yaml"]);
|
|
1827
2640
|
var FILESYSTEM_PLUGIN_ID = "@kitsy/cnos/plugins/filesystem";
|
|
1828
2641
|
async function existsDirectory(targetPath) {
|
|
1829
2642
|
try {
|
|
1830
|
-
const
|
|
1831
|
-
void
|
|
2643
|
+
const stat2 = await (0, import_promises11.readdir)(targetPath);
|
|
2644
|
+
void stat2;
|
|
1832
2645
|
return true;
|
|
1833
2646
|
} catch {
|
|
1834
2647
|
return false;
|
|
1835
2648
|
}
|
|
1836
2649
|
}
|
|
1837
2650
|
async function collectYamlFiles(root) {
|
|
1838
|
-
const entries = await (0,
|
|
2651
|
+
const entries = await (0, import_promises11.readdir)(root, { withFileTypes: true });
|
|
1839
2652
|
const results = [];
|
|
1840
2653
|
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
1841
|
-
const absolutePath =
|
|
2654
|
+
const absolutePath = import_node_path11.default.join(root, entry.name);
|
|
1842
2655
|
if (entry.isDirectory()) {
|
|
1843
2656
|
results.push(...await collectYamlFiles(absolutePath));
|
|
1844
2657
|
continue;
|
|
1845
2658
|
}
|
|
1846
|
-
if (entry.isFile() && YAML_EXTENSIONS.has(
|
|
2659
|
+
if (entry.isFile() && YAML_EXTENSIONS.has(import_node_path11.default.extname(entry.name).toLowerCase())) {
|
|
1847
2660
|
results.push(absolutePath);
|
|
1848
2661
|
}
|
|
1849
2662
|
}
|
|
@@ -1851,16 +2664,16 @@ async function collectYamlFiles(root) {
|
|
|
1851
2664
|
}
|
|
1852
2665
|
async function collectFilesystemLayerFiles(manifestRoot, workspaceRoots, sourceRoot, activeLayers) {
|
|
1853
2666
|
const files = [];
|
|
1854
|
-
const repoRoot =
|
|
2667
|
+
const repoRoot = import_node_path11.default.dirname(manifestRoot);
|
|
1855
2668
|
for (const workspaceRoot of workspaceRoots) {
|
|
1856
|
-
const resolvedRoot =
|
|
2669
|
+
const resolvedRoot = import_node_path11.default.resolve(workspaceRoot.path, sourceRoot);
|
|
1857
2670
|
for (const layer of activeLayers) {
|
|
1858
|
-
const layerRoot =
|
|
2671
|
+
const layerRoot = import_node_path11.default.join(resolvedRoot, layer);
|
|
1859
2672
|
if (!await existsDirectory(layerRoot)) {
|
|
1860
2673
|
continue;
|
|
1861
2674
|
}
|
|
1862
2675
|
for (const absolutePath of await collectYamlFiles(layerRoot)) {
|
|
1863
|
-
const relativePath =
|
|
2676
|
+
const relativePath = import_node_path11.default.relative(repoRoot, absolutePath);
|
|
1864
2677
|
files.push({
|
|
1865
2678
|
absolutePath,
|
|
1866
2679
|
relativePath: toPortablePath(relativePath.startsWith("..") ? absolutePath : relativePath),
|
|
@@ -1910,31 +2723,6 @@ function yamlObjectToEntries(document, filePath, namespace, sourceId, workspaceI
|
|
|
1910
2723
|
}
|
|
1911
2724
|
}));
|
|
1912
2725
|
}
|
|
1913
|
-
async function resolveSecretValue(value, processEnv) {
|
|
1914
|
-
if (!isSecretReference(value)) {
|
|
1915
|
-
return value;
|
|
1916
|
-
}
|
|
1917
|
-
if (value.provider === "local") {
|
|
1918
|
-
const passphrase = resolveSecretPassphrase(value.vault, processEnv);
|
|
1919
|
-
if (!passphrase) {
|
|
1920
|
-
return value;
|
|
1921
|
-
}
|
|
1922
|
-
return readLocalSecret(
|
|
1923
|
-
resolveSecretStoreRoot(processEnv),
|
|
1924
|
-
value.ref,
|
|
1925
|
-
passphrase,
|
|
1926
|
-
value.vault
|
|
1927
|
-
);
|
|
1928
|
-
}
|
|
1929
|
-
if (value.provider === "env" || value.provider === "github-secrets") {
|
|
1930
|
-
const resolved = processEnv?.[value.ref];
|
|
1931
|
-
if (resolved === void 0) {
|
|
1932
|
-
return value;
|
|
1933
|
-
}
|
|
1934
|
-
return resolved;
|
|
1935
|
-
}
|
|
1936
|
-
return value;
|
|
1937
|
-
}
|
|
1938
2726
|
function toSecretReferenceMetadata(value) {
|
|
1939
2727
|
if (!isSecretReference(value)) {
|
|
1940
2728
|
return void 0;
|
|
@@ -1962,14 +2750,12 @@ function createFilesystemSecretsPlugin() {
|
|
|
1962
2750
|
);
|
|
1963
2751
|
const entries = [];
|
|
1964
2752
|
for (const file of files) {
|
|
1965
|
-
const document = await (0,
|
|
2753
|
+
const document = await (0, import_promises12.readFile)(file.absolutePath, "utf8");
|
|
1966
2754
|
const fileEntries = filesystemSecretsReader(file.relativePath, document, file.workspaceId);
|
|
1967
2755
|
for (const entry of fileEntries) {
|
|
1968
2756
|
const metadata = toSecretReferenceMetadata(entry.value);
|
|
1969
|
-
const resolvedValue = await resolveSecretValue(entry.value, context.processEnv);
|
|
1970
2757
|
entries.push({
|
|
1971
2758
|
...entry,
|
|
1972
|
-
value: resolvedValue,
|
|
1973
2759
|
...metadata ? { metadata } : {}
|
|
1974
2760
|
});
|
|
1975
2761
|
}
|
|
@@ -1980,7 +2766,7 @@ function createFilesystemSecretsPlugin() {
|
|
|
1980
2766
|
}
|
|
1981
2767
|
|
|
1982
2768
|
// ../../plugins/filesystem/src/filesystemValuesReader.ts
|
|
1983
|
-
var
|
|
2769
|
+
var import_promises13 = require("fs/promises");
|
|
1984
2770
|
function filesystemValuesReader(filePath, document, workspaceId = "default") {
|
|
1985
2771
|
return yamlObjectToEntries(document, filePath, "value", "filesystem-values", workspaceId);
|
|
1986
2772
|
}
|
|
@@ -1998,7 +2784,7 @@ function createFilesystemValuesPlugin() {
|
|
|
1998
2784
|
);
|
|
1999
2785
|
const entries = [];
|
|
2000
2786
|
for (const file of files) {
|
|
2001
|
-
const document = await (0,
|
|
2787
|
+
const document = await (0, import_promises13.readFile)(file.absolutePath, "utf8");
|
|
2002
2788
|
entries.push(...filesystemValuesReader(file.relativePath, document, file.workspaceId));
|
|
2003
2789
|
}
|
|
2004
2790
|
return entries;
|
|
@@ -2065,12 +2851,14 @@ function defaultPlugins() {
|
|
|
2065
2851
|
// src/runtime/state.ts
|
|
2066
2852
|
var singletonRuntime;
|
|
2067
2853
|
var singletonReady;
|
|
2854
|
+
var bootstrappedSecretHydrationRequired = false;
|
|
2068
2855
|
function getSingletonRuntime() {
|
|
2069
2856
|
return singletonRuntime;
|
|
2070
2857
|
}
|
|
2071
2858
|
function setSingletonRuntime(runtime) {
|
|
2072
2859
|
singletonRuntime = runtime;
|
|
2073
2860
|
singletonReady = Promise.resolve(runtime);
|
|
2861
|
+
bootstrappedSecretHydrationRequired = false;
|
|
2074
2862
|
return runtime;
|
|
2075
2863
|
}
|
|
2076
2864
|
function getSingletonReady() {
|
|
@@ -2080,11 +2868,18 @@ function setSingletonReady(promise) {
|
|
|
2080
2868
|
singletonReady = promise;
|
|
2081
2869
|
return promise;
|
|
2082
2870
|
}
|
|
2871
|
+
function getBootstrappedSecretHydrationRequired() {
|
|
2872
|
+
return bootstrappedSecretHydrationRequired;
|
|
2873
|
+
}
|
|
2874
|
+
function setBootstrappedSecretHydrationRequired(value) {
|
|
2875
|
+
bootstrappedSecretHydrationRequired = value;
|
|
2876
|
+
}
|
|
2083
2877
|
|
|
2084
2878
|
// src/createCnos.ts
|
|
2085
2879
|
async function createCnos2(options = {}) {
|
|
2086
2880
|
const runtime = await createCnos({
|
|
2087
2881
|
...options,
|
|
2882
|
+
processEnv: options.processEnv ?? process.env,
|
|
2088
2883
|
cnosVersion: package_default.version,
|
|
2089
2884
|
plugins: [...defaultPlugins(), ...options.plugins ?? []]
|
|
2090
2885
|
});
|
|
@@ -2093,7 +2888,10 @@ async function createCnos2(options = {}) {
|
|
|
2093
2888
|
}
|
|
2094
2889
|
|
|
2095
2890
|
// src/runtime/bootstrap.ts
|
|
2891
|
+
var import_node_crypto2 = require("crypto");
|
|
2096
2892
|
var CNOS_GRAPH_ENV_VAR = "__CNOS_GRAPH__";
|
|
2893
|
+
var CNOS_SECRET_PAYLOAD_ENV_VAR = "__CNOS_SECRET_PAYLOAD__";
|
|
2894
|
+
var CNOS_SESSION_KEY_ENV_VAR = "__CNOS_SESSION_KEY__";
|
|
2097
2895
|
function deserializeRuntimeGraph(source) {
|
|
2098
2896
|
const payload = JSON.parse(source);
|
|
2099
2897
|
if (!payload || !Array.isArray(payload.entries) || typeof payload.profile !== "string" || typeof payload.resolvedAt !== "string" || !payload.profileSource || !payload.workspace || typeof payload.workspace.workspaceId !== "string" || !Array.isArray(payload.workspace.workspaceChain) || !Array.isArray(payload.workspace.workspaceRoots)) {
|
|
@@ -2118,12 +2916,41 @@ function deserializeRuntimeGraph(source) {
|
|
|
2118
2916
|
workspace: payload.workspace
|
|
2119
2917
|
};
|
|
2120
2918
|
}
|
|
2919
|
+
function decryptSecretPayload(serialized, sessionKey) {
|
|
2920
|
+
const payload = JSON.parse(serialized);
|
|
2921
|
+
if (!payload || typeof payload.iv !== "string" || typeof payload.tag !== "string" || typeof payload.ciphertext !== "string") {
|
|
2922
|
+
throw new Error("Invalid CNOS secret payload");
|
|
2923
|
+
}
|
|
2924
|
+
const key = Buffer.from(sessionKey, "hex");
|
|
2925
|
+
const iv = Buffer.from(payload.iv, "base64");
|
|
2926
|
+
const tag = Buffer.from(payload.tag, "base64");
|
|
2927
|
+
const ciphertext = Buffer.from(payload.ciphertext, "base64");
|
|
2928
|
+
const decipher = (0, import_node_crypto2.createDecipheriv)("aes-256-gcm", key, iv);
|
|
2929
|
+
decipher.setAuthTag(tag);
|
|
2930
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
|
|
2931
|
+
return JSON.parse(plaintext);
|
|
2932
|
+
}
|
|
2121
2933
|
function readRuntimeGraphFromEnv(processEnv = process.env) {
|
|
2122
2934
|
const serialized = processEnv[CNOS_GRAPH_ENV_VAR];
|
|
2123
2935
|
if (!serialized) {
|
|
2124
2936
|
return void 0;
|
|
2125
2937
|
}
|
|
2126
|
-
|
|
2938
|
+
const graph = deserializeRuntimeGraph(serialized);
|
|
2939
|
+
const secretPayload = processEnv[CNOS_SECRET_PAYLOAD_ENV_VAR];
|
|
2940
|
+
const sessionKey = processEnv[CNOS_SESSION_KEY_ENV_VAR];
|
|
2941
|
+
if (secretPayload && sessionKey) {
|
|
2942
|
+
const decrypted = decryptSecretPayload(secretPayload, sessionKey);
|
|
2943
|
+
for (const [key, value] of Object.entries(decrypted)) {
|
|
2944
|
+
const entry = graph.entries.get(key);
|
|
2945
|
+
if (entry) {
|
|
2946
|
+
entry.value = value;
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
return graph;
|
|
2951
|
+
}
|
|
2952
|
+
function graphRequiresSecretHydration(graph) {
|
|
2953
|
+
return Array.from(graph.entries.values()).some((entry) => entry.namespace === "secret" && isSecretReference(entry.value));
|
|
2127
2954
|
}
|
|
2128
2955
|
|
|
2129
2956
|
// src/runtime/index.ts
|
|
@@ -2206,14 +3033,14 @@ function attachBootstrappedGraph(graph) {
|
|
|
2206
3033
|
readOr(key, fallback) {
|
|
2207
3034
|
return readOrValue(graph, key, fallback);
|
|
2208
3035
|
},
|
|
2209
|
-
value(
|
|
2210
|
-
return readValue(graph, toLogicalKey("value",
|
|
3036
|
+
value(path12) {
|
|
3037
|
+
return readValue(graph, toLogicalKey("value", path12));
|
|
2211
3038
|
},
|
|
2212
|
-
secret(
|
|
2213
|
-
return readValue(graph, toLogicalKey("secret",
|
|
3039
|
+
secret(path12) {
|
|
3040
|
+
return readValue(graph, toLogicalKey("secret", path12));
|
|
2214
3041
|
},
|
|
2215
|
-
meta(
|
|
2216
|
-
return readValue(graph, toLogicalKey("meta",
|
|
3042
|
+
meta(path12) {
|
|
3043
|
+
return readValue(graph, toLogicalKey("meta", path12));
|
|
2217
3044
|
},
|
|
2218
3045
|
inspect(key) {
|
|
2219
3046
|
return inspectValue(graph, key);
|
|
@@ -2232,6 +3059,7 @@ function attachBootstrappedGraph(graph) {
|
|
|
2232
3059
|
}
|
|
2233
3060
|
};
|
|
2234
3061
|
setSingletonRuntime(runtime);
|
|
3062
|
+
setBootstrappedSecretHydrationRequired(graphRequiresSecretHydration(graph));
|
|
2235
3063
|
}
|
|
2236
3064
|
function bootstrapFromProcessEnv() {
|
|
2237
3065
|
if (typeof process === "undefined") {
|
|
@@ -2258,21 +3086,21 @@ var cnos = Object.assign(
|
|
|
2258
3086
|
readOr(key, fallback) {
|
|
2259
3087
|
return readOrValue(getRuntimeOrThrow().graph, key, fallback);
|
|
2260
3088
|
},
|
|
2261
|
-
value(
|
|
2262
|
-
return readValue(getRuntimeOrThrow().graph, toLogicalKey("value",
|
|
3089
|
+
value(path12) {
|
|
3090
|
+
return readValue(getRuntimeOrThrow().graph, toLogicalKey("value", path12));
|
|
2263
3091
|
},
|
|
2264
|
-
secret(
|
|
2265
|
-
return readValue(getRuntimeOrThrow().graph, toLogicalKey("secret",
|
|
3092
|
+
secret(path12) {
|
|
3093
|
+
return readValue(getRuntimeOrThrow().graph, toLogicalKey("secret", path12));
|
|
2266
3094
|
},
|
|
2267
|
-
meta(
|
|
2268
|
-
return readValue(getRuntimeOrThrow().graph, toLogicalKey("meta",
|
|
3095
|
+
meta(path12) {
|
|
3096
|
+
return readValue(getRuntimeOrThrow().graph, toLogicalKey("meta", path12));
|
|
2269
3097
|
},
|
|
2270
3098
|
async ready() {
|
|
2271
|
-
if (getSingletonRuntime()) {
|
|
3099
|
+
if (getSingletonRuntime() && !getBootstrappedSecretHydrationRequired()) {
|
|
2272
3100
|
return;
|
|
2273
3101
|
}
|
|
2274
3102
|
const existing = getSingletonReady();
|
|
2275
|
-
if (existing) {
|
|
3103
|
+
if (existing && !getBootstrappedSecretHydrationRequired()) {
|
|
2276
3104
|
await existing;
|
|
2277
3105
|
return;
|
|
2278
3106
|
}
|