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

112 lines (111 loc) • 4.32 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/. /** * Best-effort secret-shaped-param detection. Run params reach every unit * prompt in clear and are shown by `akm workflow status`; secrets belong in * `env:` bindings. This scans params for credential-looking keys or values and * returns warnings at run start (never blocking), and feeds the same values * into the dispatch redaction set. `akm task explain` reuses the heuristic to * redact input values — best-effort there too, never a guarantee. */ /** * Substrings that, when present in a param KEY (case-insensitive), suggest the * value is a credential. Deliberately specific — bare `auth` is excluded so a * key like `author` does not trip the heuristic. */ const SECRET_KEY_HINTS = [ "secret", "token", "password", "passwd", "apikey", "api_key", "api-key", "accesskey", "access_key", "privatekey", "private_key", "credential", "bearer", "auth_token", "authtoken", "client_secret", ]; /** Known credential value prefixes (OpenAI, GitHub, Slack, AWS, Google, PEM). */ const SECRET_VALUE_PREFIX = /^(sk-|rk-|ghp_|gho_|ghu_|ghs_|ghr_|github_pat_|xox[baprs]-|AKIA|ASIA|AIza|ya29\.|-----BEGIN)/; function keyLooksSecret(key) { const lower = key.toLowerCase(); return SECRET_KEY_HINTS.some((hint) => lower.includes(hint)); } /** * A string value is secret-shaped when it is long, whitespace-free, and either * carries a known credential prefix or has high character-class diversity / * base64-hex shape — an entropy proxy, not a real entropy computation. */ function valueLooksSecret(value) { const s = value.trim(); if (s.length < 20 || /\s/.test(s)) return false; if (SECRET_VALUE_PREFIX.test(s)) return true; const classes = [/[a-z]/, /[A-Z]/, /[0-9]/, /[^A-Za-z0-9]/].filter((re) => re.test(s)).length; if (s.length >= 24 && classes >= 3) return true; if (s.length >= 32 && /^[A-Za-z0-9+/=_-]+$/.test(s)) return true; return false; } const MOVE_TO_ENV = "Workflow params are copied verbatim into every native unit execution context and returned in `akm workflow run` and " + "`akm workflow status` output — move secrets to an env binding (`env:` ref), whose value native execution resolves " + "only at dispatch instead of storing it as a run param."; /** * Scan run params (recursively) for secret-suggesting key names or * credential-looking string values; one warning per path. Never throws. */ export function detectSecretShapedParams(params) { const warnings = []; const seen = new Set(); walkSecretShaped(params, (path, why) => { if (seen.has(path)) return; seen.add(path); warnings.push(`Run param "${path}" ${why}. ${MOVE_TO_ENV} (Heuristic warning; params are declared non-secret.)`); }); return warnings; } /** * The flagged string values, for the dispatch redaction set (values under 8 * characters are left out so `token_limit: "1000"` cannot blank out output). */ export function secretShapedParamValues(params) { const values = new Set(); walkSecretShaped(params, (_path, _why, value) => { if (typeof value === "string" && value.trim().length >= 8) values.add(value); }); return [...values]; } function walkSecretShaped(params, hit) { const walk = (value, path, key) => { if (key !== null && keyLooksSecret(key)) hit(path, "has a secret-suggesting name", value); if (typeof value === "string") { if (valueLooksSecret(value)) hit(path, "has a secret-shaped value (long, high-entropy string)", value); return; } if (Array.isArray(value)) { for (let i = 0; i < value.length; i++) walk(value[i], `${path}[${i}]`, null); return; } if (value && typeof value === "object") { for (const [k, v] of Object.entries(value)) { walk(v, path ? `${path}.${k}` : k, k); } } }; for (const [k, v] of Object.entries(params)) walk(v, k, k); }