UNPKG

akm-cli

Version:

akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cur

99 lines (98 loc) • 4.44 kB
// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. /** * Materialize a unit's frozen environment at dispatch. Env-file values are the * one deliberately live input to a frozen plan: freeze records each ref's * owner, key set and `${secret:…}` names; the values (and the secrets they * name) are read here, per dispatch, and nothing is returned until every read * has succeeded. */ import fs from "node:fs"; import path from "node:path"; import dotenv from "dotenv"; import { assetPathForName } from "../../core/asset/asset-placement.js"; import { compareCodePoints, isWithin } from "../../core/common.js"; import { NotFoundError, UsageError } from "../../core/errors.js"; export const SECRET_TOKEN_RE = /\$\{secret:([A-Za-z0-9_./-]+)\}/g; export function materializeFrozenWorkflowEnvironment(descriptors, options = {}) { const values = {}; const sensitive = new Set(); const audits = []; for (const descriptor of descriptors) { if (descriptor.kind === "literal") { values[descriptor.name] = descriptor.value; if (descriptor.value) sensitive.add(descriptor.value); continue; } if (descriptor.kind === "pass-through") { const value = options.readPassThrough ? options.readPassThrough(descriptor.name) : process.env[descriptor.name]; if (value !== undefined) { values[descriptor.name] = value; if (value) sensitive.add(value); } continue; } const source = options.readEnvFile ? options.readEnvFile(descriptor) : readEnvFile(descriptor.owner); const parsed = dotenv.parse(typeof source === "string" ? source : Buffer.from(source)); const keys = Object.keys(parsed).sort(compareCodePoints); if (!sameStrings(keys, descriptor.keys)) invalid(`environment ${descriptor.ref} key set changed after it was frozen`); const secretValues = new Map(); for (const name of descriptor.secretNames) { const raw = options.readSecret ? options.readSecret({ name, descriptor }) : readSecret(descriptor.owner, name); if (raw === undefined) { throw new NotFoundError(`Environment ${descriptor.ref} references missing secret ${name}; nothing was materialized.`, "FILE_NOT_FOUND"); } const value = typeof raw === "string" ? raw : Buffer.from(raw).toString("utf8"); secretValues.set(name, value); if (value) sensitive.add(value); } for (const [key, rawValue] of Object.entries(parsed)) { const resolved = rawValue.replace(SECRET_TOKEN_RE, (_token, name) => { const secret = secretValues.get(name); if (secret === undefined) invalid(`environment ${descriptor.ref} secret ${name} was not frozen`); return secret; }); values[key] = resolved; if (resolved) sensitive.add(resolved); } audits.push({ eventType: "env_access", ref: descriptor.ref, keys: [...descriptor.keys], secretNames: [...descriptor.secretNames], }); } return { values, sensitiveValues: [...sensitive], audits }; } function readEnvFile(owner) { if (!isWithin(owner.requestedPath, owner.requestedRoot)) invalid(`env file ${owner.relativePath} escapes its bundle`); return fs.readFileSync(owner.requestedPath); } function readSecret(owner, name) { const secretsRoot = path.join(owner.requestedRoot, "secrets"); const secretPath = assetPathForName("secret", secretsRoot, name); if (!isWithin(secretPath, secretsRoot)) invalid(`secret name ${name} escapes its bundle`); try { return fs.readFileSync(secretPath); } catch (cause) { if (cause.code === "ENOENT") return undefined; throw cause; } } function sameStrings(left, right) { return left.length === right.length && left.every((value, index) => value === right[index]); } function invalid(message) { throw new UsageError(`Invalid frozen workflow environment: ${message}.`, "WORKFLOW_SOURCE_INVALID"); }