UNPKG

@blundergoat/goat-flow

Version:

AI coding agent harness and local dashboard for Claude Code, OpenAI Codex, Google Antigravity, and GitHub Copilot - setup audits, guardrails, structured skills, deny hooks, and persistent learning loops.

172 lines 9.82 kB
const CAPABILITY_LABELS = { "shell-dangerous": "Dangerous shell commands", "shell-pipe-to-shell": "Pipe-to-shell commands", "secret-file-read": "Secret file-read paths", "secret-shell-read": "Secret shell-read commands", "hook-registration": "Pre-tool hook registration", "hook-self-test": "Deny hook self-test", "file-read-restrictions": "General file-read restrictions", "file-write-restrictions": "General file-write restrictions", "provider-native-enforcement": "Provider-native enforcement", }; function capability(id, status, sources, summary, evidence) { return { id, label: CAPABILITY_LABELS[id], status, sources, summary, evidence, }; } /** Initialize every status counter so dashboard readers never infer missing keys as zero silently. */ function emptySummary() { return { hard: 0, limited: 0, soft: 0, missing: 0, unknown: 0 }; } function summarize(capabilities) { const summary = emptySummary(); for (const enforcementCapability of capabilities) { summary[enforcementCapability.status]++; } return summary; } /** Treat settings and registered hooks as active deny mechanisms; a present script alone is not enough. */ function hasActiveMechanicalDeny(agentFacts) { if (agentFacts.hooks.denyIsConfigBased) return true; if (agentFacts.agent.denyMechanism && agentFacts.agent.denyMechanism.type !== "deny-script" && agentFacts.settings.hasDenyPatterns) { return true; } return agentFacts.hooks.denyIsRegistered; } function shellCapability(agentFacts, id, covered, coveredSummary, missingSummary) { const denyExists = agentFacts.hooks.denyExists || agentFacts.hooks.denyIsConfigBased; if (!denyExists) { return capability(id, "missing", ["not-observed"], missingSummary, []); } if (!covered) { return capability(id, "missing", ["local-hook"], missingSummary, [ "AgentFacts.hooks", ]); } if (hasActiveMechanicalDeny(agentFacts)) { return capability(id, "hard", ["local-hook"], coveredSummary, [ "AgentFacts.hooks", ]); } return capability(id, "limited", ["local-hook"], `${coveredSummary}; hook coverage exists but registration was not proved`, ["AgentFacts.hooks.denyIsRegistered"]); } /** Report file-tool secret protection separately because Bash commands bypass file-read denies. */ function secretFileReadCapability(agentFacts) { if (agentFacts.hooks.readDenyCoversSecrets) { return capability("secret-file-read", "hard", ["local-settings"], "Settings or Codex permission profile deny known secret-bearing file paths", ["AgentFacts.hooks.readDenyCoversSecrets"]); } if (agentFacts.agent.denyMechanism?.type === "deny-script") { return capability("secret-file-read", "limited", ["local-hook"], "Script-only deny can block shell reads, but no file-read deny layer is available", ["AgentProfile.denyMechanism"]); } return capability("secret-file-read", "missing", ["not-observed"], "No settings or permission-profile secret file-read deny coverage was observed", ["AgentFacts.hooks.readDenyCoversSecrets"]); } /** Report shell secret protection separately because settings-level read denies do not bind Bash. */ function secretShellReadCapability(agentFacts) { if (!agentFacts.hooks.bashDenyCoversSecrets) { return capability("secret-shell-read", "missing", ["local-hook"], "Bash deny hook does not prove direct literal secret shell-read blocking", ["AgentFacts.hooks.bashDenyCoversSecrets"]); } if (hasActiveMechanicalDeny(agentFacts)) { return capability("secret-shell-read", "hard", ["local-hook"], "Bash deny hook blocks direct literal secret shell-read commands", ["AgentFacts.hooks.bashDenyCoversSecrets"]); } return capability("secret-shell-read", "limited", ["local-hook"], "Bash deny hook covers secret shell reads, but hook registration was not proved", ["AgentFacts.hooks.denyIsRegistered"]); } /** Distinguish hook existence from registration so static files are not mistaken for active runtime wiring. */ function hookRegistrationCapability(agentFacts) { if (!agentFacts.hooks.denyExists && !agentFacts.hooks.denyIsConfigBased) { return capability("hook-registration", "missing", ["not-observed"], "No deny mechanism was observed", []); } if (agentFacts.hooks.denyIsConfigBased && !agentFacts.hooks.denyExists) { return capability("hook-registration", "soft", ["local-settings"], "Settings-based deny exists without a shell hook registration surface", ["AgentFacts.hooks.denyIsConfigBased"]); } const preToolEvent = agentFacts.agent.hookEvents?.preTool ?? "pre-tool"; if (agentFacts.hooks.denyIsRegistered) { return capability("hook-registration", "hard", ["local-hook"], `Deny hook is registered as ${preToolEvent}`, ["AgentFacts.hooks.denyIsRegistered"]); } return capability("hook-registration", "missing", ["local-hook"], `Deny hook exists but is not registered as ${preToolEvent}`, ["AgentFacts.hooks.denyIsRegistered"]); } function denyCheck(agentScope) { return agentScope?.checks.find((check) => check.id === "agent-guardrails"); } function hookSelfTestCapability(agentFacts, options) { if (!agentFacts.hooks.denyExists) { return capability("hook-self-test", "missing", ["not-observed"], "No deny hook exists to self-test", []); } const check = denyCheck(options.agentScope); if (!check || check.status === "skipped") { return capability("hook-self-test", "limited", ["local-hook"], "Deny hook self-test was not run in this aggregate audit context", ["agent-guardrails"]); } if (check.status === "fail") { return capability("hook-self-test", "missing", ["local-hook"], check.failure?.message ?? "Deny hook self-test or static deny check failed", ["agent-guardrails"]); } if (options.denyMechanismEvidenceLevel === "full" || options.denyMechanismEvidenceLevel === undefined) { return capability("hook-self-test", "hard", ["runtime-self-test"], "Deny hook self-test and runtime-shaped payload smoke passed in this audit run", ["agent-guardrails"]); } return capability("hook-self-test", "limited", ["local-hook"], `Deny hook static checks passed, but runtime self-test was skipped in ${options.denyMechanismEvidenceLevel} evidence mode`, ["agent-guardrails"]); } /** Keep provider-native breadth advisory because manifest capability does not prove runtime enforcement. */ function providerNativeCapability(agentFacts) { if (agentFacts.agent.denyMechanism === null) { return capability("provider-native-enforcement", "missing", ["manifest"], "Manifest records no project-local deny mechanism for this agent", ["AgentProfile.denyMechanism"]); } const mechanism = agentFacts.agent.denyMechanism.type; if (mechanism === "deny-script") { return capability("provider-native-enforcement", "limited", ["manifest"], "Manifest records script-only deny; provider-native breadth was not claimed", ["AgentProfile.denyMechanism"]); } if (mechanism === "both") { return capability("provider-native-enforcement", "limited", ["manifest"], "Manifest records settings plus script deny; provider-native breadth was not verified", ["AgentProfile.denyMechanism"]); } return capability("provider-native-enforcement", "soft", ["manifest"], "Manifest records settings-based deny; provider-native breadth was not verified", ["AgentProfile.denyMechanism"]); } function broadFilesystemCapability(id) { return capability(id, "unknown", ["not-observed"], "Not inferred from secret-path coverage, hook installation, or setup pass", []); } /** * Build the advisory enforcement matrix for one agent. * * @param agentFacts Extracted local facts for the audited agent. * @param options Evidence-mode switches from the current audit run. * @returns Non-gating enforcement capability report for audit and dashboard output. */ export function buildAgentEnforcementCapability(agentFacts, options = {}) { const capabilities = [ shellCapability(agentFacts, "shell-dangerous", agentFacts.hooks.denyBlocksRmRf && agentFacts.hooks.denyBlocksGitPush && agentFacts.hooks.denyBlocksChmod, "Deny mechanism blocks broad recursive deletion, git push, and chmod 777 patterns", "Deny mechanism does not prove coverage for broad recursive deletion, git push, and chmod 777"), shellCapability(agentFacts, "shell-pipe-to-shell", agentFacts.hooks.denyBlocksPipeToShell, "Deny mechanism blocks curl|bash and wget|sh style pipe-to-shell patterns", "Deny mechanism does not prove pipe-to-shell blocking"), secretFileReadCapability(agentFacts), secretShellReadCapability(agentFacts), hookRegistrationCapability(agentFacts), hookSelfTestCapability(agentFacts, options), broadFilesystemCapability("file-read-restrictions"), broadFilesystemCapability("file-write-restrictions"), providerNativeCapability(agentFacts), ]; return { agent: agentFacts.agent.id, name: agentFacts.agent.name, advisory: true, capabilities, summary: summarize(capabilities), }; } /** * Build the advisory enforcement matrix for every audited agent. * * @param agents Extracted local facts for all agents included in the audit. * @param options Evidence-mode switches from the current audit run. * @returns Non-gating enforcement reports in the same order as the input agents. */ export function buildEnforcementMatrix(agents, options = {}) { return agents.map((agent) => buildAgentEnforcementCapability(agent, options)); } //# sourceMappingURL=enforcement.js.map