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

107 lines (106 loc) • 4.69 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/. /** * The one redaction contract every frozen-workflow dispatch (unit or gate * judge) is held to before anything about its outcome reaches durable state. * A leaf module, so the judge path reuses it without an import cycle. */ import { collectSensitiveValues, isEnvPassthroughValueSafeToExpose, redactSensitiveText, redactSensitiveValue, } from "../../core/redaction.js"; import { lookupApiKeyFileValue, lookupApiKeySecretRefValue } from "../../integrations/agent/engine-resolution.js"; /** * Every exact value that must never reach the journal from one dispatch: the * resolved `env` bindings, the engine's (and SDK fallback's) credential from * env, file, or secret store, and unsafe passthrough values. Read at call time, * so collect no earlier than the dispatch being scrubbed. */ export function collectWorkflowDispatchSensitiveValues(dispatch, env) { const values = new Set([...Object.values(env ?? {}), ...(dispatch.sensitiveValues ?? [])]); const addCredential = (runner) => { if (!runner) return; if (runner.kind === "llm") { for (const name of runner.credential?.names ?? []) { const value = process.env[name]?.trim(); if (value) values.add(value); } // #905: file-backed credential — best-effort read, never throws, so a // broken apiKeyFile is reported by the real dispatch, not this collector. if (runner.apiKeyFile) { const value = lookupApiKeyFileValue(runner.apiKeyFile); if (value) values.add(value); } // #953: secret-store-backed credential — same best-effort rationale. if (runner.apiKeySecretRef) { const value = lookupApiKeySecretRefValue(runner.apiKeySecretRef); if (value) values.add(value); } return; } for (const name of runner.profile.envPassthrough ?? []) { const value = process.env[name]; if (!isEnvPassthroughValueSafeToExpose(name, value) && value) values.add(value); } if (runner.kind === "sdk") { for (const name of runner.fallbackCredential?.names ?? []) { const value = process.env[name]?.trim(); if (value) values.add(value); } if (runner.fallbackApiKeyFile) { const value = lookupApiKeyFileValue(runner.fallbackApiKeyFile); if (value) values.add(value); } if (runner.fallbackApiKeySecretRef) { const value = lookupApiKeySecretRefValue(runner.fallbackApiKeySecretRef); if (value) values.add(value); } } }; addCredential(dispatch.runner); return collectSensitiveValues(values); } /** * Scrub a dispatch outcome before anything about it is journaled. A * `failureReason` the scrub altered carried a secret, so it is downgraded. */ export function redactUnitOutcome(outcome, sensitiveValues) { const redacted = redactSensitiveValue(outcome, sensitiveValues); if (outcome.failureReason !== undefined && redacted.failureReason !== outcome.failureReason) { redacted.failureReason = "reported_failure"; } return redacted; } /** Scrub a value thrown out of a dispatch; re-wrapped only when the scrub changed its message. */ function redactDispatchError(err, sensitiveValues) { if (sensitiveValues.length === 0 || !(err instanceof Error)) return err; const redacted = redactSensitiveText(err.message, sensitiveValues); if (redacted === err.message) return err; const replacement = new Error(redacted); replacement.name = err.name; return replacement; } /** * Wrap a dispatcher so both its outcome and its thrown error are scrubbed with * the request's `sensitiveValues` (the gate judge; the unit path scrubs at its * own journal boundary after the structured-output parse). */ export function withDispatchRedaction(inner) { return async (request, feedback) => { const sensitiveValues = request.sensitiveValues ?? []; try { return redactUnitOutcome(await inner(request, feedback), sensitiveValues); } catch (err) { throw redactDispatchError(err, sensitiveValues); } }; }