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
135 lines (134 loc) • 6.54 kB
JavaScript
// 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/.
import fs from "node:fs";
import path from "node:path";
import * as p from "../../cli/clack.js";
import { decideDangerousKeyInstall } from "../../core/activation-policy.js";
import { ConfigError } from "../../core/errors.js";
import { warn } from "../../core/warn.js";
import { sanitizeString } from "../../sources/providers/provider-utils.js";
import { scanEnvKeyNames } from "../env/env.js";
import { isDangerousEnvKey } from "../lint/env-key-rules.js";
let scannerOverride;
/** TEST-ONLY. Inject a scanner fault at the audit boundary; undefined restores. */
export function _setDangerousKeyScannerForTests(scanner) {
scannerOverride = scanner;
}
function collectEnvFilePathsRecursive(rootDir) {
const results = [];
const walk = (dir) => {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
walk(full);
continue;
}
if (!entry.isFile())
continue;
if (entry.name !== ".env" && !entry.name.endsWith(".env"))
continue;
results.push(full);
}
};
walk(rootDir);
return results;
}
function collectDangerousKeyFindings(installedStashRoot, checkEnvForDangerousKeys) {
const allFindings = [];
if (!fs.statSync(installedStashRoot).isDirectory()) {
throw new Error(`Expected ${installedStashRoot} to be a directory.`);
}
const dir = path.join(installedStashRoot, "env");
try {
if (!fs.statSync(dir).isDirectory()) {
throw new Error(`Expected ${dir} to be a directory.`);
}
}
catch (error) {
if (error.code === "ENOENT")
return allFindings;
throw error;
}
for (const envPath of collectEnvFilePathsRecursive(dir)) {
const relToEnvDir = path.relative(dir, envPath);
const segments = relToEnvDir.split(path.sep);
const lastSegment = segments[segments.length - 1] ?? "";
const lastSegmentBase = lastSegment.endsWith(".env") ? lastSegment.slice(0, -".env".length) : lastSegment;
const refSegments = [...segments.slice(0, -1), lastSegmentBase === "" ? "default" : lastSegmentBase];
const envRef = `env/${refSegments.join("/")}`;
const relPath = path.join("env", relToEnvDir);
for (const finding of checkEnvForDangerousKeys(envPath, relPath, envRef)) {
const keyMatch = finding.detail.match(/Env key `([^`]+)`/);
allFindings.push({ envRef, keyName: keyMatch?.[1] ?? finding.file, relPath });
}
}
return allFindings;
}
function scanStashForDangerousKeys(installedStashRoot) {
const scanner = scannerOverride ??
((envPath, relPath) => scanEnvKeyNames(fs.readFileSync(envPath, "utf8"))
.filter(isDangerousEnvKey)
.map((key) => ({ file: relPath, detail: `Env key \`${key}\`` })));
return collectDangerousKeyFindings(installedStashRoot, scanner);
}
export async function auditStashForDangerousKeys(opts) {
let allFindings;
try {
allFindings = scanStashForDangerousKeys(opts.stashRoot);
}
catch (error) {
const rollbackWarning = await opts.rollback?.();
throw new ConfigError(`Dangerous environment-key audit failed for "${opts.ref}"; refusing to ${opts.operation} unverified content. ${error instanceof Error ? error.message : String(error)}${rollbackWarning ? ` ${rollbackWarning}` : ""}`, "DANGEROUS_ENV_AUDIT_FAILED");
}
const stance = decideDangerousKeyInstall({
findingsPresent: allFindings.length > 0,
allowDangerousEnvKeys: opts.allowDangerousKeys,
});
if (stance === "allow")
return { blocked: false, findings: allFindings };
if (stance === "warn-allow") {
for (const finding of allFindings) {
warn(`[dangerous-env-key] ${finding.relPath}: key \`${finding.keyName}\` in ${finding.envRef} can hijack process execution via \`akm env run\`. Proceeding because --allow-dangerous-env-keys was set.`);
}
return { blocked: false, findings: allFindings };
}
const operationTitle = opts.operation === "install" ? "Install" : "Update";
const operationPast = opts.operation === "install" ? "installed" : "updated";
let error;
if (opts.isTTY) {
const groupedByEnv = new Map();
for (const finding of allFindings) {
const existing = groupedByEnv.get(finding.envRef) ?? [];
existing.push(finding.keyName);
groupedByEnv.set(finding.envRef, existing);
}
for (const [envRef, keys] of groupedByEnv) {
warn(`[warn] Env "${sanitizeString(envRef)}" in stash "${sanitizeString(opts.ref)}" contains potentially dangerous keys:`);
for (const key of keys)
warn(` - ${sanitizeString(key)}: can hijack process execution via \`akm env run\``);
}
const confirmed = await p.confirm({ message: `${operationTitle} anyway?`, initialValue: false });
if (!p.isCancel(confirmed) && confirmed === true)
return { blocked: false, findings: allFindings };
error = `${operationTitle} aborted: stash contains dangerous env keys. Remove the keys or re-run with --allow-dangerous-env-keys to bypass.`;
}
else {
const keyList = allFindings.map((finding) => ` - ${finding.keyName} (${finding.envRef})`).join("\n");
error = `${operationTitle} blocked: stash "${opts.ref}" contains dangerous env keys that can hijack process execution via \`akm env run\`:\n${keyList}\nRe-run with --allow-dangerous-env-keys to bypass this check after reviewing the env file.`;
}
const rollbackWarning = await opts.rollback?.();
if (opts.renderBlockedError !== false) {
console.error(JSON.stringify({
ok: false,
error,
code: "DANGEROUS_ENV_KEY",
...(rollbackWarning ? { rollbackWarning } : {}),
}, null, 2));
}
if (rollbackWarning) {
warn(`The blocked ${opts.operation} could not be fully rolled back after content was ${operationPast}: ${rollbackWarning}`);
}
return { blocked: true, exitCode: 1, code: "DANGEROUS_ENV_KEY", error, findings: allFindings };
}