avorelo 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +56 -0
- package/bin/avorelo +9 -0
- package/package.json +135 -0
- package/scripts/README.md +40 -0
- package/scripts/cco-dashboard.js +252 -0
- package/scripts/cco-status.js +430 -0
- package/scripts/lib/activation/account-state.js +37 -0
- package/scripts/lib/activation/activation-runner.js +546 -0
- package/scripts/lib/activation/activation-self-healing.js +480 -0
- package/scripts/lib/activation/activation-state.js +83 -0
- package/scripts/lib/activation/activation-summary.js +191 -0
- package/scripts/lib/activation/adapters/claude-code.js +77 -0
- package/scripts/lib/activation/adapters/codex-cli.js +52 -0
- package/scripts/lib/activation/adapters/cursor.js +37 -0
- package/scripts/lib/activation/adapters/github-agent.js +39 -0
- package/scripts/lib/activation/adapters/terminal.js +42 -0
- package/scripts/lib/activation/adapters/vscode.js +39 -0
- package/scripts/lib/activation/adapters/windsurf.js +37 -0
- package/scripts/lib/activation/ai-surface-detector.js +151 -0
- package/scripts/lib/activation/connect-account.js +145 -0
- package/scripts/lib/activation/detect-environment.js +75 -0
- package/scripts/lib/activation/detect-hosts.js +62 -0
- package/scripts/lib/activation/format-activation-output.js +109 -0
- package/scripts/lib/activation/next-action.js +43 -0
- package/scripts/lib/activation/repair-engine.js +219 -0
- package/scripts/lib/activation-distribution-readiness.js +507 -0
- package/scripts/lib/adapter-conformance.js +176 -0
- package/scripts/lib/adapter-readiness.js +417 -0
- package/scripts/lib/adapter-safety-boundaries.js +335 -0
- package/scripts/lib/adapter-technical-readiness-gate.js +205 -0
- package/scripts/lib/agent-access-governance.js +455 -0
- package/scripts/lib/agent-enforcement.js +765 -0
- package/scripts/lib/agent-policy-profile.js +210 -0
- package/scripts/lib/agent-security/action-evaluator.js +507 -0
- package/scripts/lib/agent-security/adapter-registry.js +98 -0
- package/scripts/lib/agent-security/auto-policy.js +139 -0
- package/scripts/lib/agent-security/bounded-scan.js +93 -0
- package/scripts/lib/agent-security/enforcement-adapter.js +174 -0
- package/scripts/lib/agent-security/enforcement-engine.js +1129 -0
- package/scripts/lib/agent-security/file-write-adapter.js +183 -0
- package/scripts/lib/agent-security/file-write-rules.js +178 -0
- package/scripts/lib/agent-security/index.js +3342 -0
- package/scripts/lib/agent-security/instruction-risk.js +181 -0
- package/scripts/lib/agent-security/mcp-action-adapter.js +185 -0
- package/scripts/lib/agent-security/mcp-action-rules.js +184 -0
- package/scripts/lib/agent-security/package-action-adapter.js +175 -0
- package/scripts/lib/agent-security/package-action-rules.js +233 -0
- package/scripts/lib/agent-security/performance.js +148 -0
- package/scripts/lib/agent-security/permission-minimizer.js +403 -0
- package/scripts/lib/agent-security/scan-cache.js +74 -0
- package/scripts/lib/agent-security/source-trust.js +146 -0
- package/scripts/lib/ai-install-prompt.js +288 -0
- package/scripts/lib/ai-workspace-hygiene.js +1499 -0
- package/scripts/lib/alpha-activation.js +520 -0
- package/scripts/lib/alpha-feedback.js +263 -0
- package/scripts/lib/alpha-readiness-gate.js +332 -0
- package/scripts/lib/anti-gaming.js +169 -0
- package/scripts/lib/artifact-health.js +431 -0
- package/scripts/lib/attribution.js +180 -0
- package/scripts/lib/audit.js +289 -0
- package/scripts/lib/avorelo-skill-registry.js +810 -0
- package/scripts/lib/batch-jobs.js +71 -0
- package/scripts/lib/brain-pack.js +578 -0
- package/scripts/lib/brand-boundary.js +424 -0
- package/scripts/lib/brand.js +74 -0
- package/scripts/lib/browser-capability.js +1048 -0
- package/scripts/lib/browser-proof-preflight.js +321 -0
- package/scripts/lib/cache-readiness.js +187 -0
- package/scripts/lib/canonical-reentry.js +162 -0
- package/scripts/lib/capability-packs.js +314 -0
- package/scripts/lib/capability-recommender.js +512 -0
- package/scripts/lib/capability-registry.js +1059 -0
- package/scripts/lib/carry-forward-surfacing.js +194 -0
- package/scripts/lib/ccusage-adapter.js +188 -0
- package/scripts/lib/company-loop.js +1149 -0
- package/scripts/lib/config.js +637 -0
- package/scripts/lib/context-acquisition-plan.js +287 -0
- package/scripts/lib/context-budget-guard.js +170 -0
- package/scripts/lib/context-budget-scanner.js +257 -0
- package/scripts/lib/context-optimizer.js +715 -0
- package/scripts/lib/context-reduction-plan.js +178 -0
- package/scripts/lib/context-safety.js +88 -0
- package/scripts/lib/context-savings-engine.js +158 -0
- package/scripts/lib/cost-evidence.js +254 -0
- package/scripts/lib/cross-host-install-plan.js +308 -0
- package/scripts/lib/cross-host-install-readiness.js +237 -0
- package/scripts/lib/cross-host-value-flow.js +268 -0
- package/scripts/lib/dashboard.js +900 -0
- package/scripts/lib/design-partner-feedback.js +346 -0
- package/scripts/lib/entitlements.js +100 -0
- package/scripts/lib/execution-packet.js +559 -0
- package/scripts/lib/experimentation-events.js +547 -0
- package/scripts/lib/external-capability-compliance.js +107 -0
- package/scripts/lib/external-user-simulation.js +166 -0
- package/scripts/lib/failure-recovery-readiness.js +81 -0
- package/scripts/lib/failure-recovery.js +419 -0
- package/scripts/lib/feedback-intelligence.js +537 -0
- package/scripts/lib/feedback-signals.js +205 -0
- package/scripts/lib/file-integrity.js +68 -0
- package/scripts/lib/fsx.js +127 -0
- package/scripts/lib/full-readiness-gate.js +451 -0
- package/scripts/lib/guidance-builder.js +174 -0
- package/scripts/lib/hook-apply.js +1019 -0
- package/scripts/lib/hook-baseline.js +310 -0
- package/scripts/lib/hook-config-preview.js +275 -0
- package/scripts/lib/hook-contracts.js +290 -0
- package/scripts/lib/hook-safety-boundary-readiness.js +80 -0
- package/scripts/lib/host-capability-matrix.js +351 -0
- package/scripts/lib/host-support-context.js +254 -0
- package/scripts/lib/http-hook-action.js +538 -0
- package/scripts/lib/install-ai-readiness.js +84 -0
- package/scripts/lib/install-intake-risk.js +1037 -0
- package/scripts/lib/install-journey-intelligence.js +329 -0
- package/scripts/lib/intervention-guidance.js +57 -0
- package/scripts/lib/known-limitations.js +115 -0
- package/scripts/lib/l8-path-truth.js +146 -0
- package/scripts/lib/launch-hardening-gate.js +436 -0
- package/scripts/lib/launch-readiness.js +628 -0
- package/scripts/lib/learning-memory.js +686 -0
- package/scripts/lib/lifecycle-hooks.js +802 -0
- package/scripts/lib/local-package-smoke.js +423 -0
- package/scripts/lib/local-pricing.js +299 -0
- package/scripts/lib/mcp-enforcement.js +311 -0
- package/scripts/lib/mcp-least-privilege-policy.js +303 -0
- package/scripts/lib/mcp-tool-inventory.js +388 -0
- package/scripts/lib/mcp-tool-risk.js +0 -0
- package/scripts/lib/memory.js +335 -0
- package/scripts/lib/metrics.js +699 -0
- package/scripts/lib/micro-proof.js +133 -0
- package/scripts/lib/next-run-context.js +436 -0
- package/scripts/lib/operating-value.js +1648 -0
- package/scripts/lib/optimization-v3.js +122 -0
- package/scripts/lib/orchestration/adapters/_shared.js +49 -0
- package/scripts/lib/orchestration/adapters/aider.js +18 -0
- package/scripts/lib/orchestration/adapters/claude-code.js +35 -0
- package/scripts/lib/orchestration/adapters/codex.js +35 -0
- package/scripts/lib/orchestration/adapters/gemini-cli.js +18 -0
- package/scripts/lib/orchestration/adapters/git.js +25 -0
- package/scripts/lib/orchestration/adapters/index.js +31 -0
- package/scripts/lib/orchestration/adapters/lm-studio.js +18 -0
- package/scripts/lib/orchestration/adapters/ollama.js +18 -0
- package/scripts/lib/orchestration/adapters/opencode.js +18 -0
- package/scripts/lib/orchestration/adapters/openrouter.js +18 -0
- package/scripts/lib/orchestration/adapters/test-runner.js +25 -0
- package/scripts/lib/orchestration/cli.js +438 -0
- package/scripts/lib/orchestration/execution-manager.js +279 -0
- package/scripts/lib/orchestration/handoff.js +314 -0
- package/scripts/lib/orchestration/index.js +456 -0
- package/scripts/lib/orchestration/inventory.js +47 -0
- package/scripts/lib/orchestration/model-discovery.js +498 -0
- package/scripts/lib/orchestration/model-profiler.js +170 -0
- package/scripts/lib/orchestration/model-profiles.js +252 -0
- package/scripts/lib/orchestration/model-refresh-policy.js +72 -0
- package/scripts/lib/orchestration/proof-writer.js +349 -0
- package/scripts/lib/orchestration/provider-discovery/aider.js +49 -0
- package/scripts/lib/orchestration/provider-discovery/claude-code.js +56 -0
- package/scripts/lib/orchestration/provider-discovery/codex.js +49 -0
- package/scripts/lib/orchestration/provider-discovery/common.js +186 -0
- package/scripts/lib/orchestration/provider-discovery/gemini.js +106 -0
- package/scripts/lib/orchestration/provider-discovery/lm-studio.js +118 -0
- package/scripts/lib/orchestration/provider-discovery/models-dev.js +12 -0
- package/scripts/lib/orchestration/provider-discovery/ollama.js +100 -0
- package/scripts/lib/orchestration/provider-discovery/opencode.js +47 -0
- package/scripts/lib/orchestration/provider-discovery/openrouter.js +44 -0
- package/scripts/lib/orchestration/risk-classifier.js +130 -0
- package/scripts/lib/orchestration/routing-policy.js +486 -0
- package/scripts/lib/orchestration/settings.js +112 -0
- package/scripts/lib/orchestration/state.js +165 -0
- package/scripts/lib/orchestration/verification-manager.js +138 -0
- package/scripts/lib/output-profiles.js +146 -0
- package/scripts/lib/package-content-audit.js +368 -0
- package/scripts/lib/package-runtime.js +278 -0
- package/scripts/lib/plan-surface.js +53 -0
- package/scripts/lib/plans.js +2318 -0
- package/scripts/lib/policy-provider.js +27 -0
- package/scripts/lib/prelaunch-activation-readiness.js +409 -0
- package/scripts/lib/prelaunch-evidence-store.js +816 -0
- package/scripts/lib/prelaunch-intelligence.js +869 -0
- package/scripts/lib/pricing-experiment.js +118 -0
- package/scripts/lib/pro-moment-events.js +77 -0
- package/scripts/lib/pro-moment-state.js +227 -0
- package/scripts/lib/pro-moments.js +1216 -0
- package/scripts/lib/product-learning-events.js +629 -0
- package/scripts/lib/project-profile.js +555 -0
- package/scripts/lib/prompt-compiler.js +280 -0
- package/scripts/lib/prompt-lint.js +32 -0
- package/scripts/lib/prompt-suggestions.js +52 -0
- package/scripts/lib/proof-canonical.js +398 -0
- package/scripts/lib/proof-drilldown.js +383 -0
- package/scripts/lib/proof-events.js +342 -0
- package/scripts/lib/proof-history.js +243 -0
- package/scripts/lib/proof-metrics.js +296 -0
- package/scripts/lib/proof-outcome-evidence.js +134 -0
- package/scripts/lib/proof-receipt.js +335 -0
- package/scripts/lib/proof-record.js +461 -0
- package/scripts/lib/public-activation-distribution-gate.js +258 -0
- package/scripts/lib/public-cli.js +3891 -0
- package/scripts/lib/public-distribution-truth.js +211 -0
- package/scripts/lib/public-install-claim-checker.js +294 -0
- package/scripts/lib/publish-provenance-readiness.js +283 -0
- package/scripts/lib/readiness-delta.js +218 -0
- package/scripts/lib/readiness-evidence-closure.js +196 -0
- package/scripts/lib/reentry-memory-capture.js +241 -0
- package/scripts/lib/reentry-memory-retrieval.js +302 -0
- package/scripts/lib/reentry-memory-status.js +146 -0
- package/scripts/lib/reentry-memory-store.js +178 -0
- package/scripts/lib/reentry-state.js +66 -0
- package/scripts/lib/release-candidate-bundle.js +166 -0
- package/scripts/lib/remediation.js +81 -0
- package/scripts/lib/repo-map.js +391 -0
- package/scripts/lib/run-improvements-lifecycle.js +330 -0
- package/scripts/lib/run-improvements.js +789 -0
- package/scripts/lib/runtime-decision-policy.js +387 -0
- package/scripts/lib/safe-path-engine.js +705 -0
- package/scripts/lib/safe-run-controller.js +887 -0
- package/scripts/lib/score.js +262 -0
- package/scripts/lib/seamless-enforcement.js +329 -0
- package/scripts/lib/seamless-outcome.js +689 -0
- package/scripts/lib/seamless-reality-gate.js +5043 -0
- package/scripts/lib/security-risk-classifier.js +511 -0
- package/scripts/lib/security-scan.js +384 -0
- package/scripts/lib/session-context-optimizer.js +1211 -0
- package/scripts/lib/session-timing.js +315 -0
- package/scripts/lib/skill-hygiene.js +805 -0
- package/scripts/lib/skill-packs.js +161 -0
- package/scripts/lib/skills-operating-layer.js +580 -0
- package/scripts/lib/smart-work-routing.js +768 -0
- package/scripts/lib/source-catalog.js +700 -0
- package/scripts/lib/status-value-summary.js +32 -0
- package/scripts/lib/support-bundle.js +578 -0
- package/scripts/lib/task-continuation.js +440 -0
- package/scripts/lib/test-helpers.js +15 -0
- package/scripts/lib/tier.js +38 -0
- package/scripts/lib/token-context-quality-gate.js +370 -0
- package/scripts/lib/token-cost-capture.js +187 -0
- package/scripts/lib/token-cost-intelligence.js +358 -0
- package/scripts/lib/token-efficiency-evidence.js +213 -0
- package/scripts/lib/token-evidence.js +699 -0
- package/scripts/lib/tokenish.js +17 -0
- package/scripts/lib/tool-output-sandbox.js +304 -0
- package/scripts/lib/trust-audit.js +136 -0
- package/scripts/lib/unified-events.js +396 -0
- package/scripts/lib/upgrade-interruption-recovery.js +407 -0
- package/scripts/lib/usage-ledger.js +201 -0
- package/scripts/lib/value-ledger.js +130 -0
- package/scripts/lib/value-proof-calibration.js +531 -0
- package/scripts/lib/visual-qa.js +231 -0
- package/scripts/lib/voice-alpha.js +29 -0
- package/scripts/lib/work-aware-orchestration.js +976 -0
- package/scripts/lib/work-control-receipts.js +577 -0
- package/scripts/lib/work-ledger.js +1123 -0
- package/scripts/lib/work-panel-preview.js +352 -0
- package/scripts/lib/workflow-discipline.js +280 -0
- package/scripts/lib/workflow-signals.js +419 -0
- package/scripts/lib/workspace-map.js +281 -0
- package/scripts/lib/workspace-registry.js +1367 -0
- package/scripts/lib/workspace-resolver.js +480 -0
|
@@ -0,0 +1,3342 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const crypto = require("crypto");
|
|
6
|
+
const { safeReadJson, safeWriteJson, nowIso } = require("../fsx");
|
|
7
|
+
const { buildActionId, normalizeAgentSecurityAction, evaluateAgentSecurityAction } = require("./action-evaluator");
|
|
8
|
+
const { buildPermissionLimitPlan } = require("./permission-minimizer");
|
|
9
|
+
const {
|
|
10
|
+
ENFORCEMENT_SUMMARY_REL_PATH,
|
|
11
|
+
ENFORCEMENT_CAPABILITIES_REL_PATH,
|
|
12
|
+
JIT_GRANTS_REL_PATH,
|
|
13
|
+
ONE_TIME_GRANTS_REL_PATH,
|
|
14
|
+
ENFORCEMENT_AUDIT_LOG_REL_PATH,
|
|
15
|
+
loadJitGrantStore,
|
|
16
|
+
loadEnforcementSummary,
|
|
17
|
+
recordEnforcementOutcome,
|
|
18
|
+
enforceAgentSecurityAction,
|
|
19
|
+
} = require("./enforcement-engine");
|
|
20
|
+
const { resolveAgentSecurityAdapter, writeEnforcementCapabilityMatrix } = require("./adapter-registry");
|
|
21
|
+
const { decideAutoAction } = require("./auto-policy");
|
|
22
|
+
const { scanInstructionRisk, sanitizeInstructionLikeContent } = require("./instruction-risk");
|
|
23
|
+
const { buildSourceRecord, sourceTypeForComponentType } = require("./source-trust");
|
|
24
|
+
const {
|
|
25
|
+
PERFORMANCE_SUMMARY_REL_PATH,
|
|
26
|
+
PERFORMANCE_GUARDRAILS,
|
|
27
|
+
createPerformanceTracker,
|
|
28
|
+
recordPerformanceSummary,
|
|
29
|
+
buildCompactPerformanceSummary,
|
|
30
|
+
} = require("./performance");
|
|
31
|
+
const { SCAN_CACHE_REL_PATH, cacheLookup, updateScanCache, buildCacheKey } = require("./scan-cache");
|
|
32
|
+
const { createBoundedScanSession } = require("./bounded-scan");
|
|
33
|
+
|
|
34
|
+
const AGENT_SECURITY_MODES = ["off", "basic", "visibility", "warn", "auto"];
|
|
35
|
+
const SNAPSHOT_REL_PATH = ".claude/cco/security/agent-surface-snapshot.json";
|
|
36
|
+
const SUMMARY_REL_PATH = ".claude/cco/security/agent-basic-check-summary.json";
|
|
37
|
+
const VISIBILITY_SUMMARY_REL_PATH = ".claude/cco/security/agent-visibility-summary.json";
|
|
38
|
+
const TRUST_CARDS_REL_PATH = ".claude/cco/security/agent-trust-cards.json";
|
|
39
|
+
const RISK_DIFF_REL_PATH = ".claude/cco/security/agent-risk-diff.json";
|
|
40
|
+
const PERMISSION_PREVIEW_REL_PATH = ".claude/cco/security/agent-permission-preview.json";
|
|
41
|
+
const INSTRUCTION_RISK_REL_PATH = ".claude/cco/security/agent-instruction-risk.json";
|
|
42
|
+
const SOURCE_TRUST_REL_PATH = ".claude/cco/security/agent-source-trust.json";
|
|
43
|
+
const INSTRUCTION_REMEDIATIONS_REL_PATH = ".claude/cco/security/agent-instruction-remediations.json";
|
|
44
|
+
const SANITIZED_CONTENT_REL_PATH = ".claude/cco/security/agent-sanitized-content.json";
|
|
45
|
+
const AUDIT_LOG_REL_PATH = ".claude/cco/audit/agent-security.jsonl";
|
|
46
|
+
const INSTRUCTION_AUDIT_LOG_REL_PATH = ".claude/cco/audit/agent-instruction-risk.jsonl";
|
|
47
|
+
const ACTION_PREFLIGHT_REL_PATH = ".claude/cco/security/agent-action-preflight.json";
|
|
48
|
+
const DECISION_SUMMARY_REL_PATH = ".claude/cco/security/agent-decision-summary.json";
|
|
49
|
+
const DECISION_AUDIT_LOG_REL_PATH = ".claude/cco/audit/agent-security-decisions.jsonl";
|
|
50
|
+
|
|
51
|
+
const TEXT_EXTENSIONS = new Set([".md", ".txt", ".json", ".js", ".ts", ".yaml", ".yml", ".sh", ".ps1", ".toml", ".mjs", ".cjs"]);
|
|
52
|
+
const EXCLUDED_DIR_NAMES = new Set([".git", "node_modules", ".claude/cco", "coverage", "dist", "build", ".next", ".turbo", ".cache", ".wasp/out"]);
|
|
53
|
+
const EXCLUDED_DIR_BASENAMES = new Set([".git", "node_modules", "coverage", "dist", "build", ".next", ".turbo", ".cache"]);
|
|
54
|
+
const CAPABILITY_KEYS = [
|
|
55
|
+
"filesystemRead",
|
|
56
|
+
"filesystemWrite",
|
|
57
|
+
"shell",
|
|
58
|
+
"network",
|
|
59
|
+
"mcpRead",
|
|
60
|
+
"mcpWrite",
|
|
61
|
+
"externalMutation",
|
|
62
|
+
"sensitiveAccess",
|
|
63
|
+
];
|
|
64
|
+
const AGENT_SECURITY_SCANNER_VERSION = "slice-8-6";
|
|
65
|
+
|
|
66
|
+
function buildAgentSecurityModeControl(config) {
|
|
67
|
+
const currentMode = config?.agentSecurity?.mode || "off";
|
|
68
|
+
return {
|
|
69
|
+
title: "Agent Security",
|
|
70
|
+
currentMode,
|
|
71
|
+
options: [
|
|
72
|
+
{
|
|
73
|
+
value: "off",
|
|
74
|
+
label: "Off",
|
|
75
|
+
description: "Wuz stays out of the way. No Agent Security scans, warnings, or dashboard section.",
|
|
76
|
+
available: true,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
value: "basic",
|
|
80
|
+
label: "Basic Check",
|
|
81
|
+
description: "Check new Skills and MCP entries. Included in the basic/free layer.",
|
|
82
|
+
available: true,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
value: "visibility",
|
|
86
|
+
label: "Visibility",
|
|
87
|
+
description: "Map the full AI agent surface and show risk diff + permission preview.",
|
|
88
|
+
available: true,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
value: "warn",
|
|
92
|
+
label: "Warn & Approve",
|
|
93
|
+
description: "Warn before sensitive actions. Approve once, deny, or let Wuz limit.",
|
|
94
|
+
available: true,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
value: "auto",
|
|
98
|
+
label: "Auto-Minimize",
|
|
99
|
+
description: "Wuz automatically applies least-privilege limits only for safe, enforceable actions. Risky or unknown actions still require approval.",
|
|
100
|
+
available: true,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function normalizePath(value) {
|
|
107
|
+
return String(value || "").replace(/\\/g, "/");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function relPath(cwd, absPath) {
|
|
111
|
+
return normalizePath(path.relative(cwd, absPath));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function stableStringify(value) {
|
|
115
|
+
if (Array.isArray(value)) {
|
|
116
|
+
return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
117
|
+
}
|
|
118
|
+
if (value && typeof value === "object") {
|
|
119
|
+
return `{${Object.keys(value)
|
|
120
|
+
.sort((left, right) => left.localeCompare(right))
|
|
121
|
+
.map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`)
|
|
122
|
+
.join(",")}}`;
|
|
123
|
+
}
|
|
124
|
+
return JSON.stringify(value);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function sha256(value) {
|
|
128
|
+
return crypto.createHash("sha256").update(value).digest("hex");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function shortHash(value) {
|
|
132
|
+
return sha256(String(value || "")).slice(0, 12);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function ensureDir(absPath) {
|
|
136
|
+
fs.mkdirSync(absPath, { recursive: true });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function readJsonAbs(absPath, fallback = null) {
|
|
140
|
+
try {
|
|
141
|
+
return JSON.parse(fs.readFileSync(absPath, "utf8").replace(/^\uFEFF/, ""));
|
|
142
|
+
} catch {
|
|
143
|
+
return fallback;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function readTextAbs(absPath) {
|
|
148
|
+
try {
|
|
149
|
+
return fs.readFileSync(absPath, "utf8").replace(/^\uFEFF/, "");
|
|
150
|
+
} catch {
|
|
151
|
+
return "";
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function appendJsonl(cwd, relPathValue, entry) {
|
|
156
|
+
const absPath = path.join(cwd, relPathValue);
|
|
157
|
+
ensureDir(path.dirname(absPath));
|
|
158
|
+
fs.appendFileSync(absPath, `${JSON.stringify(entry)}\n`, "utf8");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function boundedPreview(value, max = 120) {
|
|
162
|
+
const text = String(value || "").replace(/\s+/g, " ").trim();
|
|
163
|
+
return text.length > max ? `${text.slice(0, max)}...` : text;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function shouldExcludeDir(cwd, absDir) {
|
|
167
|
+
const rel = normalizePath(path.relative(cwd, absDir));
|
|
168
|
+
if (rel === ".claude/cco") return true;
|
|
169
|
+
if (EXCLUDED_DIR_NAMES.has(rel)) return true;
|
|
170
|
+
return EXCLUDED_DIR_BASENAMES.has(path.basename(absDir));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function walkFiles(absDir, out, cwd, options = {}, depth = 0) {
|
|
174
|
+
if (!fs.existsSync(absDir)) return;
|
|
175
|
+
const scanSession = options.scanSession || null;
|
|
176
|
+
const entries = fs.readdirSync(absDir, { withFileTypes: true });
|
|
177
|
+
entries.forEach((entry) => {
|
|
178
|
+
const entryAbs = path.join(absDir, entry.name);
|
|
179
|
+
if (entry.isDirectory()) {
|
|
180
|
+
if (shouldExcludeDir(cwd, entryAbs)) {
|
|
181
|
+
if (scanSession?.tracker) scanSession.tracker.noteIgnoredDirectory(relPath(cwd, entryAbs));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const maxDepth = Number.isFinite(Number(options.maxDepth)) ? Number(options.maxDepth) : Infinity;
|
|
185
|
+
if (depth >= maxDepth) return;
|
|
186
|
+
if (scanSession && !scanSession.canDescend(depth, entryAbs)) return;
|
|
187
|
+
walkFiles(entryAbs, out, cwd, options, depth + 1);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (entry.isFile()) {
|
|
191
|
+
const stat = fs.statSync(entryAbs);
|
|
192
|
+
if (scanSession && !scanSession.canReadFile(entryAbs, stat)) return;
|
|
193
|
+
if (scanSession) scanSession.noteFile(entryAbs, stat);
|
|
194
|
+
out.push(entryAbs);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function listFiles(absDir, cwd, options = {}) {
|
|
200
|
+
const out = [];
|
|
201
|
+
walkFiles(absDir, out, cwd, options, 0);
|
|
202
|
+
return out.sort((left, right) => left.localeCompare(right));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function listTextFiles(absDir, cwd, options = {}) {
|
|
206
|
+
return listFiles(absDir, cwd, options).filter((absPath) => TEXT_EXTENSIONS.has(path.extname(absPath).toLowerCase()));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function hashDirectory(cwd, absDir, options = {}) {
|
|
210
|
+
const pieces = listFiles(absDir, cwd, options).map((absPath) => {
|
|
211
|
+
const fileRel = relPath(cwd, absPath);
|
|
212
|
+
const content = fs.readFileSync(absPath);
|
|
213
|
+
return `${fileRel}\n${sha256(content)}\n`;
|
|
214
|
+
});
|
|
215
|
+
return sha256(pieces.join(""));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function hashFile(cwd, absPath) {
|
|
219
|
+
const buffer = fs.readFileSync(absPath);
|
|
220
|
+
return sha256(`${relPath(cwd, absPath)}\n${buffer.toString("utf8")}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function hashPathStat(cwd, absPath) {
|
|
224
|
+
try {
|
|
225
|
+
const stat = fs.statSync(absPath);
|
|
226
|
+
return sha256(`${relPath(cwd, absPath)}\n${stat.isDirectory() ? "dir" : "file"}\n${Number(stat.size || 0)}`);
|
|
227
|
+
} catch {
|
|
228
|
+
return sha256(relPath(cwd, absPath));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function hashNormalizedObject(value) {
|
|
233
|
+
return sha256(stableStringify(value));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function createComponentId(type, name, sourcePath) {
|
|
237
|
+
const safeName = String(name || type)
|
|
238
|
+
.toLowerCase()
|
|
239
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
240
|
+
.replace(/^-+|-+$/g, "") || type;
|
|
241
|
+
return `${type}-${safeName}-${shortHash(`${type}:${sourcePath}`)}`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function maybePush(list, message, seen) {
|
|
245
|
+
if (!message || seen.has(message)) return;
|
|
246
|
+
seen.add(message);
|
|
247
|
+
list.push(message);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function emptyCapabilities() {
|
|
251
|
+
return {
|
|
252
|
+
filesystemRead: false,
|
|
253
|
+
filesystemWrite: false,
|
|
254
|
+
shell: false,
|
|
255
|
+
network: false,
|
|
256
|
+
mcpRead: false,
|
|
257
|
+
mcpWrite: false,
|
|
258
|
+
externalMutation: false,
|
|
259
|
+
sensitiveAccess: false,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function scrubSecretValues(value) {
|
|
264
|
+
if (Array.isArray(value)) {
|
|
265
|
+
return value.map((item) => scrubSecretValues(item));
|
|
266
|
+
}
|
|
267
|
+
if (!value || typeof value !== "object") return value;
|
|
268
|
+
|
|
269
|
+
const out = {};
|
|
270
|
+
Object.keys(value).forEach((key) => {
|
|
271
|
+
const lowered = String(key || "").toLowerCase();
|
|
272
|
+
if (lowered === "env" && value[key] && typeof value[key] === "object" && !Array.isArray(value[key])) {
|
|
273
|
+
out[key] = Object.keys(value[key]).sort();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
out[key] = scrubSecretValues(value[key]);
|
|
277
|
+
});
|
|
278
|
+
return out;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function commandTextFromEntry(entry) {
|
|
282
|
+
if (!entry || typeof entry !== "object") return "";
|
|
283
|
+
const command = String(entry.command || "");
|
|
284
|
+
const args = Array.isArray(entry.args) ? entry.args.map((item) => String(item || "")).join(" ") : "";
|
|
285
|
+
return `${command} ${args}`.trim();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function envKeyNames(entry) {
|
|
289
|
+
const env = entry && entry.env && typeof entry.env === "object" && !Array.isArray(entry.env) ? entry.env : null;
|
|
290
|
+
return env ? Object.keys(env).sort() : [];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function collectSkillDirectories(absDir, cwd, out, options = {}, depth = 0) {
|
|
294
|
+
if (!fs.existsSync(absDir)) return;
|
|
295
|
+
const skillRoots = new Set([path.join(cwd, "skills"), path.join(cwd, ".claude/skills")]);
|
|
296
|
+
if (options.scanSession && !skillRoots.has(absDir) && !options.scanSession.canDescend(depth, absDir)) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const entries = fs.readdirSync(absDir, { withFileTypes: true });
|
|
300
|
+
const hasSkillFile = entries.some((entry) => entry.isFile() && entry.name.toLowerCase() === "skill.md");
|
|
301
|
+
if (hasSkillFile) {
|
|
302
|
+
const textFiles = listTextFiles(absDir, cwd, { scanSession: options.scanSession });
|
|
303
|
+
out.push({
|
|
304
|
+
name: path.basename(absDir),
|
|
305
|
+
type: "skill",
|
|
306
|
+
sourcePath: relPath(cwd, absDir),
|
|
307
|
+
sourceKind: "local",
|
|
308
|
+
contentHash: hashDirectory(cwd, absDir, { scanSession: options.scanSession }),
|
|
309
|
+
contentText: textFiles.map((filePath) => `# ${relPath(cwd, filePath)}\n${readTextAbs(filePath)}`).join("\n"),
|
|
310
|
+
mtimeMs: textFiles.reduce((max, filePath) => Math.max(max, statMeta(filePath).mtimeMs), 0),
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
entries
|
|
315
|
+
.filter((entry) => entry.isDirectory())
|
|
316
|
+
.forEach((entry) => {
|
|
317
|
+
const nextDir = path.join(absDir, entry.name);
|
|
318
|
+
collectSkillDirectories(nextDir, cwd, out, options, depth + 1);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function detectSkillComponents(cwd, options = {}) {
|
|
323
|
+
const out = [];
|
|
324
|
+
["skills", ".claude/skills"].forEach((rootRel) => {
|
|
325
|
+
collectSkillDirectories(path.join(cwd, rootRel), cwd, out, options);
|
|
326
|
+
});
|
|
327
|
+
return out;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function detectCommandComponents(cwd, options = {}) {
|
|
331
|
+
const commandsRoot = path.join(cwd, ".claude", "commands");
|
|
332
|
+
if (!fs.existsSync(commandsRoot)) return [];
|
|
333
|
+
|
|
334
|
+
return listFiles(commandsRoot, cwd, { scanSession: options.scanSession })
|
|
335
|
+
.filter((absPath) => fs.statSync(absPath).isFile())
|
|
336
|
+
.map((absPath) => ({
|
|
337
|
+
mtimeMs: statMeta(absPath).mtimeMs,
|
|
338
|
+
name: path.parse(absPath).name,
|
|
339
|
+
type: "command",
|
|
340
|
+
sourcePath: relPath(cwd, absPath),
|
|
341
|
+
sourceKind: "local",
|
|
342
|
+
contentHash: hashFile(cwd, absPath),
|
|
343
|
+
contentText: readTextAbs(absPath),
|
|
344
|
+
}));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function extractNamedEntries(container) {
|
|
348
|
+
if (!container) return [];
|
|
349
|
+
|
|
350
|
+
if (Array.isArray(container)) {
|
|
351
|
+
return container
|
|
352
|
+
.map((item, index) => {
|
|
353
|
+
const name = item && typeof item === "object" ? item.name || item.id || item.server || `server-${index + 1}` : `server-${index + 1}`;
|
|
354
|
+
return { name: String(name), entry: item };
|
|
355
|
+
})
|
|
356
|
+
.filter((item) => item.name);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (typeof container === "object") {
|
|
360
|
+
return Object.keys(container).map((name) => ({ name, entry: container[name] }));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return [];
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function extractMcpEntriesFromConfig(config) {
|
|
367
|
+
const candidates = [
|
|
368
|
+
config?.mcpServers,
|
|
369
|
+
config?.servers,
|
|
370
|
+
config?.mcp?.servers,
|
|
371
|
+
config?.mcp?.mcpServers,
|
|
372
|
+
];
|
|
373
|
+
|
|
374
|
+
for (const candidate of candidates) {
|
|
375
|
+
const entries = extractNamedEntries(candidate);
|
|
376
|
+
if (entries.length > 0) return entries;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return [];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function detectMcpComponents(cwd) {
|
|
383
|
+
const configFiles = [
|
|
384
|
+
".mcp.json",
|
|
385
|
+
".cursor/mcp.json",
|
|
386
|
+
".claude/settings.json",
|
|
387
|
+
".claude/settings.local.json",
|
|
388
|
+
];
|
|
389
|
+
|
|
390
|
+
const out = [];
|
|
391
|
+
configFiles.forEach((configRel) => {
|
|
392
|
+
const absPath = path.join(cwd, configRel);
|
|
393
|
+
if (!fs.existsSync(absPath)) return;
|
|
394
|
+
|
|
395
|
+
const parsed = readJsonAbs(absPath, null);
|
|
396
|
+
if (!parsed || typeof parsed !== "object") return;
|
|
397
|
+
|
|
398
|
+
extractMcpEntriesFromConfig(parsed).forEach(({ name, entry }) => {
|
|
399
|
+
const normalizedEntry = entry && typeof entry === "object" ? scrubSecretValues(entry) : { value: entry };
|
|
400
|
+
out.push({
|
|
401
|
+
name: String(name),
|
|
402
|
+
type: "mcp",
|
|
403
|
+
sourcePath: normalizePath(`${configRel}#${name}`),
|
|
404
|
+
sourceKind: "config",
|
|
405
|
+
contentHash: hashNormalizedObject(normalizedEntry),
|
|
406
|
+
contentText: stableStringify(normalizedEntry),
|
|
407
|
+
rawConfigPath: configRel,
|
|
408
|
+
rawEntry: normalizedEntry,
|
|
409
|
+
envKeys: envKeyNames(entry),
|
|
410
|
+
commandText: commandTextFromEntry(entry),
|
|
411
|
+
mtimeMs: statMeta(absPath).mtimeMs,
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
return out;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function detectConfigComponents(cwd) {
|
|
420
|
+
const configFiles = [
|
|
421
|
+
".claude/settings.json",
|
|
422
|
+
".claude/settings.local.json",
|
|
423
|
+
".mcp.json",
|
|
424
|
+
".cursor/mcp.json",
|
|
425
|
+
];
|
|
426
|
+
const out = [];
|
|
427
|
+
|
|
428
|
+
configFiles.forEach((configRel) => {
|
|
429
|
+
const absPath = path.join(cwd, configRel);
|
|
430
|
+
if (!fs.existsSync(absPath) || !fs.statSync(absPath).isFile()) return;
|
|
431
|
+
out.push({
|
|
432
|
+
mtimeMs: statMeta(absPath).mtimeMs,
|
|
433
|
+
name: configRel.replace(/[\\/]/g, "-").replace(/^-+|-+$/g, ""),
|
|
434
|
+
type: "config",
|
|
435
|
+
sourcePath: configRel,
|
|
436
|
+
sourceKind: "config",
|
|
437
|
+
contentHash: hashFile(cwd, absPath),
|
|
438
|
+
contentText: readTextAbs(absPath),
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
const packageJsonAbs = path.join(cwd, "package.json");
|
|
443
|
+
if (fs.existsSync(packageJsonAbs) && fs.statSync(packageJsonAbs).isFile()) {
|
|
444
|
+
out.push({
|
|
445
|
+
mtimeMs: statMeta(packageJsonAbs).mtimeMs,
|
|
446
|
+
name: "package-json",
|
|
447
|
+
type: "config",
|
|
448
|
+
sourcePath: "package.json",
|
|
449
|
+
sourceKind: "config",
|
|
450
|
+
contentHash: hashFile(cwd, packageJsonAbs),
|
|
451
|
+
contentText: readTextAbs(packageJsonAbs),
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const lockfiles = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"];
|
|
456
|
+
lockfiles.forEach((name) => {
|
|
457
|
+
const absPath = path.join(cwd, name);
|
|
458
|
+
if (!fs.existsSync(absPath) || !fs.statSync(absPath).isFile()) return;
|
|
459
|
+
out.push({
|
|
460
|
+
mtimeMs: statMeta(absPath).mtimeMs,
|
|
461
|
+
name: name.replace(/\W+/g, "-"),
|
|
462
|
+
type: "config",
|
|
463
|
+
sourcePath: name,
|
|
464
|
+
sourceKind: "local",
|
|
465
|
+
contentHash: hashFile(cwd, absPath),
|
|
466
|
+
contentText: `Lockfile present: ${name}`,
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
const dockerCandidates = fs
|
|
471
|
+
.readdirSync(cwd, { withFileTypes: true })
|
|
472
|
+
.filter((entry) => entry.isFile())
|
|
473
|
+
.map((entry) => entry.name)
|
|
474
|
+
.filter((name) => /^dockerfile(?:\..+)?$/i.test(name) || /^(?:docker-)?compose(?:\..+)?\.(?:ya?ml)$/i.test(name));
|
|
475
|
+
|
|
476
|
+
dockerCandidates.forEach((name) => {
|
|
477
|
+
const absPath = path.join(cwd, name);
|
|
478
|
+
out.push({
|
|
479
|
+
mtimeMs: statMeta(absPath).mtimeMs,
|
|
480
|
+
name: name.replace(/\W+/g, "-").toLowerCase(),
|
|
481
|
+
type: "config",
|
|
482
|
+
sourcePath: name,
|
|
483
|
+
sourceKind: "local",
|
|
484
|
+
contentHash: hashFile(cwd, absPath),
|
|
485
|
+
contentText: readTextAbs(absPath),
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
return out;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function detectCursorRuleComponents(cwd, options = {}) {
|
|
493
|
+
const rulesRoot = path.join(cwd, ".cursor", "rules");
|
|
494
|
+
if (!fs.existsSync(rulesRoot)) return [];
|
|
495
|
+
|
|
496
|
+
return listFiles(rulesRoot, cwd, { scanSession: options.scanSession })
|
|
497
|
+
.filter((absPath) => fs.statSync(absPath).isFile())
|
|
498
|
+
.map((absPath) => ({
|
|
499
|
+
mtimeMs: statMeta(absPath).mtimeMs,
|
|
500
|
+
name: path.parse(absPath).name,
|
|
501
|
+
type: "cursor-rule",
|
|
502
|
+
sourcePath: relPath(cwd, absPath),
|
|
503
|
+
sourceKind: "local",
|
|
504
|
+
contentHash: hashFile(cwd, absPath),
|
|
505
|
+
contentText: readTextAbs(absPath),
|
|
506
|
+
}));
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function detectPackageScriptComponents(cwd) {
|
|
510
|
+
const packageJsonAbs = path.join(cwd, "package.json");
|
|
511
|
+
if (!fs.existsSync(packageJsonAbs)) return [];
|
|
512
|
+
|
|
513
|
+
const pkg = readJsonAbs(packageJsonAbs, null);
|
|
514
|
+
const scripts = pkg && pkg.scripts && typeof pkg.scripts === "object" ? pkg.scripts : null;
|
|
515
|
+
if (!scripts) return [];
|
|
516
|
+
|
|
517
|
+
return Object.keys(scripts)
|
|
518
|
+
.sort((left, right) => left.localeCompare(right))
|
|
519
|
+
.map((name) => {
|
|
520
|
+
const command = String(scripts[name] || "");
|
|
521
|
+
return {
|
|
522
|
+
mtimeMs: statMeta(packageJsonAbs).mtimeMs,
|
|
523
|
+
name,
|
|
524
|
+
type: "package-script",
|
|
525
|
+
sourcePath: `package.json#scripts.${name}`,
|
|
526
|
+
sourceKind: "config",
|
|
527
|
+
contentHash: hashNormalizedObject({ name, command }),
|
|
528
|
+
contentText: command,
|
|
529
|
+
};
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function detectWorkflowComponents(cwd, options = {}) {
|
|
534
|
+
const workflowsRoot = path.join(cwd, ".github", "workflows");
|
|
535
|
+
if (!fs.existsSync(workflowsRoot)) return [];
|
|
536
|
+
|
|
537
|
+
return listFiles(workflowsRoot, cwd, { scanSession: options.scanSession })
|
|
538
|
+
.filter((absPath) => fs.statSync(absPath).isFile())
|
|
539
|
+
.map((absPath) => ({
|
|
540
|
+
mtimeMs: statMeta(absPath).mtimeMs,
|
|
541
|
+
name: path.parse(absPath).name,
|
|
542
|
+
type: "workflow",
|
|
543
|
+
sourcePath: relPath(cwd, absPath),
|
|
544
|
+
sourceKind: "local",
|
|
545
|
+
contentHash: hashFile(cwd, absPath),
|
|
546
|
+
contentText: readTextAbs(absPath),
|
|
547
|
+
}));
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function detectSensitiveSurfaceComponents(cwd, options = {}) {
|
|
551
|
+
const out = [];
|
|
552
|
+
const seen = new Set();
|
|
553
|
+
|
|
554
|
+
function pushComponent(component) {
|
|
555
|
+
if (seen.has(component.sourcePath)) return;
|
|
556
|
+
seen.add(component.sourcePath);
|
|
557
|
+
out.push(component);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
try {
|
|
561
|
+
fs.readdirSync(cwd, { withFileTypes: true }).forEach((entry) => {
|
|
562
|
+
if (!entry.isFile()) return;
|
|
563
|
+
if (!/^\.env(?:\..+)?$/i.test(entry.name)) return;
|
|
564
|
+
const absPath = path.join(cwd, entry.name);
|
|
565
|
+
pushComponent({
|
|
566
|
+
mtimeMs: statMeta(absPath).mtimeMs,
|
|
567
|
+
name: entry.name,
|
|
568
|
+
type: "sensitive-surface",
|
|
569
|
+
sourcePath: entry.name,
|
|
570
|
+
sourceKind: "local",
|
|
571
|
+
contentHash: hashPathStat(cwd, absPath),
|
|
572
|
+
contentText: `Sensitive surface present: ${entry.name}`,
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
} catch {
|
|
576
|
+
// Ignore missing root read access and keep scan local-first.
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
[".ssh", ".aws", ".gcp", ".azure"].forEach((dirName) => {
|
|
580
|
+
const absPath = path.join(cwd, dirName);
|
|
581
|
+
if (!fs.existsSync(absPath)) return;
|
|
582
|
+
pushComponent({
|
|
583
|
+
mtimeMs: statMeta(absPath).mtimeMs,
|
|
584
|
+
name: dirName.replace(/^\./, ""),
|
|
585
|
+
type: "sensitive-surface",
|
|
586
|
+
sourcePath: normalizePath(dirName),
|
|
587
|
+
sourceKind: "local",
|
|
588
|
+
contentHash: hashPathStat(cwd, absPath),
|
|
589
|
+
contentText: `Sensitive directory present: ${dirName}`,
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
listFiles(cwd, cwd, { maxDepth: 3, scanSession: options.scanSession }).forEach((absPath) => {
|
|
594
|
+
const fileRel = relPath(cwd, absPath);
|
|
595
|
+
if (fileRel.startsWith(".claude/cco/")) return;
|
|
596
|
+
const baseName = path.basename(absPath).toLowerCase();
|
|
597
|
+
if (!/(secret|token|private.?key|id_rsa|id_dsa|id_ed25519|credential)/i.test(baseName)) return;
|
|
598
|
+
pushComponent({
|
|
599
|
+
mtimeMs: statMeta(absPath).mtimeMs,
|
|
600
|
+
name: path.basename(absPath),
|
|
601
|
+
type: "sensitive-surface",
|
|
602
|
+
sourcePath: fileRel,
|
|
603
|
+
sourceKind: "local",
|
|
604
|
+
contentHash: hashPathStat(cwd, absPath),
|
|
605
|
+
contentText: `Sensitive file naming pattern detected: ${path.basename(absPath)}`,
|
|
606
|
+
});
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
return out.sort((left, right) => left.sourcePath.localeCompare(right.sourcePath));
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function hasPattern(text, pattern) {
|
|
613
|
+
return pattern.test(String(text || ""));
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function inferCapabilities(component) {
|
|
617
|
+
const capabilities = emptyCapabilities();
|
|
618
|
+
const entry = component.rawEntry || {};
|
|
619
|
+
const text = `${component.name}\n${component.sourcePath}\n${component.contentText}\n${component.commandText || ""}`;
|
|
620
|
+
const lowerText = text.toLowerCase();
|
|
621
|
+
|
|
622
|
+
if (component.type === "sensitive-surface") {
|
|
623
|
+
capabilities.filesystemRead = true;
|
|
624
|
+
capabilities.sensitiveAccess = true;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (component.type === "mcp") {
|
|
628
|
+
capabilities.mcpRead = true;
|
|
629
|
+
capabilities.filesystemRead = true;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (component.type === "package-script") {
|
|
633
|
+
capabilities.shell = true;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (component.type === "command") {
|
|
637
|
+
capabilities.shell = true;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (/\b(read|cat|open|grep|search|inspect|review|list files|find files|docs)\b/i.test(text)) {
|
|
641
|
+
capabilities.filesystemRead = true;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (/\b(write|modify|edit|delete|remove-item|rm\s+-rf|touch|truncate|mkdir|copy-item|move-item|tee\b|git commit|git push|workflow file|update workflow)\b/i.test(text)) {
|
|
645
|
+
capabilities.filesystemWrite = true;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (/\b(shell|terminal|bash|sh\b|pwsh|powershell|cmd(?:\.exe)?\b|subprocess|spawn|exec|npm run|pnpm run|yarn run|make\b)\b/i.test(text)) {
|
|
649
|
+
capabilities.shell = true;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (/\b(curl|wget|invoke-webrequest|invoke-restmethod|downloadstring|https?:\/\/|gh api|npm install|pnpm add|yarn add|docker pull|npx\b|pnpm dlx|yarn dlx|uvx)\b/i.test(text)) {
|
|
653
|
+
capabilities.network = true;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (/\b(secret|secrets|token|tokens|private key|private keys|ssh key|ssh keys|\.env\b|credentials?|aws|gcp|azure)\b/i.test(text)) {
|
|
657
|
+
capabilities.sensitiveAccess = true;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
if (component.type === "mcp") {
|
|
661
|
+
const nameAndCommand = `${component.name}\n${component.commandText || ""}\n${stableStringify(entry)}`;
|
|
662
|
+
if (/\b(write|delete|admin|workflow|workflows|secret|secrets|token|tokens|push|merge|deploy|prod|production|create pr|pull request)\b/i.test(nameAndCommand)) {
|
|
663
|
+
capabilities.mcpWrite = true;
|
|
664
|
+
capabilities.externalMutation = true;
|
|
665
|
+
}
|
|
666
|
+
if (Array.isArray(entry.capabilities)) {
|
|
667
|
+
const joined = entry.capabilities.join(" ").toLowerCase();
|
|
668
|
+
if (/\bwrite|mutate|admin|delete\b/.test(joined)) {
|
|
669
|
+
capabilities.mcpWrite = true;
|
|
670
|
+
capabilities.externalMutation = true;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (entry.transport && /https?|sse|stream/i.test(String(entry.transport))) {
|
|
674
|
+
capabilities.network = true;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
if (component.type === "workflow" && /\b(deploy|publish|release|workflow_dispatch|secrets?|gh api|actions\/checkout|permissions: write-all)\b/i.test(text)) {
|
|
679
|
+
capabilities.externalMutation = true;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (component.type === "config" && /\bmcpservers\b/i.test(lowerText)) {
|
|
683
|
+
capabilities.mcpRead = true;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
return capabilities;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function deriveTrustLevel(component) {
|
|
690
|
+
if (component.type !== "mcp") {
|
|
691
|
+
return component.sourceKind === "local" || component.sourceKind === "config" ? "local" : "unknown";
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const entry = component.rawEntry || {};
|
|
695
|
+
const command = component.commandText || "";
|
|
696
|
+
const image = String(entry.image || "");
|
|
697
|
+
const text = `${component.name}\n${component.contentText}\n${command}\n${image}`;
|
|
698
|
+
|
|
699
|
+
const localScriptPath = String(entry.command || "");
|
|
700
|
+
if (localScriptPath && /^(node|python|py|bash|sh|powershell|pwsh)$/i.test(localScriptPath)) {
|
|
701
|
+
const args = Array.isArray(entry.args) ? entry.args : [];
|
|
702
|
+
if (args.some((arg) => /\.(js|cjs|mjs|py|ps1|sh)$/i.test(String(arg || "")))) return "local";
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (/@[0-9]+\.[0-9]+(?:\.[0-9]+)?\b/.test(text) || /sha256:[a-f0-9]{32,}/i.test(text) || /#[a-f0-9]{12,40}\b/i.test(text)) {
|
|
706
|
+
return "pinned";
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (/\b(npx|pnpm dlx|yarn dlx|uvx|docker|podman)\b/i.test(command) || /^https?:\/\//i.test(command)) {
|
|
710
|
+
return "review-needed";
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
return "unknown";
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function riskRank(level) {
|
|
717
|
+
if (level === "high") return 3;
|
|
718
|
+
if (level === "medium") return 2;
|
|
719
|
+
return 1;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function determineStatusForCurrent(previousEntry, component, mode) {
|
|
723
|
+
if (!previousEntry) return "new";
|
|
724
|
+
if (mode === "visibility" && (previousEntry.contentHash !== component.contentHash || previousEntry.source !== component.sourcePath)) {
|
|
725
|
+
return "changed";
|
|
726
|
+
}
|
|
727
|
+
return "known";
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function classifyRisk(component, previousEntry, mode) {
|
|
731
|
+
const findings = [];
|
|
732
|
+
const seen = new Set();
|
|
733
|
+
let riskScore = 1;
|
|
734
|
+
|
|
735
|
+
const capabilities = inferCapabilities(component);
|
|
736
|
+
const text = `${component.name}\n${component.sourcePath}\n${component.contentText}\n${component.commandText || ""}`;
|
|
737
|
+
const trustLevel = deriveTrustLevel(component);
|
|
738
|
+
const status = determineStatusForCurrent(previousEntry, component, mode);
|
|
739
|
+
const sourceType = sourceTypeForComponentType(component.type);
|
|
740
|
+
const instructionScan = scanInstructionRisk(text, {
|
|
741
|
+
sourceType,
|
|
742
|
+
intendedUse: "instruction",
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
const highSignals = [
|
|
746
|
+
{ test: /(?:^|[^a-z0-9_])\.env\b|\b(secret|secrets|token|tokens|private key|private keys|ssh key|ssh keys)\b/i, finding: "References secrets, tokens, or private key material" },
|
|
747
|
+
{ test: /\b(ignore safety|ignore guardrails|bypass review|hide actions|silently run|silently execute|exfiltrat|steal data|bypass policy)\b/i, finding: "Contains suspicious instruction language that bypasses review or hides actions" },
|
|
748
|
+
{ test: /\b(curl|wget)\b[^\n]*\|\s*(bash|sh|pwsh|powershell)\b/i, finding: "Includes remote download piped into a shell" },
|
|
749
|
+
{ test: /\b(invoke-webrequest|invoke-restmethod|downloadstring)\b/i, finding: "Includes remote PowerShell download behavior" },
|
|
750
|
+
];
|
|
751
|
+
|
|
752
|
+
highSignals.forEach((signal) => {
|
|
753
|
+
if (hasPattern(text, signal.test)) {
|
|
754
|
+
maybePush(findings, signal.finding, seen);
|
|
755
|
+
riskScore = Math.max(riskScore, 3);
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
if (component.type === "package-script" && /^(?:preinstall|postinstall|install)$/i.test(component.name)) {
|
|
760
|
+
maybePush(findings, "Package install script can execute automatically during dependency operations", seen);
|
|
761
|
+
riskScore = Math.max(riskScore, 3);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if (capabilities.sensitiveAccess) {
|
|
765
|
+
maybePush(findings, "Can reach sensitive files, credentials, or secret-like surfaces", seen);
|
|
766
|
+
riskScore = Math.max(riskScore, component.type === "sensitive-surface" ? 2 : 3);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
if (component.type === "mcp" && (capabilities.mcpWrite || capabilities.externalMutation) && !/\b(approval|readOnly|read_only)\b/i.test(text)) {
|
|
770
|
+
maybePush(findings, "This MCP can modify external systems or has elevated posture", seen);
|
|
771
|
+
riskScore = Math.max(riskScore, 3);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
if (component.type === "workflow" && /\b(workflows?|permissions:\s*write-all|secrets?|deploy|publish|release)\b/i.test(text)) {
|
|
775
|
+
maybePush(findings, "Workflow can affect automation, releases, or secret-backed execution", seen);
|
|
776
|
+
riskScore = Math.max(riskScore, 3);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (status === "new") {
|
|
780
|
+
maybePush(findings, "New AI component detected", seen);
|
|
781
|
+
riskScore = Math.max(riskScore, 2);
|
|
782
|
+
}
|
|
783
|
+
if (status === "changed" && mode === "visibility") {
|
|
784
|
+
maybePush(findings, "Component changed since the previous recorded scan", seen);
|
|
785
|
+
riskScore = Math.max(riskScore, 2);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
if (component.sourceKind === "unknown") {
|
|
789
|
+
maybePush(findings, "Source could not be verified locally", seen);
|
|
790
|
+
riskScore = Math.max(riskScore, 2);
|
|
791
|
+
}
|
|
792
|
+
if (trustLevel === "unknown") {
|
|
793
|
+
maybePush(findings, "Trust level is still unknown", seen);
|
|
794
|
+
riskScore = Math.max(riskScore, 2);
|
|
795
|
+
}
|
|
796
|
+
if (trustLevel === "review-needed") {
|
|
797
|
+
maybePush(findings, "Source should be reviewed before use", seen);
|
|
798
|
+
riskScore = Math.max(riskScore, 2);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
if (capabilities.filesystemWrite) {
|
|
802
|
+
maybePush(findings, "Can modify local files or project automation", seen);
|
|
803
|
+
riskScore = Math.max(riskScore, 2);
|
|
804
|
+
}
|
|
805
|
+
if (capabilities.shell) {
|
|
806
|
+
maybePush(findings, "Can run shell commands or subprocesses", seen);
|
|
807
|
+
riskScore = Math.max(riskScore, 2);
|
|
808
|
+
}
|
|
809
|
+
if (capabilities.network) {
|
|
810
|
+
maybePush(findings, "Can reach remote network resources or pull remote tooling", seen);
|
|
811
|
+
riskScore = Math.max(riskScore, 2);
|
|
812
|
+
}
|
|
813
|
+
if (component.type === "package-script" && /\b(build|prepare|compile|bundle)\b/i.test(component.name)) {
|
|
814
|
+
maybePush(findings, "Package script can mutate generated outputs or build artifacts", seen);
|
|
815
|
+
riskScore = Math.max(riskScore, 2);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
instructionScan.findings.forEach((finding) => {
|
|
819
|
+
maybePush(findings, finding.message, seen);
|
|
820
|
+
});
|
|
821
|
+
riskScore = Math.max(riskScore, riskRank(instructionScan.instructionRisk));
|
|
822
|
+
|
|
823
|
+
const riskLevel = riskScore >= 3 ? "high" : riskScore >= 2 ? "medium" : "low";
|
|
824
|
+
if (findings.length === 0) {
|
|
825
|
+
findings.push("No obvious risky indicators found");
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
let recommendation = "No high-risk findings";
|
|
829
|
+
if (riskLevel === "high") recommendation = "Review before use";
|
|
830
|
+
else if (riskLevel === "medium" || status === "new" || status === "changed") recommendation = "Review details before use";
|
|
831
|
+
|
|
832
|
+
return {
|
|
833
|
+
status,
|
|
834
|
+
trustLevel,
|
|
835
|
+
riskLevel,
|
|
836
|
+
findings: findings.slice(0, 6),
|
|
837
|
+
recommendation,
|
|
838
|
+
requiresCheck: status === "new" || status === "changed" || riskLevel !== "low",
|
|
839
|
+
capabilities,
|
|
840
|
+
instructionRisk: instructionScan.instructionRisk,
|
|
841
|
+
instructionSignals: instructionScan.signals,
|
|
842
|
+
instructionFindings: instructionScan.findings.slice(0, 8),
|
|
843
|
+
sourceType,
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function statMeta(absPath) {
|
|
848
|
+
try {
|
|
849
|
+
const stat = fs.statSync(absPath);
|
|
850
|
+
return {
|
|
851
|
+
mtimeMs: Number(stat.mtimeMs || 0),
|
|
852
|
+
size: Number(stat.size || 0),
|
|
853
|
+
};
|
|
854
|
+
} catch {
|
|
855
|
+
return {
|
|
856
|
+
mtimeMs: 0,
|
|
857
|
+
size: 0,
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
function classifyRiskCached(cwd, component, previousEntry, mode, tracker) {
|
|
863
|
+
const size = Buffer.byteLength(String(component.contentText || ""), "utf8");
|
|
864
|
+
const expectedStatus = determineStatusForCurrent(previousEntry, component, mode);
|
|
865
|
+
const cacheKey = buildCacheKey({
|
|
866
|
+
scannerVersion: AGENT_SECURITY_SCANNER_VERSION,
|
|
867
|
+
type: component.type,
|
|
868
|
+
path: component.sourcePath,
|
|
869
|
+
mode,
|
|
870
|
+
contentHash: component.contentHash,
|
|
871
|
+
});
|
|
872
|
+
const cached = cacheLookup(cwd, {
|
|
873
|
+
cacheKey,
|
|
874
|
+
path: component.sourcePath,
|
|
875
|
+
mtimeMs: Number(component.mtimeMs || 0),
|
|
876
|
+
size,
|
|
877
|
+
contentHash: component.contentHash,
|
|
878
|
+
scannerVersion: AGENT_SECURITY_SCANNER_VERSION,
|
|
879
|
+
});
|
|
880
|
+
if (cached && cached.result) {
|
|
881
|
+
if (cached.result.status === expectedStatus) {
|
|
882
|
+
if (tracker) tracker.noteCacheHit();
|
|
883
|
+
return cached.result;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
if (tracker) tracker.noteCacheMiss();
|
|
888
|
+
const result = classifyRisk(component, previousEntry, mode);
|
|
889
|
+
updateScanCache(cwd, AGENT_SECURITY_SCANNER_VERSION, {
|
|
890
|
+
cacheKey,
|
|
891
|
+
path: component.sourcePath,
|
|
892
|
+
mtimeMs: Number(component.mtimeMs || 0),
|
|
893
|
+
size,
|
|
894
|
+
contentHash: component.contentHash,
|
|
895
|
+
scannerVersion: AGENT_SECURITY_SCANNER_VERSION,
|
|
896
|
+
lastScannedAt: nowIso(),
|
|
897
|
+
result,
|
|
898
|
+
});
|
|
899
|
+
return result;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function toSnapshotComponent(component) {
|
|
903
|
+
return {
|
|
904
|
+
id: component.id,
|
|
905
|
+
type: component.type,
|
|
906
|
+
name: component.name,
|
|
907
|
+
source: component.source,
|
|
908
|
+
sourceKind: component.sourceKind,
|
|
909
|
+
status: component.status,
|
|
910
|
+
riskLevel: component.riskLevel,
|
|
911
|
+
trustLevel: component.trustLevel,
|
|
912
|
+
sourceType: component.sourceType || null,
|
|
913
|
+
sourceTrust: component.sourceTrust || null,
|
|
914
|
+
instructionRisk: component.instructionRisk || "low",
|
|
915
|
+
instructionSignals: component.instructionSignals || [],
|
|
916
|
+
instructionFindings: component.instructionFindings || [],
|
|
917
|
+
contentHash: component.contentHash,
|
|
918
|
+
capabilities: component.capabilities,
|
|
919
|
+
findings: component.findings,
|
|
920
|
+
recommendation: component.recommendation,
|
|
921
|
+
firstSeenAt: component.firstSeenAt,
|
|
922
|
+
lastSeenAt: component.lastSeenAt,
|
|
923
|
+
lastCheckedAt: component.lastCheckedAt,
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
function normalizeSnapshotItems(snapshot, mode) {
|
|
928
|
+
const scoped = snapshot && snapshot.scopedComponents && typeof snapshot.scopedComponents === "object"
|
|
929
|
+
? snapshot.scopedComponents
|
|
930
|
+
: {};
|
|
931
|
+
const scopedItems = mode && Array.isArray(scoped[mode]) ? scoped[mode] : null;
|
|
932
|
+
const fallbackItems = Array.isArray(snapshot?.components)
|
|
933
|
+
? snapshot.components
|
|
934
|
+
: snapshot?.components && typeof snapshot.components === "object"
|
|
935
|
+
? Object.values(snapshot.components)
|
|
936
|
+
: [];
|
|
937
|
+
return scopedItems || fallbackItems;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function loadPreviousSnapshot(cwd, mode = "basic") {
|
|
941
|
+
const snapshot = safeReadJson(cwd, SNAPSHOT_REL_PATH, null);
|
|
942
|
+
if (!snapshot || typeof snapshot !== "object") {
|
|
943
|
+
return {
|
|
944
|
+
snapshotVersion: 2,
|
|
945
|
+
generatedAt: null,
|
|
946
|
+
scope: mode,
|
|
947
|
+
scanMode: null,
|
|
948
|
+
scopedComponents: {},
|
|
949
|
+
components: [],
|
|
950
|
+
componentsById: {},
|
|
951
|
+
raw: null,
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
const componentItems = normalizeSnapshotItems(snapshot, mode);
|
|
956
|
+
const componentsById = {};
|
|
957
|
+
componentItems.forEach((item) => {
|
|
958
|
+
if (item && item.id) componentsById[item.id] = item;
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
return {
|
|
962
|
+
snapshotVersion: Number(snapshot.snapshotVersion || 1),
|
|
963
|
+
generatedAt: snapshot.generatedAt || null,
|
|
964
|
+
scope: mode,
|
|
965
|
+
scanMode: snapshot.scanMode || null,
|
|
966
|
+
scopedComponents: snapshot.scopedComponents || {},
|
|
967
|
+
components: componentItems,
|
|
968
|
+
componentsById,
|
|
969
|
+
raw: snapshot,
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
function buildMergedSnapshot(previousSnapshot, mode, components, generatedAt) {
|
|
974
|
+
const snapshotComponents = components.map((component) => toSnapshotComponent(component));
|
|
975
|
+
const priorScoped = previousSnapshot?.raw?.scopedComponents && typeof previousSnapshot.raw.scopedComponents === "object"
|
|
976
|
+
? previousSnapshot.raw.scopedComponents
|
|
977
|
+
: previousSnapshot?.scopedComponents && typeof previousSnapshot.scopedComponents === "object"
|
|
978
|
+
? previousSnapshot.scopedComponents
|
|
979
|
+
: {};
|
|
980
|
+
const priorGeneratedAts = previousSnapshot?.raw?.scopedGeneratedAts && typeof previousSnapshot.raw.scopedGeneratedAts === "object"
|
|
981
|
+
? previousSnapshot.raw.scopedGeneratedAts
|
|
982
|
+
: {};
|
|
983
|
+
|
|
984
|
+
return {
|
|
985
|
+
snapshotVersion: 2,
|
|
986
|
+
generatedAt,
|
|
987
|
+
scanMode: mode,
|
|
988
|
+
componentCount: snapshotComponents.length,
|
|
989
|
+
components: snapshotComponents,
|
|
990
|
+
scopedComponents: {
|
|
991
|
+
...priorScoped,
|
|
992
|
+
[mode]: snapshotComponents,
|
|
993
|
+
},
|
|
994
|
+
scopedGeneratedAts: {
|
|
995
|
+
...priorGeneratedAts,
|
|
996
|
+
[mode]: generatedAt,
|
|
997
|
+
},
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
function sortTrustCards(cards) {
|
|
1002
|
+
return cards.sort((left, right) => {
|
|
1003
|
+
const severityRank = { high: 3, medium: 2, low: 1 };
|
|
1004
|
+
if (severityRank[right.riskLevel] !== severityRank[left.riskLevel]) {
|
|
1005
|
+
return severityRank[right.riskLevel] - severityRank[left.riskLevel];
|
|
1006
|
+
}
|
|
1007
|
+
const statusRank = { new: 0, changed: 1, removed: 2, known: 3 };
|
|
1008
|
+
if ((statusRank[left.status] ?? 99) !== (statusRank[right.status] ?? 99)) {
|
|
1009
|
+
return (statusRank[left.status] ?? 99) - (statusRank[right.status] ?? 99);
|
|
1010
|
+
}
|
|
1011
|
+
if (left.type !== right.type) return left.type.localeCompare(right.type);
|
|
1012
|
+
return left.name.localeCompare(right.name);
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
function buildRemovedTrustCard(previousEntry, generatedAt) {
|
|
1017
|
+
return {
|
|
1018
|
+
id: previousEntry.id,
|
|
1019
|
+
type: previousEntry.type,
|
|
1020
|
+
name: previousEntry.name,
|
|
1021
|
+
source: previousEntry.source,
|
|
1022
|
+
sourceKind: previousEntry.sourceKind || "local",
|
|
1023
|
+
status: "removed",
|
|
1024
|
+
requiresCheck: false,
|
|
1025
|
+
riskLevel: previousEntry.riskLevel || "low",
|
|
1026
|
+
trustLevel: previousEntry.trustLevel || "unknown",
|
|
1027
|
+
sourceType: previousEntry.sourceType || sourceTypeForComponentType(previousEntry.type),
|
|
1028
|
+
sourceTrust: previousEntry.sourceTrust || null,
|
|
1029
|
+
instructionRisk: previousEntry.instructionRisk || "low",
|
|
1030
|
+
instructionSignals: previousEntry.instructionSignals || [],
|
|
1031
|
+
instructionFindings: previousEntry.instructionFindings || [],
|
|
1032
|
+
findings: ["Component no longer detected locally"],
|
|
1033
|
+
recommendation: "Confirm removal was intentional",
|
|
1034
|
+
contentHash: previousEntry.contentHash || null,
|
|
1035
|
+
capabilities: previousEntry.capabilities || emptyCapabilities(),
|
|
1036
|
+
firstSeenAt: previousEntry.firstSeenAt || generatedAt,
|
|
1037
|
+
lastSeenAt: previousEntry.lastSeenAt || generatedAt,
|
|
1038
|
+
lastCheckedAt: generatedAt,
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
function detectComponentsForMode(cwd, mode, options = {}) {
|
|
1043
|
+
const base = [...detectSkillComponents(cwd, options), ...detectCommandComponents(cwd, options), ...detectMcpComponents(cwd)];
|
|
1044
|
+
if (mode !== "visibility") return base;
|
|
1045
|
+
return [
|
|
1046
|
+
...base,
|
|
1047
|
+
...detectConfigComponents(cwd),
|
|
1048
|
+
...detectCursorRuleComponents(cwd, options),
|
|
1049
|
+
...detectPackageScriptComponents(cwd),
|
|
1050
|
+
...detectWorkflowComponents(cwd, options),
|
|
1051
|
+
...detectSensitiveSurfaceComponents(cwd, options),
|
|
1052
|
+
];
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
function buildTrustCardsForMode(cwd, previousSnapshot, generatedAt, mode, options = {}) {
|
|
1056
|
+
const tracker = options.tracker || null;
|
|
1057
|
+
const currentCards = detectComponentsForMode(cwd, mode, options).map((component) => {
|
|
1058
|
+
const id = createComponentId(component.type, component.name, component.sourcePath);
|
|
1059
|
+
const previousEntry = previousSnapshot.componentsById[id] || null;
|
|
1060
|
+
const classification = classifyRiskCached(cwd, component, previousEntry, mode, tracker);
|
|
1061
|
+
const sourceTrust = buildSourceRecord({
|
|
1062
|
+
sourceId: id,
|
|
1063
|
+
sourceType: classification.sourceType,
|
|
1064
|
+
origin: component.type === "mcp" ? component.rawConfigPath || component.sourcePath : component.sourcePath,
|
|
1065
|
+
path: component.sourcePath,
|
|
1066
|
+
content: component.contentText,
|
|
1067
|
+
instructionRisk: classification.instructionRisk,
|
|
1068
|
+
findings: classification.instructionFindings,
|
|
1069
|
+
intendedUse: ["skill_instruction", "claude_command", "cursor_rule", "mcp_config"].includes(classification.sourceType)
|
|
1070
|
+
? "instruction"
|
|
1071
|
+
: "evidence",
|
|
1072
|
+
createdAt: previousEntry?.sourceTrust?.createdAt || previousEntry?.firstSeenAt || generatedAt,
|
|
1073
|
+
updatedAt: generatedAt,
|
|
1074
|
+
remediationActions: previousEntry?.sourceTrust?.remediationActions || [],
|
|
1075
|
+
});
|
|
1076
|
+
const firstSeenAt = previousEntry?.firstSeenAt || generatedAt;
|
|
1077
|
+
|
|
1078
|
+
return {
|
|
1079
|
+
id,
|
|
1080
|
+
type: component.type,
|
|
1081
|
+
name: component.name,
|
|
1082
|
+
source: component.sourcePath,
|
|
1083
|
+
sourceKind: component.sourceKind,
|
|
1084
|
+
status: classification.status,
|
|
1085
|
+
requiresCheck: classification.requiresCheck,
|
|
1086
|
+
riskLevel: classification.riskLevel,
|
|
1087
|
+
trustLevel: classification.trustLevel,
|
|
1088
|
+
sourceType: classification.sourceType,
|
|
1089
|
+
sourceTrust,
|
|
1090
|
+
instructionRisk: classification.instructionRisk,
|
|
1091
|
+
instructionSignals: classification.instructionSignals,
|
|
1092
|
+
instructionFindings: classification.instructionFindings,
|
|
1093
|
+
findings: classification.findings,
|
|
1094
|
+
recommendation: classification.recommendation,
|
|
1095
|
+
contentHash: component.contentHash,
|
|
1096
|
+
capabilities: classification.capabilities,
|
|
1097
|
+
firstSeenAt,
|
|
1098
|
+
lastSeenAt: generatedAt,
|
|
1099
|
+
lastCheckedAt: generatedAt,
|
|
1100
|
+
};
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
if (mode !== "visibility") {
|
|
1104
|
+
return sortTrustCards(currentCards);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
const currentIds = new Set(currentCards.map((card) => card.id));
|
|
1108
|
+
const removedCards = previousSnapshot.components
|
|
1109
|
+
.filter((item) => item && item.id && !currentIds.has(item.id))
|
|
1110
|
+
.map((item) => buildRemovedTrustCard(item, generatedAt));
|
|
1111
|
+
|
|
1112
|
+
return sortTrustCards([...currentCards, ...removedCards]);
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
function summarizeTrustCards(mode, trustCards, generatedAt, persistedSummary = null, extras = {}) {
|
|
1116
|
+
const presentCards = trustCards.filter((card) => card.status !== "removed");
|
|
1117
|
+
const counts = {
|
|
1118
|
+
componentsScanned: presentCards.length,
|
|
1119
|
+
newComponents: trustCards.filter((card) => card.status === "new").length,
|
|
1120
|
+
changedComponents: trustCards.filter((card) => card.status === "changed").length,
|
|
1121
|
+
removedComponents: trustCards.filter((card) => card.status === "removed").length,
|
|
1122
|
+
highRisk: presentCards.filter((card) => card.riskLevel === "high").length,
|
|
1123
|
+
mediumRisk: presentCards.filter((card) => card.riskLevel === "medium").length,
|
|
1124
|
+
lowRisk: presentCards.filter((card) => card.riskLevel === "low").length,
|
|
1125
|
+
suspiciousInstructionSources: presentCards.filter((card) => card.instructionRisk === "medium" || card.instructionRisk === "high").length,
|
|
1126
|
+
highInstructionFindings: presentCards.reduce((sum, card) => sum + (Array.isArray(card.instructionFindings)
|
|
1127
|
+
? card.instructionFindings.filter((item) => item.severity === "high").length
|
|
1128
|
+
: 0), 0),
|
|
1129
|
+
sourcesTreatedAsData: presentCards.filter((card) => card.sourceTrust && card.sourceTrust.allowedToInfluenceTools === false).length,
|
|
1130
|
+
};
|
|
1131
|
+
|
|
1132
|
+
const newComponents = trustCards
|
|
1133
|
+
.filter((card) => card.status === "new")
|
|
1134
|
+
.slice(0, 5)
|
|
1135
|
+
.map((card) => ({
|
|
1136
|
+
id: card.id,
|
|
1137
|
+
name: card.name,
|
|
1138
|
+
type: card.type,
|
|
1139
|
+
}));
|
|
1140
|
+
|
|
1141
|
+
const topFindings = trustCards
|
|
1142
|
+
.filter((card) => card.riskLevel !== "low" || card.status !== "known")
|
|
1143
|
+
.slice(0, 5)
|
|
1144
|
+
.map((card) => ({
|
|
1145
|
+
id: card.id,
|
|
1146
|
+
name: card.name,
|
|
1147
|
+
type: card.type,
|
|
1148
|
+
riskLevel: card.riskLevel,
|
|
1149
|
+
status: card.status,
|
|
1150
|
+
recommendation: card.recommendation,
|
|
1151
|
+
}));
|
|
1152
|
+
|
|
1153
|
+
if (mode === "visibility") {
|
|
1154
|
+
return {
|
|
1155
|
+
summaryVersion: 2,
|
|
1156
|
+
what: "Agent Security Visibility summary.",
|
|
1157
|
+
mode,
|
|
1158
|
+
generatedAt,
|
|
1159
|
+
lastCompletedCheckAt: persistedSummary?.generatedAt || null,
|
|
1160
|
+
componentsScanned: counts.componentsScanned,
|
|
1161
|
+
newComponents: counts.newComponents,
|
|
1162
|
+
changedComponents: counts.changedComponents,
|
|
1163
|
+
removedComponents: counts.removedComponents,
|
|
1164
|
+
highRisk: counts.highRisk,
|
|
1165
|
+
mediumRisk: counts.mediumRisk,
|
|
1166
|
+
lowRisk: counts.lowRisk,
|
|
1167
|
+
suspiciousInstructionSources: counts.suspiciousInstructionSources,
|
|
1168
|
+
highInstructionFindings: counts.highInstructionFindings,
|
|
1169
|
+
sourcesTreatedAsData: counts.sourcesTreatedAsData,
|
|
1170
|
+
riskIncreases: Number(extras.riskIncreases || 0),
|
|
1171
|
+
permissionExpansions: Number(extras.permissionExpansions || 0),
|
|
1172
|
+
permissionPreviews: Number(extras.permissionPreviews || 0),
|
|
1173
|
+
newComponentNames: newComponents.map((item) => item.name),
|
|
1174
|
+
newComponentsList: newComponents,
|
|
1175
|
+
topFindings,
|
|
1176
|
+
externalScannersRequired: false,
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
return {
|
|
1181
|
+
summaryVersion: 1,
|
|
1182
|
+
what: "Agent Security Basic Check summary.",
|
|
1183
|
+
mode,
|
|
1184
|
+
generatedAt,
|
|
1185
|
+
lastCompletedCheckAt: persistedSummary?.generatedAt || null,
|
|
1186
|
+
componentsScanned: counts.componentsScanned,
|
|
1187
|
+
newComponents: counts.newComponents,
|
|
1188
|
+
highRisk: counts.highRisk,
|
|
1189
|
+
mediumRisk: counts.mediumRisk,
|
|
1190
|
+
lowRisk: counts.lowRisk,
|
|
1191
|
+
suspiciousInstructionSources: counts.suspiciousInstructionSources,
|
|
1192
|
+
highInstructionFindings: counts.highInstructionFindings,
|
|
1193
|
+
sourcesTreatedAsData: counts.sourcesTreatedAsData,
|
|
1194
|
+
newComponentNames: newComponents.map((item) => item.name),
|
|
1195
|
+
newComponentsList: newComponents,
|
|
1196
|
+
topFindings,
|
|
1197
|
+
externalScannersRequired: false,
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
function capabilityExpansions(previousCapabilities, currentCapabilities) {
|
|
1202
|
+
const expansions = [];
|
|
1203
|
+
CAPABILITY_KEYS.forEach((key) => {
|
|
1204
|
+
if (!previousCapabilities?.[key] && currentCapabilities?.[key]) expansions.push(key);
|
|
1205
|
+
});
|
|
1206
|
+
return expansions;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
function buildRiskDiff(previousSnapshot, trustCards, generatedAt) {
|
|
1210
|
+
const currentPresent = trustCards.filter((card) => card.status !== "removed");
|
|
1211
|
+
const currentById = {};
|
|
1212
|
+
currentPresent.forEach((card) => {
|
|
1213
|
+
currentById[card.id] = card;
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
const changes = [];
|
|
1217
|
+
let added = 0;
|
|
1218
|
+
let changed = 0;
|
|
1219
|
+
let removed = 0;
|
|
1220
|
+
let riskIncreases = 0;
|
|
1221
|
+
let permissionExpansions = 0;
|
|
1222
|
+
|
|
1223
|
+
currentPresent.forEach((card) => {
|
|
1224
|
+
const previousEntry = previousSnapshot.componentsById[card.id] || null;
|
|
1225
|
+
if (!previousEntry) {
|
|
1226
|
+
added += 1;
|
|
1227
|
+
changes.push({
|
|
1228
|
+
id: card.id,
|
|
1229
|
+
name: card.name,
|
|
1230
|
+
type: card.type,
|
|
1231
|
+
changeType: "added",
|
|
1232
|
+
previousRisk: null,
|
|
1233
|
+
currentRisk: card.riskLevel,
|
|
1234
|
+
reasons: ["New component added to the local agent surface"],
|
|
1235
|
+
});
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
const reasons = [];
|
|
1240
|
+
const permissionExpansionKeys = capabilityExpansions(previousEntry.capabilities, card.capabilities);
|
|
1241
|
+
if (previousEntry.contentHash !== card.contentHash) reasons.push("Component content hash changed");
|
|
1242
|
+
if (previousEntry.source !== card.source) reasons.push("Component source path changed");
|
|
1243
|
+
if (permissionExpansionKeys.length > 0) {
|
|
1244
|
+
permissionExpansions += 1;
|
|
1245
|
+
reasons.push(`Permission expansion: ${permissionExpansionKeys.join(", ")}`);
|
|
1246
|
+
}
|
|
1247
|
+
if (riskRank(card.riskLevel) > riskRank(previousEntry.riskLevel)) {
|
|
1248
|
+
riskIncreases += 1;
|
|
1249
|
+
reasons.push(`Risk increased from ${previousEntry.riskLevel || "low"} to ${card.riskLevel}`);
|
|
1250
|
+
}
|
|
1251
|
+
if (!reasons.length) return;
|
|
1252
|
+
|
|
1253
|
+
changed += 1;
|
|
1254
|
+
changes.push({
|
|
1255
|
+
id: card.id,
|
|
1256
|
+
name: card.name,
|
|
1257
|
+
type: card.type,
|
|
1258
|
+
changeType: "changed",
|
|
1259
|
+
previousRisk: previousEntry.riskLevel || "low",
|
|
1260
|
+
currentRisk: card.riskLevel,
|
|
1261
|
+
reasons,
|
|
1262
|
+
});
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
previousSnapshot.components.forEach((previousEntry) => {
|
|
1266
|
+
if (currentById[previousEntry.id]) return;
|
|
1267
|
+
removed += 1;
|
|
1268
|
+
changes.push({
|
|
1269
|
+
id: previousEntry.id,
|
|
1270
|
+
name: previousEntry.name,
|
|
1271
|
+
type: previousEntry.type,
|
|
1272
|
+
changeType: "removed",
|
|
1273
|
+
previousRisk: previousEntry.riskLevel || "low",
|
|
1274
|
+
currentRisk: null,
|
|
1275
|
+
reasons: ["Component was present in the previous recorded scan but is no longer detected"],
|
|
1276
|
+
});
|
|
1277
|
+
});
|
|
1278
|
+
|
|
1279
|
+
return {
|
|
1280
|
+
generatedAt,
|
|
1281
|
+
summary: {
|
|
1282
|
+
added,
|
|
1283
|
+
changed,
|
|
1284
|
+
removed,
|
|
1285
|
+
riskIncreases,
|
|
1286
|
+
permissionExpansions,
|
|
1287
|
+
},
|
|
1288
|
+
changes,
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
function buildPermissionPreview(trustCards, generatedAt) {
|
|
1293
|
+
const previews = trustCards
|
|
1294
|
+
.filter((card) => card.status !== "removed")
|
|
1295
|
+
.filter((card) => {
|
|
1296
|
+
const caps = card.capabilities || emptyCapabilities();
|
|
1297
|
+
return (
|
|
1298
|
+
card.type === "mcp" ||
|
|
1299
|
+
card.type === "package-script" ||
|
|
1300
|
+
card.type === "workflow" ||
|
|
1301
|
+
card.type === "sensitive-surface" ||
|
|
1302
|
+
caps.filesystemWrite ||
|
|
1303
|
+
caps.shell ||
|
|
1304
|
+
caps.network ||
|
|
1305
|
+
caps.externalMutation ||
|
|
1306
|
+
caps.sensitiveAccess
|
|
1307
|
+
);
|
|
1308
|
+
})
|
|
1309
|
+
.map((card) => {
|
|
1310
|
+
const currentCapabilities = CAPABILITY_KEYS.filter((key) => card.capabilities?.[key]);
|
|
1311
|
+
let recommendedMode = "read-only";
|
|
1312
|
+
let ttlRecommendation = "No TTL needed for read-only components.";
|
|
1313
|
+
const wouldAllow = [];
|
|
1314
|
+
const wouldLimit = [];
|
|
1315
|
+
let reason = "Local read-only components can stay visible without tighter controls.";
|
|
1316
|
+
|
|
1317
|
+
if (card.capabilities?.externalMutation || card.capabilities?.mcpWrite || card.capabilities?.sensitiveAccess) {
|
|
1318
|
+
recommendedMode = "approval-required";
|
|
1319
|
+
ttlRecommendation = "30 minutes for write actions";
|
|
1320
|
+
reason = "External mutation or sensitive access should be scoped and time-bound.";
|
|
1321
|
+
} else if (card.capabilities?.filesystemWrite || card.capabilities?.shell || card.capabilities?.network) {
|
|
1322
|
+
recommendedMode = "scoped-write";
|
|
1323
|
+
ttlRecommendation = "15 minutes for shell or write-capable actions";
|
|
1324
|
+
reason = "Write-capable or shell-capable components should run with a bounded scope.";
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
if (card.type === "mcp") {
|
|
1328
|
+
wouldAllow.push("read repository metadata");
|
|
1329
|
+
if (card.capabilities?.mcpWrite || card.capabilities?.externalMutation) {
|
|
1330
|
+
wouldAllow.push("create pull request or issue updates with review");
|
|
1331
|
+
wouldLimit.push("no delete actions");
|
|
1332
|
+
wouldLimit.push("no workflow or secrets modification");
|
|
1333
|
+
wouldLimit.push("no push to main without approval");
|
|
1334
|
+
} else {
|
|
1335
|
+
wouldAllow.push("read external system state");
|
|
1336
|
+
wouldLimit.push("no external write actions");
|
|
1337
|
+
}
|
|
1338
|
+
} else if (card.type === "package-script" || card.capabilities?.shell) {
|
|
1339
|
+
wouldAllow.push("run declared local commands");
|
|
1340
|
+
wouldLimit.push("no remote script execution without review");
|
|
1341
|
+
wouldLimit.push("no background shell escalation");
|
|
1342
|
+
} else if (card.type === "sensitive-surface" || card.capabilities?.sensitiveAccess) {
|
|
1343
|
+
wouldAllow.push("inspect presence of sensitive surfaces");
|
|
1344
|
+
wouldLimit.push("no secret value reads");
|
|
1345
|
+
wouldLimit.push("no credential export or copying");
|
|
1346
|
+
} else if (card.capabilities?.filesystemWrite) {
|
|
1347
|
+
wouldAllow.push("modify project files within the working tree");
|
|
1348
|
+
wouldLimit.push("no destructive delete actions");
|
|
1349
|
+
} else {
|
|
1350
|
+
wouldAllow.push("read local configuration");
|
|
1351
|
+
wouldLimit.push("no write actions by default");
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
return {
|
|
1355
|
+
componentId: card.id,
|
|
1356
|
+
name: card.name,
|
|
1357
|
+
type: card.type,
|
|
1358
|
+
currentCapabilities,
|
|
1359
|
+
recommendedMode,
|
|
1360
|
+
wouldAllow,
|
|
1361
|
+
wouldLimit,
|
|
1362
|
+
ttlRecommendation,
|
|
1363
|
+
reason,
|
|
1364
|
+
};
|
|
1365
|
+
});
|
|
1366
|
+
|
|
1367
|
+
return {
|
|
1368
|
+
generatedAt,
|
|
1369
|
+
previews,
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
function summaryPathForMode(mode) {
|
|
1374
|
+
return mode === "visibility" ? VISIBILITY_SUMMARY_REL_PATH : SUMMARY_REL_PATH;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
function loadLatestAgentSecuritySummary(cwd, mode = "basic") {
|
|
1378
|
+
return safeReadJson(cwd, summaryPathForMode(mode), null);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
function loadTrustCardsEvidence(cwd) {
|
|
1382
|
+
const doc = safeReadJson(cwd, TRUST_CARDS_REL_PATH, null);
|
|
1383
|
+
return Array.isArray(doc?.items) ? doc.items : [];
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
function loadAgentSecurityEvidenceBundle(cwd, mode) {
|
|
1387
|
+
return {
|
|
1388
|
+
summary: loadLatestAgentSecuritySummary(cwd, mode),
|
|
1389
|
+
trustCards: loadTrustCardsEvidence(cwd),
|
|
1390
|
+
riskDiff: mode === "visibility" ? safeReadJson(cwd, RISK_DIFF_REL_PATH, null) : null,
|
|
1391
|
+
permissionPreview: mode === "visibility" ? safeReadJson(cwd, PERMISSION_PREVIEW_REL_PATH, null) : null,
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
function loadPersistedInventoryForActions(cwd) {
|
|
1396
|
+
return {
|
|
1397
|
+
trustCards: loadTrustCardsEvidence(cwd),
|
|
1398
|
+
summary: loadLatestAgentSecuritySummary(cwd, "visibility") || loadLatestAgentSecuritySummary(cwd, "basic"),
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
function buildSourceTrustDocument(generatedAt, trustCards) {
|
|
1403
|
+
return {
|
|
1404
|
+
sourceTrustVersion: 1,
|
|
1405
|
+
what: "Agent Security source trust classifications.",
|
|
1406
|
+
generatedAt,
|
|
1407
|
+
items: trustCards
|
|
1408
|
+
.filter((card) => card.status !== "removed")
|
|
1409
|
+
.map((card) => ({
|
|
1410
|
+
...card.sourceTrust,
|
|
1411
|
+
sourceId: card.id,
|
|
1412
|
+
})),
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
function buildInstructionRiskDocument(generatedAt, trustCards) {
|
|
1417
|
+
const items = trustCards
|
|
1418
|
+
.filter((card) => card.status !== "removed")
|
|
1419
|
+
.filter((card) => card.instructionRisk !== "low" || (Array.isArray(card.instructionFindings) && card.instructionFindings.length > 0))
|
|
1420
|
+
.map((card) => ({
|
|
1421
|
+
sourceId: card.id,
|
|
1422
|
+
sourceType: card.sourceType,
|
|
1423
|
+
name: card.name,
|
|
1424
|
+
source: card.source,
|
|
1425
|
+
instructionRisk: card.instructionRisk,
|
|
1426
|
+
signals: card.instructionSignals || [],
|
|
1427
|
+
findings: (card.instructionFindings || []).map((finding) => ({
|
|
1428
|
+
category: finding.category,
|
|
1429
|
+
severity: finding.severity,
|
|
1430
|
+
message: finding.message,
|
|
1431
|
+
evidencePreview: boundedPreview(finding.evidencePreview || "", 120),
|
|
1432
|
+
location: finding.location || null,
|
|
1433
|
+
})),
|
|
1434
|
+
recommendation: card.sourceTrust?.recommendation || card.recommendation,
|
|
1435
|
+
}));
|
|
1436
|
+
|
|
1437
|
+
return {
|
|
1438
|
+
instructionRiskVersion: 1,
|
|
1439
|
+
what: "Agent Security instruction risk findings.",
|
|
1440
|
+
generatedAt,
|
|
1441
|
+
counts: {
|
|
1442
|
+
suspiciousSources: items.length,
|
|
1443
|
+
highRiskSources: items.filter((item) => item.instructionRisk === "high").length,
|
|
1444
|
+
highRiskFindings: items.reduce((sum, item) => sum + item.findings.filter((finding) => finding.severity === "high").length, 0),
|
|
1445
|
+
},
|
|
1446
|
+
items,
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
function writeInstructionRiskEvidence(cwd, generatedAt, trustCards, mode, options = {}) {
|
|
1451
|
+
const sourceTrustDoc = buildSourceTrustDocument(generatedAt, trustCards);
|
|
1452
|
+
const instructionRiskDoc = buildInstructionRiskDocument(generatedAt, trustCards);
|
|
1453
|
+
safeWriteJson(cwd, SOURCE_TRUST_REL_PATH, sourceTrustDoc);
|
|
1454
|
+
safeWriteJson(cwd, INSTRUCTION_RISK_REL_PATH, instructionRiskDoc);
|
|
1455
|
+
|
|
1456
|
+
if (options.writeAudit !== false) {
|
|
1457
|
+
sourceTrustDoc.items.forEach((item) => {
|
|
1458
|
+
appendJsonl(cwd, INSTRUCTION_AUDIT_LOG_REL_PATH, {
|
|
1459
|
+
eventType: "agent_security_source_trust_classified",
|
|
1460
|
+
mode,
|
|
1461
|
+
sourceId: item.sourceId,
|
|
1462
|
+
sourceType: item.sourceType,
|
|
1463
|
+
trustLevel: item.trustLevel,
|
|
1464
|
+
instructionRisk: item.instructionRisk,
|
|
1465
|
+
signals: [],
|
|
1466
|
+
reason: item.recommendation,
|
|
1467
|
+
timestamp: generatedAt,
|
|
1468
|
+
});
|
|
1469
|
+
});
|
|
1470
|
+
instructionRiskDoc.items.forEach((item) => {
|
|
1471
|
+
appendJsonl(cwd, INSTRUCTION_AUDIT_LOG_REL_PATH, {
|
|
1472
|
+
eventType: "agent_security_instruction_risk_detected",
|
|
1473
|
+
mode,
|
|
1474
|
+
sourceId: item.sourceId,
|
|
1475
|
+
sourceType: item.sourceType,
|
|
1476
|
+
trustLevel: sourceTrustDoc.items.find((source) => source.sourceId === item.sourceId)?.trustLevel || "unknown",
|
|
1477
|
+
instructionRisk: item.instructionRisk,
|
|
1478
|
+
signals: item.signals,
|
|
1479
|
+
reason: item.recommendation,
|
|
1480
|
+
timestamp: generatedAt,
|
|
1481
|
+
});
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
return {
|
|
1486
|
+
sourceTrustPath: SOURCE_TRUST_REL_PATH,
|
|
1487
|
+
instructionRiskPath: INSTRUCTION_RISK_REL_PATH,
|
|
1488
|
+
auditPath: INSTRUCTION_AUDIT_LOG_REL_PATH,
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
function loadInstructionRemediations(cwd) {
|
|
1493
|
+
const doc = safeReadJson(cwd, INSTRUCTION_REMEDIATIONS_REL_PATH, null);
|
|
1494
|
+
if (!doc || typeof doc !== "object" || !Array.isArray(doc.items)) {
|
|
1495
|
+
return {
|
|
1496
|
+
remediationVersion: 1,
|
|
1497
|
+
items: [],
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
return doc;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
function activeRemediationActionsBySource(cwd) {
|
|
1504
|
+
const doc = loadInstructionRemediations(cwd);
|
|
1505
|
+
const bySource = new Map();
|
|
1506
|
+
doc.items.forEach((item) => {
|
|
1507
|
+
if (!item || !item.sourceId) return;
|
|
1508
|
+
const actions = bySource.get(item.sourceId) || [];
|
|
1509
|
+
actions.push(String(item.action || ""));
|
|
1510
|
+
bySource.set(item.sourceId, actions);
|
|
1511
|
+
});
|
|
1512
|
+
return bySource;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
function loadSourceTrustRecords(cwd) {
|
|
1516
|
+
const doc = safeReadJson(cwd, SOURCE_TRUST_REL_PATH, null);
|
|
1517
|
+
if (!doc || typeof doc !== "object" || !Array.isArray(doc.items)) return [];
|
|
1518
|
+
const remediationMap = activeRemediationActionsBySource(cwd);
|
|
1519
|
+
return doc.items.map((item) => ({
|
|
1520
|
+
...item,
|
|
1521
|
+
remediationActions: remediationMap.get(item.sourceId) || item.remediationActions || [],
|
|
1522
|
+
allowedToInfluenceTools: (remediationMap.get(item.sourceId) || []).includes("trust_source")
|
|
1523
|
+
? item.allowedToInfluenceTools
|
|
1524
|
+
: item.allowedToInfluenceTools && !(remediationMap.get(item.sourceId) || []).some((action) => action === "treat_as_data_only" || action === "quarantine_source"),
|
|
1525
|
+
}));
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
function resolveRelatedSourcesForAction(cwd, action, componentCard) {
|
|
1529
|
+
const sourceRecords = loadSourceTrustRecords(cwd);
|
|
1530
|
+
const requestedIds = Array.isArray(action?.relatedSourceIds)
|
|
1531
|
+
? action.relatedSourceIds
|
|
1532
|
+
: Array.isArray(action?.context?.relatedSourceIds)
|
|
1533
|
+
? action.context.relatedSourceIds
|
|
1534
|
+
: [];
|
|
1535
|
+
const ids = requestedIds.length > 0
|
|
1536
|
+
? requestedIds.map((item) => String(item || "")).filter(Boolean)
|
|
1537
|
+
: componentCard?.id
|
|
1538
|
+
? [componentCard.id]
|
|
1539
|
+
: [];
|
|
1540
|
+
if (!ids.length) return [];
|
|
1541
|
+
const byId = new Map(sourceRecords.map((item) => [item.sourceId, item]));
|
|
1542
|
+
const resolved = ids.map((id) => byId.get(id)).filter(Boolean);
|
|
1543
|
+
if (resolved.length === 0 && componentCard?.sourceTrust && ids.includes(componentCard.id)) {
|
|
1544
|
+
resolved.push({
|
|
1545
|
+
...componentCard.sourceTrust,
|
|
1546
|
+
sourceId: componentCard.id,
|
|
1547
|
+
sourceType: componentCard.sourceType || componentCard.sourceTrust.sourceType,
|
|
1548
|
+
instructionRisk: componentCard.instructionRisk || componentCard.sourceTrust.instructionRisk || "low",
|
|
1549
|
+
remediationActions: componentCard.sourceTrust.remediationActions || [],
|
|
1550
|
+
});
|
|
1551
|
+
}
|
|
1552
|
+
return resolved;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
function recordAgentSecurityOperation(cwd, operation, tracker, details, writeEvidence) {
|
|
1556
|
+
if (writeEvidence === false || !tracker) return null;
|
|
1557
|
+
const entry = tracker.finish({
|
|
1558
|
+
operation,
|
|
1559
|
+
...details,
|
|
1560
|
+
});
|
|
1561
|
+
recordPerformanceSummary(cwd, entry);
|
|
1562
|
+
return entry;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
function writeBasicEvidence(cwd, generatedAt, previousSnapshot, trustCards) {
|
|
1566
|
+
const summary = summarizeTrustCards("basic", trustCards, generatedAt, null);
|
|
1567
|
+
const nextSnapshot = buildMergedSnapshot(previousSnapshot, "basic", trustCards.filter((card) => card.status !== "removed"), generatedAt);
|
|
1568
|
+
const instructionEvidence = writeInstructionRiskEvidence(cwd, generatedAt, trustCards, "basic");
|
|
1569
|
+
|
|
1570
|
+
safeWriteJson(cwd, SNAPSHOT_REL_PATH, nextSnapshot);
|
|
1571
|
+
safeWriteJson(cwd, SUMMARY_REL_PATH, summary);
|
|
1572
|
+
safeWriteJson(cwd, TRUST_CARDS_REL_PATH, {
|
|
1573
|
+
trustCardsVersion: 1,
|
|
1574
|
+
what: "Agent Security trust cards.",
|
|
1575
|
+
generatedAt,
|
|
1576
|
+
items: trustCards,
|
|
1577
|
+
});
|
|
1578
|
+
|
|
1579
|
+
appendJsonl(cwd, AUDIT_LOG_REL_PATH, {
|
|
1580
|
+
eventType: "agent_security_basic_check",
|
|
1581
|
+
mode: "basic",
|
|
1582
|
+
componentsScanned: summary.componentsScanned,
|
|
1583
|
+
newComponents: summary.newComponents,
|
|
1584
|
+
highRisk: summary.highRisk,
|
|
1585
|
+
mediumRisk: summary.mediumRisk,
|
|
1586
|
+
lowRisk: summary.lowRisk,
|
|
1587
|
+
timestamp: generatedAt,
|
|
1588
|
+
});
|
|
1589
|
+
|
|
1590
|
+
return {
|
|
1591
|
+
snapshotPath: SNAPSHOT_REL_PATH,
|
|
1592
|
+
summaryPath: SUMMARY_REL_PATH,
|
|
1593
|
+
trustCardsPath: TRUST_CARDS_REL_PATH,
|
|
1594
|
+
sourceTrustPath: instructionEvidence.sourceTrustPath,
|
|
1595
|
+
instructionRiskPath: instructionEvidence.instructionRiskPath,
|
|
1596
|
+
performanceSummaryPath: PERFORMANCE_SUMMARY_REL_PATH,
|
|
1597
|
+
scanCachePath: SCAN_CACHE_REL_PATH,
|
|
1598
|
+
auditPath: AUDIT_LOG_REL_PATH,
|
|
1599
|
+
summary,
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
function writeVisibilityEvidence(cwd, generatedAt, previousSnapshot, trustCards) {
|
|
1604
|
+
const riskDiff = buildRiskDiff(previousSnapshot, trustCards, generatedAt);
|
|
1605
|
+
const permissionPreview = buildPermissionPreview(trustCards, generatedAt);
|
|
1606
|
+
const summary = summarizeTrustCards("visibility", trustCards, generatedAt, null, {
|
|
1607
|
+
riskIncreases: riskDiff.summary.riskIncreases,
|
|
1608
|
+
permissionExpansions: riskDiff.summary.permissionExpansions,
|
|
1609
|
+
permissionPreviews: permissionPreview.previews.length,
|
|
1610
|
+
});
|
|
1611
|
+
const nextSnapshot = buildMergedSnapshot(previousSnapshot, "visibility", trustCards.filter((card) => card.status !== "removed"), generatedAt);
|
|
1612
|
+
const instructionEvidence = writeInstructionRiskEvidence(cwd, generatedAt, trustCards, "visibility");
|
|
1613
|
+
|
|
1614
|
+
safeWriteJson(cwd, SNAPSHOT_REL_PATH, nextSnapshot);
|
|
1615
|
+
safeWriteJson(cwd, VISIBILITY_SUMMARY_REL_PATH, summary);
|
|
1616
|
+
safeWriteJson(cwd, TRUST_CARDS_REL_PATH, {
|
|
1617
|
+
trustCardsVersion: 2,
|
|
1618
|
+
what: "Agent Security trust cards.",
|
|
1619
|
+
generatedAt,
|
|
1620
|
+
items: trustCards,
|
|
1621
|
+
});
|
|
1622
|
+
safeWriteJson(cwd, RISK_DIFF_REL_PATH, riskDiff);
|
|
1623
|
+
safeWriteJson(cwd, PERMISSION_PREVIEW_REL_PATH, permissionPreview);
|
|
1624
|
+
|
|
1625
|
+
appendJsonl(cwd, AUDIT_LOG_REL_PATH, {
|
|
1626
|
+
eventType: "agent_security_visibility_scan",
|
|
1627
|
+
mode: "visibility",
|
|
1628
|
+
componentsScanned: summary.componentsScanned,
|
|
1629
|
+
newComponents: summary.newComponents,
|
|
1630
|
+
changedComponents: summary.changedComponents,
|
|
1631
|
+
riskIncreases: summary.riskIncreases,
|
|
1632
|
+
permissionExpansions: summary.permissionExpansions,
|
|
1633
|
+
highRisk: summary.highRisk,
|
|
1634
|
+
mediumRisk: summary.mediumRisk,
|
|
1635
|
+
timestamp: generatedAt,
|
|
1636
|
+
});
|
|
1637
|
+
|
|
1638
|
+
return {
|
|
1639
|
+
snapshotPath: SNAPSHOT_REL_PATH,
|
|
1640
|
+
summaryPath: VISIBILITY_SUMMARY_REL_PATH,
|
|
1641
|
+
trustCardsPath: TRUST_CARDS_REL_PATH,
|
|
1642
|
+
sourceTrustPath: instructionEvidence.sourceTrustPath,
|
|
1643
|
+
instructionRiskPath: instructionEvidence.instructionRiskPath,
|
|
1644
|
+
performanceSummaryPath: PERFORMANCE_SUMMARY_REL_PATH,
|
|
1645
|
+
scanCachePath: SCAN_CACHE_REL_PATH,
|
|
1646
|
+
riskDiffPath: RISK_DIFF_REL_PATH,
|
|
1647
|
+
permissionPreviewPath: PERMISSION_PREVIEW_REL_PATH,
|
|
1648
|
+
auditPath: AUDIT_LOG_REL_PATH,
|
|
1649
|
+
summary,
|
|
1650
|
+
riskDiff,
|
|
1651
|
+
permissionPreview,
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
function loadDecisionSummary(cwd) {
|
|
1656
|
+
return safeReadJson(cwd, DECISION_SUMMARY_REL_PATH, null);
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
function saveDecisionSummary(cwd, summary) {
|
|
1660
|
+
safeWriteJson(cwd, DECISION_SUMMARY_REL_PATH, summary);
|
|
1661
|
+
return summary;
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
function updateDecisionSummary(cwd, payload) {
|
|
1665
|
+
const previous = loadDecisionSummary(cwd) || {
|
|
1666
|
+
decisionSummaryVersion: 1,
|
|
1667
|
+
what: "Agent Security warn-mode decision summary.",
|
|
1668
|
+
generatedAt: null,
|
|
1669
|
+
mode: "warn",
|
|
1670
|
+
counts: {
|
|
1671
|
+
approveOnce: 0,
|
|
1672
|
+
denied: 0,
|
|
1673
|
+
limited: 0,
|
|
1674
|
+
},
|
|
1675
|
+
latestDecision: null,
|
|
1676
|
+
recentDecisions: [],
|
|
1677
|
+
};
|
|
1678
|
+
|
|
1679
|
+
const counts = {
|
|
1680
|
+
approveOnce: Number(previous.counts?.approveOnce || 0),
|
|
1681
|
+
denied: Number(previous.counts?.denied || 0),
|
|
1682
|
+
limited: Number(previous.counts?.limited || 0),
|
|
1683
|
+
};
|
|
1684
|
+
|
|
1685
|
+
if (payload.decision === "approve_once") counts.approveOnce += 1;
|
|
1686
|
+
if (payload.decision === "deny") counts.denied += 1;
|
|
1687
|
+
if (payload.decision === "let_wuz_limit") counts.limited += 1;
|
|
1688
|
+
|
|
1689
|
+
const nextDecision = {
|
|
1690
|
+
decision: payload.decision,
|
|
1691
|
+
effectiveBehavior: payload.effectiveBehavior,
|
|
1692
|
+
actionId: payload.actionId,
|
|
1693
|
+
componentId: payload.componentId,
|
|
1694
|
+
componentType: payload.componentType,
|
|
1695
|
+
actionType: payload.actionType,
|
|
1696
|
+
riskLevel: payload.riskLevel,
|
|
1697
|
+
timestamp: payload.timestamp,
|
|
1698
|
+
};
|
|
1699
|
+
|
|
1700
|
+
const recentDecisions = [nextDecision, ...(Array.isArray(previous.recentDecisions) ? previous.recentDecisions : [])]
|
|
1701
|
+
.slice(0, 5);
|
|
1702
|
+
|
|
1703
|
+
return saveDecisionSummary(cwd, {
|
|
1704
|
+
decisionSummaryVersion: 1,
|
|
1705
|
+
what: "Agent Security warn-mode decision summary.",
|
|
1706
|
+
generatedAt: payload.timestamp,
|
|
1707
|
+
mode: "warn",
|
|
1708
|
+
counts,
|
|
1709
|
+
latestDecision: nextDecision,
|
|
1710
|
+
recentDecisions,
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
function resolveComponentCard(trustCards, action) {
|
|
1715
|
+
if (!Array.isArray(trustCards)) return null;
|
|
1716
|
+
if (action?.componentId) {
|
|
1717
|
+
const exact = trustCards.find((card) => card.id === action.componentId);
|
|
1718
|
+
if (exact) return exact;
|
|
1719
|
+
}
|
|
1720
|
+
if (action?.componentType && action?.source) {
|
|
1721
|
+
const exactSource = trustCards.find((card) => card.type === action.componentType && card.source === action.source);
|
|
1722
|
+
if (exactSource) return exactSource;
|
|
1723
|
+
}
|
|
1724
|
+
if (action?.componentType && action?.target) {
|
|
1725
|
+
const byTarget = trustCards.find((card) => card.type === action.componentType && String(action.target).includes(card.name));
|
|
1726
|
+
if (byTarget) return byTarget;
|
|
1727
|
+
}
|
|
1728
|
+
return null;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
function effectiveBehaviorForDecision(decision) {
|
|
1732
|
+
if (decision === "deny") return "deny_recommended";
|
|
1733
|
+
if (decision === "let_wuz_limit") return "limit_plan_only";
|
|
1734
|
+
if (decision === "auto_allow_limited") return "limited";
|
|
1735
|
+
if (decision === "auto_deny") return "blocked";
|
|
1736
|
+
return "allowed";
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
function ttlSecondsFromRecommendation(value) {
|
|
1740
|
+
const normalized = String(value || "").toLowerCase();
|
|
1741
|
+
if (normalized.includes("30 minute")) return 1800;
|
|
1742
|
+
if (normalized.includes("15 minute")) return 900;
|
|
1743
|
+
if (normalized.includes("this session")) return null;
|
|
1744
|
+
if (normalized.includes("once")) return null;
|
|
1745
|
+
return null;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
function buildDecisionRecord(input) {
|
|
1749
|
+
return {
|
|
1750
|
+
decisionId: `decision-${shortHash(`${input.timestamp}:${input.actionId}:${input.decision}`)}`,
|
|
1751
|
+
actionId: input.actionId,
|
|
1752
|
+
componentId: input.componentId || null,
|
|
1753
|
+
decision: input.decision,
|
|
1754
|
+
mode: input.mode || "warn",
|
|
1755
|
+
effectiveBehavior: effectiveBehaviorForDecision(input.decision),
|
|
1756
|
+
ttlSeconds: ttlSecondsFromRecommendation(input.limitPlan?.ttlRecommendation || input.ttlRecommendation),
|
|
1757
|
+
scope: input.limitPlan?.scope || {
|
|
1758
|
+
actionType: input.actionType || null,
|
|
1759
|
+
target: input.target || null,
|
|
1760
|
+
},
|
|
1761
|
+
denied: input.limitPlan?.wouldDeny || [],
|
|
1762
|
+
reason: input.limitPlan?.reason || input.reason || "",
|
|
1763
|
+
timestamp: input.timestamp,
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
function writeDecisionAuditEvents(cwd, payload) {
|
|
1768
|
+
const baseEvent = {
|
|
1769
|
+
mode: "warn",
|
|
1770
|
+
actionId: payload.actionId,
|
|
1771
|
+
componentId: payload.componentId || null,
|
|
1772
|
+
componentType: payload.componentType || null,
|
|
1773
|
+
actionType: payload.actionType,
|
|
1774
|
+
riskLevel: payload.riskLevel,
|
|
1775
|
+
recommendedDecision: payload.recommendedDecision,
|
|
1776
|
+
userDecision: payload.userDecision,
|
|
1777
|
+
effectiveBehavior: payload.effectiveBehavior,
|
|
1778
|
+
timestamp: payload.timestamp,
|
|
1779
|
+
};
|
|
1780
|
+
|
|
1781
|
+
appendJsonl(cwd, DECISION_AUDIT_LOG_REL_PATH, {
|
|
1782
|
+
eventType: "agent_security_action_preflight",
|
|
1783
|
+
...baseEvent,
|
|
1784
|
+
});
|
|
1785
|
+
appendJsonl(cwd, DECISION_AUDIT_LOG_REL_PATH, {
|
|
1786
|
+
eventType: "agent_security_decision_recorded",
|
|
1787
|
+
...baseEvent,
|
|
1788
|
+
});
|
|
1789
|
+
|
|
1790
|
+
if (payload.userDecision === "let_wuz_limit" && payload.limitPlan) {
|
|
1791
|
+
appendJsonl(cwd, DECISION_AUDIT_LOG_REL_PATH, {
|
|
1792
|
+
eventType: "agent_security_limit_plan_created",
|
|
1793
|
+
...baseEvent,
|
|
1794
|
+
ttlRecommendation: payload.limitPlan.ttlRecommendation || null,
|
|
1795
|
+
denied: payload.limitPlan.wouldDeny || [],
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
function buildBasicStatusLine(summary, options = {}) {
|
|
1801
|
+
if (!summary) return null;
|
|
1802
|
+
const needsBaselineRecording = options.needsBaselineRecording === true;
|
|
1803
|
+
if (summary.newComponents > 0) {
|
|
1804
|
+
if (needsBaselineRecording) {
|
|
1805
|
+
return "Agent security: New component detected. Run Basic Check to record it as reviewed.";
|
|
1806
|
+
}
|
|
1807
|
+
return `Agent security: ${summary.newComponents} new component${summary.newComponents === 1 ? "" : "s"} need review`;
|
|
1808
|
+
}
|
|
1809
|
+
if (summary.highRisk > 0) {
|
|
1810
|
+
return `Agent security: ${summary.highRisk} high risk component${summary.highRisk === 1 ? "" : "s"} need review`;
|
|
1811
|
+
}
|
|
1812
|
+
if (summary.suspiciousInstructionSources > 0) {
|
|
1813
|
+
return `Agent security: Basic check passed - ${pluralize(summary.suspiciousInstructionSources, "suspicious instruction source")}`;
|
|
1814
|
+
}
|
|
1815
|
+
return `Agent security: Basic check passed - ${summary.componentsScanned} components - 0 high risk`;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
function buildBasicDashboardBlurb(summary, options = {}) {
|
|
1819
|
+
if (!summary) return null;
|
|
1820
|
+
const needsBaselineRecording = options.needsBaselineRecording === true;
|
|
1821
|
+
const blurb = {
|
|
1822
|
+
title: "Basic Skill/MCP Check",
|
|
1823
|
+
headline: "Basic Check active",
|
|
1824
|
+
summary: summary.suspiciousInstructionSources > 0
|
|
1825
|
+
? `${summary.componentsScanned} components - ${pluralize(summary.suspiciousInstructionSources, "suspicious instruction source")}`
|
|
1826
|
+
: `${summary.componentsScanned} components - ${summary.highRisk} high risk`,
|
|
1827
|
+
nextBestAction: needsBaselineRecording && summary.newComponents > 0
|
|
1828
|
+
? "Run Basic Check to record new components as reviewed."
|
|
1829
|
+
: null,
|
|
1830
|
+
detailsAvailable: true,
|
|
1831
|
+
checkedLabel: `${summary.componentsScanned} components checked`,
|
|
1832
|
+
newLabel: `${summary.newComponents} new component${summary.newComponents === 1 ? "" : "s"}`,
|
|
1833
|
+
riskLabel: `${summary.highRisk} high risk`,
|
|
1834
|
+
};
|
|
1835
|
+
if (needsBaselineRecording && summary.newComponents > 0) {
|
|
1836
|
+
blurb.note = "New AI component detected. Run Basic Check to record it as reviewed.";
|
|
1837
|
+
}
|
|
1838
|
+
return blurb;
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
function pluralize(count, singular, plural) {
|
|
1842
|
+
return `${count} ${count === 1 ? singular : (plural || `${singular}s`)}`;
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
function buildProductSummary(mode, fields = {}) {
|
|
1846
|
+
return {
|
|
1847
|
+
mode,
|
|
1848
|
+
headline: fields.headline || "",
|
|
1849
|
+
summary: fields.summary || "",
|
|
1850
|
+
safeActionsLimited: Number(fields.safeActionsLimited || 0),
|
|
1851
|
+
actionsBlocked: Number(fields.actionsBlocked || 0),
|
|
1852
|
+
approvalRequired: Number(fields.approvalRequired || 0),
|
|
1853
|
+
jitGrantsActive: Number(fields.jitGrantsActive || 0),
|
|
1854
|
+
instructionRiskSources: Number(fields.instructionRiskSources || 0),
|
|
1855
|
+
sourcesTreatedAsData: Number(fields.sourcesTreatedAsData || 0),
|
|
1856
|
+
remediationsActive: Number(fields.remediationsActive || 0),
|
|
1857
|
+
performance: fields.performance || { lastOperationMs: null, slow: false },
|
|
1858
|
+
nextBestAction: fields.nextBestAction || null,
|
|
1859
|
+
detailsAvailable: fields.detailsAvailable !== false,
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
function buildBasicProductSummary(summary, options = {}) {
|
|
1864
|
+
const needsBaselineRecording = options.needsBaselineRecording === true;
|
|
1865
|
+
return buildProductSummary("basic", {
|
|
1866
|
+
headline: "Basic Check active",
|
|
1867
|
+
summary: needsBaselineRecording && summary.newComponents > 0
|
|
1868
|
+
? `${pluralize(summary.newComponents, "new component")} needs review`
|
|
1869
|
+
: summary.suspiciousInstructionSources > 0
|
|
1870
|
+
? `${pluralize(summary.componentsScanned, "component")} - ${pluralize(summary.suspiciousInstructionSources, "suspicious instruction source")}`
|
|
1871
|
+
: `${pluralize(summary.componentsScanned, "component")} - ${summary.highRisk} high risk`,
|
|
1872
|
+
nextBestAction: needsBaselineRecording && summary.newComponents > 0
|
|
1873
|
+
? "Run Basic Check to record new components as reviewed."
|
|
1874
|
+
: summary.suspiciousInstructionSources > 0
|
|
1875
|
+
? `Review ${pluralize(summary.suspiciousInstructionSources, "suspicious instruction source")}.`
|
|
1876
|
+
: null,
|
|
1877
|
+
performance: options.performance || { lastOperationMs: null, slow: false },
|
|
1878
|
+
});
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
function visibilityRiskLabel(summary) {
|
|
1882
|
+
if (summary.highRisk > 0) return "High risk";
|
|
1883
|
+
if (summary.mediumRisk > 0) return "Medium risk";
|
|
1884
|
+
return "Low risk";
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
function buildVisibilityStatusLine(summary, options = {}) {
|
|
1888
|
+
if (!summary) return null;
|
|
1889
|
+
const needsBaselineRecording = options.needsBaselineRecording === true;
|
|
1890
|
+
if (needsBaselineRecording && summary.newComponents > 0) {
|
|
1891
|
+
return "Agent security: New component detected. Run Visibility Scan to record it as reviewed.";
|
|
1892
|
+
}
|
|
1893
|
+
if (needsBaselineRecording && (summary.changedComponents > 0 || summary.removedComponents > 0)) {
|
|
1894
|
+
return "Agent security: Surface change detected. Run Visibility Scan to record it as reviewed.";
|
|
1895
|
+
}
|
|
1896
|
+
if (summary.highRisk === 0 && summary.permissionExpansions === 0 && summary.newComponents === 0 && summary.changedComponents === 0) {
|
|
1897
|
+
return `Agent security: Visibility clean - ${summary.componentsScanned} components - 0 high risk`;
|
|
1898
|
+
}
|
|
1899
|
+
if (summary.suspiciousInstructionSources > 0) {
|
|
1900
|
+
return `Agent security: Visibility - ${pluralize(summary.suspiciousInstructionSources, "suspicious instruction source")} - ${summary.componentsScanned} components`;
|
|
1901
|
+
}
|
|
1902
|
+
return `Agent security: Visibility - ${summary.componentsScanned} components - ${summary.highRisk} high risk - ${summary.permissionExpansions} permission expansion${summary.permissionExpansions === 1 ? "" : "s"}`;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
function buildVisibilityDashboardCard(summary, options = {}) {
|
|
1906
|
+
if (!summary) return null;
|
|
1907
|
+
const needsBaselineRecording = options.needsBaselineRecording === true;
|
|
1908
|
+
const topFinding = (summary.topFindings || []).find((item) => item.riskLevel === "high") || summary.topFindings?.[0] || null;
|
|
1909
|
+
const card = {
|
|
1910
|
+
title: "Agent Security",
|
|
1911
|
+
subtitle: `Full visibility - ${visibilityRiskLabel(summary)}`,
|
|
1912
|
+
headline: "Visibility active",
|
|
1913
|
+
summary: `${pluralize(summary.componentsScanned, "component")} - ${summary.highRisk} high risk - ${pluralize(summary.permissionExpansions, "permission expansion")}`,
|
|
1914
|
+
nextBestAction: needsBaselineRecording && summary.newComponents > 0
|
|
1915
|
+
? "Run Visibility Scan to record the new baseline."
|
|
1916
|
+
: null,
|
|
1917
|
+
detailsAvailable: true,
|
|
1918
|
+
surfaceLabel: `${summary.componentsScanned} components mapped`,
|
|
1919
|
+
changeLabel: `${summary.newComponents} new - ${summary.changedComponents} changed - ${summary.permissionExpansions} permission expansion${summary.permissionExpansions === 1 ? "" : "s"}`,
|
|
1920
|
+
topRiskLabel: topFinding
|
|
1921
|
+
? `Top risk: ${topFinding.name} ${topFinding.type === "mcp" ? "MCP" : topFinding.type} ${topFinding.riskLevel === "high" ? "needs review" : "changed"}`
|
|
1922
|
+
: "Top risk: No high-risk findings",
|
|
1923
|
+
previewLabel: `${summary.permissionPreviews || 0} permission preview${summary.permissionPreviews === 1 ? "" : "s"}`,
|
|
1924
|
+
};
|
|
1925
|
+
if (needsBaselineRecording && summary.newComponents > 0) {
|
|
1926
|
+
card.note = "New AI component detected. Run Visibility Scan to record it as reviewed.";
|
|
1927
|
+
} else if (needsBaselineRecording && (summary.changedComponents > 0 || summary.removedComponents > 0)) {
|
|
1928
|
+
card.note = "Agent surface changed. Run Visibility Scan to record the new baseline.";
|
|
1929
|
+
}
|
|
1930
|
+
return card;
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
function buildWarnEnforcementLabel(enforcementSummary) {
|
|
1934
|
+
const counts = enforcementSummary?.counts || {};
|
|
1935
|
+
const blocked = Number(counts.blocked || 0);
|
|
1936
|
+
const approvedOnce = Number(counts.approvedOnce || 0);
|
|
1937
|
+
const limited = Number(counts.limited || 0);
|
|
1938
|
+
const unavailable = Number(counts.unavailable || 0);
|
|
1939
|
+
const fileWritesBlocked = Number(counts.fileWritesBlocked || 0);
|
|
1940
|
+
const fileWritesLimited = Number(counts.fileWritesLimited || 0);
|
|
1941
|
+
const fileWritesApprovedOnce = Number(counts.fileWritesApprovedOnce || 0);
|
|
1942
|
+
const packageActionsBlocked = Number(counts.packageActionsBlocked || 0);
|
|
1943
|
+
const packageActionsLimited = Number(counts.packageActionsLimited || 0);
|
|
1944
|
+
const packageActionsApprovedOnce = Number(counts.packageActionsApprovedOnce || 0);
|
|
1945
|
+
const mcpActionsBlocked = Number(counts.mcpActionsBlocked || 0);
|
|
1946
|
+
const mcpActionsLimited = Number(counts.mcpActionsLimited || 0);
|
|
1947
|
+
const mcpActionsApprovedOnce = Number(counts.mcpActionsApprovedOnce || 0);
|
|
1948
|
+
const specificBlockedKinds = [
|
|
1949
|
+
mcpActionsBlocked > 0,
|
|
1950
|
+
packageActionsBlocked > 0,
|
|
1951
|
+
fileWritesBlocked > 0,
|
|
1952
|
+
].filter(Boolean).length;
|
|
1953
|
+
const specificLimitedKinds = [
|
|
1954
|
+
mcpActionsLimited > 0,
|
|
1955
|
+
packageActionsLimited > 0,
|
|
1956
|
+
fileWritesLimited > 0,
|
|
1957
|
+
].filter(Boolean).length;
|
|
1958
|
+
|
|
1959
|
+
if (specificBlockedKinds > 1) return `${blocked} actions blocked`;
|
|
1960
|
+
if (specificLimitedKinds > 1 && blocked === 0) return `${limited} actions limited`;
|
|
1961
|
+
if (mcpActionsBlocked > 0) return `${mcpActionsBlocked} MCP action blocked`;
|
|
1962
|
+
if (mcpActionsLimited > 0) return `${mcpActionsLimited} MCP action limited`;
|
|
1963
|
+
if (mcpActionsApprovedOnce > 0) return `${mcpActionsApprovedOnce} MCP action approved once`;
|
|
1964
|
+
|
|
1965
|
+
if (packageActionsBlocked > 0) return `${packageActionsBlocked} package action blocked`;
|
|
1966
|
+
if (packageActionsLimited > 0) return `${packageActionsLimited} package action limited`;
|
|
1967
|
+
if (packageActionsApprovedOnce > 0) return `${packageActionsApprovedOnce} package action approved once`;
|
|
1968
|
+
|
|
1969
|
+
if (fileWritesBlocked > 0) return `${fileWritesBlocked} file write blocked`;
|
|
1970
|
+
if (fileWritesLimited > 0) return `${fileWritesLimited} file write limited`;
|
|
1971
|
+
if (fileWritesApprovedOnce > 0) return `${fileWritesApprovedOnce} file write approved once`;
|
|
1972
|
+
|
|
1973
|
+
if (blocked > 0) return `${blocked} action blocked`;
|
|
1974
|
+
if (approvedOnce > 0) return `${approvedOnce} approved once`;
|
|
1975
|
+
if (limited > 0) return `${limited} action limited`;
|
|
1976
|
+
if (unavailable > 0) return `${unavailable} unavailable`;
|
|
1977
|
+
return "no enforced actions yet";
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
function buildWarnProductSummary(summary, decisionSummary, enforcementSummary) {
|
|
1981
|
+
const enforcementCounts = enforcementSummary?.counts || {};
|
|
1982
|
+
const decisionCounts = decisionSummary?.counts || {};
|
|
1983
|
+
const blocked = Number(enforcementCounts.blocked || 0);
|
|
1984
|
+
const limited = Number(enforcementCounts.limited || 0);
|
|
1985
|
+
const approvedOnce = Number(enforcementCounts.approvedOnce || 0) + Number(decisionCounts.approveOnce || 0);
|
|
1986
|
+
const nextBestAction = summary.highRisk > 0
|
|
1987
|
+
? `Review ${pluralize(summary.highRisk, "high-risk action")} before proceeding.`
|
|
1988
|
+
: null;
|
|
1989
|
+
|
|
1990
|
+
if (blocked > 0 || limited > 0 || approvedOnce > 0) {
|
|
1991
|
+
const parts = [];
|
|
1992
|
+
if (limited > 0) parts.push(`${pluralize(limited, "action")} limited`);
|
|
1993
|
+
if (blocked > 0) parts.push(`${pluralize(blocked, "action")} blocked`);
|
|
1994
|
+
if (approvedOnce > 0) parts.push(`${pluralize(approvedOnce, "action")} approved once`);
|
|
1995
|
+
return buildProductSummary("warn", {
|
|
1996
|
+
headline: "Warn & Approve active",
|
|
1997
|
+
summary: parts.join(" - "),
|
|
1998
|
+
safeActionsLimited: limited,
|
|
1999
|
+
actionsBlocked: blocked,
|
|
2000
|
+
approvalRequired: summary.highRisk,
|
|
2001
|
+
nextBestAction,
|
|
2002
|
+
performance: buildCompactPerformanceSummary(summary.cwd || "", ["agent_security_preflight", "agent_security_guarded_action"]),
|
|
2003
|
+
});
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
return buildProductSummary("warn", {
|
|
2007
|
+
headline: "Warn & Approve active",
|
|
2008
|
+
summary: "Approvals enabled",
|
|
2009
|
+
approvalRequired: summary.highRisk,
|
|
2010
|
+
nextBestAction,
|
|
2011
|
+
performance: buildCompactPerformanceSummary(summary.cwd || "", ["agent_security_preflight", "agent_security_guarded_action"]),
|
|
2012
|
+
});
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
function buildWarnStatusLine(summary, decisionSummary, enforcementSummary) {
|
|
2016
|
+
const enforcementCounts = enforcementSummary?.counts || {};
|
|
2017
|
+
const hasEnforcementActivity =
|
|
2018
|
+
Number(enforcementCounts.attempted || 0) > 0 ||
|
|
2019
|
+
Number(enforcementCounts.blocked || 0) > 0 ||
|
|
2020
|
+
Number(enforcementCounts.approvedOnce || 0) > 0 ||
|
|
2021
|
+
Number(enforcementCounts.limited || 0) > 0 ||
|
|
2022
|
+
Number(enforcementCounts.unavailable || 0) > 0;
|
|
2023
|
+
|
|
2024
|
+
if (hasEnforcementActivity) {
|
|
2025
|
+
return `Agent security: Warn mode - approvals enabled - ${buildWarnEnforcementLabel(enforcementSummary)}`;
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
const counts = decisionSummary?.counts || {};
|
|
2029
|
+
const limited = Number(counts.limited || 0);
|
|
2030
|
+
const denied = Number(counts.denied || 0);
|
|
2031
|
+
if (limited > 0 || denied > 0) {
|
|
2032
|
+
return `Agent security: Warn mode - ${limited} action limited - ${denied} denied`;
|
|
2033
|
+
}
|
|
2034
|
+
if (Number(summary?.suspiciousInstructionSources || 0) > 0) {
|
|
2035
|
+
return "Agent security: Warn mode - approval needed for suspicious source";
|
|
2036
|
+
}
|
|
2037
|
+
return "Agent security: Warn mode - approvals enabled";
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
function buildWarnDashboardCard(summary, decisionSummary, enforcementSummary) {
|
|
2041
|
+
const counts = decisionSummary?.counts || {};
|
|
2042
|
+
const topFinding = (summary.topFindings || []).find((item) => item.riskLevel === "high") || summary.topFindings?.[0] || null;
|
|
2043
|
+
const enforcementCounts = enforcementSummary?.counts || {};
|
|
2044
|
+
const card = {
|
|
2045
|
+
title: "Agent Security",
|
|
2046
|
+
subtitle: `Warn mode active - ${summary.componentsScanned} components mapped - ${summary.highRisk} high risk`,
|
|
2047
|
+
headline: "Warn & Approve active",
|
|
2048
|
+
summary: Number(enforcementCounts.blocked || 0) > 0 || Number(enforcementCounts.limited || 0) > 0
|
|
2049
|
+
? buildWarnEnforcementLabel(enforcementSummary)
|
|
2050
|
+
: "Approvals enabled",
|
|
2051
|
+
nextBestAction: topFinding ? `Review ${topFinding.name} before continuing.` : null,
|
|
2052
|
+
detailsAvailable: true,
|
|
2053
|
+
surfaceLabel: `${summary.componentsScanned} components mapped`,
|
|
2054
|
+
changeLabel: `${summary.newComponents} new - ${summary.changedComponents} changed - ${summary.permissionExpansions} permission expansion${summary.permissionExpansions === 1 ? "" : "s"}`,
|
|
2055
|
+
decisionsLabel: `${Number(counts.approveOnce || 0)} action approved once - ${Number(counts.limited || 0)} action limited - ${Number(counts.denied || 0)} denied`,
|
|
2056
|
+
pendingRiskLabel: topFinding
|
|
2057
|
+
? `Top pending risk: ${topFinding.name} ${topFinding.type === "mcp" ? "MCP" : topFinding.type} actions require approval`
|
|
2058
|
+
: "Top pending risk: no high-risk findings",
|
|
2059
|
+
};
|
|
2060
|
+
|
|
2061
|
+
if (
|
|
2062
|
+
Number(enforcementCounts.attempted || 0) > 0 ||
|
|
2063
|
+
Number(enforcementCounts.blocked || 0) > 0 ||
|
|
2064
|
+
Number(enforcementCounts.approvedOnce || 0) > 0 ||
|
|
2065
|
+
Number(enforcementCounts.limited || 0) > 0 ||
|
|
2066
|
+
Number(enforcementCounts.unavailable || 0) > 0
|
|
2067
|
+
) {
|
|
2068
|
+
card.enforcementLabel = buildWarnEnforcementLabel(enforcementSummary);
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
return card;
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
function buildAutoStatusLine(summary, enforcementSummary) {
|
|
2075
|
+
const counts = enforcementSummary?.counts || {};
|
|
2076
|
+
const autoLimited = Number(counts.autoLimited || 0);
|
|
2077
|
+
const autoDenied = Number(counts.autoDenied || 0) || Number(counts.blocked || 0);
|
|
2078
|
+
const autoRequiresApproval = Number(counts.autoRequiresApproval || 0);
|
|
2079
|
+
const autoRecommendedOnly = Number(counts.autoRecommendedOnly || 0);
|
|
2080
|
+
const suspiciousSources = Number(summary?.suspiciousInstructionSources || 0);
|
|
2081
|
+
|
|
2082
|
+
if (autoLimited > 0 || autoRequiresApproval > 0 || autoDenied > 0) {
|
|
2083
|
+
const parts = [];
|
|
2084
|
+
if (autoLimited > 0) parts.push(`${pluralize(autoLimited, "safe action")} limited`);
|
|
2085
|
+
if (autoRequiresApproval > 0) {
|
|
2086
|
+
parts.push(autoRequiresApproval === 1 ? "1 approval needed" : `${autoRequiresApproval} approvals needed`);
|
|
2087
|
+
} else if (autoDenied > 0) {
|
|
2088
|
+
parts.push(`${pluralize(autoDenied, "dangerous action")} blocked`);
|
|
2089
|
+
}
|
|
2090
|
+
if (suspiciousSources > 0) parts.push(`${pluralize(suspiciousSources, "suspicious source")} treated as data`);
|
|
2091
|
+
return `Agent security: Auto-Minimize - ${parts.join(" - ")}`;
|
|
2092
|
+
}
|
|
2093
|
+
if (autoRecommendedOnly > 0) {
|
|
2094
|
+
return `Agent security: Auto-Minimize - no safe enforcement available for ${autoRecommendedOnly} action${autoRecommendedOnly === 1 ? "" : "s"}`;
|
|
2095
|
+
}
|
|
2096
|
+
return "Agent security: Auto-Minimize - safe actions limited automatically - risky actions still require approval";
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
function buildAutoProductSummary(summary, enforcementSummary, options = {}) {
|
|
2100
|
+
const counts = enforcementSummary?.counts || {};
|
|
2101
|
+
const autoLimited = Number(counts.autoLimited || 0);
|
|
2102
|
+
const autoDenied = Number(counts.autoDenied || 0) || Number(counts.blocked || 0);
|
|
2103
|
+
const autoRequiresApproval = Number(counts.autoRequiresApproval || 0);
|
|
2104
|
+
const autoRecommendedOnly = Number(counts.autoRecommendedOnly || 0);
|
|
2105
|
+
const activeJitGrants = Number(counts.activeJitGrants || 0);
|
|
2106
|
+
const suspiciousSources = Number(summary?.suspiciousInstructionSources || 0);
|
|
2107
|
+
const sourcesTreatedAsData = Number(summary?.sourcesTreatedAsData || 0);
|
|
2108
|
+
const activeRemediations = Number(options.activeRemediations || 0);
|
|
2109
|
+
const nextBestAction = autoRequiresApproval > 0
|
|
2110
|
+
? `Review ${autoRequiresApproval} risky action${autoRequiresApproval === 1 ? "" : "s"}.`
|
|
2111
|
+
: suspiciousSources > 0
|
|
2112
|
+
? `Review ${pluralize(suspiciousSources, "suspicious instruction source")}.`
|
|
2113
|
+
: autoDenied > 0
|
|
2114
|
+
? `Review ${autoDenied} blocked risky action${autoDenied === 1 ? "" : "s"}.`
|
|
2115
|
+
: autoRecommendedOnly > 0
|
|
2116
|
+
? `Review ${autoRecommendedOnly} unsupported action path${autoRecommendedOnly === 1 ? "" : "s"}.`
|
|
2117
|
+
: null;
|
|
2118
|
+
|
|
2119
|
+
let compactSummary = "Safe actions limited automatically";
|
|
2120
|
+
if (autoLimited > 0 || autoRequiresApproval > 0 || autoDenied > 0) {
|
|
2121
|
+
const parts = [];
|
|
2122
|
+
if (autoLimited > 0) parts.push(`${pluralize(autoLimited, "safe action")} limited`);
|
|
2123
|
+
if (autoRequiresApproval > 0) {
|
|
2124
|
+
parts.push(autoRequiresApproval === 1 ? "1 approval needed" : `${autoRequiresApproval} approvals needed`);
|
|
2125
|
+
} else if (autoDenied > 0) {
|
|
2126
|
+
parts.push(`${pluralize(autoDenied, "dangerous action")} blocked`);
|
|
2127
|
+
}
|
|
2128
|
+
compactSummary = parts.join(" - ");
|
|
2129
|
+
} else if (autoRecommendedOnly > 0) {
|
|
2130
|
+
compactSummary = `No safe enforcement available for ${pluralize(autoRecommendedOnly, "action")}`;
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
return buildProductSummary("auto", {
|
|
2134
|
+
headline: "Auto-Minimize active",
|
|
2135
|
+
summary: compactSummary,
|
|
2136
|
+
safeActionsLimited: autoLimited,
|
|
2137
|
+
actionsBlocked: autoDenied,
|
|
2138
|
+
approvalRequired: autoRequiresApproval,
|
|
2139
|
+
jitGrantsActive: activeJitGrants,
|
|
2140
|
+
instructionRiskSources: suspiciousSources,
|
|
2141
|
+
sourcesTreatedAsData,
|
|
2142
|
+
remediationsActive: activeRemediations,
|
|
2143
|
+
performance: options.performance || { lastOperationMs: null, slow: false },
|
|
2144
|
+
nextBestAction,
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
function buildAutoDashboardCard(summary, enforcementSummary, productSummary) {
|
|
2149
|
+
return {
|
|
2150
|
+
title: "Agent Security",
|
|
2151
|
+
subtitle: productSummary.headline || "Auto-Minimize active",
|
|
2152
|
+
headline: productSummary.headline || "Auto-Minimize active",
|
|
2153
|
+
summary: productSummary.summary || "Safe actions limited automatically",
|
|
2154
|
+
nextBestAction: productSummary.nextBestAction || null,
|
|
2155
|
+
detailsAvailable: true,
|
|
2156
|
+
surfaceLabel: `${summary.componentsScanned} components mapped`,
|
|
2157
|
+
changeLabel: `${summary.newComponents} new - ${summary.changedComponents} changed - ${summary.permissionExpansions} permission expansion${summary.permissionExpansions === 1 ? "" : "s"}`,
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
function buildVisibilityProductSummary(summary, options = {}) {
|
|
2162
|
+
const needsBaselineRecording = options.needsBaselineRecording === true;
|
|
2163
|
+
return buildProductSummary("visibility", {
|
|
2164
|
+
headline: "Visibility active",
|
|
2165
|
+
summary: needsBaselineRecording && summary.newComponents > 0
|
|
2166
|
+
? `${pluralize(summary.newComponents, "new component")} needs review`
|
|
2167
|
+
: summary.suspiciousInstructionSources > 0
|
|
2168
|
+
? `${pluralize(summary.suspiciousInstructionSources, "suspicious instruction source")} - ${summary.componentsScanned} components`
|
|
2169
|
+
: `${pluralize(summary.componentsScanned, "component")} - ${summary.highRisk} high risk - ${pluralize(summary.permissionExpansions, "permission expansion")}`,
|
|
2170
|
+
nextBestAction: needsBaselineRecording && summary.newComponents > 0
|
|
2171
|
+
? "Run Visibility Scan to record the new baseline."
|
|
2172
|
+
: summary.suspiciousInstructionSources > 0
|
|
2173
|
+
? `Review ${pluralize(summary.suspiciousInstructionSources, "suspicious instruction source")}.`
|
|
2174
|
+
: null,
|
|
2175
|
+
performance: options.performance || { lastOperationMs: null, slow: false },
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
function buildAgentSecuritySurface(cwd, config) {
|
|
2180
|
+
const mode = config?.agentSecurity?.mode || "off";
|
|
2181
|
+
const basicCheckEnabled = config?.agentSecurity?.basicCheckEnabled !== false;
|
|
2182
|
+
|
|
2183
|
+
if (mode === "off" || !basicCheckEnabled) return null;
|
|
2184
|
+
if (mode !== "basic" && mode !== "visibility" && mode !== "warn" && mode !== "auto") return null;
|
|
2185
|
+
|
|
2186
|
+
const inventoryMode = mode === "basic" ? "basic" : "visibility";
|
|
2187
|
+
const persisted = loadAgentSecurityEvidenceBundle(cwd, inventoryMode);
|
|
2188
|
+
const persistedSummary = persisted.summary;
|
|
2189
|
+
const trustCards = persisted.trustCards;
|
|
2190
|
+
const generatedAt = persistedSummary?.generatedAt || null;
|
|
2191
|
+
const summary = persistedSummary
|
|
2192
|
+
? { ...persistedSummary, cwd }
|
|
2193
|
+
: {
|
|
2194
|
+
mode: inventoryMode,
|
|
2195
|
+
generatedAt: null,
|
|
2196
|
+
cwd,
|
|
2197
|
+
componentsScanned: 0,
|
|
2198
|
+
newComponents: 0,
|
|
2199
|
+
changedComponents: 0,
|
|
2200
|
+
removedComponents: 0,
|
|
2201
|
+
highRisk: 0,
|
|
2202
|
+
mediumRisk: 0,
|
|
2203
|
+
lowRisk: 0,
|
|
2204
|
+
suspiciousInstructionSources: 0,
|
|
2205
|
+
highInstructionFindings: 0,
|
|
2206
|
+
sourcesTreatedAsData: 0,
|
|
2207
|
+
permissionExpansions: 0,
|
|
2208
|
+
permissionPreviews: 0,
|
|
2209
|
+
topFindings: [],
|
|
2210
|
+
};
|
|
2211
|
+
const noRecentScanLine = mode === "basic"
|
|
2212
|
+
? "Agent security: Basic check active - Last checked: never"
|
|
2213
|
+
: mode === "visibility"
|
|
2214
|
+
? "Agent security: Visibility active - No recent scan"
|
|
2215
|
+
: mode === "warn"
|
|
2216
|
+
? "Agent security: Warn mode - no recent scan"
|
|
2217
|
+
: "Agent security: Auto-Minimize - no recent scan";
|
|
2218
|
+
|
|
2219
|
+
if (mode === "basic") {
|
|
2220
|
+
const needsBaselineRecording = Boolean(persistedSummary) && summary.newComponents > 0;
|
|
2221
|
+
const showInStatus = true;
|
|
2222
|
+
const productSummary = buildBasicProductSummary(summary, {
|
|
2223
|
+
needsBaselineRecording,
|
|
2224
|
+
performance: buildCompactPerformanceSummary(cwd, "agent_security_basic_check"),
|
|
2225
|
+
});
|
|
2226
|
+
|
|
2227
|
+
return {
|
|
2228
|
+
mode,
|
|
2229
|
+
summary,
|
|
2230
|
+
productSummary,
|
|
2231
|
+
showInDashboard: true,
|
|
2232
|
+
showInStatus,
|
|
2233
|
+
hasRecordedCheck: Boolean(persistedSummary),
|
|
2234
|
+
needsBaselineRecording,
|
|
2235
|
+
statusLine: persistedSummary ? buildBasicStatusLine(summary, { needsBaselineRecording }) : noRecentScanLine,
|
|
2236
|
+
dashboardCard: persistedSummary ? buildBasicDashboardBlurb(summary, { needsBaselineRecording }) : {
|
|
2237
|
+
title: "Basic Skill/MCP Check",
|
|
2238
|
+
headline: "Basic Check active",
|
|
2239
|
+
summary: "No recent scan",
|
|
2240
|
+
nextBestAction: "Run Basic Check.",
|
|
2241
|
+
detailsAvailable: true,
|
|
2242
|
+
checkedLabel: "Last checked: never",
|
|
2243
|
+
newLabel: "0 new components",
|
|
2244
|
+
riskLabel: "0 high risk",
|
|
2245
|
+
},
|
|
2246
|
+
detailsPath: persistedSummary ? SUMMARY_REL_PATH : null,
|
|
2247
|
+
};
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
const riskDiff = persisted.riskDiff;
|
|
2251
|
+
const permissionPreview = persisted.permissionPreview;
|
|
2252
|
+
const needsBaselineRecording = Boolean(persistedSummary) && (summary.newComponents > 0 || summary.changedComponents > 0 || summary.removedComponents > 0);
|
|
2253
|
+
|
|
2254
|
+
if (mode === "warn") {
|
|
2255
|
+
const decisionSummary = loadDecisionSummary(cwd);
|
|
2256
|
+
const enforcementSummary = loadEnforcementSummary(cwd);
|
|
2257
|
+
const jitGrantStore = loadJitGrantStore(cwd);
|
|
2258
|
+
const productSummary = buildWarnProductSummary(summary, decisionSummary, enforcementSummary);
|
|
2259
|
+
return {
|
|
2260
|
+
mode,
|
|
2261
|
+
summary: {
|
|
2262
|
+
...summary,
|
|
2263
|
+
mode: "warn",
|
|
2264
|
+
},
|
|
2265
|
+
riskDiff,
|
|
2266
|
+
permissionPreview,
|
|
2267
|
+
decisionSummary,
|
|
2268
|
+
enforcementSummary,
|
|
2269
|
+
jitGrantStore,
|
|
2270
|
+
productSummary,
|
|
2271
|
+
showInDashboard: true,
|
|
2272
|
+
showInStatus: true,
|
|
2273
|
+
hasRecordedCheck: Boolean(persistedSummary),
|
|
2274
|
+
needsBaselineRecording,
|
|
2275
|
+
statusLine: persistedSummary ? buildWarnStatusLine(summary, decisionSummary, enforcementSummary) : noRecentScanLine,
|
|
2276
|
+
dashboardCard: persistedSummary ? buildWarnDashboardCard(summary, decisionSummary, enforcementSummary) : {
|
|
2277
|
+
title: "Agent Security",
|
|
2278
|
+
subtitle: "Warn mode active",
|
|
2279
|
+
headline: "Warn & Approve active",
|
|
2280
|
+
summary: "No recent scan",
|
|
2281
|
+
nextBestAction: "Run Visibility Scan.",
|
|
2282
|
+
detailsAvailable: true,
|
|
2283
|
+
},
|
|
2284
|
+
detailsPath: persistedSummary ? VISIBILITY_SUMMARY_REL_PATH : null,
|
|
2285
|
+
};
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
if (mode === "auto") {
|
|
2289
|
+
const enforcementSummary = loadEnforcementSummary(cwd);
|
|
2290
|
+
const jitGrantStore = loadJitGrantStore(cwd);
|
|
2291
|
+
const activeRemediations = loadInstructionRemediations(cwd).items.length;
|
|
2292
|
+
const productSummary = buildAutoProductSummary(summary, enforcementSummary, {
|
|
2293
|
+
activeRemediations,
|
|
2294
|
+
performance: buildCompactPerformanceSummary(cwd, ["agent_security_guarded_action", "agent_security_auto_policy"]),
|
|
2295
|
+
});
|
|
2296
|
+
return {
|
|
2297
|
+
mode,
|
|
2298
|
+
summary: {
|
|
2299
|
+
...summary,
|
|
2300
|
+
mode: "auto",
|
|
2301
|
+
},
|
|
2302
|
+
riskDiff,
|
|
2303
|
+
permissionPreview,
|
|
2304
|
+
enforcementSummary,
|
|
2305
|
+
jitGrantStore,
|
|
2306
|
+
productSummary,
|
|
2307
|
+
showInDashboard: true,
|
|
2308
|
+
showInStatus: true,
|
|
2309
|
+
hasRecordedCheck: Boolean(persistedSummary),
|
|
2310
|
+
needsBaselineRecording,
|
|
2311
|
+
statusLine: persistedSummary ? buildAutoStatusLine(summary, enforcementSummary) : noRecentScanLine,
|
|
2312
|
+
dashboardCard: persistedSummary ? buildAutoDashboardCard(summary, enforcementSummary, productSummary) : {
|
|
2313
|
+
title: "Agent Security",
|
|
2314
|
+
subtitle: "Auto-Minimize active",
|
|
2315
|
+
headline: "Auto-Minimize active",
|
|
2316
|
+
summary: "No recent scan",
|
|
2317
|
+
nextBestAction: "Run Visibility Scan.",
|
|
2318
|
+
detailsAvailable: true,
|
|
2319
|
+
},
|
|
2320
|
+
detailsPath: persistedSummary ? VISIBILITY_SUMMARY_REL_PATH : null,
|
|
2321
|
+
};
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
const productSummary = buildVisibilityProductSummary(summary, {
|
|
2325
|
+
needsBaselineRecording,
|
|
2326
|
+
performance: buildCompactPerformanceSummary(cwd, "agent_security_visibility_scan"),
|
|
2327
|
+
});
|
|
2328
|
+
return {
|
|
2329
|
+
mode,
|
|
2330
|
+
summary,
|
|
2331
|
+
productSummary,
|
|
2332
|
+
riskDiff,
|
|
2333
|
+
permissionPreview,
|
|
2334
|
+
showInDashboard: true,
|
|
2335
|
+
showInStatus: true,
|
|
2336
|
+
hasRecordedCheck: Boolean(persistedSummary),
|
|
2337
|
+
needsBaselineRecording,
|
|
2338
|
+
statusLine: persistedSummary ? buildVisibilityStatusLine(summary, { needsBaselineRecording }) : noRecentScanLine,
|
|
2339
|
+
dashboardCard: persistedSummary ? buildVisibilityDashboardCard(summary, { needsBaselineRecording }) : {
|
|
2340
|
+
title: "Agent Security",
|
|
2341
|
+
subtitle: "Full visibility",
|
|
2342
|
+
headline: "Visibility active",
|
|
2343
|
+
summary: "No recent scan",
|
|
2344
|
+
nextBestAction: "Run Visibility Scan.",
|
|
2345
|
+
detailsAvailable: true,
|
|
2346
|
+
surfaceLabel: "Last checked: never",
|
|
2347
|
+
changeLabel: "0 new - 0 changed - 0 permission expansions",
|
|
2348
|
+
topRiskLabel: "Top risk: No recent scan",
|
|
2349
|
+
previewLabel: "0 permission previews",
|
|
2350
|
+
},
|
|
2351
|
+
detailsPath: persistedSummary ? VISIBILITY_SUMMARY_REL_PATH : null,
|
|
2352
|
+
};
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
function runAgentSecurityScan(cwd, options = {}) {
|
|
2356
|
+
const configuredMode = AGENT_SECURITY_MODES.includes(options.mode) ? options.mode : "basic";
|
|
2357
|
+
const explicitMode = options.scanMode === "visibility" || options.scanMode === "basic" ? options.scanMode : null;
|
|
2358
|
+
const mode = explicitMode || (configuredMode === "visibility" || configuredMode === "warn" || configuredMode === "auto" ? "visibility" : "basic");
|
|
2359
|
+
const generatedAt = nowIso();
|
|
2360
|
+
const operation = mode === "visibility" ? "agent_security_visibility_scan" : "agent_security_basic_check";
|
|
2361
|
+
const tracker = createPerformanceTracker(operation);
|
|
2362
|
+
const scanSession = createBoundedScanSession(cwd, tracker, {
|
|
2363
|
+
maxFiles: PERFORMANCE_GUARDRAILS.scanMaxFiles,
|
|
2364
|
+
maxFileBytes: PERFORMANCE_GUARDRAILS.scanMaxFileBytes,
|
|
2365
|
+
maxTotalBytes: PERFORMANCE_GUARDRAILS.scanMaxTotalBytes,
|
|
2366
|
+
maxDepth: PERFORMANCE_GUARDRAILS.scanMaxDepth,
|
|
2367
|
+
maxDurationMs: PERFORMANCE_GUARDRAILS.scanMaxDurationMs,
|
|
2368
|
+
});
|
|
2369
|
+
const previousSnapshot = loadPreviousSnapshot(cwd, mode);
|
|
2370
|
+
const trustCards = buildTrustCardsForMode(cwd, previousSnapshot, generatedAt, mode, { tracker, scanSession });
|
|
2371
|
+
|
|
2372
|
+
let summary;
|
|
2373
|
+
let riskDiff = null;
|
|
2374
|
+
let permissionPreview = null;
|
|
2375
|
+
if (mode === "visibility") {
|
|
2376
|
+
riskDiff = buildRiskDiff(previousSnapshot, trustCards, generatedAt);
|
|
2377
|
+
permissionPreview = buildPermissionPreview(trustCards, generatedAt);
|
|
2378
|
+
summary = summarizeTrustCards(mode, trustCards, generatedAt, null, {
|
|
2379
|
+
riskIncreases: riskDiff.summary.riskIncreases,
|
|
2380
|
+
permissionExpansions: riskDiff.summary.permissionExpansions,
|
|
2381
|
+
permissionPreviews: permissionPreview.previews.length,
|
|
2382
|
+
});
|
|
2383
|
+
} else {
|
|
2384
|
+
summary = summarizeTrustCards(mode, trustCards, generatedAt, null);
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
let evidence = null;
|
|
2388
|
+
if (options.writeEvidence !== false) {
|
|
2389
|
+
evidence = mode === "visibility"
|
|
2390
|
+
? writeVisibilityEvidence(cwd, generatedAt, previousSnapshot, trustCards)
|
|
2391
|
+
: writeBasicEvidence(cwd, generatedAt, previousSnapshot, trustCards);
|
|
2392
|
+
recordAgentSecurityOperation(cwd, operation, tracker, {
|
|
2393
|
+
operation,
|
|
2394
|
+
mode,
|
|
2395
|
+
componentsScanned: summary.componentsScanned,
|
|
2396
|
+
cachePath: SCAN_CACHE_REL_PATH,
|
|
2397
|
+
}, options.writeEvidence);
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
return {
|
|
2401
|
+
generatedAt,
|
|
2402
|
+
mode,
|
|
2403
|
+
configuredMode,
|
|
2404
|
+
summary,
|
|
2405
|
+
trustCards,
|
|
2406
|
+
riskDiff,
|
|
2407
|
+
permissionPreview,
|
|
2408
|
+
evidence,
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
function runAgentSecurityBasicCheck(cwd, options = {}) {
|
|
2413
|
+
return runAgentSecurityScan(cwd, {
|
|
2414
|
+
...options,
|
|
2415
|
+
scanMode: "basic",
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
function runAgentSecurityVisibilityScan(cwd, options = {}) {
|
|
2420
|
+
return runAgentSecurityScan(cwd, {
|
|
2421
|
+
...options,
|
|
2422
|
+
scanMode: "visibility",
|
|
2423
|
+
});
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
function inspectAgentSecurityContent(cwd, input, options = {}) {
|
|
2427
|
+
const configuredMode = AGENT_SECURITY_MODES.includes(options.mode) ? options.mode : "off";
|
|
2428
|
+
const mode = configuredMode;
|
|
2429
|
+
const generatedAt = nowIso();
|
|
2430
|
+
const sourceType = input?.sourceType || "unknown_external";
|
|
2431
|
+
const content = String(input?.content || "");
|
|
2432
|
+
const inspection = scanInstructionRisk(content, {
|
|
2433
|
+
sourceType,
|
|
2434
|
+
intendedUse: input?.intendedUse || "unknown",
|
|
2435
|
+
});
|
|
2436
|
+
const sourceRecord = buildSourceRecord({
|
|
2437
|
+
sourceId: input?.sourceId,
|
|
2438
|
+
sourceType,
|
|
2439
|
+
origin: input?.origin || null,
|
|
2440
|
+
path: input?.path || null,
|
|
2441
|
+
content,
|
|
2442
|
+
instructionRisk: inspection.instructionRisk,
|
|
2443
|
+
findings: inspection.findings,
|
|
2444
|
+
intendedUse: input?.intendedUse || "unknown",
|
|
2445
|
+
createdAt: generatedAt,
|
|
2446
|
+
updatedAt: generatedAt,
|
|
2447
|
+
});
|
|
2448
|
+
|
|
2449
|
+
const previousSourceDoc = safeReadJson(cwd, SOURCE_TRUST_REL_PATH, {
|
|
2450
|
+
sourceTrustVersion: 1,
|
|
2451
|
+
what: "Agent Security source trust classifications.",
|
|
2452
|
+
generatedAt,
|
|
2453
|
+
items: [],
|
|
2454
|
+
});
|
|
2455
|
+
const previousRiskDoc = safeReadJson(cwd, INSTRUCTION_RISK_REL_PATH, {
|
|
2456
|
+
instructionRiskVersion: 1,
|
|
2457
|
+
what: "Agent Security instruction risk findings.",
|
|
2458
|
+
generatedAt,
|
|
2459
|
+
counts: {
|
|
2460
|
+
suspiciousSources: 0,
|
|
2461
|
+
highRiskSources: 0,
|
|
2462
|
+
highRiskFindings: 0,
|
|
2463
|
+
},
|
|
2464
|
+
items: [],
|
|
2465
|
+
});
|
|
2466
|
+
|
|
2467
|
+
const nextSourceItems = previousSourceDoc.items.filter((item) => item.sourceId !== sourceRecord.sourceId);
|
|
2468
|
+
nextSourceItems.push(sourceRecord);
|
|
2469
|
+
const nextRiskItems = previousRiskDoc.items.filter((item) => item.sourceId !== sourceRecord.sourceId);
|
|
2470
|
+
nextRiskItems.push({
|
|
2471
|
+
sourceId: sourceRecord.sourceId,
|
|
2472
|
+
sourceType: sourceRecord.sourceType,
|
|
2473
|
+
name: input?.origin || sourceRecord.path || sourceRecord.sourceId,
|
|
2474
|
+
source: sourceRecord.path || sourceRecord.origin || null,
|
|
2475
|
+
instructionRisk: inspection.instructionRisk,
|
|
2476
|
+
signals: inspection.signals,
|
|
2477
|
+
findings: inspection.findings.map((finding) => ({
|
|
2478
|
+
category: finding.category,
|
|
2479
|
+
severity: finding.severity,
|
|
2480
|
+
message: finding.message,
|
|
2481
|
+
evidencePreview: boundedPreview(finding.evidencePreview, 120),
|
|
2482
|
+
location: finding.location || null,
|
|
2483
|
+
})),
|
|
2484
|
+
recommendation: sourceRecord.recommendation,
|
|
2485
|
+
});
|
|
2486
|
+
|
|
2487
|
+
const nextSourceDoc = {
|
|
2488
|
+
sourceTrustVersion: 1,
|
|
2489
|
+
what: "Agent Security source trust classifications.",
|
|
2490
|
+
generatedAt,
|
|
2491
|
+
items: nextSourceItems,
|
|
2492
|
+
};
|
|
2493
|
+
const nextRiskDoc = {
|
|
2494
|
+
instructionRiskVersion: 1,
|
|
2495
|
+
what: "Agent Security instruction risk findings.",
|
|
2496
|
+
generatedAt,
|
|
2497
|
+
counts: {
|
|
2498
|
+
suspiciousSources: nextRiskItems.filter((item) => item.instructionRisk !== "low").length,
|
|
2499
|
+
highRiskSources: nextRiskItems.filter((item) => item.instructionRisk === "high").length,
|
|
2500
|
+
highRiskFindings: nextRiskItems.reduce((sum, item) => sum + item.findings.filter((finding) => finding.severity === "high").length, 0),
|
|
2501
|
+
},
|
|
2502
|
+
items: nextRiskItems,
|
|
2503
|
+
};
|
|
2504
|
+
|
|
2505
|
+
if (options.writeEvidence !== false) {
|
|
2506
|
+
safeWriteJson(cwd, SOURCE_TRUST_REL_PATH, nextSourceDoc);
|
|
2507
|
+
safeWriteJson(cwd, INSTRUCTION_RISK_REL_PATH, nextRiskDoc);
|
|
2508
|
+
appendJsonl(cwd, INSTRUCTION_AUDIT_LOG_REL_PATH, {
|
|
2509
|
+
eventType: "agent_security_source_trust_classified",
|
|
2510
|
+
mode,
|
|
2511
|
+
sourceId: sourceRecord.sourceId,
|
|
2512
|
+
sourceType: sourceRecord.sourceType,
|
|
2513
|
+
trustLevel: sourceRecord.trustLevel,
|
|
2514
|
+
instructionRisk: sourceRecord.instructionRisk,
|
|
2515
|
+
signals: inspection.signals,
|
|
2516
|
+
reason: sourceRecord.recommendation,
|
|
2517
|
+
timestamp: generatedAt,
|
|
2518
|
+
});
|
|
2519
|
+
if (inspection.findings.length > 0) {
|
|
2520
|
+
appendJsonl(cwd, INSTRUCTION_AUDIT_LOG_REL_PATH, {
|
|
2521
|
+
eventType: "agent_security_instruction_risk_detected",
|
|
2522
|
+
mode,
|
|
2523
|
+
sourceId: sourceRecord.sourceId,
|
|
2524
|
+
sourceType: sourceRecord.sourceType,
|
|
2525
|
+
trustLevel: sourceRecord.trustLevel,
|
|
2526
|
+
instructionRisk: sourceRecord.instructionRisk,
|
|
2527
|
+
signals: inspection.signals,
|
|
2528
|
+
reason: sourceRecord.recommendation,
|
|
2529
|
+
timestamp: generatedAt,
|
|
2530
|
+
});
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
return {
|
|
2535
|
+
generatedAt,
|
|
2536
|
+
mode,
|
|
2537
|
+
sourceRecord,
|
|
2538
|
+
inspection,
|
|
2539
|
+
remediation: {
|
|
2540
|
+
defaultAction: sourceRecord.recommendation,
|
|
2541
|
+
availableActions: ["treat_as_data_only", "use_sanitized_copy", "quarantine_source", "trust_source"],
|
|
2542
|
+
},
|
|
2543
|
+
evidence: options.writeEvidence === false ? null : {
|
|
2544
|
+
sourceTrustPath: SOURCE_TRUST_REL_PATH,
|
|
2545
|
+
instructionRiskPath: INSTRUCTION_RISK_REL_PATH,
|
|
2546
|
+
auditPath: INSTRUCTION_AUDIT_LOG_REL_PATH,
|
|
2547
|
+
},
|
|
2548
|
+
};
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
function remediateAgentSecurityInstruction(cwd, input, options = {}) {
|
|
2552
|
+
const mode = AGENT_SECURITY_MODES.includes(options.mode) ? options.mode : "warn";
|
|
2553
|
+
const generatedAt = nowIso();
|
|
2554
|
+
const sourceId = String(input?.sourceId || "");
|
|
2555
|
+
const action = String(input?.action || "");
|
|
2556
|
+
const reason = String(input?.reason || "");
|
|
2557
|
+
if (!sourceId) throw new Error("Instruction remediation requires sourceId.");
|
|
2558
|
+
if (!["treat_as_data_only", "use_sanitized_copy", "quarantine_source", "trust_source"].includes(action)) {
|
|
2559
|
+
throw new Error("Unsupported instruction remediation action.");
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
const sourceDoc = safeReadJson(cwd, SOURCE_TRUST_REL_PATH, {
|
|
2563
|
+
sourceTrustVersion: 1,
|
|
2564
|
+
what: "Agent Security source trust classifications.",
|
|
2565
|
+
generatedAt,
|
|
2566
|
+
items: [],
|
|
2567
|
+
});
|
|
2568
|
+
const sourceRecord = sourceDoc.items.find((item) => item.sourceId === sourceId);
|
|
2569
|
+
if (!sourceRecord) throw new Error(`Unknown sourceId: ${sourceId}`);
|
|
2570
|
+
|
|
2571
|
+
const remediations = loadInstructionRemediations(cwd);
|
|
2572
|
+
const remediationRecord = {
|
|
2573
|
+
remediationId: `rem-${shortHash(`${sourceId}:${action}:${generatedAt}`)}`,
|
|
2574
|
+
sourceId,
|
|
2575
|
+
action,
|
|
2576
|
+
mode,
|
|
2577
|
+
reason,
|
|
2578
|
+
createdAt: generatedAt,
|
|
2579
|
+
expiresAt: null,
|
|
2580
|
+
userDecision: action === "trust_source",
|
|
2581
|
+
};
|
|
2582
|
+
|
|
2583
|
+
safeWriteJson(cwd, INSTRUCTION_REMEDIATIONS_REL_PATH, {
|
|
2584
|
+
remediationVersion: 1,
|
|
2585
|
+
what: "Agent Security instruction remediations.",
|
|
2586
|
+
generatedAt,
|
|
2587
|
+
items: [...remediations.items, remediationRecord],
|
|
2588
|
+
});
|
|
2589
|
+
|
|
2590
|
+
let sanitizedCopy = null;
|
|
2591
|
+
if (action === "use_sanitized_copy") {
|
|
2592
|
+
const sanitizedDoc = safeReadJson(cwd, SANITIZED_CONTENT_REL_PATH, {
|
|
2593
|
+
sanitizedContentVersion: 1,
|
|
2594
|
+
what: "Agent Security sanitized content copies.",
|
|
2595
|
+
generatedAt,
|
|
2596
|
+
items: [],
|
|
2597
|
+
});
|
|
2598
|
+
sanitizedCopy = {
|
|
2599
|
+
sourceId,
|
|
2600
|
+
sourceType: sourceRecord.sourceType,
|
|
2601
|
+
contentHash: sourceRecord.contentHash,
|
|
2602
|
+
sanitizedContent: sanitizeInstructionLikeContent(sourceRecord.contentSample || ""),
|
|
2603
|
+
createdAt: generatedAt,
|
|
2604
|
+
};
|
|
2605
|
+
safeWriteJson(cwd, SANITIZED_CONTENT_REL_PATH, {
|
|
2606
|
+
sanitizedContentVersion: 1,
|
|
2607
|
+
what: "Agent Security sanitized content copies.",
|
|
2608
|
+
generatedAt,
|
|
2609
|
+
items: [...sanitizedDoc.items.filter((item) => item.sourceId !== sourceId), sanitizedCopy],
|
|
2610
|
+
});
|
|
2611
|
+
appendJsonl(cwd, INSTRUCTION_AUDIT_LOG_REL_PATH, {
|
|
2612
|
+
eventType: "agent_security_sanitized_copy_created",
|
|
2613
|
+
mode,
|
|
2614
|
+
sourceId,
|
|
2615
|
+
sourceType: sourceRecord.sourceType,
|
|
2616
|
+
trustLevel: sourceRecord.trustLevel,
|
|
2617
|
+
instructionRisk: sourceRecord.instructionRisk,
|
|
2618
|
+
signals: [],
|
|
2619
|
+
remediationAction: action,
|
|
2620
|
+
reason,
|
|
2621
|
+
timestamp: generatedAt,
|
|
2622
|
+
});
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
appendJsonl(cwd, INSTRUCTION_AUDIT_LOG_REL_PATH, {
|
|
2626
|
+
eventType: "agent_security_instruction_remediation_recorded",
|
|
2627
|
+
mode,
|
|
2628
|
+
sourceId,
|
|
2629
|
+
sourceType: sourceRecord.sourceType,
|
|
2630
|
+
trustLevel: sourceRecord.trustLevel,
|
|
2631
|
+
instructionRisk: sourceRecord.instructionRisk,
|
|
2632
|
+
signals: [],
|
|
2633
|
+
remediationAction: action,
|
|
2634
|
+
reason,
|
|
2635
|
+
timestamp: generatedAt,
|
|
2636
|
+
});
|
|
2637
|
+
|
|
2638
|
+
return {
|
|
2639
|
+
generatedAt,
|
|
2640
|
+
mode,
|
|
2641
|
+
remediationRecord,
|
|
2642
|
+
sanitizedCopy,
|
|
2643
|
+
evidence: {
|
|
2644
|
+
remediationPath: INSTRUCTION_REMEDIATIONS_REL_PATH,
|
|
2645
|
+
sanitizedContentPath: action === "use_sanitized_copy" ? SANITIZED_CONTENT_REL_PATH : null,
|
|
2646
|
+
auditPath: INSTRUCTION_AUDIT_LOG_REL_PATH,
|
|
2647
|
+
},
|
|
2648
|
+
};
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
function runAgentSecurityPreflight(cwd, actionInput, options = {}) {
|
|
2652
|
+
const mode = "warn";
|
|
2653
|
+
const generatedAt = nowIso();
|
|
2654
|
+
const tracker = createPerformanceTracker("agent_security_preflight");
|
|
2655
|
+
const inventory = loadPersistedInventoryForActions(cwd);
|
|
2656
|
+
|
|
2657
|
+
const action = {
|
|
2658
|
+
...(actionInput && typeof actionInput === "object" ? actionInput : {}),
|
|
2659
|
+
};
|
|
2660
|
+
action.context = action.context && typeof action.context === "object" ? { ...action.context } : {};
|
|
2661
|
+
if (!action.context.cwd) action.context.cwd = cwd;
|
|
2662
|
+
if (!action.actionId) action.actionId = buildActionId(action);
|
|
2663
|
+
|
|
2664
|
+
const componentCard = resolveComponentCard(inventory.trustCards, action);
|
|
2665
|
+
const relatedSources = resolveRelatedSourcesForAction(cwd, action, componentCard);
|
|
2666
|
+
const evaluation = evaluateAgentSecurityAction(action, { componentCard, relatedSources });
|
|
2667
|
+
const requestedDecision = action.decision || options.decision || null;
|
|
2668
|
+
const limitPlan = evaluation.decisionRequired || requestedDecision === "let_wuz_limit"
|
|
2669
|
+
? buildPermissionLimitPlan({ evaluation, action }, { componentCard })
|
|
2670
|
+
: null;
|
|
2671
|
+
|
|
2672
|
+
const decision = requestedDecision;
|
|
2673
|
+
const decisionRecord = decision
|
|
2674
|
+
? buildDecisionRecord({
|
|
2675
|
+
actionId: evaluation.actionId,
|
|
2676
|
+
componentId: evaluation.componentId,
|
|
2677
|
+
decision,
|
|
2678
|
+
actionType: evaluation.actionType,
|
|
2679
|
+
target: evaluation.target,
|
|
2680
|
+
limitPlan,
|
|
2681
|
+
reason: evaluation.reasons[0],
|
|
2682
|
+
timestamp: generatedAt,
|
|
2683
|
+
})
|
|
2684
|
+
: null;
|
|
2685
|
+
|
|
2686
|
+
let decisionSummary = null;
|
|
2687
|
+
if (options.writeEvidence !== false) {
|
|
2688
|
+
writeEnforcementCapabilityMatrix(cwd, generatedAt, mode);
|
|
2689
|
+
safeWriteJson(cwd, ACTION_PREFLIGHT_REL_PATH, {
|
|
2690
|
+
what: "Agent Security preflight result.",
|
|
2691
|
+
generatedAt,
|
|
2692
|
+
mode,
|
|
2693
|
+
action: {
|
|
2694
|
+
actionId: evaluation.actionId,
|
|
2695
|
+
componentId: evaluation.componentId,
|
|
2696
|
+
componentType: evaluation.componentType,
|
|
2697
|
+
actionType: evaluation.actionType,
|
|
2698
|
+
target: evaluation.target,
|
|
2699
|
+
command: evaluation.command,
|
|
2700
|
+
packageManager: evaluation.packageManager || null,
|
|
2701
|
+
scriptName: evaluation.scriptName || null,
|
|
2702
|
+
packageName: evaluation.packageName || null,
|
|
2703
|
+
mcpServer: evaluation.mcpServer || null,
|
|
2704
|
+
toolName: evaluation.toolName || null,
|
|
2705
|
+
argsHash: evaluation.argsHash || null,
|
|
2706
|
+
targetHash: evaluation.targetHash || null,
|
|
2707
|
+
relatedSourceIds: evaluation.relatedSourceIds || [],
|
|
2708
|
+
},
|
|
2709
|
+
evaluation,
|
|
2710
|
+
...(limitPlan ? { limitPlan } : {}),
|
|
2711
|
+
...(decisionRecord ? { decisionRecord } : {}),
|
|
2712
|
+
});
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
if (decision && options.writeEvidence !== false) {
|
|
2716
|
+
decisionSummary = updateDecisionSummary(cwd, {
|
|
2717
|
+
decision,
|
|
2718
|
+
effectiveBehavior: decisionRecord.effectiveBehavior,
|
|
2719
|
+
actionId: evaluation.actionId,
|
|
2720
|
+
componentId: evaluation.componentId,
|
|
2721
|
+
componentType: evaluation.componentType,
|
|
2722
|
+
actionType: evaluation.actionType,
|
|
2723
|
+
riskLevel: evaluation.riskLevel,
|
|
2724
|
+
timestamp: generatedAt,
|
|
2725
|
+
});
|
|
2726
|
+
writeDecisionAuditEvents(cwd, {
|
|
2727
|
+
actionId: evaluation.actionId,
|
|
2728
|
+
componentId: evaluation.componentId,
|
|
2729
|
+
componentType: evaluation.componentType,
|
|
2730
|
+
actionType: evaluation.actionType,
|
|
2731
|
+
riskLevel: evaluation.riskLevel,
|
|
2732
|
+
recommendedDecision: evaluation.recommendedDecision,
|
|
2733
|
+
userDecision: decision,
|
|
2734
|
+
effectiveBehavior: decisionRecord.effectiveBehavior,
|
|
2735
|
+
limitPlan,
|
|
2736
|
+
timestamp: generatedAt,
|
|
2737
|
+
});
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
recordAgentSecurityOperation(cwd, "agent_security_preflight", tracker, {
|
|
2741
|
+
mode,
|
|
2742
|
+
actionId: evaluation.actionId,
|
|
2743
|
+
actionType: evaluation.actionType,
|
|
2744
|
+
relatedSourceIds: evaluation.relatedSourceIds || [],
|
|
2745
|
+
}, options.writeEvidence);
|
|
2746
|
+
|
|
2747
|
+
return {
|
|
2748
|
+
generatedAt,
|
|
2749
|
+
mode,
|
|
2750
|
+
evaluation,
|
|
2751
|
+
limitPlan,
|
|
2752
|
+
decision: decision || null,
|
|
2753
|
+
decisionRecord,
|
|
2754
|
+
decisionSummary,
|
|
2755
|
+
evidence: options.writeEvidence === false ? null : {
|
|
2756
|
+
preflightPath: ACTION_PREFLIGHT_REL_PATH,
|
|
2757
|
+
decisionSummaryPath: decisionSummary ? DECISION_SUMMARY_REL_PATH : null,
|
|
2758
|
+
decisionAuditPath: decision ? DECISION_AUDIT_LOG_REL_PATH : null,
|
|
2759
|
+
enforcementCapabilitiesPath: ENFORCEMENT_CAPABILITIES_REL_PATH,
|
|
2760
|
+
performanceSummaryPath: PERFORMANCE_SUMMARY_REL_PATH,
|
|
2761
|
+
},
|
|
2762
|
+
};
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2765
|
+
function runAgentSecurityGuardedRun(cwd, actionInput, options = {}) {
|
|
2766
|
+
const mode = "warn";
|
|
2767
|
+
const generatedAt = nowIso();
|
|
2768
|
+
const tracker = createPerformanceTracker("agent_security_guarded_action");
|
|
2769
|
+
const inventory = loadPersistedInventoryForActions(cwd);
|
|
2770
|
+
|
|
2771
|
+
const action = {
|
|
2772
|
+
...(actionInput && typeof actionInput === "object" ? actionInput : {}),
|
|
2773
|
+
};
|
|
2774
|
+
action.context = action.context && typeof action.context === "object" ? { ...action.context } : {};
|
|
2775
|
+
if (!action.context.cwd) action.context.cwd = cwd;
|
|
2776
|
+
if (!action.actionId) action.actionId = buildActionId(action);
|
|
2777
|
+
|
|
2778
|
+
const componentCard = resolveComponentCard(inventory.trustCards, action);
|
|
2779
|
+
const relatedSources = resolveRelatedSourcesForAction(cwd, action, componentCard);
|
|
2780
|
+
const evaluation = evaluateAgentSecurityAction(action, { componentCard, relatedSources });
|
|
2781
|
+
const requestedDecision = action.decision || options.decision || null;
|
|
2782
|
+
const limitPlan = evaluation.decisionRequired || requestedDecision === "let_wuz_limit"
|
|
2783
|
+
? buildPermissionLimitPlan({ evaluation, action }, { componentCard })
|
|
2784
|
+
: null;
|
|
2785
|
+
|
|
2786
|
+
const decision = requestedDecision;
|
|
2787
|
+
const decisionRecord = decision
|
|
2788
|
+
? buildDecisionRecord({
|
|
2789
|
+
actionId: evaluation.actionId,
|
|
2790
|
+
componentId: evaluation.componentId,
|
|
2791
|
+
decision,
|
|
2792
|
+
actionType: evaluation.actionType,
|
|
2793
|
+
target: evaluation.target,
|
|
2794
|
+
limitPlan,
|
|
2795
|
+
reason: evaluation.reasons[0],
|
|
2796
|
+
timestamp: generatedAt,
|
|
2797
|
+
})
|
|
2798
|
+
: null;
|
|
2799
|
+
|
|
2800
|
+
if (options.writeEvidence !== false) {
|
|
2801
|
+
safeWriteJson(cwd, ACTION_PREFLIGHT_REL_PATH, {
|
|
2802
|
+
what: "Agent Security guarded action preflight result.",
|
|
2803
|
+
generatedAt,
|
|
2804
|
+
mode,
|
|
2805
|
+
action: {
|
|
2806
|
+
actionId: evaluation.actionId,
|
|
2807
|
+
componentId: evaluation.componentId,
|
|
2808
|
+
componentType: evaluation.componentType,
|
|
2809
|
+
actionType: evaluation.actionType,
|
|
2810
|
+
target: evaluation.target,
|
|
2811
|
+
command: evaluation.command,
|
|
2812
|
+
packageManager: evaluation.packageManager || null,
|
|
2813
|
+
scriptName: evaluation.scriptName || null,
|
|
2814
|
+
packageName: evaluation.packageName || null,
|
|
2815
|
+
mcpServer: evaluation.mcpServer || null,
|
|
2816
|
+
toolName: evaluation.toolName || null,
|
|
2817
|
+
argsHash: evaluation.argsHash || null,
|
|
2818
|
+
targetHash: evaluation.targetHash || null,
|
|
2819
|
+
relatedSourceIds: evaluation.relatedSourceIds || [],
|
|
2820
|
+
},
|
|
2821
|
+
evaluation,
|
|
2822
|
+
...(limitPlan ? { limitPlan } : {}),
|
|
2823
|
+
...(decisionRecord ? { decisionRecord } : {}),
|
|
2824
|
+
});
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
let decisionSummary = null;
|
|
2828
|
+
if (decision && options.writeEvidence !== false) {
|
|
2829
|
+
decisionSummary = updateDecisionSummary(cwd, {
|
|
2830
|
+
decision,
|
|
2831
|
+
effectiveBehavior: decisionRecord.effectiveBehavior,
|
|
2832
|
+
actionId: evaluation.actionId,
|
|
2833
|
+
componentId: evaluation.componentId,
|
|
2834
|
+
componentType: evaluation.componentType,
|
|
2835
|
+
actionType: evaluation.actionType,
|
|
2836
|
+
riskLevel: evaluation.riskLevel,
|
|
2837
|
+
timestamp: generatedAt,
|
|
2838
|
+
});
|
|
2839
|
+
writeDecisionAuditEvents(cwd, {
|
|
2840
|
+
actionId: evaluation.actionId,
|
|
2841
|
+
componentId: evaluation.componentId,
|
|
2842
|
+
componentType: evaluation.componentType,
|
|
2843
|
+
actionType: evaluation.actionType,
|
|
2844
|
+
riskLevel: evaluation.riskLevel,
|
|
2845
|
+
recommendedDecision: evaluation.recommendedDecision,
|
|
2846
|
+
userDecision: decision,
|
|
2847
|
+
effectiveBehavior: decisionRecord.effectiveBehavior,
|
|
2848
|
+
limitPlan,
|
|
2849
|
+
timestamp: generatedAt,
|
|
2850
|
+
});
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
const enforcement = enforceAgentSecurityAction(cwd, {
|
|
2854
|
+
action: {
|
|
2855
|
+
...action,
|
|
2856
|
+
actionId: evaluation.actionId,
|
|
2857
|
+
componentId: evaluation.componentId,
|
|
2858
|
+
componentType: evaluation.componentType,
|
|
2859
|
+
actionType: evaluation.actionType,
|
|
2860
|
+
target: evaluation.target,
|
|
2861
|
+
command: evaluation.command,
|
|
2862
|
+
packageManager: evaluation.packageManager || action.packageManager || null,
|
|
2863
|
+
scriptName: evaluation.scriptName || action.scriptName || null,
|
|
2864
|
+
packageName: evaluation.packageName || action.packageName || null,
|
|
2865
|
+
mcpServer: evaluation.mcpServer || action.mcpServer || null,
|
|
2866
|
+
toolName: evaluation.toolName || action.toolName || null,
|
|
2867
|
+
args: action.args && typeof action.args === "object" ? action.args : null,
|
|
2868
|
+
resource: action.resource && typeof action.resource === "object" ? action.resource : null,
|
|
2869
|
+
},
|
|
2870
|
+
evaluation,
|
|
2871
|
+
limitPlan,
|
|
2872
|
+
decisionRecord,
|
|
2873
|
+
}, {
|
|
2874
|
+
timestamp: generatedAt,
|
|
2875
|
+
execute: action.execute === true,
|
|
2876
|
+
executor: options.executor,
|
|
2877
|
+
writeEvidence: options.writeEvidence !== false,
|
|
2878
|
+
});
|
|
2879
|
+
|
|
2880
|
+
recordAgentSecurityOperation(cwd, "agent_security_guarded_action", tracker, {
|
|
2881
|
+
mode,
|
|
2882
|
+
actionId: evaluation.actionId,
|
|
2883
|
+
actionType: evaluation.actionType,
|
|
2884
|
+
relatedSourceIds: evaluation.relatedSourceIds || [],
|
|
2885
|
+
enforcementAvailable: enforcement.enforcementAvailable === true,
|
|
2886
|
+
}, options.writeEvidence);
|
|
2887
|
+
|
|
2888
|
+
return {
|
|
2889
|
+
generatedAt,
|
|
2890
|
+
mode,
|
|
2891
|
+
evaluation,
|
|
2892
|
+
limitPlan,
|
|
2893
|
+
decision: decision || null,
|
|
2894
|
+
decisionRecord,
|
|
2895
|
+
decisionSummary,
|
|
2896
|
+
enforcement,
|
|
2897
|
+
evidence: options.writeEvidence === false ? null : {
|
|
2898
|
+
preflightPath: ACTION_PREFLIGHT_REL_PATH,
|
|
2899
|
+
decisionSummaryPath: decisionSummary ? DECISION_SUMMARY_REL_PATH : null,
|
|
2900
|
+
decisionAuditPath: decision ? DECISION_AUDIT_LOG_REL_PATH : null,
|
|
2901
|
+
enforcementSummaryPath: ENFORCEMENT_SUMMARY_REL_PATH,
|
|
2902
|
+
jitGrantsPath: JIT_GRANTS_REL_PATH,
|
|
2903
|
+
grantsPath: ONE_TIME_GRANTS_REL_PATH,
|
|
2904
|
+
enforcementAuditPath: ENFORCEMENT_AUDIT_LOG_REL_PATH,
|
|
2905
|
+
enforcementCapabilitiesPath: ENFORCEMENT_CAPABILITIES_REL_PATH,
|
|
2906
|
+
performanceSummaryPath: PERFORMANCE_SUMMARY_REL_PATH,
|
|
2907
|
+
},
|
|
2908
|
+
};
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
function writeGuardedActionPreflight(cwd, generatedAt, mode, evaluation, limitPlan, decisionRecord, autoPolicy, writeEvidence) {
|
|
2912
|
+
if (writeEvidence === false) return;
|
|
2913
|
+
safeWriteJson(cwd, ACTION_PREFLIGHT_REL_PATH, {
|
|
2914
|
+
what: "Agent Security guarded action preflight result.",
|
|
2915
|
+
generatedAt,
|
|
2916
|
+
mode,
|
|
2917
|
+
action: {
|
|
2918
|
+
actionId: evaluation.actionId,
|
|
2919
|
+
componentId: evaluation.componentId,
|
|
2920
|
+
componentType: evaluation.componentType,
|
|
2921
|
+
actionType: evaluation.actionType,
|
|
2922
|
+
target: evaluation.target,
|
|
2923
|
+
command: evaluation.command,
|
|
2924
|
+
packageManager: evaluation.packageManager || null,
|
|
2925
|
+
scriptName: evaluation.scriptName || null,
|
|
2926
|
+
packageName: evaluation.packageName || null,
|
|
2927
|
+
mcpServer: evaluation.mcpServer || null,
|
|
2928
|
+
toolName: evaluation.toolName || null,
|
|
2929
|
+
argsHash: evaluation.argsHash || null,
|
|
2930
|
+
targetHash: evaluation.targetHash || null,
|
|
2931
|
+
contentHash: evaluation.contentHash || null,
|
|
2932
|
+
scopeKey: evaluation.scopeKey || null,
|
|
2933
|
+
},
|
|
2934
|
+
evaluation,
|
|
2935
|
+
...(limitPlan ? { limitPlan } : {}),
|
|
2936
|
+
...(decisionRecord ? { decisionRecord } : {}),
|
|
2937
|
+
...(autoPolicy ? { autoPolicy } : {}),
|
|
2938
|
+
});
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
function buildAutoDecisionRecord(input) {
|
|
2942
|
+
if (input.autoDecision === "auto_allow_limited") {
|
|
2943
|
+
return buildDecisionRecord({
|
|
2944
|
+
...input,
|
|
2945
|
+
decision: "let_wuz_limit",
|
|
2946
|
+
mode: "auto",
|
|
2947
|
+
});
|
|
2948
|
+
}
|
|
2949
|
+
if (input.autoDecision === "auto_allow_once") {
|
|
2950
|
+
return buildDecisionRecord({
|
|
2951
|
+
...input,
|
|
2952
|
+
decision: "approve_once",
|
|
2953
|
+
mode: "auto",
|
|
2954
|
+
});
|
|
2955
|
+
}
|
|
2956
|
+
if (input.autoDecision === "auto_deny") {
|
|
2957
|
+
return buildDecisionRecord({
|
|
2958
|
+
...input,
|
|
2959
|
+
decision: "deny",
|
|
2960
|
+
mode: "auto",
|
|
2961
|
+
});
|
|
2962
|
+
}
|
|
2963
|
+
return null;
|
|
2964
|
+
}
|
|
2965
|
+
|
|
2966
|
+
function buildImplicitGuardedComponentCard(action) {
|
|
2967
|
+
const componentType = String(action?.componentType || "");
|
|
2968
|
+
if (componentType === "file-write" || componentType === "command" || componentType === "package-manager") {
|
|
2969
|
+
return {
|
|
2970
|
+
id: String(action?.componentId || componentType),
|
|
2971
|
+
type: componentType,
|
|
2972
|
+
source: "guarded",
|
|
2973
|
+
status: "known",
|
|
2974
|
+
trustLevel: "local",
|
|
2975
|
+
capabilities: {},
|
|
2976
|
+
};
|
|
2977
|
+
}
|
|
2978
|
+
if (componentType === "mcp" && String(action?.mcpServer || "").toLowerCase() === "github") {
|
|
2979
|
+
return {
|
|
2980
|
+
id: String(action?.componentId || "mcp-github"),
|
|
2981
|
+
type: "mcp",
|
|
2982
|
+
source: "guarded",
|
|
2983
|
+
status: "known",
|
|
2984
|
+
trustLevel: "local",
|
|
2985
|
+
capabilities: {},
|
|
2986
|
+
};
|
|
2987
|
+
}
|
|
2988
|
+
return null;
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
function runAgentSecurityAutoGuardedAction(cwd, actionInput, options = {}) {
|
|
2992
|
+
const mode = "auto";
|
|
2993
|
+
const generatedAt = nowIso();
|
|
2994
|
+
const tracker = createPerformanceTracker("agent_security_guarded_action");
|
|
2995
|
+
const inventory = loadPersistedInventoryForActions(cwd);
|
|
2996
|
+
|
|
2997
|
+
const action = {
|
|
2998
|
+
...(actionInput && typeof actionInput === "object" ? actionInput : {}),
|
|
2999
|
+
};
|
|
3000
|
+
action.context = action.context && typeof action.context === "object" ? { ...action.context } : {};
|
|
3001
|
+
if (!action.context.cwd) action.context.cwd = cwd;
|
|
3002
|
+
if (!action.actionId) action.actionId = buildActionId(action);
|
|
3003
|
+
|
|
3004
|
+
const componentCard = resolveComponentCard(inventory.trustCards, action) || buildImplicitGuardedComponentCard(action);
|
|
3005
|
+
const relatedSources = resolveRelatedSourcesForAction(cwd, action, componentCard);
|
|
3006
|
+
const evaluation = evaluateAgentSecurityAction(action, { componentCard, relatedSources });
|
|
3007
|
+
const normalizedAction = normalizeAgentSecurityAction({
|
|
3008
|
+
...action,
|
|
3009
|
+
actionId: evaluation.actionId,
|
|
3010
|
+
componentId: evaluation.componentId,
|
|
3011
|
+
componentType: evaluation.componentType,
|
|
3012
|
+
actionType: evaluation.actionType,
|
|
3013
|
+
target: evaluation.target,
|
|
3014
|
+
command: evaluation.command,
|
|
3015
|
+
packageManager: evaluation.packageManager || action.packageManager || null,
|
|
3016
|
+
scriptName: evaluation.scriptName || action.scriptName || null,
|
|
3017
|
+
packageName: evaluation.packageName || action.packageName || null,
|
|
3018
|
+
mcpServer: evaluation.mcpServer || action.mcpServer || null,
|
|
3019
|
+
toolName: evaluation.toolName || action.toolName || null,
|
|
3020
|
+
relatedSourceIds: evaluation.relatedSourceIds || [],
|
|
3021
|
+
});
|
|
3022
|
+
const adapter = resolveAgentSecurityAdapter(evaluation.actionType);
|
|
3023
|
+
const limitPlan = buildPermissionLimitPlan({ evaluation, action: normalizedAction }, { componentCard });
|
|
3024
|
+
const explicitDecision = action.decision || options.decision || null;
|
|
3025
|
+
|
|
3026
|
+
if (explicitDecision) {
|
|
3027
|
+
const decisionRecord = buildDecisionRecord({
|
|
3028
|
+
actionId: evaluation.actionId,
|
|
3029
|
+
componentId: evaluation.componentId,
|
|
3030
|
+
decision: explicitDecision,
|
|
3031
|
+
actionType: evaluation.actionType,
|
|
3032
|
+
target: evaluation.target,
|
|
3033
|
+
limitPlan,
|
|
3034
|
+
reason: evaluation.reasons[0],
|
|
3035
|
+
timestamp: generatedAt,
|
|
3036
|
+
mode,
|
|
3037
|
+
});
|
|
3038
|
+
writeGuardedActionPreflight(cwd, generatedAt, mode, evaluation, limitPlan, decisionRecord, null, options.writeEvidence);
|
|
3039
|
+
const enforcement = enforceAgentSecurityAction(cwd, {
|
|
3040
|
+
action: normalizedAction,
|
|
3041
|
+
evaluation,
|
|
3042
|
+
limitPlan,
|
|
3043
|
+
decisionRecord,
|
|
3044
|
+
}, {
|
|
3045
|
+
timestamp: generatedAt,
|
|
3046
|
+
execute: action.execute === true,
|
|
3047
|
+
executor: options.executor,
|
|
3048
|
+
writeEvidence: options.writeEvidence !== false,
|
|
3049
|
+
mode,
|
|
3050
|
+
autoDecision: explicitDecision === "deny"
|
|
3051
|
+
? "auto_deny"
|
|
3052
|
+
: explicitDecision === "let_wuz_limit"
|
|
3053
|
+
? "auto_allow_limited"
|
|
3054
|
+
: "auto_allow_once",
|
|
3055
|
+
confidence: "high",
|
|
3056
|
+
});
|
|
3057
|
+
|
|
3058
|
+
recordAgentSecurityOperation(cwd, "agent_security_guarded_action", tracker, {
|
|
3059
|
+
mode,
|
|
3060
|
+
actionId: evaluation.actionId,
|
|
3061
|
+
actionType: evaluation.actionType,
|
|
3062
|
+
autoDecision: explicitDecision === "deny"
|
|
3063
|
+
? "auto_deny"
|
|
3064
|
+
: explicitDecision === "let_wuz_limit"
|
|
3065
|
+
? "auto_allow_limited"
|
|
3066
|
+
: "auto_allow_once",
|
|
3067
|
+
relatedSourceIds: evaluation.relatedSourceIds || [],
|
|
3068
|
+
enforcementAvailable: enforcement.enforcementAvailable === true,
|
|
3069
|
+
}, options.writeEvidence);
|
|
3070
|
+
|
|
3071
|
+
return {
|
|
3072
|
+
generatedAt,
|
|
3073
|
+
mode,
|
|
3074
|
+
action: normalizedAction,
|
|
3075
|
+
evaluation,
|
|
3076
|
+
limitPlan,
|
|
3077
|
+
autoDecision: explicitDecision === "deny"
|
|
3078
|
+
? "auto_deny"
|
|
3079
|
+
: explicitDecision === "let_wuz_limit"
|
|
3080
|
+
? "auto_allow_limited"
|
|
3081
|
+
: "auto_allow_once",
|
|
3082
|
+
confidence: "high",
|
|
3083
|
+
decision: explicitDecision,
|
|
3084
|
+
decisionRecord,
|
|
3085
|
+
enforcement,
|
|
3086
|
+
evidence: options.writeEvidence === false ? null : {
|
|
3087
|
+
preflightPath: ACTION_PREFLIGHT_REL_PATH,
|
|
3088
|
+
enforcementSummaryPath: ENFORCEMENT_SUMMARY_REL_PATH,
|
|
3089
|
+
jitGrantsPath: JIT_GRANTS_REL_PATH,
|
|
3090
|
+
grantsPath: ONE_TIME_GRANTS_REL_PATH,
|
|
3091
|
+
enforcementAuditPath: ENFORCEMENT_AUDIT_LOG_REL_PATH,
|
|
3092
|
+
enforcementCapabilitiesPath: ENFORCEMENT_CAPABILITIES_REL_PATH,
|
|
3093
|
+
performanceSummaryPath: PERFORMANCE_SUMMARY_REL_PATH,
|
|
3094
|
+
},
|
|
3095
|
+
};
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
const autoPolicyTracker = createPerformanceTracker("agent_security_auto_policy");
|
|
3099
|
+
const autoPolicy = decideAutoAction(normalizedAction, evaluation, limitPlan, adapter, {
|
|
3100
|
+
mode,
|
|
3101
|
+
cwd,
|
|
3102
|
+
});
|
|
3103
|
+
recordAgentSecurityOperation(cwd, "agent_security_auto_policy", autoPolicyTracker, {
|
|
3104
|
+
mode,
|
|
3105
|
+
actionId: evaluation.actionId,
|
|
3106
|
+
actionType: evaluation.actionType,
|
|
3107
|
+
autoDecision: autoPolicy.autoDecision,
|
|
3108
|
+
relatedSourceIds: evaluation.relatedSourceIds || [],
|
|
3109
|
+
}, options.writeEvidence);
|
|
3110
|
+
writeGuardedActionPreflight(cwd, generatedAt, mode, evaluation, limitPlan, null, autoPolicy, options.writeEvidence);
|
|
3111
|
+
|
|
3112
|
+
if (autoPolicy.autoDecision === "require_approval") {
|
|
3113
|
+
const enforcement = recordEnforcementOutcome(cwd, {
|
|
3114
|
+
actionId: normalizedAction.actionId,
|
|
3115
|
+
componentId: normalizedAction.componentId,
|
|
3116
|
+
componentType: normalizedAction.componentType,
|
|
3117
|
+
actionType: normalizedAction.actionType,
|
|
3118
|
+
target: normalizedAction.target || null,
|
|
3119
|
+
command: normalizedAction.command || "",
|
|
3120
|
+
packageManager: normalizedAction.packageManager || null,
|
|
3121
|
+
scriptName: normalizedAction.scriptName || null,
|
|
3122
|
+
packageName: normalizedAction.packageName || null,
|
|
3123
|
+
mcpServer: normalizedAction.mcpServer || null,
|
|
3124
|
+
toolName: normalizedAction.toolName || null,
|
|
3125
|
+
decision: null,
|
|
3126
|
+
adapterId: adapter?.adapterId || null,
|
|
3127
|
+
enforcementAvailable: adapter?.canEnforce === true,
|
|
3128
|
+
effectiveBehavior: "limit_plan_only",
|
|
3129
|
+
executed: false,
|
|
3130
|
+
exitCode: null,
|
|
3131
|
+
stdoutPreview: "",
|
|
3132
|
+
stderrPreview: "",
|
|
3133
|
+
reason: autoPolicy.reason,
|
|
3134
|
+
timestamp: generatedAt,
|
|
3135
|
+
mode,
|
|
3136
|
+
autoDecision: autoPolicy.autoDecision,
|
|
3137
|
+
confidence: autoPolicy.confidence,
|
|
3138
|
+
jitGrant: null,
|
|
3139
|
+
jitGrantConsumed: null,
|
|
3140
|
+
jitGrantExpired: null,
|
|
3141
|
+
}, {
|
|
3142
|
+
mode,
|
|
3143
|
+
writeEvidence: options.writeEvidence !== false,
|
|
3144
|
+
});
|
|
3145
|
+
|
|
3146
|
+
recordAgentSecurityOperation(cwd, "agent_security_guarded_action", tracker, {
|
|
3147
|
+
mode,
|
|
3148
|
+
actionId: evaluation.actionId,
|
|
3149
|
+
actionType: evaluation.actionType,
|
|
3150
|
+
autoDecision: autoPolicy.autoDecision,
|
|
3151
|
+
relatedSourceIds: evaluation.relatedSourceIds || [],
|
|
3152
|
+
enforcementAvailable: enforcement.enforcementAvailable === true,
|
|
3153
|
+
}, options.writeEvidence);
|
|
3154
|
+
|
|
3155
|
+
return {
|
|
3156
|
+
generatedAt,
|
|
3157
|
+
mode,
|
|
3158
|
+
action: normalizedAction,
|
|
3159
|
+
evaluation,
|
|
3160
|
+
limitPlan,
|
|
3161
|
+
autoDecision: autoPolicy.autoDecision,
|
|
3162
|
+
confidence: autoPolicy.confidence,
|
|
3163
|
+
enforcement,
|
|
3164
|
+
evidence: options.writeEvidence === false ? null : {
|
|
3165
|
+
preflightPath: ACTION_PREFLIGHT_REL_PATH,
|
|
3166
|
+
enforcementSummaryPath: ENFORCEMENT_SUMMARY_REL_PATH,
|
|
3167
|
+
enforcementAuditPath: ENFORCEMENT_AUDIT_LOG_REL_PATH,
|
|
3168
|
+
enforcementCapabilitiesPath: ENFORCEMENT_CAPABILITIES_REL_PATH,
|
|
3169
|
+
performanceSummaryPath: PERFORMANCE_SUMMARY_REL_PATH,
|
|
3170
|
+
},
|
|
3171
|
+
};
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
if (autoPolicy.autoDecision === "recommended_only" || autoPolicy.autoDecision === "limit_plan_only") {
|
|
3175
|
+
const enforcement = recordEnforcementOutcome(cwd, {
|
|
3176
|
+
actionId: normalizedAction.actionId,
|
|
3177
|
+
componentId: normalizedAction.componentId,
|
|
3178
|
+
componentType: normalizedAction.componentType,
|
|
3179
|
+
actionType: normalizedAction.actionType,
|
|
3180
|
+
target: normalizedAction.target || null,
|
|
3181
|
+
command: normalizedAction.command || "",
|
|
3182
|
+
packageManager: normalizedAction.packageManager || null,
|
|
3183
|
+
scriptName: normalizedAction.scriptName || null,
|
|
3184
|
+
packageName: normalizedAction.packageName || null,
|
|
3185
|
+
mcpServer: normalizedAction.mcpServer || null,
|
|
3186
|
+
toolName: normalizedAction.toolName || null,
|
|
3187
|
+
decision: null,
|
|
3188
|
+
adapterId: adapter?.adapterId || null,
|
|
3189
|
+
enforcementAvailable: adapter?.canEnforce === true,
|
|
3190
|
+
effectiveBehavior: autoPolicy.autoDecision === "recommended_only" ? "recommended_only" : "limit_plan_only",
|
|
3191
|
+
executed: false,
|
|
3192
|
+
exitCode: null,
|
|
3193
|
+
stdoutPreview: "",
|
|
3194
|
+
stderrPreview: "",
|
|
3195
|
+
reason: autoPolicy.reason,
|
|
3196
|
+
timestamp: generatedAt,
|
|
3197
|
+
mode,
|
|
3198
|
+
autoDecision: autoPolicy.autoDecision,
|
|
3199
|
+
confidence: autoPolicy.confidence,
|
|
3200
|
+
jitGrant: null,
|
|
3201
|
+
jitGrantConsumed: null,
|
|
3202
|
+
jitGrantExpired: null,
|
|
3203
|
+
}, {
|
|
3204
|
+
mode,
|
|
3205
|
+
writeEvidence: options.writeEvidence !== false,
|
|
3206
|
+
unsupportedActionTypes: adapter ? [] : [normalizedAction.actionType],
|
|
3207
|
+
});
|
|
3208
|
+
|
|
3209
|
+
recordAgentSecurityOperation(cwd, "agent_security_guarded_action", tracker, {
|
|
3210
|
+
mode,
|
|
3211
|
+
actionId: evaluation.actionId,
|
|
3212
|
+
actionType: evaluation.actionType,
|
|
3213
|
+
autoDecision: autoPolicy.autoDecision,
|
|
3214
|
+
relatedSourceIds: evaluation.relatedSourceIds || [],
|
|
3215
|
+
enforcementAvailable: enforcement.enforcementAvailable === true,
|
|
3216
|
+
}, options.writeEvidence);
|
|
3217
|
+
|
|
3218
|
+
return {
|
|
3219
|
+
generatedAt,
|
|
3220
|
+
mode,
|
|
3221
|
+
action: normalizedAction,
|
|
3222
|
+
evaluation,
|
|
3223
|
+
limitPlan,
|
|
3224
|
+
autoDecision: autoPolicy.autoDecision,
|
|
3225
|
+
confidence: autoPolicy.confidence,
|
|
3226
|
+
enforcement,
|
|
3227
|
+
evidence: options.writeEvidence === false ? null : {
|
|
3228
|
+
preflightPath: ACTION_PREFLIGHT_REL_PATH,
|
|
3229
|
+
enforcementSummaryPath: ENFORCEMENT_SUMMARY_REL_PATH,
|
|
3230
|
+
enforcementAuditPath: ENFORCEMENT_AUDIT_LOG_REL_PATH,
|
|
3231
|
+
enforcementCapabilitiesPath: ENFORCEMENT_CAPABILITIES_REL_PATH,
|
|
3232
|
+
performanceSummaryPath: PERFORMANCE_SUMMARY_REL_PATH,
|
|
3233
|
+
},
|
|
3234
|
+
};
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
const decisionRecord = buildAutoDecisionRecord({
|
|
3238
|
+
actionId: evaluation.actionId,
|
|
3239
|
+
componentId: evaluation.componentId,
|
|
3240
|
+
actionType: evaluation.actionType,
|
|
3241
|
+
target: evaluation.target,
|
|
3242
|
+
limitPlan,
|
|
3243
|
+
reason: autoPolicy.reason,
|
|
3244
|
+
timestamp: generatedAt,
|
|
3245
|
+
autoDecision: autoPolicy.autoDecision,
|
|
3246
|
+
});
|
|
3247
|
+
|
|
3248
|
+
const enforcement = enforceAgentSecurityAction(cwd, {
|
|
3249
|
+
action: normalizedAction,
|
|
3250
|
+
evaluation,
|
|
3251
|
+
limitPlan,
|
|
3252
|
+
decisionRecord,
|
|
3253
|
+
}, {
|
|
3254
|
+
timestamp: generatedAt,
|
|
3255
|
+
execute: action.execute === true,
|
|
3256
|
+
executor: options.executor,
|
|
3257
|
+
writeEvidence: options.writeEvidence !== false,
|
|
3258
|
+
mode,
|
|
3259
|
+
autoDecision: autoPolicy.autoDecision,
|
|
3260
|
+
confidence: autoPolicy.confidence,
|
|
3261
|
+
});
|
|
3262
|
+
|
|
3263
|
+
recordAgentSecurityOperation(cwd, "agent_security_guarded_action", tracker, {
|
|
3264
|
+
mode,
|
|
3265
|
+
actionId: evaluation.actionId,
|
|
3266
|
+
actionType: evaluation.actionType,
|
|
3267
|
+
autoDecision: autoPolicy.autoDecision,
|
|
3268
|
+
relatedSourceIds: evaluation.relatedSourceIds || [],
|
|
3269
|
+
enforcementAvailable: enforcement.enforcementAvailable === true,
|
|
3270
|
+
}, options.writeEvidence);
|
|
3271
|
+
|
|
3272
|
+
return {
|
|
3273
|
+
generatedAt,
|
|
3274
|
+
mode,
|
|
3275
|
+
action: normalizedAction,
|
|
3276
|
+
evaluation,
|
|
3277
|
+
limitPlan,
|
|
3278
|
+
autoDecision: autoPolicy.autoDecision,
|
|
3279
|
+
confidence: autoPolicy.confidence,
|
|
3280
|
+
decisionRecord,
|
|
3281
|
+
enforcement,
|
|
3282
|
+
evidence: options.writeEvidence === false ? null : {
|
|
3283
|
+
preflightPath: ACTION_PREFLIGHT_REL_PATH,
|
|
3284
|
+
enforcementSummaryPath: ENFORCEMENT_SUMMARY_REL_PATH,
|
|
3285
|
+
jitGrantsPath: JIT_GRANTS_REL_PATH,
|
|
3286
|
+
grantsPath: ONE_TIME_GRANTS_REL_PATH,
|
|
3287
|
+
enforcementAuditPath: ENFORCEMENT_AUDIT_LOG_REL_PATH,
|
|
3288
|
+
enforcementCapabilitiesPath: ENFORCEMENT_CAPABILITIES_REL_PATH,
|
|
3289
|
+
performanceSummaryPath: PERFORMANCE_SUMMARY_REL_PATH,
|
|
3290
|
+
},
|
|
3291
|
+
};
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
function runGuardedAction(cwd, actionInput, options = {}) {
|
|
3295
|
+
const requestedMode = AGENT_SECURITY_MODES.includes(options.mode) ? options.mode : "warn";
|
|
3296
|
+
if (requestedMode === "auto") {
|
|
3297
|
+
return runAgentSecurityAutoGuardedAction(cwd, actionInput, options);
|
|
3298
|
+
}
|
|
3299
|
+
return runAgentSecurityGuardedRun(cwd, actionInput, options);
|
|
3300
|
+
}
|
|
3301
|
+
|
|
3302
|
+
module.exports = {
|
|
3303
|
+
AGENT_SECURITY_MODES,
|
|
3304
|
+
SNAPSHOT_REL_PATH,
|
|
3305
|
+
SUMMARY_REL_PATH,
|
|
3306
|
+
VISIBILITY_SUMMARY_REL_PATH,
|
|
3307
|
+
TRUST_CARDS_REL_PATH,
|
|
3308
|
+
RISK_DIFF_REL_PATH,
|
|
3309
|
+
PERMISSION_PREVIEW_REL_PATH,
|
|
3310
|
+
INSTRUCTION_RISK_REL_PATH,
|
|
3311
|
+
SOURCE_TRUST_REL_PATH,
|
|
3312
|
+
INSTRUCTION_REMEDIATIONS_REL_PATH,
|
|
3313
|
+
SANITIZED_CONTENT_REL_PATH,
|
|
3314
|
+
AUDIT_LOG_REL_PATH,
|
|
3315
|
+
INSTRUCTION_AUDIT_LOG_REL_PATH,
|
|
3316
|
+
ACTION_PREFLIGHT_REL_PATH,
|
|
3317
|
+
DECISION_SUMMARY_REL_PATH,
|
|
3318
|
+
DECISION_AUDIT_LOG_REL_PATH,
|
|
3319
|
+
ENFORCEMENT_CAPABILITIES_REL_PATH,
|
|
3320
|
+
ENFORCEMENT_SUMMARY_REL_PATH,
|
|
3321
|
+
JIT_GRANTS_REL_PATH,
|
|
3322
|
+
ONE_TIME_GRANTS_REL_PATH,
|
|
3323
|
+
ENFORCEMENT_AUDIT_LOG_REL_PATH,
|
|
3324
|
+
stableStringify,
|
|
3325
|
+
loadPreviousSnapshot,
|
|
3326
|
+
loadLatestAgentSecuritySummary,
|
|
3327
|
+
loadDecisionSummary,
|
|
3328
|
+
loadJitGrantStore,
|
|
3329
|
+
loadEnforcementSummary,
|
|
3330
|
+
buildAgentSecurityModeControl,
|
|
3331
|
+
buildAgentSecuritySurface,
|
|
3332
|
+
buildRiskDiff,
|
|
3333
|
+
buildPermissionPreview,
|
|
3334
|
+
runAgentSecurityScan,
|
|
3335
|
+
runAgentSecurityBasicCheck,
|
|
3336
|
+
runAgentSecurityVisibilityScan,
|
|
3337
|
+
inspectAgentSecurityContent,
|
|
3338
|
+
remediateAgentSecurityInstruction,
|
|
3339
|
+
runAgentSecurityPreflight,
|
|
3340
|
+
runAgentSecurityGuardedRun,
|
|
3341
|
+
runGuardedAction,
|
|
3342
|
+
};
|