Commit Graph

700 Commits

Author SHA1 Message Date
molecule-ai[bot]
ac04b60b36 docs(platform-api): Breaking Changes section for PR #701 auth + validation
Updates docs/api-protocol/platform-api.md:
- Add ## Breaking Changes section with full before/after table for PR #701
  (PATCH wsAuth, templates AdminAuth, UUID validation, field length/char limits)
- PATCH /workspaces/:id row: add WorkspaceAuth note + validation details
- GET /templates: add AdminAuth note
- GET /org/templates: add row with AdminAuth note
- Migration steps for E2E scripts and automation callers

Source PR: #701 (SHA 3f5dea79) — fix(security): input validation, route auth, UUID safety

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 12:44:11 +00:00
molecule-ai[bot]
3f5dea791b Merge pull request #701 from Molecule-AI/fix/issue-685-686-687-688-input-validation
fix(security): input validation, route auth, UUID safety (#685 #686 #687 #688)
2026-04-17 12:32:03 +00:00
Hongming Wang
fa6d100a1e Merge pull request #708 from Molecule-AI/fix/e2e-test-token-bootstrap
fix(router): remove AdminAuth from test-token — unblocks E2E CI bootstrap
2026-04-17 05:17:12 -07:00
molecule-ai[bot]
bdd56b1489 fix(security): rebase #685-688 onto main — preserve wsAuth PATCH, add yamlSpecialChars
- Rebased onto 350288f1 (main HEAD, post-#692 IDOR fix)
- PATCH /workspaces/:id remains under wsAuth group (not open router)
- Added validateWorkspaceID (uuid.Parse check) in Get/Update/Delete
- Added validateWorkspaceFields: rejects \n\r in all fields,
  yamlSpecialChars {}[]|>*&! in name/role only, enforces max lengths
- Template endpoints (GET /templates, GET /org/templates) now require AdminAuth
- Replaced stale in-handler sensitiveUpdateFields gate tests with
  TestWorkspaceUpdate_SensitiveField_AuthEnforcedByMiddleware

Closes #685 #686 #687 #688
2026-04-17 12:13:44 +00:00
rabbitblood
327cc3ea55 fix(router): remove AdminAuth from test-token — unblocks E2E bootstrap
#612 added AdminAuth to GET /admin/workspaces/:id/test-token, breaking
the chicken-and-egg bootstrap that E2E tests rely on:

1. POST /workspaces creates first workspace (fail-open, no tokens)
2. Provision generates a workspace auth token → inserts into DB
3. AdminAuth now sees a live token → requires auth on ALL routes
4. E2E calls test-token to get its first admin bearer → 401
5. All subsequent E2E calls fail → EVERY open PR CI blocked

The test-token handler already has its own production guard
(TestTokensEnabled returns false when MOLECULE_ENV=prod). That's
sufficient — AdminAuth was defence-in-depth but broke the only
bootstrap path in dev/CI environments.

This has been blocking CI for 6+ cycles, stalling 4 PRs (#650,
#651, #696, #701) and masking as 'flaky E2E Postgres timeout'
until root-cause analysis this cycle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 04:50:14 -07:00
molecule-ai[bot]
350288f186 Merge pull request #695 from Molecule-AI/chore/eco-watch-2026-04-17-c
chore(eco-watch): add Anthropic Agent Skills + Microsoft APM — 2026-04-17
2026-04-17 11:21:21 +00:00
Molecule AI Research Lead
786c7566cd chore(eco-watch): add Anthropic Agent Skills + Microsoft APM — 2026-04-17
Two new ecosystem entries from daily trending survey:

- anthropics/skills (119k★, GitHub trending #1): cross-platform Agent Skills
  open standard (SKILL.md format); Molecule already natively compliant per
  GH #677 spike; 26+ adopters (Cursor, Codex, Copilot, Gemini CLI); feeds #676

- microsoft/apm (1.8k★, v0.8.11): Agent Package Manager for apm.yml manifests
  managing plugins/skills/MCP servers; overlaps with Molecule plugin system;
  content-security (apm audit) worth borrowing for #675; tracked in GH #694

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 11:12:46 +00:00
molecule-ai[bot]
1bbda53142 Merge pull request #692 from Molecule-AI/fix/issue-680-681-workspace-auth
fix(security): auth+ownership on PATCH /workspaces/:id (#680 #681)
2026-04-17 11:03:25 +00:00
molecule-ai[bot]
c825e44b50 Merge pull request #659 from Molecule-AI/infra/rebuild-runtime-images-script
infra: add rebuild-runtime-images.sh — patches all 6 adapter images with git credential helper (#658)
2026-04-17 10:59:33 +00:00
molecule-ai[bot]
627946528d fix(security): add auth+ownership to PATCH /workspaces/:id (#680 #681)
ISSUE #680 — IDOR on PATCH /workspaces/🆔
- Route was on the open router with no auth middleware. Any unauthenticated
  caller could rename, change role, or update any workspace field of any
  workspace ID without credentials (zero auth + no ownership check).
- Fix: register under wsAuth (WorkspaceAuth middleware) which (a) requires a
  valid bearer token and (b) validates the token belongs to the target
  workspace, providing auth + ownership in a single check.
- Remove the now-redundant in-handler field-level auth block — the middleware
  is a strictly stronger gate. Dead code gone.
- Remove unused `middleware` import from workspace.go.
- Update tests: two tests that asserted the old in-handler 401 are replaced
  by TestWorkspaceUpdate_SensitiveField_AuthEnforcedByMiddleware (documents
  that auth is now at the router layer); cosmetic-field test renamed.

ISSUE #681 — test-token endpoint auth:
- Confirmed: GET /admin/workspaces/:id/test-token already has
  middleware.AdminAuth(db.DB). No change needed — finding was from older state.

Build: `go build ./...` clean. All 15 test packages pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:55:23 +00:00
Hongming Wang
b4590023cd Merge pull request #671 from Molecule-AI/feat/issue-618-admin-schedules-health
feat(platform): GET /admin/schedules/health — cross-workspace cron firing status (#618)
2026-04-17 03:47:44 -07:00
molecule-ai[bot]
c07793eedf fix(security): cap discord error response body read at 4096 bytes
Unbounded io.ReadAll on the Discord webhook error response body was a LOW
OOM risk: a malicious gateway or misconfigured proxy could return a multi-MB
body and exhaust agent memory. Cap with io.LimitReader(resp.Body, 4096) —
error messages are always short; any extra content is irrelevant noise.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:46:09 +00:00
molecule-ai[bot]
4a27866c45 fix(router): restore artifacts routes, remove stray audit route from #618 scope
FIX 1: Cloudflare Artifacts routes (wsAuth POST/GET /artifacts, /fork, /token)
were accidentally dropped when #618 modified router.go. Restored along with the
handler and client packages that were already on main (#595/#641) but missing
from this branch.

FIX 2: Stray `audh := handlers.NewAuditHandler()` / `wsAuth.GET("/audit", ...)` block
was added out-of-scope during #618 work. Removed — #594 (audit-ledger) is a
separate merged PR and its routes live on main independently.

Build: `go build ./...` clean. All 17 test packages pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:44:34 +00:00
molecule-ai[bot]
470704416e fix(security): Ed25519 signature verification for Discord webhooks + strip token from error chain
HIGH (#659-1): POST /webhooks/discord had no signature verification, allowing
any attacker to POST forged Discord slash-command payloads. Add Ed25519
verification via verifyDiscordSignature() before adapter.ParseWebhook() is
called. The function reads r.Body, verifies Ed25519(pubKey, timestamp+body,
X-Signature-Ed25519), then restores r.Body with io.NopCloser so ParseWebhook
can still read the payload. The public key is resolved from the first enabled
Discord channel's app_public_key config (plaintext — it is a public key and
not in sensitiveFields) with a fallback to DISCORD_APP_PUBLIC_KEY env var;
no key configured -> 401 (fail-closed). discordPublicKey() is the DB helper.

MEDIUM (#659-2): discord.go SendMessage() wrapped http.Client.Do errors with
%w, propagating the *url.Error which includes the full webhook URL
(https://discord.com/api/webhooks/{id}/{token}) into logs and error responses.
Replace with a static "discord: HTTP request failed" string.

Tests added (11 new):
- TestVerifyDiscordSignature_Valid / _WrongKey / _TamperedBody /
  _MissingTimestamp / _MissingSignature / _InvalidHexSignature /
  _InvalidHexPubKey / _WrongLengthPubKey (real Ed25519 key pairs)
- TestChannelHandler_Webhook_Discord_NoKey_Returns401
- TestChannelHandler_Webhook_Discord_InvalidSig_Returns401
- TestChannelHandler_Webhook_Discord_ValidSig_PingAccepted
- TestDiscordAdapter_SendMessage_ErrorDoesNotLeakToken

go test ./... green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:36:51 +00:00
molecule-ai[bot]
6e4979954b feat(platform): add GET /admin/schedules/health for cross-workspace schedule monitoring (#618)
Operators and audit agents can now detect silent cron failures across all
workspaces with a single AdminAuth-gated request — no per-workspace bearer
tokens required. This closes the proactive detection gap that left issue #85
(cron died silently 10+ hours) undetectable until users noticed missing work.

Changes:
- platform/internal/handlers/admin_schedules_health.go: new AdminSchedulesHealthHandler
  - GET /admin/schedules/health joins workspace_schedules + workspaces (excluding
    removed workspaces), computes status (ok|stale|never_run) and
    stale_threshold_seconds (2 × cron interval via scheduler.ComputeNextRun)
  - computeStaleThreshold() and classifyScheduleStatus() extracted as
    package-level helpers for direct unit testing
- platform/internal/handlers/admin_schedules_health_test.go: 16 tests
  - Unit tests for computeStaleThreshold (5min/hourly/daily crons, invalid expr,
    invalid timezone) and classifyScheduleStatus (never_run/stale/ok/zero-threshold)
  - Integration tests via sqlmock: empty result, never_run classification,
    stale detection, ok status, DB error → 500, multi-workspace response,
    required JSON fields coverage
- platform/internal/router/router.go: register GET /admin/schedules/health
  behind middleware.AdminAuth(db.DB), mirroring the /admin/liveness gate

Closes #618

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:28:55 +00:00
06938e8335 fix(security): allowlist-validate runtime arg in rebuild-runtime-images.sh
The optional $1 argument flowed directly into Docker image tag names
(workspace-template:<runtime>) and filesystem paths (RUNTIME_DIR) with
no validation, enabling path traversal or unexpected tag injection via
e.g. `bash rebuild-runtime-images.sh '../evil'`.

Fix: introduce VALID_RUNTIMES allowlist and validate $1 against it
before setting RUNTIMES. Any unlisted value now exits with a clear
error message. The RUNTIMES array is populated from VALID_RUNTIMES
when no argument is given, keeping the all-runtimes default path.

shellcheck clean; $1 only appears inside the validated block.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:27:11 +00:00
cd6c82030d fix(infra): rename TMPDIR→RUNTIME_DIR, fix PIPESTATUS docker exit check
Bug 1: TMPDIR is a POSIX-reserved variable used by mktemp, Docker
BuildKit, and git subprocesses as their system temp directory.
Overwriting it redirected those tools to the build context, causing
unpredictable failures. Renamed all 6 occurrences to RUNTIME_DIR.

Bug 2: `docker build ... | grep` made grep's exit code (0=match,
1=no match) determine if the build succeeded, not docker's. Fixed by
reading PIPESTATUS[0] immediately after the pipeline so docker's real
exit code drives the SUCCESS/FAILED tracking.

Also fixed two pre-existing shellcheck warnings:
- SC2034: removed unused REPO_ROOT variable
- SC2064: trap now uses single quotes so TMPBASE expands at signal time

shellcheck clean with no warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:25:43 +00:00
molecule-ai[bot]
b7f79a4be1 Merge pull request #669 from Molecule-AI/feat/issue-652-effort-taskbudget-v2
feat(issue-652): wire effort + task_budget to Anthropic output_config
2026-04-17 10:11:09 +00:00
molecule-ai[bot]
588848f701 fix(migrations): TEXT→UUID in 028_workspace_artifacts — unblocks all E2E CI
fix(migrations): TEXT→UUID in 028_workspace_artifacts — unblocks all E2E CI
2026-04-17 10:08:51 +00:00
Molecule AI QA Engineer
dc2c5817bc test: add _load_config_dict coverage for issue #652
Cover the four paths that were exercised only via mock in the
_build_options tests: valid YAML, missing file, malformed YAML,
and empty file (safe_load → None → {} via `or {}`).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:08:45 +00:00
rabbitblood
8eaffc49aa fix(migrations): TEXT→UUID in 028_workspace_artifacts — unblocks all E2E CI
Migration 028 declared workspace_id as TEXT with a FK to workspaces(id)
which is UUID. Postgres rejects the FK: 'cannot be implemented' because
the types don't match. Same class of bug as #646 (which fixed 025).

This has been blocking ALL open PRs' E2E API Smoke Test for 5+ cycles
(since 028 was introduced in #641 Cloudflare Artifacts). Every PR CI
run applies all migrations from scratch → hits this → platform exits
with log.Fatalf → /health never responds → 30s timeout → FAIL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 02:48:08 -07:00
Molecule AI Backend Engineer
e11e077027 feat(issue-652): wire effort and task_budget to claude sdk output_config
Adds _load_config_dict() helper to ClaudeSDKExecutor and wires the new
effort and task_budget config fields into _build_options() before the
Anthropic API call:

- effort (str): low|medium|high|xhigh|max — populates output_config.effort
- task_budget (int): advisory total-token budget; must be >= 20000 when set;
  automatically adds task-budgets-2026-03-13 beta header

Also adds WorkspaceConfig.effort and WorkspaceConfig.task_budget fields in
config.py and 5 acceptance tests covering all code paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 07:33:07 +00:00
molecule-ai[bot]
a2a26c6cce Merge pull request #656 from Molecule-AI/feat/issue-625-discord-adapter-clean
feat(channels): add Discord adapter (#625)
2026-04-17 07:30:39 +00:00
molecule-ai[bot]
84c3177b06 Merge pull request #655 from Molecule-AI/feat/issue-499-hermes-stacked-system-messages
feat(hermes): stacked system message merge + Nous sampling defaults (#499 #500)
2026-04-17 07:30:35 +00:00
molecule-ai[bot]
d814dd8b7f Merge pull request #647 from Molecule-AI/chore/eco-watch-2026-04-17-b
chore(eco-watch): 2026-04-17 daily survey (pass 2) — AI Hedge Fund
2026-04-17 07:30:22 +00:00
dcbc5f3e54 fix(gate-1): merge eco-watch pass-2 + pass-3 entries (AI Hedge Fund + Strix)
Both chore/eco-watch-2026-04-17-b and chore/eco-watch-2026-04-17-c added
entries at the end of ecosystem-watch.md. Kept both entries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 07:29:55 +00:00
molecule-ai[bot]
7b2f5a7a8e Merge pull request #660 from Molecule-AI/chore/eco-watch-2026-04-17-c
chore(eco-watch): add Strix — AI security agent graph (Apr 17 pass 3)
2026-04-17 07:27:54 +00:00
Molecule AI Backend Engineer
54737d58a2 feat(platform): merge stacked system messages for Hermes/vLLM (#499)
vLLM (and Nous Hermes portal) only accept a single system message.
When the platform builds a messages array from multiple sources
(base system prompt + workspace config + per-session override), the
consecutive system entries at the front cause vLLM to reject or
silently drop all but the first.

Adds mergeSystemMessages() — a stateless pre-flight transform in the
handlers package that collapses the uninterrupted leading run of
{"role":"system"} entries into one, joining their content with "\n\n".
Non-system messages between system messages are not touched; a single
system message is returned as-is (no allocation).

10 unit tests cover: stacked merge, single-unchanged, no-system passthrough,
three-message collapse, interleaved user (trailing system not merged),
only-system-messages, empty slice, nil slice, non-string content, and
assistant-leading passthrough.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 07:19:30 +00:00
Molecule AI Research Lead
56782bc85c chore(eco-watch): add Strix (usestrix/strix) — AI security agent graph
24.1k-star Apache-2.0 security testing platform using a graph-of-agents
architecture; +202 stars Apr 17 2026. Demand signal for domain-specific
multi-agent orchestration and audit-trail patterns adjacent to GH #594.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 07:17:11 +00:00
b13dbc212b infra: add rebuild-runtime-images.sh for post-PR#640 image fix (#658)
Standalone adapter images (langgraph, claude-code, etc.) use
ENTRYPOINT ["molecule-runtime"] which bypasses entrypoint.sh. PR #640's
entrypoint.sh fix therefore never runs in adapter images. The correct fix
is to bake git config --system into the image at build time.

This script:
1. Rebuilds workspace-template:base from the monorepo Dockerfile (which
   has the fixed entrypoint.sh and molecule-git-token-helper.sh)
2. For each of the 6 runtime adapters: clones the standalone repo, patches
   its Dockerfile to COPY the credential helper and run git config --system,
   then builds the final image tagged as workspace-template:<runtime>

Usage (run on the host machine, not inside a workspace container):
  bash workspace-template/rebuild-runtime-images.sh          # all 6
  bash workspace-template/rebuild-runtime-images.sh claude-code  # one

See issue #658 for the architectural explanation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 07:14:12 +00:00
molecule-ai[bot]
9827d1e498 feat(canvas): add max effort level to ConfigTab dropdown (#653)
feat(canvas): add max effort level to ConfigTab dropdown (#653)
2026-04-17 07:04:57 +00:00
molecule-ai[bot]
7793edee33 feat(hermes): plumb response_format=json_schema for structured output (#498)
feat(hermes): plumb response_format=json_schema for structured output (#498)
2026-04-17 07:03:45 +00:00
fad6aad734 fix(merge): combine response_format (#498) and tools (#497) in hermes_executor
Both PRs restructured the same chat.completions.create() call to use a
create_kwargs dict. Resolved by keeping both __init__ params and both
conditionals in the create_kwargs block.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 07:03:22 +00:00
1b9be1e289 feat(channels): add Discord adapter (#625)
Implements DiscordAdapter conforming to the ChannelAdapter interface,
using Discord Incoming Webhooks for outbound messages and the Interactions
endpoint for inbound slash commands.

Changes:
- platform/internal/channels/discord.go: DiscordAdapter + splitMessage
  helper (Discord enforces 2000-char limit; long messages are split at
  newline/space boundaries). ParseWebhook handles type-1 PING (returns
  nil so the router layer can respond), type-2 APPLICATION_COMMAND, and
  type-3 MESSAGE_COMPONENT payloads. ValidateConfig rejects non-discord
  webhook URLs (SSRF guard matches Slack pattern).
- platform/internal/channels/discord_test.go: 20 unit tests covering
  Type/DisplayName, ValidateConfig (valid + 5 invalid cases), SendMessage
  error paths, ParseWebhook (PING / slash command / DM user / unknown type /
  invalid JSON), StartPolling, GetAdapter registry lookup, ListAdapters
  inclusion, and splitMessage edge cases.
- platform/internal/channels/registry.go: register "discord" adapter.
- .env.example: document DISCORD_WEBHOOK_URL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 07:02:50 +00:00
Molecule AI Frontend Engineer
3cb21b4bd5 feat(canvas): add max effort level to ConfigTab dropdown (#653)
Adds a fifth option to the effort <select> in the Claude Settings section:

  <option value="max">max — absolute ceiling</option>

The dropdown now offers: low / medium / high / xhigh / max.

effort is typed as string? so no interface update required.
Test updated: source-assertion count "four" → "five", new toYaml
serialization test for effort: max.

641/641 tests pass. Build clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:58:29 +00:00
molecule-ai[bot]
816ea3e565 feat(hermes): native tools=[] parameter instead of text-in-prompt workaround (#497)
feat(hermes): native tools=[] parameter instead of text-in-prompt workaround (#497)
2026-04-17 06:56:10 +00:00
molecule-ai[bot]
031dd15424 Merge pull request #639 from Molecule-AI/feat/issue-608-effort-task-budget-ui
Merge gate passed (all 7 gates). Adds effort + task_budget to ConfigTab Claude Settings section. Dark zinc palette, conditionally shown for claude/anthropic runtimes, yaml serialization omits zero/empty values. UNSTABLE = known App token scope gap.
2026-04-17 06:49:28 +00:00
molecule-ai[bot]
6cf795eb26 Merge pull request #640 from Molecule-AI/fix/issue-613-git-token-helper-path
Merge gate passed (all 7 gates). Root cause fix for GH_TOKEN expiry: copies molecule-git-token-helper.sh into /app/scripts/ and corrects entrypoint.sh path. UNSTABLE = known App token scope gap.
2026-04-17 06:49:21 +00:00
molecule-ai[bot]
ed9106d83a Merge pull request #646 from Molecule-AI/fix/migration-025-fk-type
Merge gate passed. +2/-2 FK type fix: workspace_id TEXT→UUID in 025, org_id TEXT→UUID in 026 — matches workspaces.id (UUID PK). Schema migration — CEO explicit authorization in chat (boot-blocker/urgent). UNSTABLE = known App token scope gap.
2026-04-17 06:46:08 +00:00
Molecule AI Research Lead
c5621bafe3 chore(eco-watch): 2026-04-17 daily survey — AI Hedge Fund
New LOW entry: virattt/ai-hedge-fund (55.7k, +763 today) — 19-agent
financial-analysis reference implementation. High-visibility demand signal
for domain-specific multi-agent orchestration in finance. Not a competing
platform but a compelling org-template opportunity (19 specialist agents
coordinated by a PM workspace via A2A).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:43:34 +00:00
molecule-ai[bot]
a6f4678b9e Merge pull request #641 from Molecule-AI/feat/issue-595-cloudflare-artifacts-demo
Merge gate passed (all 7 gates). Cloudflare Artifacts demo integration: 4 routes behind WorkspaceAuth, CF token from env only, import_url HTTPS enforced, CF 5xx errors sanitized, parameterized SQL throughout. Migration 028 uses CREATE TABLE IF NOT EXISTS. Schema migration — CEO explicit authorization in chat (urgent/first-mover). Tip SHA dc89d8f verified. UNSTABLE = known App token scope gap.
2026-04-17 06:43:21 +00:00
Hongming Wang
7dcb36b9eb fix(migrations): TEXT→UUID FK type mismatch blocking all E2E runs
Migrations 025 + 026 declared workspace_id/org_id as TEXT but
workspaces.id is UUID — Postgres rejects the FK constraint, crashing
every E2E run on main since these migrations were merged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 23:40:22 -07:00
Molecule AI Backend Engineer
dc89d8fd7b fix(platform): address security review findings on CF Artifacts (#641)
Four findings from the security audit on PR #641:

FIX 1 (MEDIUM): import_url scheme validation
- Reject non-HTTPS import URLs with 400 before forwarding to CF API.
  Prevents SSRF via http://, git://, ssh://, file:// etc.

FIX 2 (MEDIUM): CF 5xx error leakage
- Add cfErrMessage() helper: returns "upstream service error" for CF 5xx
  responses and non-CF errors, passes through 4xx messages.
- Applied at all four CF-error response sites (Create, Get, Fork, Token).

FIX 3 (LOW): repo name validation
- Add package-level repoNameRE = ^[a-zA-Z0-9][a-zA-Z0-9_-]{0,62}$
- Validate in Create and Fork handlers when caller supplies an explicit name.
  Auto-generated names ("molecule-ws-<id>") are always safe and skip validation.

FIX 4 (LOW): response body size limit in CF client
- Wrap resp.Body with io.LimitReader(1 MB) before json.NewDecoder in do().
  Prevents memory exhaustion from a runaway/malicious CF response.

Tests: 16 new tests covering all four fixes (cfErrMessage 4xx/5xx/non-API,
import_url non-HTTPS cases, invalid repo names in Create and Fork).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:39:47 +00:00
Molecule AI Backend Engineer
57ff2eab78 feat(platform): Cloudflare Artifacts demo integration (#595)
Add a minimal but complete integration with the Cloudflare Artifacts API
(private beta Apr 2026, public beta May 2026) — "Git for agents" versioned
workspace-snapshot storage.

## What's included

**`platform/internal/artifacts/client.go`** — typed Go HTTP client for the
CF Artifacts REST API:
- CreateRepo, GetRepo, ForkRepo, ImportRepo, DeleteRepo
- CreateToken, RevokeToken
- CF v4 response-envelope decoding; *APIError with StatusCode + Message

**`platform/internal/handlers/artifacts.go`** — four workspace-scoped
Gin handlers (all behind WorkspaceAuth middleware):
- POST /workspaces/:id/artifacts — attach or import a CF Artifacts repo
- GET  /workspaces/:id/artifacts — get linked repo info (DB + live CF)
- POST /workspaces/:id/artifacts/fork — fork the workspace's repo
- POST /workspaces/:id/artifacts/token — mint a short-lived git credential

**`platform/migrations/028_workspace_artifacts.up.sql`** — `workspace_artifacts`
table: one-to-one link between a workspace and its CF Artifacts repo.
Credentials are never stored; only the credential-stripped remote URL.

**`platform/internal/router/router.go`** — wire the four routes into the
existing wsAuth group.

## Configuration
Two env vars gate the feature (returns 503 when either is absent):
- CF_ARTIFACTS_API_TOKEN — Cloudflare API token with Artifacts write perms
- CF_ARTIFACTS_NAMESPACE — Cloudflare Artifacts namespace name

## Tests
- 10 client-level tests (httptest.Server + CF v4 envelope mocks)
- 14 handler-level tests (sqlmock DB + mock CF server)
- Helper unit tests for stripCredentials, cfErrToHTTP

All 21 packages pass (go test ./...).

Closes #595

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:28:58 +00:00
molecule-ai[bot]
c596da663f Merge pull request #634 from Molecule-AI/fix/issue-615-cap-monthly-spend
Merge gate passed (all 7 gates). Caps monthly_spend on heartbeat upsert: negative→0, >0B→0B, zero=no-update path. Comment-only conflicts resolved (identical logic both sides). Depends on #611's monthly_spend column — merged first. UNSTABLE = known App token scope gap.
2026-04-17 06:27:35 +00:00
7afd1cfc6d fix(gate-1): resolve merge conflicts with main
Both conflicts were comment-only — identical logic on both sides:
- registry.go: kept main's wording ("accidentally clearing") for the
  monthly_spend comment in Heartbeat; logic is unchanged
- workspace.go: kept HEAD's comment (describes PR #634's clamping
  behaviour: [0, maxMonthlySpend]); logic is unchanged

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:27:14 +00:00
6169135954 fix(template): copy molecule-git-token-helper.sh into image and fix path
Two bugs prevented the git credential helper (merged in #567) from ever
running at workspace boot:

1. Dockerfile never COPY'd scripts/molecule-git-token-helper.sh into the
   image — only gh-wrapper.sh was copied from scripts/. Result: the helper
   binary did not exist in any built container image.

2. entrypoint.sh looked for the helper at /workspace-template/scripts/...
   but /workspace-template/ is not a path that exists inside the container
   (WORKDIR is /app, no /workspace-template mount). The `if [ -f ... ]`
   guard silently fell through to the WARNING branch on every boot since
   #567 merged — the helper was never registered.

Fix:
- Add `COPY scripts/molecule-git-token-helper.sh ./scripts/` to Dockerfile
  so the script lands at /app/scripts/ in the image (matching WORKDIR /app)
- Update HELPER_SCRIPT path in entrypoint.sh from
  /workspace-template/scripts/... to /app/scripts/...

After this fix, every workspace container registers the helper at boot via:
  git config --global credential.https://github.com.helper \
    "!/app/scripts/molecule-git-token-helper.sh"

Closes #613.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:27:08 +00:00
molecule-ai[bot]
3a1c119fec Merge pull request #611 from Molecule-AI/feat/issue-541-budget-limit-backend
Merge gate passed (all 7 gates). Adds budget_limit + monthly_spend columns via 027_workspace_budget (ADD COLUMN IF NOT EXISTS — idempotent). A2A budget enforcement is fail-open on DB errors. WorkspaceAuth on all budget routes. Schema migration — CEO explicit authorization in chat. Merging before #634 which writes to monthly_spend.
2026-04-17 06:25:02 +00:00
Molecule AI Frontend Engineer
99d5ef6866 feat(canvas): expose effort + task_budget in ConfigTab (#608)
Adds two new Claude API primitives (Opus 4.7+) as configurable workspace
fields in the Config tab form:

  effort: 'low' | 'medium' | 'high' | 'xhigh'
    Maps to output_config.effort in the Anthropic Messages API.
    Controls thinking depth — xhigh enables extended thinking mode.

  task_budget: integer (token count, 0 = unset)
    Maps to output_config.task_budget.total; requires beta header
    task-budgets-2026-03-13. Lets operators cap token spend per task.

Both fields are stored as top-level keys in config.yaml and read by
claude_sdk_executor.py (workspace-template side, tracked in #608).

Canvas changes:
- form-inputs.tsx: effort?: string, task_budget?: number added to
  ConfigData; DEFAULT_CONFIG initialises them to "" / 0
- yaml-utils.ts: toYaml() emits effort + task_budget (omits when
  empty/zero); parseYaml() already handles plain string/integer keys
- ConfigTab.tsx: new collapsible "Claude Settings" section (defaultOpen=false)
  shown when runtime === "claude-code" OR model name contains "claude"
  or "anthropic". Dropdown for effort (4 options + unset), number input
  for task_budget (step 1000, 0 = unset).

Tests (25 cases in ClaudeSettings.test.tsx):
  - toYaml serialises all four effort values + omits empty/undefined
  - toYaml serialises task_budget + omits 0/undefined
  - effort appears before task_budget in YAML output
  - parseYaml round-trips both fields correctly
  - DEFAULT_CONFIG shape assertions
  - Source assertions for section guards + option values
  - React rendering: section visible for claude-code/claude model,
    hidden for non-Claude runtime (crewai + gpt-4o)

640/640 tests pass. Build clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 06:24:36 +00:00
molecule-ai[bot]
d7c247a985 Merge pull request #636 from Molecule-AI/fix/issue-631-migration-gap
Merge gate passed. Pure file renames (+0/-0): 026→025 (workspace_token_usage), 027→026 (org_plugin_allowlist). Closes migration numbering gap so sequential runners proceed past 024. Schema migration — CEO explicit authorization in chat. NOTE: if production DB recorded old filenames 026/027 as applied, verify runner idempotency before restart to avoid double-application.
2026-04-17 06:23:05 +00:00