Commit Graph

8 Commits

Author SHA1 Message Date
molecule-ai[bot]
fde90efde5
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]
15d4b25c78
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
afd9c3b5bb 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
rabbitblood
e7710d2e6f feat(channels): Lark / Feishu adapter (outbound webhook + Events API inbound)
New ChannelAdapter implementation for Lark (international, open.larksuite.com)
and Feishu (China, open.feishu.cn). Both speak the same payload format —
only the host differs — so a single adapter covers both.

Outbound: POST text to a Custom Bot webhook URL with msg_type:"text".
Lark returns 200 OK even when delivery fails — the body's `code` field is
the truth. Adapter parses the response and returns a Go error when
code != 0 so callers don't think a revoked-webhook send succeeded.

Inbound: handles both v1 url_verification (handshake) and v2 event_callback
(im.message.receive_v1) shapes. Optional verify_token field — when set,
inbound payloads with mismatching tokens are rejected via constant-time
compare (#337 class — never raw == against a stored secret).

Sender ID resolution prefers user_id → falls back to open_id (open_id is
always present; user_id only when the bot has the contacts permission).
Non-text message types and non-message events return nil, nil so the
receiver responds 200 OK without dispatching.

Tests: 23 cases — identity, ValidateConfig (6 sub-cases incl. URL prefix
matrix), SendMessage (no URL / invalid prefix / happy-path body shape /
api-error-code surfacing), ParseWebhook (handshake + token mismatch +
text message + open_id fallback + non-message + non-text + token mismatch
+ malformed JSON + malformed content + empty text), StartPolling no-op,
registry presence.

Also: make migration 023 idempotent (ADD COLUMN IF NOT EXISTS) — the
platform's migration runner has no schema_migrations tracking table, so
every .up.sql replays on every boot. Without IF NOT EXISTS the second
boot against an existing volume crashes with "column already exists".
Followup issue to be filed for proper migration tracking.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 07:10:58 -07:00
Molecule AI Backend Engineer
68c9b37048 feat(channels): add Slack adapter with webhook URL validation (#384)
Implement SlackAdapter satisfying the ChannelAdapter interface:
- ValidateConfig: rejects any webhook_url that doesn't start with
  https://hooks.slack.com/ — returns "invalid Slack webhook URL" so
  the handler surfaces 400 {"error":"invalid config: invalid Slack webhook URL"}
- SendMessage: HTTP POST JSON {"text":"..."} to the webhook URL with a
  10s timeout; rejects invalid-prefix URLs at send time too (defence in depth)
- ParseWebhook: handles both slash-command (form-encoded) and Events API
  (JSON) payloads; no-ops on url_verification and non-message events
- StartPolling: returns nil immediately (Slack doesn't support polling via
  Incoming Webhooks)

Register "slack" in the adapter registry. Twelve unit tests cover
Type/DisplayName, happy-path validation, every bad-URL variant (wrong scheme,
wrong host, SSRF lookalike, empty string), empty webhook in SendMessage,
StartPolling nil return, and registry lookup/listing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 11:14:31 +00:00
Hongming Wang
a205c92428
fix(security): scope PausePollersForToken to requesting workspace (closes #329)
CI 5/6 pass (E2E cancel = run-supersession pattern). Dev Lead review 04:21:  Approved. Fixes cross-tenant token exposure: PausePollersForToken now scoped to requesting workspace_id via SQL WHERE clause. Closes #329.
2026-04-15 21:22:50 -07:00
Hongming Wang
d85ee97472
fix(security): encrypt channel_config bot_token at rest (closes #319)
CI fully green. Dev Lead code review:  clean, all read/write paths verified, tests cover round-trip + idempotency + legacy plaintext. Closes #319.
2026-04-15 21:09:34 -07:00
Hongming Wang
24fec62d7f initial commit — Molecule AI platform
Forked clean from public hackathon repo (Starfire-AgentTeam, BSL 1.1)
with full rebrand to Molecule AI under github.com/Molecule-AI/molecule-monorepo.

Brand: Starfire → Molecule AI.
Slug: starfire / agent-molecule → molecule.
Env vars: STARFIRE_* → MOLECULE_*.
Go module: github.com/agent-molecule/platform → github.com/Molecule-AI/molecule-monorepo/platform.
Python packages: starfire_plugin → molecule_plugin, starfire_agent → molecule_agent.
DB: agentmolecule → molecule.

History truncated; see public repo for prior commits and contributor
attribution. Verified green: go test -race ./... (platform), pytest
(workspace-template 1129 + sdk 132), vitest (canvas 352), build (mcp).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:55:37 -07:00