infraweaver 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 +661 -0
- package/README.md +244 -0
- package/dist/agents/claude.d.ts +113 -0
- package/dist/agents/claudePretoolGate.d.ts +137 -0
- package/dist/agents/dispatchMetrics.d.ts +77 -0
- package/dist/agents/gateServer.d.ts +7 -0
- package/dist/agents/index.d.ts +6 -0
- package/dist/agents/nativeFsDenies.d.ts +46 -0
- package/dist/agents/opencode.d.ts +284 -0
- package/dist/agents/opencodePlugin.d.ts +96 -0
- package/dist/agents/opencodeShared.d.ts +40 -0
- package/dist/agents/postRun.d.ts +126 -0
- package/dist/agents/reviewer.d.ts +41 -0
- package/dist/agents/sessionLabeler.d.ts +97 -0
- package/dist/agents/shared.d.ts +246 -0
- package/dist/agents/subagentModels.d.ts +19 -0
- package/dist/agents/tokenQuota.d.ts +118 -0
- package/dist/agents/writeGateSource.d.ts +22 -0
- package/dist/agents/writePolicy.d.ts +79 -0
- package/dist/brand.d.ts +67 -0
- package/dist/cli.mjs +247846 -0
- package/dist/external.d.ts +227 -0
- package/dist/i18n/scaffolding.d.ts +59 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +247061 -0
- package/dist/internal/index.d.ts +18 -0
- package/dist/internal.js +2398 -0
- package/dist/lifecycle.d.ts +2 -0
- package/dist/main.d.ts +8 -0
- package/dist/mcp/arkConfig.d.ts +1 -0
- package/dist/mcp/assess.d.ts +157 -0
- package/dist/mcp/capabilityContext.d.ts +71 -0
- package/dist/mcp/changeSummary.d.ts +52 -0
- package/dist/mcp/checkSuite.d.ts +27 -0
- package/dist/mcp/checkout.d.ts +92 -0
- package/dist/mcp/comment.d.ts +127 -0
- package/dist/mcp/commitInfo.d.ts +11 -0
- package/dist/mcp/crosswalk.d.ts +178 -0
- package/dist/mcp/crosswalkDigest.d.ts +1 -0
- package/dist/mcp/cyberEssentials.d.ts +24 -0
- package/dist/mcp/dashboard.d.ts +125 -0
- package/dist/mcp/dependencies.d.ts +12 -0
- package/dist/mcp/frameworks.d.ts +74 -0
- package/dist/mcp/geminiSanitizer.d.ts +28 -0
- package/dist/mcp/git.d.ts +60 -0
- package/dist/mcp/guardrails.d.ts +222 -0
- package/dist/mcp/issue.d.ts +20 -0
- package/dist/mcp/issueComments.d.ts +11 -0
- package/dist/mcp/issueEvents.d.ts +11 -0
- package/dist/mcp/issueInfo.d.ts +11 -0
- package/dist/mcp/labels.d.ts +14 -0
- package/dist/mcp/localContext.d.ts +26 -0
- package/dist/mcp/moduleExtraction.d.ts +79 -0
- package/dist/mcp/moduleTests.d.ts +106 -0
- package/dist/mcp/modules.d.ts +198 -0
- package/dist/mcp/output.d.ts +16 -0
- package/dist/mcp/pathSafety.d.ts +14 -0
- package/dist/mcp/policy.d.ts +50 -0
- package/dist/mcp/pr.d.ts +64 -0
- package/dist/mcp/prInfo.d.ts +11 -0
- package/dist/mcp/providerSchema.d.ts +52 -0
- package/dist/mcp/review.d.ts +212 -0
- package/dist/mcp/reviewComments.d.ts +245 -0
- package/dist/mcp/roots.d.ts +60 -0
- package/dist/mcp/scope.d.ts +26 -0
- package/dist/mcp/selectMode.d.ts +20 -0
- package/dist/mcp/server.d.ts +61 -0
- package/dist/mcp/shared.d.ts +62 -0
- package/dist/mcp/shell.d.ts +58 -0
- package/dist/mcp/staleFix.d.ts +138 -0
- package/dist/mcp/terraform/azurePrices.d.ts +8 -0
- package/dist/mcp/terraform/baseline.d.ts +57 -0
- package/dist/mcp/terraform/concernResult.d.ts +38 -0
- package/dist/mcp/terraform/cost.d.ts +55 -0
- package/dist/mcp/terraform/cspm.d.ts +80 -0
- package/dist/mcp/terraform/currency.d.ts +110 -0
- package/dist/mcp/terraform/decisions.d.ts +178 -0
- package/dist/mcp/terraform/eidas.d.ts +100 -0
- package/dist/mcp/terraform/evidence.d.ts +343 -0
- package/dist/mcp/terraform/findings.d.ts +76 -0
- package/dist/mcp/terraform/fixMemory.d.ts +81 -0
- package/dist/mcp/terraform/governance.d.ts +56 -0
- package/dist/mcp/terraform/hcl.d.ts +115 -0
- package/dist/mcp/terraform/iacLanguages.d.ts +120 -0
- package/dist/mcp/terraform/idleFloor.d.ts +78 -0
- package/dist/mcp/terraform/keyless.d.ts +68 -0
- package/dist/mcp/terraform/moduleDocs.d.ts +56 -0
- package/dist/mcp/terraform/nativeRules.d.ts +38 -0
- package/dist/mcp/terraform/nativeScan.d.ts +11 -0
- package/dist/mcp/terraform/noiseProfile.d.ts +100 -0
- package/dist/mcp/terraform/normalization.d.ts +52 -0
- package/dist/mcp/terraform/oscal.d.ts +103 -0
- package/dist/mcp/terraform/packs/azurerm-3-4.d.ts +2 -0
- package/dist/mcp/terraform/packs/cloudflare-4-5.d.ts +2 -0
- package/dist/mcp/terraform/paths.d.ts +28 -0
- package/dist/mcp/terraform/plan.d.ts +157 -0
- package/dist/mcp/terraform/planDrift.d.ts +199 -0
- package/dist/mcp/terraform/policyAuthor.d.ts +191 -0
- package/dist/mcp/terraform/prowlerOcsf.d.ts +136 -0
- package/dist/mcp/terraform/refactor.d.ts +183 -0
- package/dist/mcp/terraform/registry.d.ts +108 -0
- package/dist/mcp/terraform/risk.d.ts +41 -0
- package/dist/mcp/terraform/scanSession.d.ts +116 -0
- package/dist/mcp/terraform/scannerCache.d.ts +15 -0
- package/dist/mcp/terraform/scannerEgress.d.ts +72 -0
- package/dist/mcp/terraform/scannerJson.d.ts +13 -0
- package/dist/mcp/terraform/scanners.d.ts +205 -0
- package/dist/mcp/terraform/signing.d.ts +61 -0
- package/dist/mcp/terraform/stateLocking.d.ts +18 -0
- package/dist/mcp/terraform/subprocess.d.ts +43 -0
- package/dist/mcp/terraform/suppressions.d.ts +90 -0
- package/dist/mcp/terraform/taxonomy.d.ts +39 -0
- package/dist/mcp/terraform/tfquery.d.ts +47 -0
- package/dist/mcp/terraform/toolchainIntegrity.d.ts +82 -0
- package/dist/mcp/terraform/tools/authorPolicy.d.ts +59 -0
- package/dist/mcp/terraform/tools/detectPlanDrift.d.ts +20 -0
- package/dist/mcp/terraform/tools/emitCost.d.ts +11 -0
- package/dist/mcp/terraform/tools/emitSarif.d.ts +14 -0
- package/dist/mcp/terraform/tools/fixMemory.d.ts +19 -0
- package/dist/mcp/terraform/tools/infracostDiff.d.ts +22 -0
- package/dist/mcp/terraform/tools/ingestExternalFindings.d.ts +21 -0
- package/dist/mcp/terraform/tools/moduleLookup.d.ts +14 -0
- package/dist/mcp/terraform/tools/moduleSearch.d.ts +23 -0
- package/dist/mcp/terraform/tools/plan.d.ts +59 -0
- package/dist/mcp/terraform/tools/providerUpgrade.d.ts +55 -0
- package/dist/mcp/terraform/tools/readFindings.d.ts +17 -0
- package/dist/mcp/terraform/tools/scan.d.ts +114 -0
- package/dist/mcp/terraform/tools/validate.d.ts +31 -0
- package/dist/mcp/terraform/tools/verifyRemediation.d.ts +31 -0
- package/dist/mcp/terraform/tools/versionCurrency.d.ts +5 -0
- package/dist/mcp/terraform/tools.d.ts +16 -0
- package/dist/mcp/terraform/transformPacks.d.ts +267 -0
- package/dist/mcp/terraform/types.d.ts +226 -0
- package/dist/mcp/terraform/verification.d.ts +81 -0
- package/dist/mcp/terraform/vex.d.ts +115 -0
- package/dist/mcp/terraform/writeOnly.d.ts +67 -0
- package/dist/mcp/terraform.d.ts +51 -0
- package/dist/mcp/terratest.d.ts +85 -0
- package/dist/mcp/untrustedContent.d.ts +76 -0
- package/dist/mcp/upload.d.ts +8 -0
- package/dist/models.d.ts +181 -0
- package/dist/modes/address-reviews.d.ts +2 -0
- package/dist/modes/assess.d.ts +2 -0
- package/dist/modes/build.d.ts +2 -0
- package/dist/modes/compliance-audit.d.ts +2 -0
- package/dist/modes/cost-optimization.d.ts +2 -0
- package/dist/modes/drift-detect.d.ts +2 -0
- package/dist/modes/fix.d.ts +2 -0
- package/dist/modes/incremental-review.d.ts +2 -0
- package/dist/modes/index.d.ts +24 -0
- package/dist/modes/modernize-deprecated.d.ts +2 -0
- package/dist/modes/plan.d.ts +2 -0
- package/dist/modes/policy-gate.d.ts +2 -0
- package/dist/modes/prFormats.d.ts +3 -0
- package/dist/modes/refactor.d.ts +2 -0
- package/dist/modes/refresh-remediation.d.ts +2 -0
- package/dist/modes/remediate-and-refactor.d.ts +2 -0
- package/dist/modes/remediate.d.ts +2 -0
- package/dist/modes/resolve-conflicts.d.ts +2 -0
- package/dist/modes/review.d.ts +2 -0
- package/dist/modes/summarize-pr.d.ts +2 -0
- package/dist/modes/task.d.ts +2 -0
- package/dist/modes/terraform-code-review.d.ts +2 -0
- package/dist/modes/types.d.ts +9 -0
- package/dist/modes/update-dependencies.d.ts +2 -0
- package/dist/prep/index.d.ts +7 -0
- package/dist/prep/installNodeDependencies.d.ts +2 -0
- package/dist/prep/installPythonDependencies.d.ts +2 -0
- package/dist/prep/types.d.ts +31 -0
- package/dist/reviewQuality.d.ts +107 -0
- package/dist/skills/terraform-best-practices/SKILL.md +379 -0
- package/dist/toolState.d.ts +123 -0
- package/dist/utils/activity.d.ts +40 -0
- package/dist/utils/agent.d.ts +47 -0
- package/dist/utils/agentHangReport.d.ts +38 -0
- package/dist/utils/annotations.d.ts +17 -0
- package/dist/utils/apiFetch.d.ts +11 -0
- package/dist/utils/apiKeys.d.ts +48 -0
- package/dist/utils/apiUrl.d.ts +28 -0
- package/dist/utils/assets.d.ts +8 -0
- package/dist/utils/baseRefConfig.d.ts +121 -0
- package/dist/utils/billingErrors.d.ts +85 -0
- package/dist/utils/body.d.ts +34 -0
- package/dist/utils/buildInfraweaverFooter.d.ts +29 -0
- package/dist/utils/byokFallback.d.ts +85 -0
- package/dist/utils/changeImpact.d.ts +101 -0
- package/dist/utils/claudeSubscription.d.ts +30 -0
- package/dist/utils/cli.d.ts +10 -0
- package/dist/utils/cloudClient.d.ts +33 -0
- package/dist/utils/cloudFetchCore.d.ts +42 -0
- package/dist/utils/cloudReport.d.ts +114 -0
- package/dist/utils/codexHome.d.ts +29 -0
- package/dist/utils/codexOAuth.d.ts +60 -0
- package/dist/utils/diffCoverage.d.ts +63 -0
- package/dist/utils/env.d.ts +46 -0
- package/dist/utils/errorReport.d.ts +17 -0
- package/dist/utils/exitHandler.d.ts +8 -0
- package/dist/utils/fixDoubleEscapedString.d.ts +1 -0
- package/dist/utils/gitAuth.d.ts +84 -0
- package/dist/utils/gitAuthServer.d.ts +24 -0
- package/dist/utils/github.d.ts +104 -0
- package/dist/utils/globals.d.ts +3 -0
- package/dist/utils/infraweaverConfig.d.ts +56 -0
- package/dist/utils/install.d.ts +37 -0
- package/dist/utils/instructions.d.ts +48 -0
- package/dist/utils/keylessOidc.d.ts +28 -0
- package/dist/utils/leapingComment.d.ts +11 -0
- package/dist/utils/learnings.d.ts +62 -0
- package/dist/utils/learningsTruncate.d.ts +25 -0
- package/dist/utils/lifecycle.d.ts +57 -0
- package/dist/utils/log.d.ts +111 -0
- package/dist/utils/modelResolver.d.ts +63 -0
- package/dist/utils/moduleFetch.d.ts +39 -0
- package/dist/utils/normalizeEnv.d.ts +30 -0
- package/dist/utils/openCodeModels.d.ts +11 -0
- package/dist/utils/openaiCompatible.d.ts +26 -0
- package/dist/utils/opentofuMcp.d.ts +50 -0
- package/dist/utils/overrides.d.ts +40 -0
- package/dist/utils/packageManager.d.ts +49 -0
- package/dist/utils/patchWorkflowRunFields.d.ts +29 -0
- package/dist/utils/payload.d.ts +249 -0
- package/dist/utils/prSummary.d.ts +61 -0
- package/dist/utils/presets.d.ts +36 -0
- package/dist/utils/progressComment.d.ts +146 -0
- package/dist/utils/providerErrors.d.ts +31 -0
- package/dist/utils/proxyModel.d.ts +76 -0
- package/dist/utils/rangeDiff.d.ts +51 -0
- package/dist/utils/redact.d.ts +33 -0
- package/dist/utils/registryAuth.d.ts +52 -0
- package/dist/utils/remediationCommand.d.ts +58 -0
- package/dist/utils/retry.d.ts +38 -0
- package/dist/utils/reviewCleanup.d.ts +14 -0
- package/dist/utils/run.d.ts +9 -0
- package/dist/utils/runContext.d.ts +112 -0
- package/dist/utils/runContextData.d.ts +42 -0
- package/dist/utils/runErrorRenderer.d.ts +64 -0
- package/dist/utils/runLifecycle.d.ts +89 -0
- package/dist/utils/runStartupLog.d.ts +15 -0
- package/dist/utils/secrets.d.ts +34 -0
- package/dist/utils/setup.d.ts +90 -0
- package/dist/utils/shell.d.ts +44 -0
- package/dist/utils/skills.d.ts +10 -0
- package/dist/utils/subprocess.d.ts +81 -0
- package/dist/utils/terraformMcp.d.ts +46 -0
- package/dist/utils/time.d.ts +15 -0
- package/dist/utils/timer.d.ts +23 -0
- package/dist/utils/todoTracking.d.ts +16 -0
- package/dist/utils/token.d.ts +52 -0
- package/dist/utils/toolLicensing.d.ts +56 -0
- package/dist/utils/toolSelection.d.ts +73 -0
- package/dist/utils/toon.d.ts +16 -0
- package/dist/utils/version.d.ts +2 -0
- package/dist/utils/versioning.d.ts +7 -0
- package/dist/utils/vertex.d.ts +16 -0
- package/dist/utils/workflow.d.ts +13 -0
- package/package.json +132 -0
- package/src/agents/claude.ts +1588 -0
- package/src/agents/claudePretoolGate.ts +286 -0
- package/src/agents/dispatchMetrics.ts +162 -0
- package/src/agents/gateServer.ts +178 -0
- package/src/agents/index.ts +10 -0
- package/src/agents/nativeFsDenies.ts +164 -0
- package/src/agents/opencode.ts +1600 -0
- package/src/agents/opencodePlugin.ts +304 -0
- package/src/agents/opencodeShared.ts +134 -0
- package/src/agents/postRun.ts +615 -0
- package/src/agents/reviewer.ts +134 -0
- package/src/agents/sessionLabeler.ts +185 -0
- package/src/agents/shared.ts +377 -0
- package/src/agents/subagentModels.ts +40 -0
- package/src/agents/tokenQuota.ts +203 -0
- package/src/agents/writeGateSource.ts +148 -0
- package/src/agents/writePolicy.ts +132 -0
- package/src/brand.ts +84 -0
- package/src/cli.ts +117 -0
- package/src/commands/gha.ts +188 -0
- package/src/commands/mcp.ts +126 -0
- package/src/commands/verifyEvidence.ts +100 -0
- package/src/entry.ts +7 -0
- package/src/entryPost.ts +109 -0
- package/src/external.ts +308 -0
- package/src/i18n/scaffolding.ts +191 -0
- package/src/index.ts +7 -0
- package/src/internal/index.ts +74 -0
- package/src/lifecycle.ts +2 -0
- package/src/main.ts +815 -0
- package/src/mcp/__fixtures__/infraweaver-scratch-pr-49-review-3485940013.json +110 -0
- package/src/mcp/__fixtures__/infraweaver-scratch-pr-64-review-3531000326.json +14 -0
- package/src/mcp/__fixtures__/infraweaver-test-repo-pr-1.diff.json +67 -0
- package/src/mcp/__snapshots__/checkout.test.ts.snap +109 -0
- package/src/mcp/__snapshots__/reviewComments.test.ts.snap +71 -0
- package/src/mcp/arkConfig.ts +7 -0
- package/src/mcp/assess.ts +723 -0
- package/src/mcp/capabilityContext.ts +80 -0
- package/src/mcp/changeSummary.ts +145 -0
- package/src/mcp/checkSuite.ts +261 -0
- package/src/mcp/checkout.ts +1015 -0
- package/src/mcp/comment.ts +683 -0
- package/src/mcp/commitInfo.ts +57 -0
- package/src/mcp/crosswalk.ts +1253 -0
- package/src/mcp/crosswalkDigest.ts +5 -0
- package/src/mcp/cyberEssentials.ts +60 -0
- package/src/mcp/dashboard.ts +232 -0
- package/src/mcp/dependencies.ts +190 -0
- package/src/mcp/frameworks.ts +236 -0
- package/src/mcp/geminiSanitizer.ts +212 -0
- package/src/mcp/git.ts +1116 -0
- package/src/mcp/guardrails.ts +771 -0
- package/src/mcp/issue.ts +74 -0
- package/src/mcp/issueComments.ts +48 -0
- package/src/mcp/issueEvents.ts +100 -0
- package/src/mcp/issueInfo.ts +72 -0
- package/src/mcp/labels.ts +35 -0
- package/src/mcp/localContext.ts +65 -0
- package/src/mcp/localServer.ts +217 -0
- package/src/mcp/moduleExtraction.ts +368 -0
- package/src/mcp/moduleTests.ts +421 -0
- package/src/mcp/modules.ts +752 -0
- package/src/mcp/output.ts +71 -0
- package/src/mcp/pathSafety.ts +28 -0
- package/src/mcp/policy.ts +226 -0
- package/src/mcp/pr.ts +238 -0
- package/src/mcp/prInfo.ts +91 -0
- package/src/mcp/providerSchema.ts +175 -0
- package/src/mcp/review.ts +1081 -0
- package/src/mcp/reviewComments.ts +1137 -0
- package/src/mcp/roots.ts +217 -0
- package/src/mcp/scope.ts +82 -0
- package/src/mcp/selectMode.ts +207 -0
- package/src/mcp/server.ts +498 -0
- package/src/mcp/shared.ts +120 -0
- package/src/mcp/shell.ts +631 -0
- package/src/mcp/staleFix.ts +557 -0
- package/src/mcp/terraform/__snapshots__/moduleDocs.test.ts.snap +40 -0
- package/src/mcp/terraform/azurePrices.ts +38 -0
- package/src/mcp/terraform/baseline.ts +172 -0
- package/src/mcp/terraform/concernResult.ts +111 -0
- package/src/mcp/terraform/cost.ts +175 -0
- package/src/mcp/terraform/cspm.ts +326 -0
- package/src/mcp/terraform/currency.ts +354 -0
- package/src/mcp/terraform/decisions.ts +590 -0
- package/src/mcp/terraform/eidas.ts +134 -0
- package/src/mcp/terraform/evidence.ts +771 -0
- package/src/mcp/terraform/findings.ts +383 -0
- package/src/mcp/terraform/fixMemory.ts +253 -0
- package/src/mcp/terraform/governance.ts +231 -0
- package/src/mcp/terraform/hcl.ts +273 -0
- package/src/mcp/terraform/iacLanguages.ts +431 -0
- package/src/mcp/terraform/idleFloor.ts +304 -0
- package/src/mcp/terraform/keyless.ts +123 -0
- package/src/mcp/terraform/moduleDocs.ts +303 -0
- package/src/mcp/terraform/nativeRules.ts +152 -0
- package/src/mcp/terraform/nativeScan.ts +67 -0
- package/src/mcp/terraform/noiseProfile.ts +183 -0
- package/src/mcp/terraform/normalization.ts +265 -0
- package/src/mcp/terraform/oscal.ts +283 -0
- package/src/mcp/terraform/packs/azurerm-3-4.ts +382 -0
- package/src/mcp/terraform/packs/cloudflare-4-5.ts +114 -0
- package/src/mcp/terraform/paths.ts +59 -0
- package/src/mcp/terraform/plan.ts +377 -0
- package/src/mcp/terraform/planDrift.ts +520 -0
- package/src/mcp/terraform/policyAuthor.ts +886 -0
- package/src/mcp/terraform/prowlerOcsf.ts +380 -0
- package/src/mcp/terraform/refactor.ts +879 -0
- package/src/mcp/terraform/registry.ts +317 -0
- package/src/mcp/terraform/risk.ts +99 -0
- package/src/mcp/terraform/scanSession.ts +242 -0
- package/src/mcp/terraform/scannerCache.ts +86 -0
- package/src/mcp/terraform/scannerEgress.ts +141 -0
- package/src/mcp/terraform/scannerJson.ts +22 -0
- package/src/mcp/terraform/scanners.ts +1134 -0
- package/src/mcp/terraform/signing.ts +151 -0
- package/src/mcp/terraform/stateLocking.ts +106 -0
- package/src/mcp/terraform/subprocess.ts +102 -0
- package/src/mcp/terraform/suppressions.ts +551 -0
- package/src/mcp/terraform/taxonomy.ts +0 -0
- package/src/mcp/terraform/tfquery.ts +159 -0
- package/src/mcp/terraform/toolchainIntegrity.ts +151 -0
- package/src/mcp/terraform/tools/authorPolicy.ts +185 -0
- package/src/mcp/terraform/tools/detectPlanDrift.ts +279 -0
- package/src/mcp/terraform/tools/emitCost.ts +130 -0
- package/src/mcp/terraform/tools/emitSarif.ts +108 -0
- package/src/mcp/terraform/tools/fixMemory.ts +82 -0
- package/src/mcp/terraform/tools/infracostDiff.ts +110 -0
- package/src/mcp/terraform/tools/ingestExternalFindings.ts +259 -0
- package/src/mcp/terraform/tools/moduleLookup.ts +86 -0
- package/src/mcp/terraform/tools/moduleSearch.ts +46 -0
- package/src/mcp/terraform/tools/plan.ts +384 -0
- package/src/mcp/terraform/tools/providerUpgrade.ts +217 -0
- package/src/mcp/terraform/tools/readFindings.ts +138 -0
- package/src/mcp/terraform/tools/scan.ts +303 -0
- package/src/mcp/terraform/tools/validate.ts +160 -0
- package/src/mcp/terraform/tools/verifyRemediation.ts +190 -0
- package/src/mcp/terraform/tools/versionCurrency.ts +64 -0
- package/src/mcp/terraform/tools.ts +62 -0
- package/src/mcp/terraform/transformPacks.ts +594 -0
- package/src/mcp/terraform/types.ts +382 -0
- package/src/mcp/terraform/verification.ts +150 -0
- package/src/mcp/terraform/vex.ts +367 -0
- package/src/mcp/terraform/writeOnly.ts +252 -0
- package/src/mcp/terraform.ts +52 -0
- package/src/mcp/terratest.ts +215 -0
- package/src/mcp/untrustedContent.ts +126 -0
- package/src/mcp/upload.ts +123 -0
- package/src/models.ts +753 -0
- package/src/modes/address-reviews.ts +35 -0
- package/src/modes/assess.ts +27 -0
- package/src/modes/build.ts +86 -0
- package/src/modes/compliance-audit.ts +25 -0
- package/src/modes/cost-optimization.ts +36 -0
- package/src/modes/drift-detect.ts +31 -0
- package/src/modes/fix.ts +29 -0
- package/src/modes/incremental-review.ts +103 -0
- package/src/modes/index.ts +94 -0
- package/src/modes/modernize-deprecated.ts +36 -0
- package/src/modes/plan.ts +21 -0
- package/src/modes/policy-gate.ts +27 -0
- package/src/modes/prFormats.ts +325 -0
- package/src/modes/refactor.ts +74 -0
- package/src/modes/refresh-remediation.ts +40 -0
- package/src/modes/remediate-and-refactor.ts +42 -0
- package/src/modes/remediate.ts +78 -0
- package/src/modes/resolve-conflicts.ts +32 -0
- package/src/modes/review.ts +135 -0
- package/src/modes/summarize-pr.ts +42 -0
- package/src/modes/task.ts +26 -0
- package/src/modes/terraform-code-review.ts +68 -0
- package/src/modes/types.ts +17 -0
- package/src/modes/update-dependencies.ts +44 -0
- package/src/prep/index.ts +96 -0
- package/src/prep/installNodeDependencies.ts +251 -0
- package/src/prep/installPythonDependencies.ts +235 -0
- package/src/prep/types.ts +38 -0
- package/src/reviewQuality.ts +237 -0
- package/src/runCli.ts +335 -0
- package/src/skills/terraform-best-practices/SKILL.md +379 -0
- package/src/toolState.ts +241 -0
- package/src/utils/activity.ts +210 -0
- package/src/utils/agent.ts +245 -0
- package/src/utils/agentHangReport.ts +180 -0
- package/src/utils/annotations.ts +56 -0
- package/src/utils/apiFetch.ts +19 -0
- package/src/utils/apiKeys.ts +305 -0
- package/src/utils/apiUrl.ts +42 -0
- package/src/utils/assets.ts +123 -0
- package/src/utils/baseRefConfig.ts +254 -0
- package/src/utils/billingErrors.ts +210 -0
- package/src/utils/body.ts +168 -0
- package/src/utils/buildInfraweaverFooter.ts +104 -0
- package/src/utils/byokFallback.ts +137 -0
- package/src/utils/changeImpact.ts +330 -0
- package/src/utils/claudeSubscription.ts +93 -0
- package/src/utils/cli.ts +36 -0
- package/src/utils/cloudClient.ts +57 -0
- package/src/utils/cloudFetchCore.ts +139 -0
- package/src/utils/cloudReport.ts +316 -0
- package/src/utils/codexHome.ts +204 -0
- package/src/utils/codexOAuth.ts +154 -0
- package/src/utils/codexRefreshDetect.ts +36 -0
- package/src/utils/diffCoverage.ts +404 -0
- package/src/utils/env.ts +84 -0
- package/src/utils/errorReport.ts +94 -0
- package/src/utils/exitHandler.ts +35 -0
- package/src/utils/fixDoubleEscapedString.ts +9 -0
- package/src/utils/ghaCore.ts +13 -0
- package/src/utils/gitAuth.ts +268 -0
- package/src/utils/gitAuthServer.ts +186 -0
- package/src/utils/github.ts +676 -0
- package/src/utils/globals.ts +9 -0
- package/src/utils/humanEditCapture.ts +198 -0
- package/src/utils/infraweaverConfig.ts +202 -0
- package/src/utils/install.ts +279 -0
- package/src/utils/instructions.ts +640 -0
- package/src/utils/keylessOidc.ts +47 -0
- package/src/utils/leapingComment.ts +20 -0
- package/src/utils/learnings.ts +145 -0
- package/src/utils/learningsTruncate.ts +42 -0
- package/src/utils/lifecycle.ts +198 -0
- package/src/utils/log.ts +452 -0
- package/src/utils/modelResolver.ts +105 -0
- package/src/utils/moduleFetch.ts +138 -0
- package/src/utils/normalizeEnv.ts +106 -0
- package/src/utils/openCodeModels.ts +86 -0
- package/src/utils/openaiCompatible.ts +52 -0
- package/src/utils/opentofuMcp.ts +61 -0
- package/src/utils/overrides.ts +100 -0
- package/src/utils/packageManager.ts +257 -0
- package/src/utils/patchWorkflowRunFields.ts +173 -0
- package/src/utils/payload.ts +1206 -0
- package/src/utils/prSummary.ts +148 -0
- package/src/utils/presets.ts +105 -0
- package/src/utils/progressComment.ts +266 -0
- package/src/utils/providerErrors.ts +189 -0
- package/src/utils/proxyModel.ts +239 -0
- package/src/utils/rangeDiff.ts +182 -0
- package/src/utils/redact.ts +86 -0
- package/src/utils/registryAuth.ts +115 -0
- package/src/utils/remediationCommand.ts +144 -0
- package/src/utils/retry.ts +180 -0
- package/src/utils/reviewCleanup.ts +116 -0
- package/src/utils/run.ts +100 -0
- package/src/utils/runContext.ts +276 -0
- package/src/utils/runContextData.ts +121 -0
- package/src/utils/runErrorRenderer.ts +282 -0
- package/src/utils/runFixture.ts +91 -0
- package/src/utils/runLifecycle.ts +360 -0
- package/src/utils/runStartupLog.ts +67 -0
- package/src/utils/secrets.ts +208 -0
- package/src/utils/setup.ts +368 -0
- package/src/utils/shell.ts +132 -0
- package/src/utils/skills.ts +67 -0
- package/src/utils/subprocess.ts +474 -0
- package/src/utils/terraformMcp.ts +99 -0
- package/src/utils/time.ts +59 -0
- package/src/utils/timer.ts +72 -0
- package/src/utils/todoTracking.ts +168 -0
- package/src/utils/token.ts +294 -0
- package/src/utils/toolLicensing.ts +152 -0
- package/src/utils/toolSelection.ts +239 -0
- package/src/utils/toon.ts +76 -0
- package/src/utils/version.ts +10 -0
- package/src/utils/versioning.ts +44 -0
- package/src/utils/vertex.ts +94 -0
- package/src/utils/workflow.ts +25 -0
package/src/mcp/shell.ts
ADDED
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
// changes to shell security (filterEnv, spawnShell) should be reflected in wiki/security.md and docs/security.mdx
|
|
2
|
+
import {
|
|
3
|
+
type ChildProcess,
|
|
4
|
+
type StdioOptions,
|
|
5
|
+
spawn,
|
|
6
|
+
spawnSync,
|
|
7
|
+
} from "node:child_process";
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
9
|
+
import { closeSync, openSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { userInfo } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
13
|
+
import { type } from "arktype";
|
|
14
|
+
import type { ToolContext } from "#app/mcp/server";
|
|
15
|
+
import { execute, tool } from "#app/mcp/shared";
|
|
16
|
+
import { getTempDir } from "#app/utils/env";
|
|
17
|
+
import { log } from "#app/utils/log";
|
|
18
|
+
import { resolveEnv } from "#app/utils/secrets";
|
|
19
|
+
|
|
20
|
+
export const ShellParams = type({
|
|
21
|
+
command: "string",
|
|
22
|
+
description: "string",
|
|
23
|
+
"timeout?": type.number.describe(
|
|
24
|
+
"Timeout in MILLISECONDS (not seconds). Default 30000 (30s), max 120000 (2m). e.g. timeout: 180000 for 3 minutes; timeout: 180 means 180ms and will kill the process almost immediately.",
|
|
25
|
+
),
|
|
26
|
+
"working_directory?": "string",
|
|
27
|
+
"background?": "boolean",
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
type SpawnParams = {
|
|
31
|
+
command: string;
|
|
32
|
+
env: Record<string, string | undefined>;
|
|
33
|
+
cwd: string;
|
|
34
|
+
stdio: StdioOptions;
|
|
35
|
+
/** foreground timeout in ms. Under sudo-unshare it's enforced by a coreutils
|
|
36
|
+
* `timeout` wrapping the command INSIDE the PID namespace (see spawnShell) —
|
|
37
|
+
* the JS-side killProcessGroup can't reach that root-owned group. Omit for
|
|
38
|
+
* background procs (they run until kill_background). */
|
|
39
|
+
timeoutMs?: number;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** wrap `s` as a single-quoted shell token, escaping any embedded single quote
|
|
43
|
+
* (`'` → `'\''`: close, literal-quote, reopen). */
|
|
44
|
+
function shSingleQuote(s: string): string {
|
|
45
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type SandboxMethod = "unshare" | "sudo-unshare" | "none";
|
|
49
|
+
|
|
50
|
+
/** cached result of sandbox capability check */
|
|
51
|
+
let detectedSandboxMethod: SandboxMethod | undefined;
|
|
52
|
+
|
|
53
|
+
/** get the current sandbox method (for testing/diagnostics) */
|
|
54
|
+
export function getSandboxMethod(): SandboxMethod {
|
|
55
|
+
return detectSandboxMethod();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** detect which sandbox method is available on this system */
|
|
59
|
+
function detectSandboxMethod(): SandboxMethod {
|
|
60
|
+
if (detectedSandboxMethod !== undefined) {
|
|
61
|
+
return detectedSandboxMethod;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// only attempt in CI environments - sandbox has overhead and is primarily for untrusted code
|
|
65
|
+
if (process.env.CI !== "true") {
|
|
66
|
+
detectedSandboxMethod = "none";
|
|
67
|
+
log.debug("sandbox disabled (CI !== true)");
|
|
68
|
+
return "none";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// try unprivileged unshare first (works on some systems)
|
|
72
|
+
try {
|
|
73
|
+
const result = spawnSync(
|
|
74
|
+
"unshare",
|
|
75
|
+
["--pid", "--fork", "--mount-proc", "true"],
|
|
76
|
+
{
|
|
77
|
+
timeout: 5000,
|
|
78
|
+
stdio: "ignore",
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
if (result.status === 0) {
|
|
82
|
+
detectedSandboxMethod = "unshare";
|
|
83
|
+
log.debug("PID namespace isolation enabled (unprivileged unshare)");
|
|
84
|
+
return "unshare";
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
// continue to try sudo
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// sudo unshare (works on GHA runners)
|
|
91
|
+
try {
|
|
92
|
+
const result = spawnSync(
|
|
93
|
+
"sudo",
|
|
94
|
+
["unshare", "--pid", "--fork", "--mount-proc", "true"],
|
|
95
|
+
{
|
|
96
|
+
timeout: 5000,
|
|
97
|
+
stdio: "ignore",
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
if (result.status === 0) {
|
|
101
|
+
detectedSandboxMethod = "sudo-unshare";
|
|
102
|
+
log.debug("PID namespace isolation enabled (sudo unshare)");
|
|
103
|
+
return "sudo-unshare";
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
// no sandbox available
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
detectedSandboxMethod = "none";
|
|
110
|
+
log.info("PID namespace isolation not available");
|
|
111
|
+
return "none";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// strip inherited proc mount that sits underneath --mount-proc's overlay.
|
|
115
|
+
// --mount-proc mounts fresh proc on top, but `umount /proc` peels it off and exposes the
|
|
116
|
+
// host's proc with all host PIDs — allowing /proc/<pid>/environ exfiltration.
|
|
117
|
+
// double-umount removes both layers, then a clean mount gives only sandbox PIDs.
|
|
118
|
+
// on unprivileged systems where umount fails, --mount-proc still provides isolation
|
|
119
|
+
// (the agent also can't umount in that case).
|
|
120
|
+
const PROC_CLEANUP =
|
|
121
|
+
"umount /proc 2>/dev/null; umount /proc 2>/dev/null; mount -t proc proc /proc 2>/dev/null;";
|
|
122
|
+
|
|
123
|
+
// block container-runtime sockets that would otherwise grant a PID-namespace
|
|
124
|
+
// escape: `docker run --pid=host --privileged busybox cat /proc/<pid>/environ`
|
|
125
|
+
// reads the parent action process's env (which contains user secrets) even
|
|
126
|
+
// though the sandbox itself is unsharing PIDs. GHA `ubuntu-latest` puts the
|
|
127
|
+
// `runner` user in the `docker` group by default, so the socket is reachable
|
|
128
|
+
// without sudo. bind-mounting /dev/null on top inside the sandbox's mount
|
|
129
|
+
// namespace makes the socket unreachable from sandboxed shells without
|
|
130
|
+
// touching the host runner (so it doesn't break user workflow steps that
|
|
131
|
+
// run before/after infraweaver and legitimately need docker). same trick for
|
|
132
|
+
// podman/containerd/cri-o sockets — all silent-fail if the path is missing.
|
|
133
|
+
const SOCKET_CLEANUP = [
|
|
134
|
+
"/var/run/docker.sock",
|
|
135
|
+
"/run/docker.sock",
|
|
136
|
+
"/var/run/podman/podman.sock",
|
|
137
|
+
"/run/podman/podman.sock",
|
|
138
|
+
"/run/containerd/containerd.sock",
|
|
139
|
+
"/var/run/crio/crio.sock",
|
|
140
|
+
]
|
|
141
|
+
.map((path) => `mount --bind /dev/null ${path} 2>/dev/null;`)
|
|
142
|
+
.join(" ");
|
|
143
|
+
|
|
144
|
+
// extend the mount-namespace isolation that PROC_CLEANUP and SOCKET_CLEANUP
|
|
145
|
+
// already establish. these mounts hide infraweaver-managed on-disk secrets,
|
|
146
|
+
// block env-injection into subsequent workflow steps, and make git's
|
|
147
|
+
// code-execution config read-only inside the bash subprocess.
|
|
148
|
+
//
|
|
149
|
+
// 1. tmpfs over /var/lib/infraweaver/ — codex auth.json and any future
|
|
150
|
+
// infraweaver-managed on-disk secret live here (see action/utils/codexHome.ts
|
|
151
|
+
// INFRAWEAVER_DATA_DIR). opencode's internal auth module runs in the agent
|
|
152
|
+
// process outside this namespace and reads the real file via bypass of
|
|
153
|
+
// external_directory; bash sees an empty tmpfs. mkdir -p the path
|
|
154
|
+
// first so the tmpfs always engages — without that, runs without
|
|
155
|
+
// CODEX_AUTH_JSON wouldn't have bootstrapped the dir, the mountpoint
|
|
156
|
+
// wouldn't exist, and `mount -t tmpfs` would silent-fail. precreate
|
|
157
|
+
// keeps the overlay active for any future on-disk secret that lands
|
|
158
|
+
// under /var/lib/infraweaver regardless of which install path created it.
|
|
159
|
+
// 2. tmpfs over $RUNNER_TEMP/_runner_file_commands/ — anything bash writes
|
|
160
|
+
// to $GITHUB_ENV / $GITHUB_PATH / $GITHUB_OUTPUT / $GITHUB_STATE lands in
|
|
161
|
+
// a per-namespace tmpfs that the GHA runner never sees. our own action
|
|
162
|
+
// process writes core.setOutput / core.saveState outside the namespace,
|
|
163
|
+
// so legitimate outputs are unaffected. requires RUNNER_TEMP to be set.
|
|
164
|
+
// 3. self-bind + remount-ro on the ENTIRE <repoRoot>/.git directory.
|
|
165
|
+
// A blanket ro-bind is free: nothing legitimately writes .git from
|
|
166
|
+
// bash (commits go through our $git(), whose binary runs OUTSIDE this
|
|
167
|
+
// namespace, so it's unaffected; bash `git` is already blocked). It
|
|
168
|
+
// robustly covers every code-exec surface an enumerated list would miss
|
|
169
|
+
// — .git/config, .git/config.worktree, .git/modules/*/config (all carry
|
|
170
|
+
// core.hooksPath / filter / alias / credential.helper exec vectors),
|
|
171
|
+
// plus .git/hooks/* and .git/info/attributes. Prevents agent-planted git
|
|
172
|
+
// filters / hooks from firing in downstream workflow steps (the threat
|
|
173
|
+
// survives ASKPASS because hooks fire after auth and our $git() uses -c
|
|
174
|
+
// core.hooksPath to override its own hooks, but downstream `git`
|
|
175
|
+
// invocations in later steps DON'T get that protection — see
|
|
176
|
+
// wiki/security.md "Filesystem Sandbox"). CONSEQUENCE: .git/info/exclude
|
|
177
|
+
// (a legit per-repo ignore file) is now read-only too — accepted, the
|
|
178
|
+
// narrow earlier bind list left it writable. Does not cover `~/.gitconfig`
|
|
179
|
+
// or `/etc/gitconfig` — see "Scope and Limitations" in wiki/security.md.
|
|
180
|
+
//
|
|
181
|
+
// these mounts run as root inside the namespace (before `exec su -p` drops
|
|
182
|
+
// to runner). after the drop, runner has no CAP_SYS_ADMIN in the host, so
|
|
183
|
+
// can't undo from outside. intra-namespace sudo undo is theoretically
|
|
184
|
+
// possible — same risk profile as SOCKET_CLEANUP, accepted per wiki/security.md
|
|
185
|
+
// "why sudo inside sandbox doesn't break security".
|
|
186
|
+
//
|
|
187
|
+
// in the unprivileged-unshare path (Docker --privileged test environments),
|
|
188
|
+
// the user retains CAP_SYS_ADMIN inside the user namespace and could
|
|
189
|
+
// `umount` these. production uses sudo-unshare where the drop seals them.
|
|
190
|
+
//
|
|
191
|
+
// repoDir is interpolated by the action process from resolveRepoRoot() —
|
|
192
|
+
// NOT $PWD — because spawnShell's cwd is agent-controllable via
|
|
193
|
+
// `working_directory` AND the action's process.cwd() may have been chdir'd
|
|
194
|
+
// to payload.cwd (monorepo subdir support in main.ts). using either would
|
|
195
|
+
// let the agent bypass the .git/* binds. resolveRepoRoot pins the actual
|
|
196
|
+
// repo root once at startup via $GITHUB_WORKSPACE or `git rev-parse`.
|
|
197
|
+
function buildFsMounts(repoDir: string): string {
|
|
198
|
+
// shell-escape via single-quote wrap; bash interprets \' as the escape
|
|
199
|
+
// for a single quote inside a single-quoted string by closing-and-reopening.
|
|
200
|
+
// repoDir paths in practice are GHA workspace paths (no quotes), but the
|
|
201
|
+
// escape keeps us correct against arbitrary user-configured workspaces.
|
|
202
|
+
const escaped = repoDir.replace(/'/g, "'\\''");
|
|
203
|
+
return [
|
|
204
|
+
`mkdir -p /var/lib/infraweaver 2>/dev/null;`,
|
|
205
|
+
`mount -t tmpfs tmpfs /var/lib/infraweaver 2>/dev/null;`,
|
|
206
|
+
`[ -n "$RUNNER_TEMP" ] && [ -d "$RUNNER_TEMP/_runner_file_commands" ] && mount -t tmpfs tmpfs "$RUNNER_TEMP/_runner_file_commands" 2>/dev/null;`,
|
|
207
|
+
`[ -e '${escaped}/.git' ] && mount --bind '${escaped}/.git' '${escaped}/.git' 2>/dev/null && mount -o remount,bind,ro '${escaped}/.git' 2>/dev/null;`,
|
|
208
|
+
].join(" ");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** locate the repo root once at action startup. process.cwd() is unreliable
|
|
212
|
+
* because main.ts may `process.chdir(payload.cwd)` for monorepo subdirs;
|
|
213
|
+
* the agent's `working_directory` shell param also moves spawn cwd. we need
|
|
214
|
+
* the actual git working tree root for the .git/* binds. memoized for the
|
|
215
|
+
* lifetime of the action process. */
|
|
216
|
+
let _repoRoot: string | undefined;
|
|
217
|
+
export function resolveRepoRoot(): string {
|
|
218
|
+
if (_repoRoot) return _repoRoot;
|
|
219
|
+
const fromEnv = process.env.GITHUB_WORKSPACE;
|
|
220
|
+
if (fromEnv) {
|
|
221
|
+
_repoRoot = fromEnv;
|
|
222
|
+
return _repoRoot;
|
|
223
|
+
}
|
|
224
|
+
// fallback: `git rev-parse --show-toplevel` from process.cwd(). only used
|
|
225
|
+
// outside GHA (local dev, custom runners). swallow errors and fall back
|
|
226
|
+
// to process.cwd() so we never throw from the shell-tool init path.
|
|
227
|
+
try {
|
|
228
|
+
_repoRoot = spawnSync("git", ["rev-parse", "--show-toplevel"], {
|
|
229
|
+
cwd: process.cwd(),
|
|
230
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
231
|
+
encoding: "utf-8",
|
|
232
|
+
}).stdout?.trim();
|
|
233
|
+
} catch {
|
|
234
|
+
// intentionally empty — fall through to process.cwd()
|
|
235
|
+
}
|
|
236
|
+
if (!_repoRoot) _repoRoot = process.cwd();
|
|
237
|
+
return _repoRoot;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function spawnShell(params: SpawnParams): ChildProcess {
|
|
241
|
+
const spawnOpts = {
|
|
242
|
+
env: params.env,
|
|
243
|
+
cwd: params.cwd,
|
|
244
|
+
stdio: params.stdio,
|
|
245
|
+
detached: true,
|
|
246
|
+
};
|
|
247
|
+
const sandboxMethod = detectSandboxMethod();
|
|
248
|
+
const ci = process.env.CI === "true";
|
|
249
|
+
|
|
250
|
+
if (ci && sandboxMethod === "none") {
|
|
251
|
+
throw new Error(
|
|
252
|
+
"pid namespace isolation is required in CI but unavailable (both unshare and sudo unshare failed)",
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// resolve the actual git repo root (NOT params.cwd which is
|
|
257
|
+
// agent-controllable via `working_directory`, NOT process.cwd() which may
|
|
258
|
+
// have been chdir'd to payload.cwd in main.ts). resolveRepoRoot prefers
|
|
259
|
+
// $GITHUB_WORKSPACE in CI and falls back to `git rev-parse`.
|
|
260
|
+
const repoRoot = resolveRepoRoot();
|
|
261
|
+
const fsMounts = buildFsMounts(repoRoot);
|
|
262
|
+
|
|
263
|
+
if (sandboxMethod === "unshare") {
|
|
264
|
+
return spawn(
|
|
265
|
+
"unshare",
|
|
266
|
+
[
|
|
267
|
+
"--pid",
|
|
268
|
+
"--fork",
|
|
269
|
+
"--mount-proc",
|
|
270
|
+
"bash",
|
|
271
|
+
"-c",
|
|
272
|
+
`${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} ${params.command}`,
|
|
273
|
+
],
|
|
274
|
+
spawnOpts,
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (sandboxMethod === "sudo-unshare") {
|
|
279
|
+
const envArgs: string[] = [];
|
|
280
|
+
for (const [k, v] of Object.entries(params.env)) {
|
|
281
|
+
if (v !== undefined) {
|
|
282
|
+
envArgs.push(`${k}=${v}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// drop back to original user after PROC_CLEANUP / FS_MOUNTS so files aren't
|
|
286
|
+
// owned by root. sudo is only needed for unshare + the mount setup; the
|
|
287
|
+
// actual command should run as the normal user to avoid ownership
|
|
288
|
+
// mismatches with files created by the Node.js parent process.
|
|
289
|
+
const username = userInfo().username;
|
|
290
|
+
// su -p resets PATH on many Linux systems (ALWAYS_SET_PATH in /etc/login.defs).
|
|
291
|
+
// restore it from the SANDBOX_PATH env var that survives the su transition.
|
|
292
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: we need to restore the PATH variable
|
|
293
|
+
const pathRestore = 'export PATH="${SANDBOX_PATH:-$PATH}"; ';
|
|
294
|
+
// enforce the timeout INSIDE the namespace with coreutils `timeout`. The
|
|
295
|
+
// process group here descends from root-owned `sudo`, so the unprivileged
|
|
296
|
+
// Node parent's killProcessGroup() gets EPERM and can't stop a runaway child
|
|
297
|
+
// — it survives to (and past) the JS ceiling. `timeout` runs as the same
|
|
298
|
+
// unprivileged user as the command, kills reliably, and --kill-after=5
|
|
299
|
+
// escalates to SIGKILL 5s after the initial SIGTERM if the child ignores it.
|
|
300
|
+
// Background procs pass no timeoutMs (they run until kill_background).
|
|
301
|
+
const userCommand =
|
|
302
|
+
params.timeoutMs && params.timeoutMs > 0
|
|
303
|
+
? `timeout --kill-after=5 --signal=TERM ${Math.ceil(params.timeoutMs / 1000)}s bash -c ${shSingleQuote(params.command)}`
|
|
304
|
+
: params.command;
|
|
305
|
+
const escaped = (pathRestore + userCommand).replace(/'/g, "'\\''");
|
|
306
|
+
envArgs.push(`SANDBOX_PATH=${params.env.PATH ?? ""}`);
|
|
307
|
+
return spawn(
|
|
308
|
+
"sudo",
|
|
309
|
+
[
|
|
310
|
+
"env",
|
|
311
|
+
...envArgs,
|
|
312
|
+
"unshare",
|
|
313
|
+
"--pid",
|
|
314
|
+
"--fork",
|
|
315
|
+
"--mount-proc",
|
|
316
|
+
"bash",
|
|
317
|
+
"-c",
|
|
318
|
+
`${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec su -p -s /bin/bash ${username} -c '${escaped}'`,
|
|
319
|
+
],
|
|
320
|
+
{ ...spawnOpts, env: {} },
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return spawn("bash", ["-c", params.command], spawnOpts);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** signal a process group through `sudo`. Background procs spawned under
|
|
328
|
+
* sudo-unshare belong to a root-owned group, so the unprivileged Node parent's
|
|
329
|
+
* process.kill(-pid) gets EPERM; `sudo kill` (already proven available by the
|
|
330
|
+
* sudo-unshare probe) reaches it. A negative pid targets the whole group,
|
|
331
|
+
* matching process.kill(-pid) semantics. Best-effort — swallows failures. */
|
|
332
|
+
function sudoKillGroup(pid: number, signal: "SIGTERM" | "SIGKILL"): void {
|
|
333
|
+
const flag = signal === "SIGKILL" ? "-KILL" : "-TERM";
|
|
334
|
+
try {
|
|
335
|
+
spawnSync("sudo", ["kill", flag, `-${pid}`], {
|
|
336
|
+
timeout: 5000,
|
|
337
|
+
stdio: "ignore",
|
|
338
|
+
});
|
|
339
|
+
} catch {
|
|
340
|
+
/* nothing more we can do from here */
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** kill process and its entire process group */
|
|
345
|
+
async function killProcessGroup(proc: ChildProcess): Promise<void> {
|
|
346
|
+
if (!proc.pid) return;
|
|
347
|
+
try {
|
|
348
|
+
process.kill(-proc.pid, "SIGTERM");
|
|
349
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
350
|
+
process.kill(-proc.pid, "SIGKILL");
|
|
351
|
+
} catch {
|
|
352
|
+
try {
|
|
353
|
+
proc.kill("SIGKILL");
|
|
354
|
+
} catch {
|
|
355
|
+
/* already dead */
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** chars of shell output kept inline in the agent reply. anything past this
|
|
361
|
+
* blows the agent's context budget on commands that dump big logs (test
|
|
362
|
+
* runners, build tools, grep on large trees), so the overflow is spilled
|
|
363
|
+
* to a tempfile the agent can re-read selectively (cat/tail/grep). */
|
|
364
|
+
export const MAX_OUTPUT_CHARS = 5000;
|
|
365
|
+
|
|
366
|
+
/** if `output` exceeds `MAX_OUTPUT_CHARS`, persist the full body to a
|
|
367
|
+
* tempfile and return the last `MAX_OUTPUT_CHARS` prefixed with a sentinel
|
|
368
|
+
* pointing at the saved path. otherwise return as-is. */
|
|
369
|
+
function capOutput(output: string): string {
|
|
370
|
+
if (output.length <= MAX_OUTPUT_CHARS) return output;
|
|
371
|
+
const fullPath = join(getTempDir(), `shell-${randomUUID().slice(0, 8)}.log`);
|
|
372
|
+
writeFileSync(fullPath, output);
|
|
373
|
+
const elided = output.length - MAX_OUTPUT_CHARS;
|
|
374
|
+
return `... [${elided} chars truncated; full output saved to ${fullPath}] ...\n${output.slice(-MAX_OUTPUT_CHARS)}`;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** hard ceiling on the stdout/stderr we hold in memory WHILE a command runs.
|
|
378
|
+
* `capOutput` only spills to a tempfile AFTER exit, so without this the live
|
|
379
|
+
* accumulator grows by string-concat on every `data` event — a child that streams
|
|
380
|
+
* hundreds of MB before exiting (runaway build/test, `cat` of a huge file) can OOM the
|
|
381
|
+
* runner first. Tail-preferring: the newest output, where errors surface, wins —
|
|
382
|
+
* which also matches `capOutput` keeping the tail. Set far above MAX_OUTPUT_CHARS so it
|
|
383
|
+
* never interferes with normal output, only bounds the pathological case. */
|
|
384
|
+
export const MAX_CAPTURE_CHARS = 5_000_000;
|
|
385
|
+
|
|
386
|
+
/** A string accumulator bounded to ~`limit` chars, keeping the TAIL. The trim is
|
|
387
|
+
* amortized (the buffer is allowed to reach 2× before slicing back) so steady-state
|
|
388
|
+
* appends stay O(1). `droppedChars()` reports how much of the head was discarded. */
|
|
389
|
+
export function createCappedBuffer(limit: number) {
|
|
390
|
+
let buf = "";
|
|
391
|
+
let dropped = 0;
|
|
392
|
+
const slack = limit * 2;
|
|
393
|
+
const trim = (): void => {
|
|
394
|
+
if (buf.length > limit) {
|
|
395
|
+
const cut = buf.length - limit;
|
|
396
|
+
buf = buf.slice(cut);
|
|
397
|
+
dropped += cut;
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
return {
|
|
401
|
+
push(chunk: string): void {
|
|
402
|
+
buf += chunk;
|
|
403
|
+
if (buf.length > slack) trim();
|
|
404
|
+
},
|
|
405
|
+
value(): string {
|
|
406
|
+
trim();
|
|
407
|
+
return buf;
|
|
408
|
+
},
|
|
409
|
+
droppedChars(): number {
|
|
410
|
+
trim();
|
|
411
|
+
return dropped;
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// detect `git` as a command invocation in any position a shell would start a new
|
|
417
|
+
// command: at the start, after a separator (`;`, `&`, `|`, newline, carriage
|
|
418
|
+
// return), or inside a subshell / command substitution / brace group (`(`,
|
|
419
|
+
// backtick, `$(`, `{`). Tolerates one or more launcher prefixes
|
|
420
|
+
// (`sudo`/`env`/`command`/`exec`/`nohup`). Matches `git` only when followed by
|
|
421
|
+
// whitespace or end-of-string, so it never fires on `.gitignore`, `digit`,
|
|
422
|
+
// `legit`, `git-lfs`, etc.
|
|
423
|
+
//
|
|
424
|
+
// NOTE: this is a UX redirect to the dedicated git tools, NOT a security
|
|
425
|
+
// boundary — in restricted mode the shell runs in a stripped, token-free sandbox
|
|
426
|
+
// with `.git` mounted read-only, so a `git` the agent slips past this still has
|
|
427
|
+
// no credentials and can't tamper with repo config. The detection is kept broad
|
|
428
|
+
// so the redirect isn't trivially defeated, but it is NOT relied on to contain a
|
|
429
|
+
// hostile `git` — that is the sandbox's job.
|
|
430
|
+
const GIT_INVOCATION =
|
|
431
|
+
/(?:^|\$\(|[\n\r;&|`({])\s*(?:(?:sudo|env|command|exec|nohup)\s+)*git(?:\s|$)/;
|
|
432
|
+
|
|
433
|
+
function isGitCommand(command: string): boolean {
|
|
434
|
+
return GIT_INVOCATION.test(command.trim());
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export function ShellTool(ctx: ToolContext) {
|
|
438
|
+
return tool({
|
|
439
|
+
name: "shell",
|
|
440
|
+
timeoutMs: 120_000,
|
|
441
|
+
description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
|
|
442
|
+
|
|
443
|
+
Example: \`shell({ command: "pnpm test", description: "run the test suite" })\`.
|
|
444
|
+
|
|
445
|
+
Use this tool to:
|
|
446
|
+
- Run shell commands (ls, cat, grep, find, etc.)
|
|
447
|
+
- Execute build tools (npm, pnpm, cargo, make, etc.)
|
|
448
|
+
- Run tests and linters
|
|
449
|
+
|
|
450
|
+
Output is capped at ${MAX_OUTPUT_CHARS} chars: if exceeded, only the tail is returned and the full body is saved to a tempfile (path included in the response). Re-read the tempfile with cat/tail/grep when you need more.
|
|
451
|
+
|
|
452
|
+
Do NOT use this tool for git commands — use the dedicated git tools instead.`,
|
|
453
|
+
parameters: ShellParams,
|
|
454
|
+
execute: execute(async (params) => {
|
|
455
|
+
if (isGitCommand(params.command)) {
|
|
456
|
+
throw new Error(
|
|
457
|
+
"git commands are not allowed in the shell tool. use the dedicated git tools instead:\n" +
|
|
458
|
+
"- git: local operations (status, log, diff, add, commit, checkout, merge, rebase, etc.)\n" +
|
|
459
|
+
"- push_branch: push to remote (handles authentication)\n" +
|
|
460
|
+
"- git_fetch: fetch from remote (handles authentication)\n" +
|
|
461
|
+
"- checkout_pr: check out PR branches",
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const timeout = Math.min(params.timeout ?? 30000, 120000);
|
|
466
|
+
const cwd = params.working_directory ?? process.cwd();
|
|
467
|
+
// "enabled" widens the command surface, not credential access: inherit
|
|
468
|
+
// still denies secret-bearing names, so no shell mode exposes a secret.
|
|
469
|
+
const env = resolveEnv(
|
|
470
|
+
ctx.payload.shell === "enabled" ? "inherit" : "restricted",
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
if (params.background) {
|
|
474
|
+
const tempDir = getTempDir();
|
|
475
|
+
const handle = `bg-${randomUUID().slice(0, 8)}`;
|
|
476
|
+
const outputPath = join(tempDir, `${handle}.log`);
|
|
477
|
+
const pidPath = join(tempDir, `${handle}.pid`);
|
|
478
|
+
const logFd = openSync(outputPath, "a");
|
|
479
|
+
let proc: ChildProcess;
|
|
480
|
+
try {
|
|
481
|
+
proc = spawnShell({
|
|
482
|
+
command: params.command,
|
|
483
|
+
env,
|
|
484
|
+
cwd,
|
|
485
|
+
stdio: ["ignore", logFd, logFd],
|
|
486
|
+
});
|
|
487
|
+
} finally {
|
|
488
|
+
closeSync(logFd);
|
|
489
|
+
}
|
|
490
|
+
if (!proc.pid) {
|
|
491
|
+
throw new Error("failed to start background process");
|
|
492
|
+
}
|
|
493
|
+
proc.unref();
|
|
494
|
+
writeFileSync(pidPath, `${proc.pid}\n`);
|
|
495
|
+
ctx.toolState.backgroundProcesses.set(handle, {
|
|
496
|
+
pid: proc.pid,
|
|
497
|
+
outputPath,
|
|
498
|
+
pidPath,
|
|
499
|
+
});
|
|
500
|
+
return {
|
|
501
|
+
handle,
|
|
502
|
+
outputPath,
|
|
503
|
+
pidPath,
|
|
504
|
+
message: `started background process ${handle} (pid ${proc.pid})`,
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const proc = spawnShell({
|
|
509
|
+
command: params.command,
|
|
510
|
+
env,
|
|
511
|
+
cwd,
|
|
512
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
513
|
+
timeoutMs: timeout,
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// bound the in-memory accumulators so a child that streams more than
|
|
517
|
+
// MAX_CAPTURE_CHARS before exiting can't OOM the runner before capOutput runs.
|
|
518
|
+
const stdoutBuf = createCappedBuffer(MAX_CAPTURE_CHARS);
|
|
519
|
+
const stderrBuf = createCappedBuffer(MAX_CAPTURE_CHARS);
|
|
520
|
+
let timedOut = false,
|
|
521
|
+
exited = false;
|
|
522
|
+
proc.stdout?.on("data", (chunk: Buffer) => {
|
|
523
|
+
stdoutBuf.push(chunk.toString());
|
|
524
|
+
});
|
|
525
|
+
proc.stderr?.on("data", (chunk: Buffer) => {
|
|
526
|
+
stderrBuf.push(chunk.toString());
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
const timeoutId = setTimeout(() => {
|
|
530
|
+
if (exited) return;
|
|
531
|
+
timedOut = true;
|
|
532
|
+
// setTimeout can't await the callback, so observe the kill promise here —
|
|
533
|
+
// a kill failure is logged instead of becoming a silent unhandled rejection.
|
|
534
|
+
// The exit race still resolves the call either way.
|
|
535
|
+
void killProcessGroup(proc).catch((err) => {
|
|
536
|
+
log.warning(
|
|
537
|
+
`shell timeout: failed to kill process group: ${err instanceof Error ? err.message : String(err)}`,
|
|
538
|
+
);
|
|
539
|
+
});
|
|
540
|
+
}, timeout);
|
|
541
|
+
|
|
542
|
+
const exitCode = await new Promise<number | null>((resolve) => {
|
|
543
|
+
const done = (code: number | null) => {
|
|
544
|
+
exited = true;
|
|
545
|
+
clearTimeout(timeoutId);
|
|
546
|
+
resolve(code);
|
|
547
|
+
};
|
|
548
|
+
proc.on("exit", done);
|
|
549
|
+
proc.on("error", () => done(null));
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
const stdout = stdoutBuf.value();
|
|
553
|
+
const stderr = stderrBuf.value();
|
|
554
|
+
let output = stderr ? (stdout ? `${stdout}\n${stderr}` : stderr) : stdout;
|
|
555
|
+
// Note the head we discarded to bound memory. Appended at the END so it
|
|
556
|
+
// survives capOutput's tail-keep and is visible inline.
|
|
557
|
+
const droppedChars = stdoutBuf.droppedChars() + stderrBuf.droppedChars();
|
|
558
|
+
if (droppedChars > 0)
|
|
559
|
+
output = `${output}\n[${droppedChars} chars of earlier output dropped to bound runner memory]`;
|
|
560
|
+
if (timedOut)
|
|
561
|
+
output = output
|
|
562
|
+
? `${output}\n[timed out after ${timeout}ms]`
|
|
563
|
+
: `[timed out after ${timeout}ms]`;
|
|
564
|
+
|
|
565
|
+
const finalExitCode = exitCode ?? (timedOut ? 124 : -1);
|
|
566
|
+
const trimmed = output.trim();
|
|
567
|
+
if (finalExitCode !== 0) {
|
|
568
|
+
log.info(
|
|
569
|
+
`shell command failed with exit code ${finalExitCode}: ${params.command}`,
|
|
570
|
+
);
|
|
571
|
+
if (trimmed) log.info(`output: ${trimmed}`);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return {
|
|
575
|
+
output: capOutput(trimmed),
|
|
576
|
+
exit_code: finalExitCode,
|
|
577
|
+
timed_out: timedOut,
|
|
578
|
+
};
|
|
579
|
+
}),
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
export const KillBackgroundParams = type({
|
|
584
|
+
handle: type.string.describe(
|
|
585
|
+
"The handle of the background process to kill (e.g., bg-a1b2c3d4)",
|
|
586
|
+
),
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
export function KillBackgroundTool(ctx: ToolContext) {
|
|
590
|
+
return tool({
|
|
591
|
+
mutates: true,
|
|
592
|
+
name: "kill_background",
|
|
593
|
+
description: `Kill a background process by its handle. Use this to stop dev servers or other long-running processes started with shell({ background: true }).`,
|
|
594
|
+
parameters: KillBackgroundParams,
|
|
595
|
+
execute: execute(async (params) => {
|
|
596
|
+
const proc = ctx.toolState.backgroundProcesses.get(params.handle);
|
|
597
|
+
if (!proc) {
|
|
598
|
+
return {
|
|
599
|
+
success: false,
|
|
600
|
+
message: `no background process with handle ${params.handle}`,
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// under sudo-unshare the group is root-owned — process.kill(-pid) EPERMs,
|
|
605
|
+
// so escalate straight through sudo. otherwise the unprivileged kill works.
|
|
606
|
+
if (getSandboxMethod() === "sudo-unshare") {
|
|
607
|
+
sudoKillGroup(proc.pid, "SIGTERM");
|
|
608
|
+
await sleep(200);
|
|
609
|
+
sudoKillGroup(proc.pid, "SIGKILL");
|
|
610
|
+
} else {
|
|
611
|
+
try {
|
|
612
|
+
process.kill(-proc.pid, "SIGTERM");
|
|
613
|
+
} catch {
|
|
614
|
+
// already dead
|
|
615
|
+
}
|
|
616
|
+
await sleep(200);
|
|
617
|
+
try {
|
|
618
|
+
process.kill(-proc.pid, "SIGKILL");
|
|
619
|
+
} catch {
|
|
620
|
+
// already dead
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
ctx.toolState.backgroundProcesses.delete(params.handle);
|
|
625
|
+
return {
|
|
626
|
+
success: true,
|
|
627
|
+
message: `killed background process ${params.handle} (pid ${proc.pid})`,
|
|
628
|
+
};
|
|
629
|
+
}),
|
|
630
|
+
});
|
|
631
|
+
}
|