@wascartech/devbot 0.0.0-stage → 2.1.1
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 +202 -0
- package/README.md +638 -2
- package/dist/access-grant-store.js +99 -0
- package/dist/access-grant-store.js.map +1 -0
- package/dist/access-resolve.js +11 -0
- package/dist/access-resolve.js.map +1 -0
- package/dist/access-rule-store.js +99 -0
- package/dist/access-rule-store.js.map +1 -0
- package/dist/admin/access-routes.js +73 -0
- package/dist/admin/access-routes.js.map +1 -0
- package/dist/admin/access-rule-routes.js +107 -0
- package/dist/admin/access-rule-routes.js.map +1 -0
- package/dist/admin/custom-repos-routes.js +93 -0
- package/dist/admin/custom-repos-routes.js.map +1 -0
- package/dist/admin/env-routes.js +74 -0
- package/dist/admin/env-routes.js.map +1 -0
- package/dist/admin/global-settings-routes.js +254 -0
- package/dist/admin/global-settings-routes.js.map +1 -0
- package/dist/admin/mcp-cleanup.js +12 -0
- package/dist/admin/mcp-cleanup.js.map +1 -0
- package/dist/admin/mcp-routes.js +169 -0
- package/dist/admin/mcp-routes.js.map +1 -0
- package/dist/admin/query-params.js +80 -0
- package/dist/admin/query-params.js.map +1 -0
- package/dist/admin/routes.js +150 -0
- package/dist/admin/routes.js.map +1 -0
- package/dist/admin/run-history-routes.js +44 -0
- package/dist/admin/run-history-routes.js.map +1 -0
- package/dist/admin/run-routes.js +99 -0
- package/dist/admin/run-routes.js.map +1 -0
- package/dist/admin/settings-routes.js +119 -0
- package/dist/admin/settings-routes.js.map +1 -0
- package/dist/admin/stats-routes.js +51 -0
- package/dist/admin/stats-routes.js.map +1 -0
- package/dist/admin/task-routes.js +213 -0
- package/dist/admin/task-routes.js.map +1 -0
- package/dist/admin/test-routes.js +38 -0
- package/dist/admin/test-routes.js.map +1 -0
- package/dist/admin/volume-routes.js +82 -0
- package/dist/admin/volume-routes.js.map +1 -0
- package/dist/admin/webhook-routes.js +174 -0
- package/dist/admin/webhook-routes.js.map +1 -0
- package/dist/agents.js +48 -0
- package/dist/agents.js.map +1 -0
- package/dist/assignee-value.js +6 -0
- package/dist/assignee-value.js.map +1 -0
- package/dist/attachments.js +76 -0
- package/dist/attachments.js.map +1 -0
- package/dist/auto-commands.js +88 -0
- package/dist/auto-commands.js.map +1 -0
- package/dist/auto-features.js +32 -0
- package/dist/auto-features.js.map +1 -0
- package/dist/base-branch-store.js +40 -0
- package/dist/base-branch-store.js.map +1 -0
- package/dist/base-branches.js +9 -0
- package/dist/base-branches.js.map +1 -0
- package/dist/build-workflow.js +116 -0
- package/dist/build-workflow.js.map +1 -0
- package/dist/change-request-lifecycle.js +16 -0
- package/dist/change-request-lifecycle.js.map +1 -0
- package/dist/change-request-store.js +72 -0
- package/dist/change-request-store.js.map +1 -0
- package/dist/ci-failure.js +80 -0
- package/dist/ci-failure.js.map +1 -0
- package/dist/cli.js +3 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.js +17 -0
- package/dist/config.js.map +1 -0
- package/dist/conflict-fix.js +178 -0
- package/dist/conflict-fix.js.map +1 -0
- package/dist/cookie.js +70 -0
- package/dist/cookie.js.map +1 -0
- package/dist/crypto-util.js +40 -0
- package/dist/crypto-util.js.map +1 -0
- package/dist/custom-repos-store.js +90 -0
- package/dist/custom-repos-store.js.map +1 -0
- package/dist/custom-repos-sync.js +118 -0
- package/dist/custom-repos-sync.js.map +1 -0
- package/dist/db.js +12 -0
- package/dist/db.js.map +1 -0
- package/dist/devbot-scripts.js +18 -0
- package/dist/devbot-scripts.js.map +1 -0
- package/dist/devcontainer.js +150 -0
- package/dist/devcontainer.js.map +1 -0
- package/dist/env-config-store.js +132 -0
- package/dist/env-config-store.js.map +1 -0
- package/dist/error-log-store.js +193 -0
- package/dist/error-log-store.js.map +1 -0
- package/dist/git.js +605 -0
- package/dist/git.js.map +1 -0
- package/dist/global-queue.js +127 -0
- package/dist/global-queue.js.map +1 -0
- package/dist/global-settings-store.js +307 -0
- package/dist/global-settings-store.js.map +1 -0
- package/dist/help.js +41 -0
- package/dist/help.js.map +1 -0
- package/dist/http-csp.js +15 -0
- package/dist/http-csp.js.map +1 -0
- package/dist/http-util.js +40 -0
- package/dist/http-util.js.map +1 -0
- package/dist/idempotency.js +50 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/lifecycle.js +234 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/logger.js +33 -0
- package/dist/logger.js.map +1 -0
- package/dist/lsp-servers.js +81 -0
- package/dist/lsp-servers.js.map +1 -0
- package/dist/mcp/auth.js +62 -0
- package/dist/mcp/auth.js.map +1 -0
- package/dist/mcp/effective-mcp.js +42 -0
- package/dist/mcp/effective-mcp.js.map +1 -0
- package/dist/mcp/oauth-refresh.js +138 -0
- package/dist/mcp/oauth-refresh.js.map +1 -0
- package/dist/mcp/peer-sync.js +109 -0
- package/dist/mcp/peer-sync.js.map +1 -0
- package/dist/mcp/registry.js +54 -0
- package/dist/mcp/registry.js.map +1 -0
- package/dist/mcp/runner-bridge.js +93 -0
- package/dist/mcp/runner-bridge.js.map +1 -0
- package/dist/mcp/server.js +173 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/_helpers.js +88 -0
- package/dist/mcp/tools/_helpers.js.map +1 -0
- package/dist/mcp/tools/attachments.js +117 -0
- package/dist/mcp/tools/attachments.js.map +1 -0
- package/dist/mcp/tools/cicd.js +105 -0
- package/dist/mcp/tools/cicd.js.map +1 -0
- package/dist/mcp/tools/discovery.js +34 -0
- package/dist/mcp/tools/discovery.js.map +1 -0
- package/dist/mcp/tools/issues.js +56 -0
- package/dist/mcp/tools/issues.js.map +1 -0
- package/dist/mcp/tools/merge_requests.js +67 -0
- package/dist/mcp/tools/merge_requests.js.map +1 -0
- package/dist/mcp/tools/metadata.js +127 -0
- package/dist/mcp/tools/metadata.js.map +1 -0
- package/dist/mcp/tools/peer_sync.js +43 -0
- package/dist/mcp/tools/peer_sync.js.map +1 -0
- package/dist/mcp/tools/repository.js +178 -0
- package/dist/mcp/tools/repository.js.map +1 -0
- package/dist/mcp/tools/work_tracking.js +125 -0
- package/dist/mcp/tools/work_tracking.js.map +1 -0
- package/dist/mcp/tools/workitems.js +30 -0
- package/dist/mcp/tools/workitems.js.map +1 -0
- package/dist/mcp/volume-files.js +187 -0
- package/dist/mcp/volume-files.js.map +1 -0
- package/dist/mcp-auth-store.js +134 -0
- package/dist/mcp-auth-store.js.map +1 -0
- package/dist/mcp-config-store.js +83 -0
- package/dist/mcp-config-store.js.map +1 -0
- package/dist/mention.js +512 -0
- package/dist/mention.js.map +1 -0
- package/dist/migrate.js +438 -0
- package/dist/migrate.js.map +1 -0
- package/dist/mr-assignee.js +71 -0
- package/dist/mr-assignee.js.map +1 -0
- package/dist/mr-content.js +90 -0
- package/dist/mr-content.js.map +1 -0
- package/dist/oauth-routes.js +54 -0
- package/dist/oauth-routes.js.map +1 -0
- package/dist/onboarding-allowlist.js +32 -0
- package/dist/onboarding-allowlist.js.map +1 -0
- package/dist/opencode.js +291 -0
- package/dist/opencode.js.map +1 -0
- package/dist/outbound-webhook-store.js +229 -0
- package/dist/outbound-webhook-store.js.map +1 -0
- package/dist/outbound-webhook.js +314 -0
- package/dist/outbound-webhook.js.map +1 -0
- package/dist/path-sandbox.js +200 -0
- package/dist/path-sandbox.js.map +1 -0
- package/dist/platform/git-auth.js +34 -0
- package/dist/platform/git-auth.js.map +1 -0
- package/dist/platform/gitealike/attachments.js +62 -0
- package/dist/platform/gitealike/attachments.js.map +1 -0
- package/dist/platform/gitealike/base.js +935 -0
- package/dist/platform/gitealike/base.js.map +1 -0
- package/dist/platform/gitealike/forgejo.js +16 -0
- package/dist/platform/gitealike/forgejo.js.map +1 -0
- package/dist/platform/gitealike/gitea.js +16 -0
- package/dist/platform/gitealike/gitea.js.map +1 -0
- package/dist/platform/gitealike/http.js +152 -0
- package/dist/platform/gitealike/http.js.map +1 -0
- package/dist/platform/gitealike/schemas.js +284 -0
- package/dist/platform/gitealike/schemas.js.map +1 -0
- package/dist/platform/github/attachments.js +52 -0
- package/dist/platform/github/attachments.js.map +1 -0
- package/dist/platform/github/http.js +153 -0
- package/dist/platform/github/http.js.map +1 -0
- package/dist/platform/github/index.js +936 -0
- package/dist/platform/github/index.js.map +1 -0
- package/dist/platform/github/schemas.js +308 -0
- package/dist/platform/github/schemas.js.map +1 -0
- package/dist/platform/github/zip.js +85 -0
- package/dist/platform/github/zip.js.map +1 -0
- package/dist/platform/gitlab/admin.js +105 -0
- package/dist/platform/gitlab/admin.js.map +1 -0
- package/dist/platform/gitlab/api.js +229 -0
- package/dist/platform/gitlab/api.js.map +1 -0
- package/dist/platform/gitlab/attachments.js +79 -0
- package/dist/platform/gitlab/attachments.js.map +1 -0
- package/dist/platform/gitlab/http.js +165 -0
- package/dist/platform/gitlab/http.js.map +1 -0
- package/dist/platform/gitlab/index.js +630 -0
- package/dist/platform/gitlab/index.js.map +1 -0
- package/dist/platform/gitlab/oauth.js +58 -0
- package/dist/platform/gitlab/oauth.js.map +1 -0
- package/dist/platform/gitlab/read.js +182 -0
- package/dist/platform/gitlab/read.js.map +1 -0
- package/dist/platform/gitlab/schemas-read.js +274 -0
- package/dist/platform/gitlab/schemas-read.js.map +1 -0
- package/dist/platform/gitlab/schemas.js +251 -0
- package/dist/platform/gitlab/schemas.js.map +1 -0
- package/dist/platform/gitlab/signature.js +52 -0
- package/dist/platform/gitlab/signature.js.map +1 -0
- package/dist/platform/index.js +38 -0
- package/dist/platform/index.js.map +1 -0
- package/dist/platform/port.js +26 -0
- package/dist/platform/port.js.map +1 -0
- package/dist/platform/types.js +80 -0
- package/dist/platform/types.js.map +1 -0
- package/dist/podman.js +119 -0
- package/dist/podman.js.map +1 -0
- package/dist/project-sync.js +69 -0
- package/dist/project-sync.js.map +1 -0
- package/dist/prompt.js +47 -0
- package/dist/prompt.js.map +1 -0
- package/dist/queue-store.js +236 -0
- package/dist/queue-store.js.map +1 -0
- package/dist/queue.js +129 -0
- package/dist/queue.js.map +1 -0
- package/dist/redact.js +23 -0
- package/dist/redact.js.map +1 -0
- package/dist/run-files.js +297 -0
- package/dist/run-files.js.map +1 -0
- package/dist/run-filters.js +54 -0
- package/dist/run-filters.js.map +1 -0
- package/dist/run-history-store.js +229 -0
- package/dist/run-history-store.js.map +1 -0
- package/dist/run-log-sink.js +270 -0
- package/dist/run-log-sink.js.map +1 -0
- package/dist/run-log.js +97 -0
- package/dist/run-log.js.map +1 -0
- package/dist/run-usage.js +29 -0
- package/dist/run-usage.js.map +1 -0
- package/dist/run-view.js +112 -0
- package/dist/run-view.js.map +1 -0
- package/dist/runner.js +1094 -0
- package/dist/runner.js.map +1 -0
- package/dist/runtime-apply.js +26 -0
- package/dist/runtime-apply.js.map +1 -0
- package/dist/runtime-settings.js +71 -0
- package/dist/runtime-settings.js.map +1 -0
- package/dist/runtime-state.js +19 -0
- package/dist/runtime-state.js.map +1 -0
- package/dist/server.js +119 -0
- package/dist/server.js.map +1 -0
- package/dist/settings-store.js +320 -0
- package/dist/settings-store.js.map +1 -0
- package/dist/status.js +121 -0
- package/dist/status.js.map +1 -0
- package/dist/stop.js +56 -0
- package/dist/stop.js.map +1 -0
- package/dist/sweeps.js +87 -0
- package/dist/sweeps.js.map +1 -0
- package/dist/task-log.js +279 -0
- package/dist/task-log.js.map +1 -0
- package/dist/task-view.js +62 -0
- package/dist/task-view.js.map +1 -0
- package/dist/token-store.js +68 -0
- package/dist/token-store.js.map +1 -0
- package/dist/types/access.js +74 -0
- package/dist/types/access.js.map +1 -0
- package/dist/types/custom-repos.js +28 -0
- package/dist/types/custom-repos.js.map +1 -0
- package/dist/types/env-config.js +25 -0
- package/dist/types/env-config.js.map +1 -0
- package/dist/types/env.js +190 -0
- package/dist/types/env.js.map +1 -0
- package/dist/types/global-settings.js +178 -0
- package/dist/types/global-settings.js.map +1 -0
- package/dist/types/lsp-servers.js +145 -0
- package/dist/types/lsp-servers.js.map +1 -0
- package/dist/types/mcp-config.js +77 -0
- package/dist/types/mcp-config.js.map +1 -0
- package/dist/types/opencode.js +59 -0
- package/dist/types/opencode.js.map +1 -0
- package/dist/types/outbound-webhook.js +56 -0
- package/dist/types/outbound-webhook.js.map +1 -0
- package/dist/types/project-settings.js +114 -0
- package/dist/types/project-settings.js.map +1 -0
- package/dist/types/provider-registry.js +37 -0
- package/dist/types/provider-registry.js.map +1 -0
- package/dist/types/runtime-settings.js +78 -0
- package/dist/types/runtime-settings.js.map +1 -0
- package/dist/usage-stats.js +159 -0
- package/dist/usage-stats.js.map +1 -0
- package/dist/version.js +19 -0
- package/dist/version.js.map +1 -0
- package/dist/volume-gc.js +193 -0
- package/dist/volume-gc.js.map +1 -0
- package/dist/volume-manage.js +191 -0
- package/dist/volume-manage.js.map +1 -0
- package/dist/volume-store.js +122 -0
- package/dist/volume-store.js.map +1 -0
- package/dist/webhook.js +1106 -0
- package/dist/webhook.js.map +1 -0
- package/dist/work-item-auto-store.js +75 -0
- package/dist/work-item-auto-store.js.map +1 -0
- package/dist/work-item-branch-store.js +37 -0
- package/dist/work-item-branch-store.js.map +1 -0
- package/migrations/0001_initial.sql +59 -0
- package/migrations/0002_queue_status.sql +15 -0
- package/migrations/0003_work_item_base_branch.sql +14 -0
- package/migrations/0004_project_settings.sql +15 -0
- package/migrations/0005_global_settings.sql +34 -0
- package/migrations/0006_model_variants.sql +8 -0
- package/migrations/0007_container_error_logs.sql +21 -0
- package/migrations/0008_project_access_grants.sql +16 -0
- package/migrations/0009_drop_imported_legacy.sql +4 -0
- package/migrations/0010_container_error_log_model.sql +6 -0
- package/migrations/0011_container_error_log_title.sql +5 -0
- package/migrations/0012_pending_task_cancel.sql +6 -0
- package/migrations/0013_builder_image.sql +6 -0
- package/migrations/0014_build_workflow.sql +7 -0
- package/migrations/0015_access_rules.sql +24 -0
- package/migrations/0016_trigger_policy.sql +17 -0
- package/migrations/0017_attachment_upload.sql +8 -0
- package/migrations/0018_work_item_branch.sql +11 -0
- package/migrations/0019_auto_push_mr.sql +18 -0
- package/migrations/0020_outbound_webhooks.sql +47 -0
- package/migrations/0021_runtime_settings.sql +12 -0
- package/migrations/0022_auto_resolve_conflicts.sql +17 -0
- package/migrations/0023_ci_fail_auto_report.sql +27 -0
- package/migrations/0024_work_item_auto_override.sql +22 -0
- package/migrations/0025_run_history.sql +37 -0
- package/migrations/0026_run_history_task_seq.sql +6 -0
- package/migrations/0027_custom_repos.sql +11 -0
- package/migrations/0028_run_usage.sql +14 -0
- package/migrations/0029_drop_ocr_langs.sql +6 -0
- package/migrations/0030_mr_assignee.sql +9 -0
- package/migrations/0031_lsp_servers.sql +14 -0
- package/opencode-config/managed/devbot-agent-policy.md +61 -0
- package/opencode-config/managed/opencode.json +77 -0
- package/opencode-config/opencode.json +211 -0
- package/opencode-config/plugins/devbot-path-guard.js +224 -0
- package/package.json +158 -4
- package/public/assets/DashboardSidebarCollapse-rIuzCmVm.js +1 -0
- package/public/assets/DashboardToolbar-DFCUqZ4Z.js +1 -0
- package/public/assets/ErrorLogModal.vue_vue_type_script_setup_true_lang-BCyNIf1E.js +1 -0
- package/public/assets/ErrorLogsPage-DyYK2Rop.js +1 -0
- package/public/assets/LogSlideover.vue_vue_type_script_setup_true_lang-33xie7TE.js +1 -0
- package/public/assets/OverviewPage-CFfa0uwg.js +1 -0
- package/public/assets/PageGrid-CBROIU87.js +1 -0
- package/public/assets/PeriodRangePicker.vue_vue_type_script_setup_true_lang-DiFUNem7.js +1 -0
- package/public/assets/ProjectDetailPage-CreyvUit.js +1 -0
- package/public/assets/ProjectsPage-CFx8-elJ.js +1 -0
- package/public/assets/RunDetailSlideover.vue_vue_type_script_setup_true_lang-DopYYmCT.js +1 -0
- package/public/assets/RunsPage-CRnnXU4P.js +1 -0
- package/public/assets/Select-Bvx32Diz.js +1 -0
- package/public/assets/SelectMenu-BjMxc4VB.js +1 -0
- package/public/assets/SettingsPage-Bb6LM3wO.js +60 -0
- package/public/assets/TimeCell.vue_vue_type_script_setup_true_lang-DW_UB0Fu.js +1 -0
- package/public/assets/UsagePage-BLMDEsv-.js +332 -0
- package/public/assets/UsagePage-DodEeAjB.css +1 -0
- package/public/assets/VolumesPage-CxcLc3L2.js +1 -0
- package/public/assets/WebhookConfigPanel.vue_vue_type_script_setup_true_lang-B2V2GF6b.js +1 -0
- package/public/assets/WorkItemCell.vue_vue_type_script_setup_true_lang-BQn6vTjd.js +1 -0
- package/public/assets/index-BMbMibyb.js +4 -0
- package/public/assets/index-C5RBBrPj.js +30 -0
- package/public/assets/index-CgX29UgC.css +1 -0
- package/public/assets/usage-format-jsb31_O8.js +1 -0
- package/public/assets/useManagedProjects-8rtjRqVJ.js +1 -0
- package/public/assets/useRouteTab-DfwrcMic.js +1 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.png +0 -0
- package/public/index.html +15 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { createHmac, randomUUID } from "node:crypto";
|
|
2
|
+
import { lookup } from "node:dns/promises";
|
|
3
|
+
import { isIP } from "node:net";
|
|
4
|
+
import { config } from "./config.js";
|
|
5
|
+
import { log } from "./logger.js";
|
|
6
|
+
import { matchingWebhooks, enqueueDelivery, getWebhook, claimDueDeliveries, settleDeliverySucceeded, settleDeliveryFailed, resetStuckDeliveries, purgeDeliveriesOlderThan, } from "./outbound-webhook-store.js";
|
|
7
|
+
import { getProject } from "./token-store.js";
|
|
8
|
+
import { getPlatform } from "./platform/index.js";
|
|
9
|
+
// Outbound webhook emitter + delivery worker. Forge-agnostic by construction:
|
|
10
|
+
// the only platform call is the neutral links.workItemUrl. emit* is a purely
|
|
11
|
+
// local DB write (one row per matching endpoint); the worker does all network
|
|
12
|
+
// I/O, so a webhook can never block or fail a run.
|
|
13
|
+
const SIGNATURE_HEADER = "X-DevBot-Signature-256";
|
|
14
|
+
export function signPayload(secret, body) {
|
|
15
|
+
return "sha256=" + createHmac("sha256", secret).update(body).digest("hex");
|
|
16
|
+
}
|
|
17
|
+
// The delivery id carried in the payload; used for the X-DevBot-Delivery header
|
|
18
|
+
// so a receiver can dedupe on the same value it sees in the body.
|
|
19
|
+
function deliveryIdOf(payload) {
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(payload);
|
|
22
|
+
return typeof parsed.delivery === "string" ? parsed.delivery : "";
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return "";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// ---- SSRF guard ------------------------------------------------------------
|
|
29
|
+
// Outbound URLs are admin-configured, but a URL like http://169.254.169.254/
|
|
30
|
+
// can reach cloud metadata. Block loopback/link-local/private/CGNAT/ULA/
|
|
31
|
+
// multicast/broadcast by default; WEBHOOK_ALLOW_PRIVATE_HOSTS=true opts in to
|
|
32
|
+
// internal CI/messaging endpoints (see docs/outbound-webhooks.md).
|
|
33
|
+
function ipv4Blocked(ip) {
|
|
34
|
+
const parts = ip.split(".").map((n) => Number(n));
|
|
35
|
+
const [a, b] = parts;
|
|
36
|
+
if (a === 0 || a === 10 || a === 127)
|
|
37
|
+
return true;
|
|
38
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
39
|
+
return true; // CGNAT 100.64/10
|
|
40
|
+
if (a === 169 && b === 254)
|
|
41
|
+
return true; // link-local
|
|
42
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
43
|
+
return true; // private
|
|
44
|
+
if (a === 192 && b === 168)
|
|
45
|
+
return true; // private
|
|
46
|
+
if (a === 192 && b === 0)
|
|
47
|
+
return true; // 192.0.0.0/24 + 192.0.2.0/24
|
|
48
|
+
if (a >= 224)
|
|
49
|
+
return true; // multicast + reserved + broadcast
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
function ipv6Blocked(ip) {
|
|
53
|
+
const v = ip.toLowerCase().split("%")[0];
|
|
54
|
+
if (v === "::1" || v === "::")
|
|
55
|
+
return true;
|
|
56
|
+
if (v.startsWith("fe80"))
|
|
57
|
+
return true; // link-local
|
|
58
|
+
const first = v.split(":")[0];
|
|
59
|
+
const n = parseInt(first || "0", 16);
|
|
60
|
+
if ((n & 0xfe00) === 0xfc00)
|
|
61
|
+
return true; // ULA fc00::/7
|
|
62
|
+
if ((n & 0xff00) === 0xff00)
|
|
63
|
+
return true; // multicast ff00::/8
|
|
64
|
+
// IPv4-mapped (::ffff:a.b.c.d) — classify on the embedded v4.
|
|
65
|
+
const mapped = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/.exec(v);
|
|
66
|
+
if (mapped)
|
|
67
|
+
return ipv4Blocked(mapped[1]);
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
export function isBlockedAddress(addr) {
|
|
71
|
+
const v = isIP(addr);
|
|
72
|
+
if (v === 4)
|
|
73
|
+
return ipv4Blocked(addr);
|
|
74
|
+
if (v === 6)
|
|
75
|
+
return ipv6Blocked(addr);
|
|
76
|
+
return true; // not an IP literal → treat as blocked/unknown
|
|
77
|
+
}
|
|
78
|
+
// Resolve a URL's host and reject when any resolved address is blocked (unless
|
|
79
|
+
// private hosts are explicitly allowed). Returns an error string, or null when
|
|
80
|
+
// the target is safe to call.
|
|
81
|
+
export async function checkTargetAllowed(rawUrl) {
|
|
82
|
+
let url;
|
|
83
|
+
try {
|
|
84
|
+
url = new URL(rawUrl);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return "invalid url";
|
|
88
|
+
}
|
|
89
|
+
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
90
|
+
return "url must be http(s)";
|
|
91
|
+
if (config.WEBHOOK_ALLOW_PRIVATE_HOSTS)
|
|
92
|
+
return null;
|
|
93
|
+
const host = url.hostname.replace(/^\[|\]$/g, "");
|
|
94
|
+
if (isIP(host))
|
|
95
|
+
return isBlockedAddress(host) ? "blocked address" : null;
|
|
96
|
+
let addrs;
|
|
97
|
+
try {
|
|
98
|
+
addrs = await lookup(host, { all: true, verbatim: true });
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return "dns resolution failed";
|
|
102
|
+
}
|
|
103
|
+
if (addrs.length === 0)
|
|
104
|
+
return "dns resolution failed";
|
|
105
|
+
if (addrs.some((a) => isBlockedAddress(a.address)))
|
|
106
|
+
return "blocked address";
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
export function buildRunOutcomePayload(event) {
|
|
110
|
+
return JSON.stringify(event);
|
|
111
|
+
}
|
|
112
|
+
export function buildMrPayload(event) {
|
|
113
|
+
return JSON.stringify(event);
|
|
114
|
+
}
|
|
115
|
+
function workItemUrl(projectId, workItemNumber) {
|
|
116
|
+
const projectPath = getProject(projectId)?.projectPath ?? "";
|
|
117
|
+
try {
|
|
118
|
+
return getPlatform().links.workItemUrl(projectPath, workItemNumber);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return "";
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function projectPath(projectId) {
|
|
125
|
+
return getProject(projectId)?.projectPath ?? "";
|
|
126
|
+
}
|
|
127
|
+
// Resolve the concrete endpoints (project + global) an event should be sent to.
|
|
128
|
+
// Kept exported for tests and the "test delivery" route.
|
|
129
|
+
export function resolveMatchingEndpoints(projectId, event) {
|
|
130
|
+
return matchingWebhooks(projectId, event);
|
|
131
|
+
}
|
|
132
|
+
// Persist one delivery row per matching endpoint. Best-effort: a store failure
|
|
133
|
+
// is logged and never propagates into the run.
|
|
134
|
+
export function emitEvent(projectId, event, payloadFor) {
|
|
135
|
+
let count = 0;
|
|
136
|
+
try {
|
|
137
|
+
for (const endpoint of resolveMatchingEndpoints(projectId, event)) {
|
|
138
|
+
const eventPayload = payloadFor();
|
|
139
|
+
eventPayload.delivery = randomUUID();
|
|
140
|
+
enqueueDelivery(projectId, endpoint.name, event, buildRunOutcomePayload(eventPayload));
|
|
141
|
+
count++;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (e) {
|
|
145
|
+
log.warn("outbound webhook emit failed", { projectId, event, err: e });
|
|
146
|
+
}
|
|
147
|
+
return count;
|
|
148
|
+
}
|
|
149
|
+
// A run emits `run.started` once per task lifecycle — only on the first
|
|
150
|
+
// attempt. A transient failure requeues the row with attempts++, so retries
|
|
151
|
+
// don't re-notify subscribers; a crash-replayed row keeps attempts 0 and does.
|
|
152
|
+
export function shouldEmitRunStarted(attempts) {
|
|
153
|
+
return attempts === 0;
|
|
154
|
+
}
|
|
155
|
+
// Emit `run.started` for a task that is about to execute. `run.status` is
|
|
156
|
+
// "running" with no failure detail (none exists yet).
|
|
157
|
+
export function emitRunStarted(input) {
|
|
158
|
+
emitEvent(input.projectId, "run.started", () => baseEvent(input, "run.started", "running", {}));
|
|
159
|
+
}
|
|
160
|
+
export function emitRunOutcome(input, outcome) {
|
|
161
|
+
const event = outcome.kind === "ok" ? "run.succeeded" : outcome.kind === "cancelled" ? "run.cancelled" : "run.failed";
|
|
162
|
+
const failure = outcome.failure;
|
|
163
|
+
emitEvent(input.projectId, event, () => baseEvent(input, event, outcome.kind, {
|
|
164
|
+
reason: outcome.reason ?? failure?.message ?? "",
|
|
165
|
+
phase: failure?.phase ?? "",
|
|
166
|
+
kind: failure?.kind ?? "",
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
export function emitMrEvent(input, changeRequest, created) {
|
|
170
|
+
const event = created ? "mr.created" : "mr.updated";
|
|
171
|
+
emitEvent(input.projectId, event, () => ({
|
|
172
|
+
...baseEvent(input, event, "ok", {}),
|
|
173
|
+
changeRequest: { number: changeRequest.number, url: changeRequest.webUrl, created },
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
function baseEvent(input, event, status, opts) {
|
|
177
|
+
return {
|
|
178
|
+
event,
|
|
179
|
+
delivery: "",
|
|
180
|
+
timestamp: new Date().toISOString(),
|
|
181
|
+
project: { id: input.projectId, path: projectPath(input.projectId) },
|
|
182
|
+
workItem: {
|
|
183
|
+
id: input.workItemId,
|
|
184
|
+
number: input.workItemNumber,
|
|
185
|
+
url: workItemUrl(input.projectId, input.workItemNumber),
|
|
186
|
+
},
|
|
187
|
+
run: {
|
|
188
|
+
mode: input.mode,
|
|
189
|
+
model: input.model,
|
|
190
|
+
variant: input.variant,
|
|
191
|
+
status,
|
|
192
|
+
phase: opts.phase ?? "",
|
|
193
|
+
kind: opts.kind ?? "",
|
|
194
|
+
reason: opts.reason ?? "",
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
// ---- Delivery worker -------------------------------------------------------
|
|
199
|
+
async function deliverOnce(endpoint, event, delivery, payload) {
|
|
200
|
+
const blocked = await checkTargetAllowed(endpoint.url);
|
|
201
|
+
if (blocked)
|
|
202
|
+
return { ok: false, responseStatus: null, responseBody: "", error: blocked };
|
|
203
|
+
const controller = new AbortController();
|
|
204
|
+
const timer = setTimeout(() => controller.abort(), config.WEBHOOK_DELIVERY_TIMEOUT_MS);
|
|
205
|
+
try {
|
|
206
|
+
const res = await fetch(endpoint.url, {
|
|
207
|
+
method: "POST",
|
|
208
|
+
redirect: "manual",
|
|
209
|
+
signal: controller.signal,
|
|
210
|
+
headers: {
|
|
211
|
+
"Content-Type": "application/json",
|
|
212
|
+
"User-Agent": "DevBot-Webhook/1.0",
|
|
213
|
+
"X-DevBot-Event": event,
|
|
214
|
+
"X-DevBot-Delivery": delivery,
|
|
215
|
+
[SIGNATURE_HEADER]: signPayload(endpoint.secret, payload),
|
|
216
|
+
...endpoint.headers,
|
|
217
|
+
},
|
|
218
|
+
body: payload,
|
|
219
|
+
});
|
|
220
|
+
const body = await res.text().catch(() => "");
|
|
221
|
+
if (res.status >= 200 && res.status < 300) {
|
|
222
|
+
return { ok: true, responseStatus: res.status, responseBody: body, error: "" };
|
|
223
|
+
}
|
|
224
|
+
if (res.status >= 300 && res.status < 400) {
|
|
225
|
+
return { ok: false, responseStatus: res.status, responseBody: body, error: `redirect not followed (${res.status})` };
|
|
226
|
+
}
|
|
227
|
+
return { ok: false, responseStatus: res.status, responseBody: body, error: `http ${res.status}` };
|
|
228
|
+
}
|
|
229
|
+
catch (e) {
|
|
230
|
+
const msg = e instanceof Error && e.name === "AbortError" ? "timeout" : e instanceof Error ? e.message : String(e);
|
|
231
|
+
return { ok: false, responseStatus: null, responseBody: "", error: msg };
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
clearTimeout(timer);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// Deliver every due row once. Exported so tests can drive it without timers.
|
|
238
|
+
export async function processDueDeliveries(deps = {}) {
|
|
239
|
+
const now = deps.now ? deps.now() : Date.now();
|
|
240
|
+
const due = claimDueDeliveries(deps.limit ?? 20, now);
|
|
241
|
+
for (const delivery of due) {
|
|
242
|
+
const endpoint = getWebhook(delivery.projectId, delivery.webhookName);
|
|
243
|
+
if (!endpoint) {
|
|
244
|
+
// Endpoint removed after enqueue — nothing to deliver.
|
|
245
|
+
settleDeliveryFailed(delivery.id, {
|
|
246
|
+
responseStatus: null,
|
|
247
|
+
responseBody: "",
|
|
248
|
+
error: "endpoint removed",
|
|
249
|
+
retryAt: null,
|
|
250
|
+
});
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const result = await deliverOnce(endpoint, delivery.event, deliveryIdOf(delivery.payload), delivery.payload);
|
|
254
|
+
if (result.ok) {
|
|
255
|
+
settleDeliverySucceeded(delivery.id, result.responseStatus, result.responseBody);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
const willRetry = delivery.attempts + 1 < config.WEBHOOK_RETRY_MAX;
|
|
259
|
+
settleDeliveryFailed(delivery.id, {
|
|
260
|
+
responseStatus: result.responseStatus,
|
|
261
|
+
responseBody: result.responseBody,
|
|
262
|
+
error: result.error,
|
|
263
|
+
retryAt: willRetry ? Date.now() + config.WEBHOOK_RETRY_BACKOFF_MS * (delivery.attempts + 1) : null,
|
|
264
|
+
});
|
|
265
|
+
log.warn("outbound webhook delivery failed", {
|
|
266
|
+
projectId: delivery.projectId,
|
|
267
|
+
webhook: delivery.webhookName,
|
|
268
|
+
event: delivery.event,
|
|
269
|
+
status: result.responseStatus,
|
|
270
|
+
willRetry,
|
|
271
|
+
error: result.error,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
return due.length;
|
|
275
|
+
}
|
|
276
|
+
let workerTimer = null;
|
|
277
|
+
export function startWebhookWorker() {
|
|
278
|
+
if (workerTimer)
|
|
279
|
+
return;
|
|
280
|
+
const stuck = resetStuckDeliveries();
|
|
281
|
+
if (stuck > 0)
|
|
282
|
+
log.info("reset stuck outbound deliveries", { count: stuck });
|
|
283
|
+
workerTimer = setInterval(() => {
|
|
284
|
+
void processDueDeliveries().catch((e) => log.warn("outbound webhook worker failed", { err: e }));
|
|
285
|
+
}, config.WEBHOOK_POLL_INTERVAL_MS);
|
|
286
|
+
workerTimer.unref();
|
|
287
|
+
log.info("outbound webhook worker scheduled", {
|
|
288
|
+
intervalMs: config.WEBHOOK_POLL_INTERVAL_MS,
|
|
289
|
+
retryMax: config.WEBHOOK_RETRY_MAX,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
export function stopWebhookWorker() {
|
|
293
|
+
if (workerTimer) {
|
|
294
|
+
clearInterval(workerTimer);
|
|
295
|
+
workerTimer = null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// Test helper: deliver one endpoint synchronously (the UI "Test" button).
|
|
299
|
+
export async function testDelivery(endpoint) {
|
|
300
|
+
const deliveryId = randomUUID();
|
|
301
|
+
const payload = buildRunOutcomePayload({
|
|
302
|
+
event: endpoint.events[0] ?? "run.succeeded",
|
|
303
|
+
delivery: deliveryId,
|
|
304
|
+
timestamp: new Date().toISOString(),
|
|
305
|
+
project: { id: endpoint.projectId, path: projectPath(endpoint.projectId) },
|
|
306
|
+
workItem: { id: 0, number: 0, url: "" },
|
|
307
|
+
run: { mode: "test", model: "", variant: "", status: "test", phase: "", kind: "", reason: "test delivery" },
|
|
308
|
+
});
|
|
309
|
+
const result = await deliverOnce(endpoint, endpoint.events[0] ?? "run.succeeded", deliveryId, payload);
|
|
310
|
+
return { ok: result.ok, status: result.responseStatus, error: result.error };
|
|
311
|
+
}
|
|
312
|
+
// Re-export for the delivery-log route.
|
|
313
|
+
export { purgeDeliveriesOlderThan };
|
|
314
|
+
//# sourceMappingURL=outbound-webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound-webhook.js","sourceRoot":"","sources":["../src/outbound-webhook.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// Project-scope path policy (work item #46). One forge-free source of truth for
|
|
2
|
+
// "which paths may the agent (and the tools acting on its behalf) touch?".
|
|
3
|
+
//
|
|
4
|
+
// The policy is intentionally lexical here: the host process never has the run
|
|
5
|
+
// volume mounted, so canonicalisation (realpath) happens where the filesystem
|
|
6
|
+
// actually lives — the container shell in volume-files.ts. This module owns the
|
|
7
|
+
// *rules*; the host validator, the container shell deny cases, the opencode
|
|
8
|
+
// permission globs, and the in-container guard plugin are all derived from it,
|
|
9
|
+
// so they can never drift.
|
|
10
|
+
//
|
|
11
|
+
// Denied by design: hidden/system state that can hold credentials or forge
|
|
12
|
+
// access — `.xdg-*` (legacy in-repo xdg dirs plus the mount name), `.git`
|
|
13
|
+
// (config can carry tokens), `.ssh`, and `.env`/`.env.*` (except `.env.example`).
|
|
14
|
+
// Allowed by design: `.devbot-out/` and `.devbot-peers/` (agent staging dirs,
|
|
15
|
+
// git-excluded) and `.devbot/` (repo init scripts) — the agent is meant to use
|
|
16
|
+
// them.
|
|
17
|
+
export const PROJECT_ROOT = "/workspace";
|
|
18
|
+
export const PATH_POLICY_ENV = "DEVBOT_PATH_POLICY";
|
|
19
|
+
export const PATH_POLICY = {
|
|
20
|
+
root: PROJECT_ROOT,
|
|
21
|
+
denied: [".git", ".ssh"],
|
|
22
|
+
deniedPrefixes: [".xdg"],
|
|
23
|
+
deniedFiles: [".env"],
|
|
24
|
+
allowedFiles: [".env.example"],
|
|
25
|
+
masked: [
|
|
26
|
+
"/home/devbot/.xdg",
|
|
27
|
+
"/home/devbot/.config",
|
|
28
|
+
"$XDG_DATA_HOME",
|
|
29
|
+
"$XDG_CONFIG_HOME",
|
|
30
|
+
"$XDG_STATE_HOME",
|
|
31
|
+
"$XDG_CACHE_HOME",
|
|
32
|
+
"~/.xdg",
|
|
33
|
+
"~/.config",
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
/** A path that the policy refuses to operate on. Carries a user-readable reason. */
|
|
37
|
+
export class PathDeniedError extends Error {
|
|
38
|
+
constructor(message) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.name = "PathDeniedError";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** JSON form of the policy, injected into the run container as
|
|
44
|
+
* `--env DEVBOT_PATH_POLICY=...` for the guard plugin to read. */
|
|
45
|
+
export function pathPolicyJson(policy = PATH_POLICY) {
|
|
46
|
+
return JSON.stringify(policy);
|
|
47
|
+
}
|
|
48
|
+
// ---- Matching --------------------------------------------------------------
|
|
49
|
+
/** True when a single path segment is denied by the policy. */
|
|
50
|
+
export function isDeniedSegment(seg, policy = PATH_POLICY) {
|
|
51
|
+
if (seg === "")
|
|
52
|
+
return false;
|
|
53
|
+
if (policy.allowedFiles.includes(seg))
|
|
54
|
+
return false;
|
|
55
|
+
if (policy.denied.includes(seg))
|
|
56
|
+
return true;
|
|
57
|
+
if (policy.deniedPrefixes.some((p) => seg === p || seg.startsWith(`${p}-`)))
|
|
58
|
+
return true;
|
|
59
|
+
if (policy.deniedFiles.includes(seg))
|
|
60
|
+
return true;
|
|
61
|
+
if (seg.startsWith(".env.") && !policy.allowedFiles.includes(seg))
|
|
62
|
+
return true;
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
/** True when any segment of an absolute (or `/`-rooted) path is denied. */
|
|
66
|
+
export function isDeniedPath(absPath, policy = PATH_POLICY) {
|
|
67
|
+
return absPath.split("/").some((seg) => isDeniedSegment(seg, policy));
|
|
68
|
+
}
|
|
69
|
+
// ---- Host-side resolution --------------------------------------------------
|
|
70
|
+
/** Resolve a tool-supplied path to an absolute path under the project root and
|
|
71
|
+
* reject traversal, escapes, and denied segments. Pure; no IO (the container
|
|
72
|
+
* shell re-checks the real path to catch symlink escapes). */
|
|
73
|
+
export function resolveProjectPath(raw, policy = PATH_POLICY) {
|
|
74
|
+
const trimmed = raw.trim();
|
|
75
|
+
if (!trimmed)
|
|
76
|
+
throw new PathDeniedError("path is required");
|
|
77
|
+
if (trimmed.includes("\0"))
|
|
78
|
+
throw new PathDeniedError("path contains a NUL byte");
|
|
79
|
+
const joined = trimmed.startsWith("/") ? trimmed : `${policy.root}/${trimmed}`;
|
|
80
|
+
const parts = [];
|
|
81
|
+
for (const seg of joined.split("/")) {
|
|
82
|
+
if (seg === "" || seg === ".")
|
|
83
|
+
continue;
|
|
84
|
+
if (seg === "..")
|
|
85
|
+
throw new PathDeniedError(`path must stay inside ${policy.root}`);
|
|
86
|
+
if (isDeniedSegment(seg, policy))
|
|
87
|
+
throw new PathDeniedError(`path is restricted: ${seg}`);
|
|
88
|
+
parts.push(seg);
|
|
89
|
+
}
|
|
90
|
+
const abs = `/${parts.join("/")}`;
|
|
91
|
+
if (abs !== policy.root && !abs.startsWith(`${policy.root}/`)) {
|
|
92
|
+
throw new PathDeniedError(`path must stay inside ${policy.root}`);
|
|
93
|
+
}
|
|
94
|
+
if (abs === policy.root)
|
|
95
|
+
throw new PathDeniedError("path must point at a file, not the project root");
|
|
96
|
+
return abs;
|
|
97
|
+
}
|
|
98
|
+
/** Validate a slash-separated *relative* path used to build a path under the
|
|
99
|
+
* root (e.g. a peer project's namespace). Rejects absolute paths, `..`,
|
|
100
|
+
* empty/denied segments. Returns the normalized relative path. */
|
|
101
|
+
export function validateProjectRelativePath(raw, policy = PATH_POLICY) {
|
|
102
|
+
const trimmed = raw.trim();
|
|
103
|
+
if (!trimmed)
|
|
104
|
+
throw new PathDeniedError("path is required");
|
|
105
|
+
if (trimmed.startsWith("/"))
|
|
106
|
+
throw new PathDeniedError("path must be relative");
|
|
107
|
+
const parts = [];
|
|
108
|
+
for (const seg of trimmed.split("/")) {
|
|
109
|
+
if (seg === "" || seg === ".")
|
|
110
|
+
continue;
|
|
111
|
+
if (seg === "..")
|
|
112
|
+
throw new PathDeniedError("path must stay inside the project root");
|
|
113
|
+
if (isDeniedSegment(seg, policy))
|
|
114
|
+
throw new PathDeniedError(`path is restricted: ${seg}`);
|
|
115
|
+
parts.push(seg);
|
|
116
|
+
}
|
|
117
|
+
if (parts.length === 0)
|
|
118
|
+
throw new PathDeniedError("path is required");
|
|
119
|
+
return parts.join("/");
|
|
120
|
+
}
|
|
121
|
+
// ---- Derived views ---------------------------------------------------------
|
|
122
|
+
/** Relative globs for opencode's `read`/`edit`/`glob`/`grep`/`list` permission
|
|
123
|
+
* objects. Put these after a `"*": "allow"` and before the allow globs so the
|
|
124
|
+
* last matching rule wins. */
|
|
125
|
+
export function denyGlobs(policy = PATH_POLICY) {
|
|
126
|
+
const globs = [];
|
|
127
|
+
for (const p of policy.deniedPrefixes) {
|
|
128
|
+
globs.push(`**/${p}`, `**/${p}/**`, `**/${p}-*`, `**/${p}-*/**`);
|
|
129
|
+
}
|
|
130
|
+
for (const d of policy.denied) {
|
|
131
|
+
globs.push(`**/${d}`, `**/${d}/**`);
|
|
132
|
+
}
|
|
133
|
+
for (const f of policy.deniedFiles) {
|
|
134
|
+
globs.push(`**/${f}`, `**/${f}.*`);
|
|
135
|
+
}
|
|
136
|
+
return globs;
|
|
137
|
+
}
|
|
138
|
+
/** Allow globs that must be evaluated *after* denyGlobs (last match wins). */
|
|
139
|
+
export function allowGlobs(policy = PATH_POLICY) {
|
|
140
|
+
return policy.allowedFiles.map((f) => `**/${f}`);
|
|
141
|
+
}
|
|
142
|
+
/** Permission object used by the generated and static opencode configs. */
|
|
143
|
+
export function permissionGlobRule(policy = PATH_POLICY) {
|
|
144
|
+
const rule = { "*": "allow" };
|
|
145
|
+
for (const g of denyGlobs(policy))
|
|
146
|
+
rule[g] = "deny";
|
|
147
|
+
for (const g of allowGlobs(policy))
|
|
148
|
+
rule[g] = "allow";
|
|
149
|
+
return rule;
|
|
150
|
+
}
|
|
151
|
+
/** `case` glob patterns for the container shell. The allow patterns must be
|
|
152
|
+
* tried first (`.env.example` also matches the `.env.*` deny pattern). */
|
|
153
|
+
export function denyShellCasePatterns(policy = PATH_POLICY) {
|
|
154
|
+
const pats = [];
|
|
155
|
+
for (const p of policy.deniedPrefixes) {
|
|
156
|
+
pats.push(`*/${p}`, `*/${p}/*`, `*/${p}-*`, `*/${p}-*/*`);
|
|
157
|
+
}
|
|
158
|
+
for (const d of policy.denied) {
|
|
159
|
+
pats.push(`*/${d}`, `*/${d}/*`);
|
|
160
|
+
}
|
|
161
|
+
for (const f of policy.deniedFiles) {
|
|
162
|
+
pats.push(`*/${f}`, `*/${f}.*`);
|
|
163
|
+
}
|
|
164
|
+
return pats;
|
|
165
|
+
}
|
|
166
|
+
export function allowShellCasePatterns(policy = PATH_POLICY) {
|
|
167
|
+
return policy.allowedFiles.map((f) => `*/${f}`);
|
|
168
|
+
}
|
|
169
|
+
/** Best-effort bash guard: a command string that references a masked root or a
|
|
170
|
+
* `..` escape is refused by the plugin. Shell cannot be fully parsed; this
|
|
171
|
+
* catches the obvious literal exfiltration commands.
|
|
172
|
+
*
|
|
173
|
+
* A masked root is a violation when it is used *as a path*: a literal path
|
|
174
|
+
* (`/home/devbot/.xdg`, `~/.config`) anywhere, or an environment-variable
|
|
175
|
+
* expansion followed by `/` (`$XDG_DATA_HOME/...`, `${XDG_CONFIG_HOME}/...`).
|
|
176
|
+
* A bare mention of a variable name — `echo $XDG_DATA_HOME`, a `grep` pattern,
|
|
177
|
+
* a heredoc — is not an access and must not be blocked, or an agent merely
|
|
178
|
+
* inspecting the repo's own docs loops on false positives (work item #96). */
|
|
179
|
+
export function bashGuardReason(command, policy = PATH_POLICY) {
|
|
180
|
+
for (const m of policy.masked) {
|
|
181
|
+
if (m.startsWith("$")) {
|
|
182
|
+
const name = m.slice(1).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
183
|
+
if (new RegExp(`\\$\\{?${name}\\}?(?=/)`).test(command)) {
|
|
184
|
+
return `command references restricted path ${m}`;
|
|
185
|
+
}
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (command.includes(m))
|
|
189
|
+
return `command references restricted path ${m}`;
|
|
190
|
+
}
|
|
191
|
+
// A `..` path segment anywhere is almost always an attempt to leave the root.
|
|
192
|
+
if (/(^|[\s'"=])\.\.(\/|[\s'"]|$)/.test(command))
|
|
193
|
+
return "command references a parent path (..)";
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
/** Convenience for the runner: `--env DEVBOT_PATH_POLICY=<json>`. */
|
|
197
|
+
export function pathPolicyEnvArgs(policy = PATH_POLICY) {
|
|
198
|
+
return ["--env", `${PATH_POLICY_ENV}=${pathPolicyJson(policy)}`];
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=path-sandbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-sandbox.js","sourceRoot":"","sources":["../src/path-sandbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Git auth helper shared by drivers. GitLab's smart-HTTP endpoint requires HTTP
|
|
2
|
+
// Basic (`oauth2:<token>`); Gitea/ForgeJo accept Basic with any username, with
|
|
3
|
+
// the token as password (common convention: the bot username); GitHub accepts
|
|
4
|
+
// any username with the token as password but conventionally uses
|
|
5
|
+
// `x-access-token` for installation/PAT auth.
|
|
6
|
+
//
|
|
7
|
+
// The header is URL-SCOPED to the forge host (`http.<url>/.extraHeader`)
|
|
8
|
+
// whenever a scope URL is supplied, so git only sends the bot token to the
|
|
9
|
+
// configured forge — a submodule on an external host is fetched without the
|
|
10
|
+
// token instead of leaking it. Scoping is the default for every driver.
|
|
11
|
+
import { config } from "../config.js";
|
|
12
|
+
import { shq } from "../redact.js";
|
|
13
|
+
/** Strip trailing slashes so the URL-scoped config key stays well-formed. */
|
|
14
|
+
export function normalizeScopeUrl(url) {
|
|
15
|
+
return url.replace(/\/+$/, "");
|
|
16
|
+
}
|
|
17
|
+
export function gitExtraHeaderFor(token, username = "git", scopeUrl) {
|
|
18
|
+
const basic = Buffer.from(`${username}:${token}`).toString("base64");
|
|
19
|
+
const key = scopeUrl ? `http.${normalizeScopeUrl(scopeUrl)}/.extraHeader` : "http.extraHeader";
|
|
20
|
+
return `git -c ${key}=${shq(`Authorization: Basic ${basic}`)}`;
|
|
21
|
+
}
|
|
22
|
+
/** GitLab convention (`oauth2` username), scoped to the forge host. */
|
|
23
|
+
export function gitExtraHeaderGitlab(token, url = config.PLATFORM_URL) {
|
|
24
|
+
return gitExtraHeaderFor(token, "oauth2", url);
|
|
25
|
+
}
|
|
26
|
+
/** Gitea/ForgeJo convention (bot username, token as password), scoped. */
|
|
27
|
+
export function gitExtraHeaderGitea(token, botUsername = config.BOT_USERNAME, url = config.PLATFORM_URL) {
|
|
28
|
+
return gitExtraHeaderFor(token, botUsername, url);
|
|
29
|
+
}
|
|
30
|
+
/** GitHub convention (`x-access-token` username for PAT/OAuth tokens), scoped. */
|
|
31
|
+
export function gitExtraHeaderGithub(token, url = config.PLATFORM_URL) {
|
|
32
|
+
return gitExtraHeaderFor(token, "x-access-token", url);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=git-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-auth.js","sourceRoot":"","sources":["../../src/platform/git-auth.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,+EAA+E;AAC/E,8EAA8E;AAC9E,kEAAkE;AAClE,8CAA8C;AAC9C,EAAE;AACF,yEAAyE;AACzE,2EAA2E;AAC3E,4EAA4E;AAC5E,wEAAwE;AAExE,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC,6EAA6E;AAC7E,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,OAAO,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAa,EAAE,QAAQ,GAAG,KAAK,EAAE,QAAiB;IAClF,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,IAAI,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACrE,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,iBAAiB,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC,kBAAkB,CAAC;IAC/F,OAAO,UAAU,GAAG,IAAI,GAAG,CAAC,wBAAwB,KAAK,EAAE,CAAC,EAAE,CAAC;AACjE,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,oBAAoB,CAAC,KAAa,EAAE,GAAG,GAAG,MAAM,CAAC,YAAY;IAC3E,OAAO,iBAAiB,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;AACjD,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,mBAAmB,CAAC,KAAa,EAAE,WAAW,GAAG,MAAM,CAAC,YAAY,EAAE,GAAG,GAAG,MAAM,CAAC,YAAY;IAC7G,OAAO,iBAAiB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,CAAC,CAAC;AACpD,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,oBAAoB,CAAC,KAAa,EAAE,GAAG,GAAG,MAAM,CAAC,YAAY;IAC3E,OAAO,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,CAAC,CAAC;AACzD,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Gitea/ForgeJo attachment discovery + fetch. Issue-comment attachments are
|
|
2
|
+
// Markdown links to `/attachments/{uuid}` (with or without a filename suffix),
|
|
3
|
+
// e.g. `[name](/attachments/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee)` or
|
|
4
|
+
// ``. The uuid is the stable id; the
|
|
5
|
+
// download endpoint is the raw URL itself. Discovery is Gitea-specific; the
|
|
6
|
+
// extraction engine lives in ../../attachments.ts.
|
|
7
|
+
import { runtime } from "../../runtime-state.js";
|
|
8
|
+
import { giteaFetchRaw, giteaUpload } from "./http.js";
|
|
9
|
+
import { GiteaAttachmentSchema } from "./schemas.js";
|
|
10
|
+
import { MD_LINK, safeDecode, stripCodeSpans, } from "../../attachments.js";
|
|
11
|
+
const GITEA_ATTACHMENT = /\/attachments\/([0-9a-fA-F-]{8,})\/?([^)\s]*)/;
|
|
12
|
+
export function findGiteaAttachmentRefs(body) {
|
|
13
|
+
const stripped = stripCodeSpans(body);
|
|
14
|
+
const refs = [];
|
|
15
|
+
const seen = new Set();
|
|
16
|
+
for (const m of stripped.matchAll(MD_LINK)) {
|
|
17
|
+
const url = m[2] ?? "";
|
|
18
|
+
const hit = GITEA_ATTACHMENT.exec(url);
|
|
19
|
+
if (!hit)
|
|
20
|
+
continue;
|
|
21
|
+
const uuid = hit[1] ?? "";
|
|
22
|
+
if (!uuid || seen.has(uuid))
|
|
23
|
+
continue;
|
|
24
|
+
seen.add(uuid);
|
|
25
|
+
const tail = safeDecode((hit[2] ?? "").replace(/^\//, ""));
|
|
26
|
+
refs.push({
|
|
27
|
+
raw: m[0] ?? "",
|
|
28
|
+
secret: uuid,
|
|
29
|
+
filename: tail || uuid,
|
|
30
|
+
absolute: /^https?:\/\//i.test(url),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
for (const m of stripped.matchAll(new RegExp(GITEA_ATTACHMENT.source, "g"))) {
|
|
34
|
+
const uuid = m[1] ?? "";
|
|
35
|
+
if (!uuid || seen.has(uuid))
|
|
36
|
+
continue;
|
|
37
|
+
seen.add(uuid);
|
|
38
|
+
const tail = safeDecode((m[2] ?? "").replace(/^\//, ""));
|
|
39
|
+
refs.push({ raw: m[0] ?? "", secret: uuid, filename: tail || uuid, absolute: false });
|
|
40
|
+
}
|
|
41
|
+
return refs;
|
|
42
|
+
}
|
|
43
|
+
/** Fetch one Gitea/ForgeJo attachment's full bytes. */
|
|
44
|
+
export async function downloadGiteaAttachment(ref) {
|
|
45
|
+
const { bytes, contentType } = await giteaFetchRaw(`/attachments/${ref.secret}`, { kind: "bot" }, runtime().attachmentMaxBytes);
|
|
46
|
+
return { bytes, contentType };
|
|
47
|
+
}
|
|
48
|
+
// Outbound upload via the issue/assets API. Gitea/ForgeJo accept multipart field
|
|
49
|
+
// `attachment` at POST /repos/:owner/:repo/issues/:index/assets and return the
|
|
50
|
+
// stored uuid + browser_download_url. The owner/repo split is done by the driver
|
|
51
|
+
// (it already resolves the numeric repo id to a full_name).
|
|
52
|
+
export async function uploadGiteaAttachment(owner, repo, number, file) {
|
|
53
|
+
const form = new FormData();
|
|
54
|
+
form.append("attachment", new Blob([file.bytes], { type: file.contentType }), file.filename);
|
|
55
|
+
const r = await giteaUpload(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${number}/assets`, form, GiteaAttachmentSchema, { kind: "bot" });
|
|
56
|
+
return {
|
|
57
|
+
markdown: ``,
|
|
58
|
+
url: r.browser_download_url,
|
|
59
|
+
filename: r.name,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=attachments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachments.js","sourceRoot":"","sources":["../../../src/platform/gitealike/attachments.ts"],"names":[],"mappings":"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"}
|