diff --git a/packages/adapters/src/command-adapter.ts b/packages/adapters/src/command-adapter.ts index 813e7165..84260e9f 100644 --- a/packages/adapters/src/command-adapter.ts +++ b/packages/adapters/src/command-adapter.ts @@ -1,5 +1,5 @@ import type { Finding } from "@submuxhq/codedecay-core"; -import { runConfiguredCommand, type CommandExecutionResult } from "@submuxhq/codedecay-execution"; +import { createSafeCommandPolicy, runConfiguredCommand, type CommandExecutionResult } from "@submuxhq/codedecay-execution"; import type { AdapterContext, AdapterResult, CodeDecayAdapter, CommandAdapterOptions } from "./types"; import { validateCommandAdapterOptions } from "./validation"; @@ -21,9 +21,11 @@ async function runCommandAdapter( command: options.command, cwd: context.rootDir, timeoutMs: options.timeoutMs ?? context.config.safety.commandTimeoutMs, - safety: { - allowCommands: options.requiresCommandAllowlist ? context.config.safety.allowCommands : true - } + safety: createSafeCommandPolicy({ + allowCommands: options.requiresCommandAllowlist ? context.config.safety.allowCommands : true, + capabilityPolicy: context.config.safety.capabilityPolicy + }), + capabilityIntentSource: "user-config" }); return adapterResultFromExecution(options, result); diff --git a/packages/cli/src/commands/loop.ts b/packages/cli/src/commands/loop.ts index c5877133..b809e3bc 100644 --- a/packages/cli/src/commands/loop.ts +++ b/packages/cli/src/commands/loop.ts @@ -68,7 +68,8 @@ export async function runLoopCommand( securityScoreThreshold: options.securityScoreThreshold, agentTimeoutMs: loadedConfig.config.safety.commandTimeoutMs, commandSafety: { - allowCommands: loadedConfig.config.safety.allowCommands + allowCommands: loadedConfig.config.safety.allowCommands, + capabilityPolicy: loadedConfig.config.safety.capabilityPolicy }, createRedteamReport: async () => await createRedteamReportForCli(rootDir, { diff --git a/packages/cli/src/product/generated-tests/runner.ts b/packages/cli/src/product/generated-tests/runner.ts index 22ffbc0e..44528b74 100644 --- a/packages/cli/src/product/generated-tests/runner.ts +++ b/packages/cli/src/product/generated-tests/runner.ts @@ -1,7 +1,7 @@ import { readFileSync } from "node:fs"; import { join } from "node:path"; import type { CodeDecayProductTarget, LoadedCodeDecayConfig } from "@submuxhq/codedecay-config"; -import { runConfiguredCommand } from "@submuxhq/codedecay-execution"; +import { createSafeCommandPolicy, runConfiguredCommand } from "@submuxhq/codedecay-execution"; import { generatedProductBaseUrl } from "./manifest"; import { elapsed } from "./strings"; import type { @@ -99,9 +99,11 @@ export async function runGeneratedProductTests( env: { CODEDECAY_PRODUCT_BASE_URL: generatedProductBaseUrl(rootDir, generatedTests) }, - safety: { - allowCommands: loadedConfig.config.safety.allowCommands - } + safety: createSafeCommandPolicy({ + allowCommands: loadedConfig.config.safety.allowCommands, + capabilityPolicy: loadedConfig.config.safety.capabilityPolicy + }), + capabilityIntentSource: "user-config" }); const testSource = readFileSync(join(rootDir, generatedTests.sourcePath), "utf8"); const impactedFiles = dependencies.findImpactedProductFiles(rootDir); diff --git a/packages/cli/src/product/generated-tests/runner/retry.ts b/packages/cli/src/product/generated-tests/runner/retry.ts index bad7b7c4..1eed110b 100644 --- a/packages/cli/src/product/generated-tests/runner/retry.ts +++ b/packages/cli/src/product/generated-tests/runner/retry.ts @@ -1,5 +1,5 @@ import type { LoadedCodeDecayConfig, CodeDecayProductTarget } from "@submuxhq/codedecay-config"; -import { runConfiguredCommand } from "@submuxhq/codedecay-execution"; +import { createSafeCommandPolicy, runConfiguredCommand } from "@submuxhq/codedecay-execution"; import { generatedProductBaseUrl } from "../manifest"; import type { ProductGeneratedTestCase, @@ -76,9 +76,11 @@ export async function attachGeneratedFailureRetryEvidence(input: { env: { CODEDECAY_PRODUCT_BASE_URL: generatedProductBaseUrl(input.rootDir, input.generatedTests) }, - safety: { - allowCommands: input.loadedConfig.config.safety.allowCommands - } + safety: createSafeCommandPolicy({ + allowCommands: input.loadedConfig.config.safety.allowCommands, + capabilityPolicy: input.loadedConfig.config.safety.capabilityPolicy + }), + capabilityIntentSource: "user-config" }); const parsed = parsePlaywrightTestRun({ stdout: execution.stdout, diff --git a/packages/cli/src/product/runtime/service.ts b/packages/cli/src/product/runtime/service.ts index 4bf8cc44..bfde8e14 100644 --- a/packages/cli/src/product/runtime/service.ts +++ b/packages/cli/src/product/runtime/service.ts @@ -2,6 +2,8 @@ import { spawn, spawnSync, type ChildProcessWithoutNullStreams } from "node:chil import type { LoadedCodeDecayConfig } from "@submuxhq/codedecay-config"; import { checkCommandSafety, + createSafeCommandPolicy, + detectShellSubstitution, runConfiguredCommand, type CommandExecutionResult } from "@submuxhq/codedecay-execution"; @@ -18,9 +20,11 @@ export async function runProductOneShotCommand( command, cwd: rootDir, timeoutMs, - safety: { - allowCommands: loadedConfig.config.safety.allowCommands - } + safety: createSafeCommandPolicy({ + allowCommands: loadedConfig.config.safety.allowCommands, + capabilityPolicy: loadedConfig.config.safety.capabilityPolicy + }), + capabilityIntentSource: "user-config" }); } @@ -42,6 +46,20 @@ export async function startManagedProductProcess( }; } + const substitution = detectShellSubstitution(command); + if (substitution) { + const message = `Command was blocked by CodeDecay capability policy: command rejected: ${substitution}.`; + return { + command, + status: "blocked", + durationMs: 0, + stdout: "", + stderr: message, + error: message, + blockedReason: `command rejected: ${substitution}` + }; + } + const safety = checkCommandSafety(command); if (!safety.safe) { const message = `Command was blocked by CodeDecay safety policy: ${safety.reason}.`; diff --git a/packages/execution/src/index.ts b/packages/execution/src/index.ts index 7f12b1b5..c7e764ac 100644 --- a/packages/execution/src/index.ts +++ b/packages/execution/src/index.ts @@ -1,5 +1,6 @@ export { runConfiguredCommand } from "./command"; export { checkCommandSafety } from "./safety"; +export { createSafeCommandPolicy } from "./safe-policy"; export { authorizeCapability, appendCapabilityAuditEvent, diff --git a/packages/execution/src/safe-policy.ts b/packages/execution/src/safe-policy.ts new file mode 100644 index 00000000..0b390998 --- /dev/null +++ b/packages/execution/src/safe-policy.ts @@ -0,0 +1,20 @@ +import { createDefaultCapabilityPolicy } from "./capability"; +import type { CapabilityPolicy } from "./capability"; +import type { SafeCommandPolicy } from "./types"; + +export function createSafeCommandPolicy(input: { + allowCommands: boolean; + capabilityPolicy?: CapabilityPolicy | undefined; + allowUnsafeCommands?: boolean | undefined; +}): SafeCommandPolicy { + const policy: SafeCommandPolicy = { + allowCommands: input.allowCommands, + capabilityPolicy: input.capabilityPolicy ?? createDefaultCapabilityPolicy() + }; + + if (input.allowUnsafeCommands !== undefined) { + policy.allowUnsafeCommands = input.allowUnsafeCommands; + } + + return policy; +} diff --git a/packages/execution/test/capability-policy.test.ts b/packages/execution/test/capability-policy.test.ts index 93e53eb5..ba6a29c7 100644 --- a/packages/execution/test/capability-policy.test.ts +++ b/packages/execution/test/capability-policy.test.ts @@ -7,6 +7,7 @@ import { authorizeCapability, checkPathWithinAllowedRoots, createDefaultCapabilityPolicy, + createSafeCommandPolicy, detectShellSubstitution, fetchWithoutExternalRedirect, resolveCapabilityAuditPath, @@ -175,6 +176,40 @@ describe("capability policy foundation", () => { expect(allowed.allowed).toBe(true); }); + it("passes loaded capabilityPolicy into configured command execution", async () => { + const root = createTempDir(); + const result = await runConfiguredCommand({ + command: "node -e \"console.log('policy')\"", + cwd: root, + timeoutMs: 1000, + safety: createSafeCommandPolicy({ + allowCommands: true, + capabilityPolicy: { + version: 1, + allow: [{ capability: "command.execute", commands: ["node"] }] + } + }) + }); + + expect(result.status).toBe("passed"); + + const denied = await runConfiguredCommand({ + command: "node -e \"console.log('nope')\"", + cwd: root, + timeoutMs: 1000, + safety: createSafeCommandPolicy({ + allowCommands: true, + capabilityPolicy: { + version: 1, + allow: [{ capability: "command.execute", commands: ["pnpm"] }] + } + }) + }); + + expect(denied.status).toBe("blocked"); + expect(denied.blockedReason).toContain("not listed in capabilityPolicy.allow commands"); + }); + it("blocks credentials, metadata hosts, and off-allowlist redirect targets", async () => { expect( validateNetworkDestination("http://user:pass@127.0.0.1/health", { diff --git a/packages/mcp/src/execution/safety.ts b/packages/mcp/src/execution/safety.ts index 54519224..7cc95a5e 100644 --- a/packages/mcp/src/execution/safety.ts +++ b/packages/mcp/src/execution/safety.ts @@ -8,7 +8,9 @@ export function createExecutionSafety( const notes = [ "This MCP tool never runs arbitrary commands from MCP input.", "Only commands explicitly configured in CodeDecay config and enabled tool adapters are eligible to run.", - "Command execution also requires safety.allowCommands: true in CodeDecay config." + "Command execution also requires safety.allowCommands: true in CodeDecay config.", + "Configured commands also pass through safety.capabilityPolicy (default deny for elevated capabilities).", + "confirmExecution authorizes only this execute_configured_checks call; it does not grant unrelated later capabilities." ]; if (!confirmExecution) { diff --git a/packages/mcp/src/product/safety.ts b/packages/mcp/src/product/safety.ts index 86d2a56b..1236894c 100644 --- a/packages/mcp/src/product/safety.ts +++ b/packages/mcp/src/product/safety.ts @@ -12,7 +12,8 @@ export function createProductSafety( allowCommands: loadedConfig.config.safety.allowCommands, notes: [ ...notes, - "Product target startup, browser automation, and generated test execution still obey safety.allowCommands in CodeDecay config.", + "Product target startup, browser automation, and generated test execution still obey safety.allowCommands and safety.capabilityPolicy in CodeDecay config.", + "MCP confirmation authorizes only this product operation; it does not grant unrelated capabilities.", "No telemetry, cloud execution, LLM calls, or arbitrary MCP-provided commands are used." ] }; diff --git a/packages/tool-adapters/src/agent-process/configured.ts b/packages/tool-adapters/src/agent-process/configured.ts index 6aa41c94..5bd38ffe 100644 --- a/packages/tool-adapters/src/agent-process/configured.ts +++ b/packages/tool-adapters/src/agent-process/configured.ts @@ -11,10 +11,11 @@ import { createAgentProcessHarness } from "./harness"; export function createConfiguredAgentProcessHarness( adapter: CodeDecayAgentProcessToolAdapter, - allowCommands: boolean + safety: { allowCommands: boolean; capabilityPolicy?: import("@submuxhq/codedecay-execution").CapabilityPolicy | undefined } ): ConfiguredToolHarness { const options: AgentProcessHarnessOptions = { - allowCommands + allowCommands: safety.allowCommands, + capabilityPolicy: safety.capabilityPolicy }; if (adapter.command !== undefined) { diff --git a/packages/tool-adapters/src/agent-process/run.ts b/packages/tool-adapters/src/agent-process/run.ts index a2fe611c..18362781 100644 --- a/packages/tool-adapters/src/agent-process/run.ts +++ b/packages/tool-adapters/src/agent-process/run.ts @@ -25,6 +25,7 @@ import { agentProcessMissingCommandEvidence } from "./evidence"; import { validateAgentProcessPlan } from "./validation"; +import { toolAdapterSafeCommandPolicy } from "../safety"; export async function runAgentProcessPlan( plan: HarnessPlan, @@ -65,10 +66,7 @@ export async function runAgentProcessPlan( CODEDECAY_AGENT_PROFILE: profile, CODEDECAY_AGENT_OUTPUT_UNTRUSTED: "1" }, - safety: { - allowCommands: options.allowCommands ?? false, - allowUnsafeCommands: options.allowUnsafeCommands - } + safety: toolAdapterSafeCommandPolicy(options) }); const durationMs = elapsed(startedAt); const artifacts = [{ path: bundle.artifactPath, description: "CodeDecay agent task bundle passed to the local agent process." }]; diff --git a/packages/tool-adapters/src/coverage/configured.ts b/packages/tool-adapters/src/coverage/configured.ts index 2cd52fb8..0558c50d 100644 --- a/packages/tool-adapters/src/coverage/configured.ts +++ b/packages/tool-adapters/src/coverage/configured.ts @@ -4,10 +4,11 @@ import { resolveCoverageDisplayCommand } from "./plan"; export function createConfiguredCoverageHarness( adapter: CodeDecayCoverageToolAdapter, - allowCommands: boolean + safety: { allowCommands: boolean; capabilityPolicy?: import("@submuxhq/codedecay-execution").CapabilityPolicy | undefined } ): ConfiguredToolHarness { const options: CoverageHarnessOptions = { - allowCommands + allowCommands: safety.allowCommands, + capabilityPolicy: safety.capabilityPolicy }; if (adapter.command !== undefined) { diff --git a/packages/tool-adapters/src/coverage/run.ts b/packages/tool-adapters/src/coverage/run.ts index d9549389..6d44c835 100644 --- a/packages/tool-adapters/src/coverage/run.ts +++ b/packages/tool-adapters/src/coverage/run.ts @@ -24,6 +24,7 @@ import { DEFAULT_COVERAGE_TIMEOUT_MS } from "./constants"; import { validateCoveragePlan } from "./validation"; +import { toolAdapterSafeCommandPolicy } from "../safety"; export async function runCoveragePlan( plan: HarnessPlan, @@ -134,9 +135,6 @@ async function runCoverageCommand( cwd: context.cwd, timeoutMs, outputLimit: options.outputLimit, - safety: { - allowCommands: options.allowCommands ?? false, - allowUnsafeCommands: options.allowUnsafeCommands - } + safety: toolAdapterSafeCommandPolicy(options) }); } diff --git a/packages/tool-adapters/src/index.ts b/packages/tool-adapters/src/index.ts index 4f213a43..f7b2f8f9 100644 --- a/packages/tool-adapters/src/index.ts +++ b/packages/tool-adapters/src/index.ts @@ -33,33 +33,37 @@ export type { export function createConfiguredToolHarnesses(config: CodeDecayConfig): ConfiguredToolHarness[] { const configured: ConfiguredToolHarness[] = []; + const safety = { + allowCommands: config.safety.allowCommands, + capabilityPolicy: config.safety.capabilityPolicy + }; if (config.toolAdapters.agentProcess?.enabled) { - configured.push(createConfiguredAgentProcessHarness(config.toolAdapters.agentProcess, config.safety.allowCommands)); + configured.push(createConfiguredAgentProcessHarness(config.toolAdapters.agentProcess, safety)); } if (config.toolAdapters.playwright?.enabled) { - configured.push(createConfiguredPlaywrightHarness(config.toolAdapters.playwright, config.safety.allowCommands)); + configured.push(createConfiguredPlaywrightHarness(config.toolAdapters.playwright, safety)); } if (config.toolAdapters.stryker?.enabled) { - configured.push(createConfiguredStrykerHarness(config.toolAdapters.stryker, config.safety.allowCommands)); + configured.push(createConfiguredStrykerHarness(config.toolAdapters.stryker, safety)); } if (config.toolAdapters.schemathesis?.enabled) { - configured.push(createConfiguredSchemathesisHarness(config.toolAdapters.schemathesis, config.safety.allowCommands)); + configured.push(createConfiguredSchemathesisHarness(config.toolAdapters.schemathesis, safety)); } if (config.toolAdapters.pact?.enabled) { - configured.push(createConfiguredPactHarness(config.toolAdapters.pact, config.safety.allowCommands)); + configured.push(createConfiguredPactHarness(config.toolAdapters.pact, safety)); } if (config.toolAdapters.semgrep?.enabled) { - configured.push(createConfiguredSemgrepHarness(config.toolAdapters.semgrep, config.safety.allowCommands)); + configured.push(createConfiguredSemgrepHarness(config.toolAdapters.semgrep, safety)); } if (config.toolAdapters.coverage?.enabled) { - configured.push(createConfiguredCoverageHarness(config.toolAdapters.coverage, config.safety.allowCommands)); + configured.push(createConfiguredCoverageHarness(config.toolAdapters.coverage, safety)); } return configured; diff --git a/packages/tool-adapters/src/pact/configured.ts b/packages/tool-adapters/src/pact/configured.ts index 04adb7dc..86b6bf6d 100644 --- a/packages/tool-adapters/src/pact/configured.ts +++ b/packages/tool-adapters/src/pact/configured.ts @@ -4,12 +4,13 @@ import type { CodeDecayCommandToolAdapter, ConfiguredToolHarness, PactHarnessOpt export function createConfiguredPactHarness( adapter: CodeDecayCommandToolAdapter, - allowCommands: boolean + safety: { allowCommands: boolean; capabilityPolicy?: import("@submuxhq/codedecay-execution").CapabilityPolicy | undefined } ): ConfiguredToolHarness { const command = adapter.command ?? DEFAULT_PACT_COMMAND; const harnessOptions: PactHarnessOptions & { command: string } = { command, - allowCommands + allowCommands: safety.allowCommands, + capabilityPolicy: safety.capabilityPolicy }; if (adapter.timeoutMs !== undefined) { diff --git a/packages/tool-adapters/src/pact/harness.ts b/packages/tool-adapters/src/pact/harness.ts index 05653bfb..53f50b4e 100644 --- a/packages/tool-adapters/src/pact/harness.ts +++ b/packages/tool-adapters/src/pact/harness.ts @@ -27,6 +27,7 @@ import { } from "../shared/execution"; import { elapsed } from "../shared/values"; import type { PactHarnessOptions } from "../types"; +import { toolAdapterSafeCommandPolicy } from "../safety"; export function createPactHarness(options: PactHarnessOptions = {}): CodeDecayHarness { const command = options.command ?? DEFAULT_PACT_COMMAND; @@ -95,10 +96,7 @@ async function runPactPlan( cwd: context.cwd, timeoutMs, outputLimit: options.outputLimit, - safety: { - allowCommands: options.allowCommands ?? false, - allowUnsafeCommands: options.allowUnsafeCommands - } + safety: toolAdapterSafeCommandPolicy(options) }); const durationMs = elapsed(startedAt); const evidence = [pactEvidenceFromExecution(execution)]; diff --git a/packages/tool-adapters/src/playwright/configured.ts b/packages/tool-adapters/src/playwright/configured.ts index 7f2de512..74d232e9 100644 --- a/packages/tool-adapters/src/playwright/configured.ts +++ b/packages/tool-adapters/src/playwright/configured.ts @@ -4,12 +4,13 @@ import type { CodeDecayCommandToolAdapter, ConfiguredToolHarness, PlaywrightHarn export function createConfiguredPlaywrightHarness( adapter: CodeDecayCommandToolAdapter, - allowCommands: boolean + safety: { allowCommands: boolean; capabilityPolicy?: import("@submuxhq/codedecay-execution").CapabilityPolicy | undefined } ): ConfiguredToolHarness { const command = adapter.command ?? DEFAULT_PLAYWRIGHT_COMMAND; const harnessOptions: PlaywrightHarnessOptions & { command: string } = { command, - allowCommands + allowCommands: safety.allowCommands, + capabilityPolicy: safety.capabilityPolicy }; if (adapter.timeoutMs !== undefined) { diff --git a/packages/tool-adapters/src/playwright/harness.ts b/packages/tool-adapters/src/playwright/harness.ts index d03645c7..5a34e6e7 100644 --- a/packages/tool-adapters/src/playwright/harness.ts +++ b/packages/tool-adapters/src/playwright/harness.ts @@ -27,6 +27,7 @@ import { } from "../shared/execution"; import { elapsed } from "../shared/values"; import type { PlaywrightHarnessOptions } from "../types"; +import { toolAdapterSafeCommandPolicy } from "../safety"; export function createPlaywrightHarness(options: PlaywrightHarnessOptions = {}): CodeDecayHarness { const command = options.command ?? DEFAULT_PLAYWRIGHT_COMMAND; @@ -95,10 +96,7 @@ async function runPlaywrightPlan( cwd: context.cwd, timeoutMs, outputLimit: options.outputLimit, - safety: { - allowCommands: options.allowCommands ?? false, - allowUnsafeCommands: options.allowUnsafeCommands - } + safety: toolAdapterSafeCommandPolicy(options) }); const durationMs = elapsed(startedAt); const evidence = [playwrightEvidenceFromExecution(execution)]; diff --git a/packages/tool-adapters/src/safety.ts b/packages/tool-adapters/src/safety.ts new file mode 100644 index 00000000..1dc1f4b5 --- /dev/null +++ b/packages/tool-adapters/src/safety.ts @@ -0,0 +1,11 @@ +import type { CapabilityPolicy } from "@submuxhq/codedecay-execution"; +import { createSafeCommandPolicy } from "@submuxhq/codedecay-execution"; +import type { ToolAdapterExecutionSafety } from "./types"; + +export function toolAdapterSafeCommandPolicy(options: ToolAdapterExecutionSafety) { + return createSafeCommandPolicy({ + allowCommands: options.allowCommands ?? false, + capabilityPolicy: options.capabilityPolicy as CapabilityPolicy | undefined, + allowUnsafeCommands: options.allowUnsafeCommands + }); +} diff --git a/packages/tool-adapters/src/schemathesis/configured.ts b/packages/tool-adapters/src/schemathesis/configured.ts index 33b9a818..78387c17 100644 --- a/packages/tool-adapters/src/schemathesis/configured.ts +++ b/packages/tool-adapters/src/schemathesis/configured.ts @@ -8,10 +8,11 @@ import type { export function createConfiguredSchemathesisHarness( adapter: CodeDecaySchemathesisToolAdapter, - allowCommands: boolean + safety: { allowCommands: boolean; capabilityPolicy?: import("@submuxhq/codedecay-execution").CapabilityPolicy | undefined } ): ConfiguredToolHarness { const options: SchemathesisHarnessOptions = { - allowCommands + allowCommands: safety.allowCommands, + capabilityPolicy: safety.capabilityPolicy }; if (adapter.command !== undefined) { diff --git a/packages/tool-adapters/src/schemathesis/harness.ts b/packages/tool-adapters/src/schemathesis/harness.ts index c3742076..531e286a 100644 --- a/packages/tool-adapters/src/schemathesis/harness.ts +++ b/packages/tool-adapters/src/schemathesis/harness.ts @@ -27,6 +27,7 @@ import { } from "../shared/execution"; import { elapsed } from "../shared/values"; import type { SchemathesisHarnessOptions } from "../types"; +import { toolAdapterSafeCommandPolicy } from "../safety"; export function createSchemathesisHarness(options: SchemathesisHarnessOptions = {}): CodeDecayHarness { const command = resolveSchemathesisCommand(options); @@ -105,10 +106,7 @@ async function runSchemathesisPlan( cwd: context.cwd, timeoutMs, outputLimit: options.outputLimit, - safety: { - allowCommands: options.allowCommands ?? false, - allowUnsafeCommands: options.allowUnsafeCommands - } + safety: toolAdapterSafeCommandPolicy(options) }); const durationMs = elapsed(startedAt); const evidence = [schemathesisEvidenceFromExecution(execution)]; diff --git a/packages/tool-adapters/src/semgrep/configured.ts b/packages/tool-adapters/src/semgrep/configured.ts index 118f3fcc..97029514 100644 --- a/packages/tool-adapters/src/semgrep/configured.ts +++ b/packages/tool-adapters/src/semgrep/configured.ts @@ -4,10 +4,11 @@ import { resolveSemgrepDisplayCommand } from "./commands"; export function createConfiguredSemgrepHarness( adapter: CodeDecaySemgrepToolAdapter, - allowCommands: boolean + safety: { allowCommands: boolean; capabilityPolicy?: import("@submuxhq/codedecay-execution").CapabilityPolicy | undefined } ): ConfiguredToolHarness { const options: SemgrepHarnessOptions = { - allowCommands + allowCommands: safety.allowCommands, + capabilityPolicy: safety.capabilityPolicy }; if (adapter.command !== undefined) { diff --git a/packages/tool-adapters/src/semgrep/run.ts b/packages/tool-adapters/src/semgrep/run.ts index 44b59457..dfccea90 100644 --- a/packages/tool-adapters/src/semgrep/run.ts +++ b/packages/tool-adapters/src/semgrep/run.ts @@ -26,6 +26,7 @@ import { } from "./evidence"; import { analyzeSemgrepReport, findingsAtOrAboveThreshold } from "./report"; import { validateSemgrepPlan } from "./validation"; +import { toolAdapterSafeCommandPolicy } from "../safety"; export async function runSemgrepPlan( plan: HarnessPlan, @@ -70,10 +71,7 @@ export async function runSemgrepPlan( cwd: context.cwd, timeoutMs, outputLimit: options.outputLimit, - safety: { - allowCommands: options.allowCommands ?? false, - allowUnsafeCommands: options.allowUnsafeCommands - } + safety: toolAdapterSafeCommandPolicy(options) }); const durationMs = elapsed(startedAt); const canParseSemgrepReport = execution.status === "passed" || execution.status === "failed"; diff --git a/packages/tool-adapters/src/stryker/configured.ts b/packages/tool-adapters/src/stryker/configured.ts index 1314731e..594e1dbf 100644 --- a/packages/tool-adapters/src/stryker/configured.ts +++ b/packages/tool-adapters/src/stryker/configured.ts @@ -4,12 +4,13 @@ import { createStrykerHarness } from "./harness"; export function createConfiguredStrykerHarness( adapter: CodeDecayStrykerToolAdapter, - allowCommands: boolean + safety: { allowCommands: boolean; capabilityPolicy?: import("@submuxhq/codedecay-execution").CapabilityPolicy | undefined } ): ConfiguredToolHarness { const command = adapter.command ?? DEFAULT_STRYKER_COMMAND; const options: StrykerHarnessOptions = { command, - allowCommands + allowCommands: safety.allowCommands, + capabilityPolicy: safety.capabilityPolicy }; if (adapter.timeoutMs !== undefined) { diff --git a/packages/tool-adapters/src/stryker/run.ts b/packages/tool-adapters/src/stryker/run.ts index ed92e978..d987abb5 100644 --- a/packages/tool-adapters/src/stryker/run.ts +++ b/packages/tool-adapters/src/stryker/run.ts @@ -26,6 +26,7 @@ import { strykerReportFailureMessage } from "./report"; import { validateStrykerPlan } from "./validation"; +import { toolAdapterSafeCommandPolicy } from "../safety"; export async function runStrykerPlan( plan: HarnessPlan, @@ -40,10 +41,7 @@ export async function runStrykerPlan( cwd: context.cwd, timeoutMs, outputLimit: options.outputLimit, - safety: { - allowCommands: options.allowCommands ?? false, - allowUnsafeCommands: options.allowUnsafeCommands - } + safety: toolAdapterSafeCommandPolicy(options) }); const durationMs = elapsed(startedAt); const mutationReport = analyzeStrykerMutationReport(context.cwd, options.reportPath ?? DEFAULT_STRYKER_REPORT_PATH); diff --git a/packages/tool-adapters/src/types.ts b/packages/tool-adapters/src/types.ts index 16f4fb88..98ab4eb2 100644 --- a/packages/tool-adapters/src/types.ts +++ b/packages/tool-adapters/src/types.ts @@ -4,6 +4,7 @@ import type { CodeDecayCoverageFailOn, CodeDecayToolSeverity, } from "@submuxhq/codedecay-config"; +import type { CapabilityPolicy } from "@submuxhq/codedecay-execution"; import type { CodeDecayHarness } from "@submuxhq/codedecay-harness"; export type { @@ -20,70 +21,56 @@ export type { CodeDecayStrykerToolAdapter } from "@submuxhq/codedecay-config"; -export interface PlaywrightHarnessOptions { - command?: string | undefined; - timeoutMs?: number | undefined; +export interface ToolAdapterExecutionSafety { allowCommands?: boolean | undefined; allowUnsafeCommands?: boolean | undefined; + capabilityPolicy?: CapabilityPolicy | undefined; outputLimit?: number | undefined; } -export interface StrykerHarnessOptions { +export interface PlaywrightHarnessOptions extends ToolAdapterExecutionSafety { + command?: string | undefined; + timeoutMs?: number | undefined; +} + +export interface StrykerHarnessOptions extends ToolAdapterExecutionSafety { command?: string | undefined; reportPath?: string | undefined; timeoutMs?: number | undefined; - allowCommands?: boolean | undefined; - allowUnsafeCommands?: boolean | undefined; - outputLimit?: number | undefined; } -export interface SchemathesisHarnessOptions { +export interface SchemathesisHarnessOptions extends ToolAdapterExecutionSafety { command?: string | undefined; schema?: string | undefined; baseUrl?: string | undefined; timeoutMs?: number | undefined; - allowCommands?: boolean | undefined; - allowUnsafeCommands?: boolean | undefined; - outputLimit?: number | undefined; } -export interface PactHarnessOptions { +export interface PactHarnessOptions extends ToolAdapterExecutionSafety { command?: string | undefined; timeoutMs?: number | undefined; - allowCommands?: boolean | undefined; - allowUnsafeCommands?: boolean | undefined; - outputLimit?: number | undefined; } -export interface SemgrepHarnessOptions { +export interface SemgrepHarnessOptions extends ToolAdapterExecutionSafety { command?: string | undefined; config?: string | undefined; reportPath?: string | undefined; failOnSeverity?: CodeDecayToolSeverity | undefined; timeoutMs?: number | undefined; - allowCommands?: boolean | undefined; - allowUnsafeCommands?: boolean | undefined; - outputLimit?: number | undefined; } -export interface CoverageHarnessOptions { +export interface CoverageHarnessOptions extends ToolAdapterExecutionSafety { command?: string | undefined; reportPaths?: string[] | undefined; failOn?: CodeDecayCoverageFailOn | undefined; timeoutMs?: number | undefined; - allowCommands?: boolean | undefined; - allowUnsafeCommands?: boolean | undefined; - outputLimit?: number | undefined; } -export interface AgentProcessHarnessOptions { +export interface AgentProcessHarnessOptions extends ToolAdapterExecutionSafety { command?: string | undefined; profile?: CodeDecayAgentProfile | undefined; bundleFormat?: CodeDecayAgentBundleFormat | undefined; timeoutMs?: number | undefined; - allowCommands?: boolean | undefined; - allowUnsafeCommands?: boolean | undefined; - outputLimit?: number | undefined; } export type ConfiguredToolAdapterKind =