Commit Graph

398 Commits

Author SHA1 Message Date
Research Lead
fe6e3032a4 chore(eco-watch): 2026-04-17 survey — GenericAgent + OpenSRE
Add two new entries to docs/ecosystem-watch.md:

- **GenericAgent** (lsdefine/GenericAgent, ~2.1k , MIT, v1.0 January
  2026): self-evolving skill tree with a four-tier memory hierarchy
  (rules/indices/facts/skills/archives). Skill crystallisation at
  runtime is the automation of our install-time plugins model. Filed
  #361 to add named memory tiers to agent_memories.

- **OpenSRE** (Tracer-Cloud/opensre, ~900 , Apache 2.0): AI SRE
  agent toolkit with 40+ production DevOps integrations and MCP
  support. Filed #362 to evaluate its adapters as a Molecule AI
  DevOps workspace skill pack.

HEAD at survey time: 93fd546

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 05:11:01 +00:00
Hongming Wang
93fd5467e2
Merge pull request #360 from Molecule-AI/chore/issue-358-wsauth-dead-constants
All CI green. Removes dead constants and stale comment left over from PR #357 grace-period test deletion (closes #358).
2026-04-15 22:05:37 -07:00
PM Bot
e257cd80d4 chore(test): remove dead constants from wsauth_middleware_test.go (#358)
PR #357 deleted the grace-period tests that used hasLiveTokenQuery and
workspaceExistsQuery, but the constants themselves (and the stale comment
describing the old HasAnyLiveToken-based dispatch) were not removed.

Remove both dead const declarations and update the header comment to
reflect the strict-enforcement contract introduced by #357.

Closes #358.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 05:02:11 +00:00
Hongming Wang
4e514aa59a
Merge pull request #357 from Molecule-AI/fix/issue-351-remove-tokenless-grace-period
All CI green. Merges strict WorkspaceAuth — removes tokenless grace period that enabled zombie workspace enumeration (#351).
2026-04-15 21:57:17 -07:00
Hongming Wang
fa239217a0 fix(security): remove WorkspaceAuth tokenless grace period (#351)
Severity HIGH. #318 closed the fake-UUID fail-open for WorkspaceAuth
but left the grace period intact for *real* workspaces with no live
tokens. Zombie test-artifact workspaces from prior DAST runs still
exist in the DB with empty configs and no tokens, so they pass
WorkspaceExists=true but HasAnyLiveToken=false — and fell through the
grace period, leaking every global-secret key name to any
unauthenticated caller on the Docker network.

Phase 30.1 shipped months ago; every production workspace has gone
through multiple boot cycles and acquired a token since. The
"legacy workspaces grandfathered" window no longer serves legitimate
traffic. Removing it entirely is the cleanest fix — and does NOT
affect registration (which is on /registry/register, outside this
middleware's scope).

New contract (strict):

  every /workspaces/:id/* request MUST carry
  Authorization: Bearer <token-for-this-workspace>

Any missing/mismatched/revoked/wrong-workspace bearer → 401. No
existence check, no fallback. The wsauth.WorkspaceExists helper is
kept in the package for any future caller but no longer used here.

Tests:
- TestWorkspaceAuth_351_NoBearer_Returns401_NoDBCalls — new, covers
  fake UUID / zombie / pre-token in one sub-table. Asserts zero DB
  calls on missing bearer.
- Existing C4/C8 + #170 tests updated to drop the stale
  HasAnyLiveToken sqlmock expectations.
- Renamed TestWorkspaceAuth_Issue170_SecretDelete_FailOpen_NoTokens
  to _NoTokensStillRejected and flipped the assertion from 200 to 401.
- Dropped TestWorkspaceAuth_318_ExistsQueryError_Returns500 — the
  code path it covered no longer exists.

Full platform test sweep green.

Closes #351

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:52:44 -07:00
Hongming Wang
75146f4314
Merge pull request #350 from Molecule-AI/chore/eco-watch-2026-04-16b
chore(eco-watch): 2026-04-16b survey — AgentScope + Plannotator
2026-04-15 21:47:50 -07:00
Research Lead
6be5d09764 chore(eco-watch): 2026-04-16b survey — AgentScope + Plannotator
Add two new entries to docs/ecosystem-watch.md:

- **AgentScope** (modelscope/agentscope, ~23.8k , Apache 2.0,
  v1.0.18 March 26 2026): Alibaba/ModelScope multi-agent framework
  with MCP support, MsgHub typed routing, and OpenTelemetry
  observability. No canvas or workspace lifecycle — framework-layer
  complement, not a platform competitor.

- **Plannotator** (backnotprop/plannotator, ~4.3k , Apache 2.0+MIT,
  v0.17.10 April 13 2026): Browser-based agent plan annotation tool
  with structured feedback types (delete/insert/replace/comment).
  Directly informs our hitl.py feedback schema. Filed #349 to add
  structured feedback types to resume_task.

HEAD at survey time: 4196876

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 04:40:51 +00:00
Hongming Wang
4196876c2b
Merge pull request #346 from Molecule-AI/chore/issue-342-auditor-prompt-drift
chore(auditor): close #319 + #337 prompt drift on Security Auditor (#342)
2026-04-15 21:31:06 -07:00
Hongming Wang
c5d40b861b
Merge pull request #343 from Molecule-AI/fix/issue-337-webhook-secret-constant-time
fix(security): constant-time webhook_secret comparison (#337)
2026-04-15 21:31:02 -07:00
Hongming Wang
af3d9904e1
Merge pull request #341 from Molecule-AI/fix/publish-platform-image-keychain-again
fix(ci): disable osxkeychain credsStore on self-hosted runner (#199 follow-up)
2026-04-15 21:30:59 -07:00
Hongming Wang
e7bde9a919
Merge pull request #338 from Molecule-AI/fix/issue-328-transcript-fail-closed
fix(security): /transcript fails closed when auth token missing (#328)
2026-04-15 21:30:56 -07:00
Hongming Wang
6b153ca3cb chore(auditor): close #319 + #337 prompt drift on Security Auditor (#342)
Two recent platform-level security changes (#319 channel_config
encryption, #337 constant-time webhook_secret compare) were not
reflected in the Security Auditor's system prompt or the schedule cron
prompt. That meant the auditor wouldn't proactively look for the
*next* instance of either class — a new credential field added to
channel_config without being added to sensitiveFields, or a new
secret comparison using raw `!=`, would slip through until a human
happened to notice.

Updated two files:

1. org-templates/molecule-dev/security-auditor/system-prompt.md
   Added two bullets to "What You Check":
   - Secret comparisons must use subtle.ConstantTimeCompare /
     crypto.timingSafeEqual (cites #337 as the repo's recent instance)
   - Secret storage at rest: any new channel_config credential field
     must be added to sensitiveFields and exercised in both the
     Encrypt (write) and Decrypt (read) boundary helpers, and the
     ec1: prefix must never leak into API responses (cites #319)

2. org-templates/molecule-dev/org.yaml
   Same two checks added to the Security Auditor's 12-hour cron
   prompt's "MANUAL REVIEW of every changed file" section. Wording
   is concrete enough to paste into a grep: "flag any `!=` / `==` /
   bytes.Equal against a user-supplied value that gates auth".

Pure config / prompt — no code changes, no tests to write. YAML parse
verified, TestPlugins_UnionWithDefaults still passes.

Closes #342

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:24:34 -07:00
Hongming Wang
50819500f0 fix(security): constant-time webhook_secret comparison (#337)
Severity LOW. The /webhooks/:type handler compared the Telegram
X-Telegram-Bot-Api-Secret-Token header against the decrypted
webhook_secret using Go's `!=` operator, which short-circuits on the
first mismatched byte. Under low-latency Docker-network conditions an
attacker could time response latency byte-by-byte and converge on the
real secret, then inject Telegram-formatted messages into any channel.

Fix: switch to crypto/subtle.ConstantTimeCompare, which runs in time
proportional to the length of the shorter input regardless of content
match. Same posture as the cdp-proxy token compare in host-bridge
(which already used timingSafeEqual).

Risk profile over the public internet is low (Telegram webhooks have
natural jitter that masks the signal), but the defensive pattern
matters for consistency across all secret comparisons.

Closes #337

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:23:12 -07: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
12dc0ebdf2
chore(eco-watch): 2026-04-16 daily survey — Gemini CLI + open-multi-agent
CI fully green. Dev Lead review:  Approved. Docs-only: adds Gemini CLI and open-multi-agent entries to ecosystem-watch.md; files issues #332 (gemini-cli adapter) and #333 (PM goal-decomp skill).
2026-04-15 21:22:37 -07:00
Hongming Wang
8ad8ae1077 fix(ci): explicitly disable osxkeychain credsStore for self-hosted runner
#273 tried to fix the macOS Keychain -25308 error by pointing
DOCKER_CONFIG at a per-run temp dir with `{"auths": {}}`. That was
necessary but not sufficient: Docker on macOS inherits `osxkeychain` as
the default credsStore even when config.json doesn't declare one
(comes from Docker Desktop's bundled binding), so the login-action
still tried to call /usr/local/bin/docker-credential-osxkeychain which
fails with -25308 from the non-interactive launchd session.

Evidence: after #273, publish-platform-image still failed on every
main merge with:

  error saving credentials: error storing credentials - err: exit
  status 1, out: `User interaction is not allowed. (-25308)`

Fix: write a config.json that explicitly sets `credsStore: ""` and
clears `credHelpers`, forcing Docker to store creds in the inline
`auths` map of this disposable config.json instead of reaching for
the keychain. Also print config.json at diagnostic time so a future
regression surfaces in the log instead of at login.

No runtime / test impact — this only changes what the runner writes
to the workflow's temp DOCKER_CONFIG directory.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:20:06 -07:00
Hongming Wang
c11d8f3ec3
fix(security): hitl task-id ownership + wire fail_open_if_no_scanner in loader (closes #265, #268)
Security audit cycle 13: hitl.py LGTM (workspace-scoped task IDs). Loader.py fix applied (commit 0557f73): fail_open_if_no_scanner now read from config and forwarded to scan_skill_dependencies(); regression test added. CI 5/6 pass (E2E cancel = run-supersession pattern). Closes #265. Closes #268.
2026-04-15 21:18:52 -07:00
Hongming Wang
5eb08332ee fix(security): /transcript endpoint fails closed when auth token missing (#328)
Severity HIGH. The /transcript route in main.py used `if expected:`
around the bearer-token compare, so `get_token()` returning None (no
/configs/.auth_token on disk — bootstrap window, deleted file, OSError)
silently skipped the entire auth check. Any container on
molecule-monorepo-net could GET /transcript during the provisioning
window and walk away with the full session log (user messages, Claude
tool calls, assistant replies).

The platform's TranscriptHandler always has a valid token (it acquired
one at workspace registration), so tightening this gate has no
legitimate-caller impact. Only unauthenticated sniffers lose access,
which was never the intended contract of #287.

Fix:

1. Extracted the auth gate into `workspace-template/transcript_auth.py`
   — a 20-line module with no heavy imports so the security-critical
   code is unit-testable without standing up the full uvicorn/a2a/httpx
   stack (the former inline guard could only be tested end-to-end,
   which explains why the regression shipped in #287).

2. `transcript_authorized(expected, auth_header)` returns False when
   `expected` is None or empty — the #328 fix — and otherwise does
   strict equality against "Bearer <expected>".

3. main.py's inline handler calls the extracted function:
     if not _transcript_authorized(get_token(), auth_header):
         return 401

4. New tests/test_transcript_auth.py covers: None token, empty token,
   valid bearer, wrong bearer, missing header, case-sensitive prefix,
   whitespace fuzzing. All 7 pass.

Closes #328

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:17:37 -07:00
Hongming Wang
d3a7e4c8f9
chore(org): wire molecule-compliance + molecule-audit + molecule-freeze-scope (closes #322)
Config-only YAML. CI green on all 6 checks (E2E cancel = run-supersession pattern). Adds missing plugin wiring: Security Auditor→compliance+audit, Backend→compliance, QA→compliance, DevOps→freeze-scope. Closes #322.
2026-04-15 21:13:26 -07:00
Hongming Wang
75dee70027
docs(glossary): add terminology disambiguation table (closes #320)
CI fully green (all 6 checks pass). Docs-only: adds docs/glossary.md, links from README.md and CLAUDE.md. Closes #320.
2026-04-15 21:13:04 -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
5c3aac11e3
fix(security): close WorkspaceAuth fail-open on non-existent workspace IDs (#318)
CI fully green. Security Audit cycle 15 LGTM. Closes #318. Closes #325.
2026-04-15 21:02:29 -07:00
Hongming Wang
4d7b1f56de
chore(template): widen idle-loop to Market Analyst + Competitive Intelligence (wave 2)
Expands autonomous orchestration reach to Market Analyst and Competitive Intelligence roles.
2026-04-15 20:29:41 -07:00
Hongming Wang
3252af6ea6
fix(template): Telegram channel for Security Auditor + DevOps Engineer (#246 #247)
Closes #246
Closes #247

Critical security findings and CI build-break alerts are now pushed via Telegram instead of waiting for someone to manually check memory/logs.
2026-04-15 19:57:34 -07:00
Hongming Wang
17b9263167
Merge pull request #314 from Molecule-AI/fix/issue-310-llm-judge-be-fe
feat(template): add molecule-skill-llm-judge to Backend + Frontend Engineer (#310)
2026-04-15 19:51:00 -07:00
Hongming Wang
ac8daf2f70 feat(template): add molecule-skill-llm-judge to Backend + Frontend Engineer (#310)
Backend Engineer and Frontend Engineer were missing molecule-skill-llm-judge
while Dev Lead, QA Engineer, and Security Auditor already have it.

llm-judge lets engineers self-gate their PR against the issue body before
requesting review, catching 'shipped the wrong thing' before Dev Lead sees it.
No new plugins needed — already installed org-wide.

Closes #310
2026-04-16 02:48:08 +00:00
Hongming Wang
fec287fce3
fix(security): add bearer token auth to /transcript endpoint (#287)
Closes #287

Any container on molecule-monorepo-net could previously read the full Claude session log without authentication. Guard uses get_token() from platform_auth — skipped only before workspace registration (dev-mode).
2026-04-15 19:47:23 -07:00
airenostars
af95a6eb78
feat(reno-stars): citation-builder — one backlink directory per day (#299)
Closes #301

Co-authored-by: airenostars <noreply@github.com>
2026-04-15 19:47:20 -07:00
Hongming Wang
8fc4940798
Merge pull request #308 from Molecule-AI/fix/uiux-cron-cadence-hourly
fix(template): UIUX Designer cron from 15min to hourly (#306)
2026-04-15 19:22:29 -07:00
Hongming Wang
ece45bbf45 fix(template): UIUX Designer cron from 15min to hourly (#306)
Closes #306. The cron expression was "5,20,35,50 * * * *" (every 15
min = 96 ticks/day) despite the schedule being named "Hourly UI/UX
audit". Each tick launches Chromium, takes 8 screenshots, runs them
through Claude vision, and delegates to PM — 768 vision calls/day
from one workspace with no meaningful delta between ticks (canvas UI
only changes on deploys).

Changed to "5 * * * *" (hourly, at :05 past the hour). 6x reduction
in cost + noise.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 19:22:19 -07:00
Hongming Wang
5c4146e09c
Merge pull request #307 from Molecule-AI/fix/backend-engineer-security-scan
feat(template): add molecule-security-scan to Backend Engineer (#303)
2026-04-15 19:21:19 -07:00
Hongming Wang
d9065bcc4d feat(template): add molecule-security-scan to Backend Engineer (#303)
Closes #303. Surfaces CVE/secret scanning at dev time instead of
waiting for the Security Auditor's 12h cron. Backend Engineer's
plugin list: [molecule-hitl, molecule-skill-code-review,
molecule-security-scan].

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 19:21:11 -07:00
Hongming Wang
e88ae9f6d0
fix(a2a-tools): auth_headers on recall_memory + commit_memory (#304)
Adds auth_headers to recall_memory and commit_memory in a2a_tools.py. Fixes the #215-class auth regression for A2A memory tools. Test mocks updated to accept headers kwarg.
2026-04-15 19:12:18 -07:00
Hongming Wang
f28bba0321
Merge pull request #297 from Molecule-AI/fix/cdp-plist-chmod-600
fix(security): chmod 600 macOS launchd plist (#296)
2026-04-15 18:20:55 -07:00
Hongming Wang
009769e263 fix(security): chmod 600 macOS launchd plist containing CDP token (#296)
One-liner oversight from #295: the macOS install path wrote the plist
with the default umask (~0644), leaving CDP_PROXY_TOKEN world-readable
to any local user account. The Linux path already writes to a chmod
600 env-file — this brings macOS to parity.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 18:20:48 -07:00
Hongming Wang
5ba54ba574
Merge pull request #295 from Molecule-AI/fix/cdp-proxy-bind-localhost
fix(security): token-auth on cdp-proxy to prevent LAN exposure (#293)
2026-04-15 18:00:30 -07:00
Hongming Wang
c0be9baab1 fix(security): token-auth on cdp-proxy to prevent LAN exposure (#293)
HIGH finding from security-auditor on PR #291 (merged tick-37). The
cdp-proxy bound to 0.0.0.0:9223 with no authentication, exposing
Chrome DevTools Protocol — full remote control of any tab, including
cookie/localStorage exfiltration — to anyone on the same WiFi/LAN.

Root cause: Docker Desktop on macOS routes host.docker.internal
through the VM network interface, not loopback. Binding to 127.0.0.1
would break the primary use case (containers reaching the host
Chrome). The design trade was "bind wide for reachability, accept LAN
exposure" — #293 makes that trade unacceptable.

Fix: bearer token auth on every HTTP + WebSocket request. The proxy
REFUSES TO START without a token — no unauth mode.

Three-file change:

1. cdp-proxy.cjs
   - Read token from CDP_PROXY_TOKEN env OR ~/.molecule-cdp-proxy-token
   - Fail loudly if neither is set (exit 1 with install-host-bridge.sh
     pointer)
   - Validate X-CDP-Proxy-Token header via crypto.timingSafeEqual on
     every HTTP request AND every WS upgrade
   - Strip the header before forwarding to Chrome (defense in depth —
     token never leaks into Chrome's request log)

2. install-host-bridge.sh
   - New ensure_token() function generates a 64-char hex token via
     openssl rand -hex 32 (fallback to /dev/urandom). Written to
     ~/.molecule-cdp-proxy-token with chmod 600.
   - macOS: token injected into launchd plist EnvironmentVariables
   - Linux: written to ~/.molecule-cdp-proxy.env (chmod 600) and
     referenced via systemd EnvironmentFile — avoids embedding the
     token in the often world-readable unit file
   - Install reuses existing token if present (16+ chars); uninstall
     preserves token file so a reinstall keeps the same token
   - Verify command now includes the token header
   - Documents container-side bind-mount pattern
     (-v ~/.molecule-cdp-proxy-token:/run/secrets/cdp-proxy-token:ro)

3. lib/connect.js
   - New loadProxyToken() with precedence: env var >
     /run/secrets/cdp-proxy-token > ~/.molecule-cdp-proxy-token
   - Attaches X-CDP-Proxy-Token header on both /json/version probe +
     final puppeteer.connect() call via headers: {} option
     (puppeteer-core v21+ supports this natively)
   - Host-direct fallback (CDP port 9222 on loopback) unchanged —
     Chrome's own port is loopback-only so it doesn't need the token

Attack surface now:
  - LAN attacker must also steal the token file from the user's home
    directory (requires shell access) OR the env var (requires
    launchd/systemd process inspection as the same user) — reduces to
    local-privilege-escalation territory
  - Containers on the same Docker network still have access (they
    mount the token by design) — intentional, any workspace-template
    install already runs inside the platform's trust boundary

Not fixing in this PR:
  - Rate limiting on /json/version (low priority — probe-and-mine is
    expensive even without)
  - IP allowlist on top of token auth (diminishing returns)
  - Rotating the token periodically (user can rm ~/.molecule-cdp-proxy-token
    and reinstall)

Closes #293.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 18:00:02 -07:00
Hongming Wang
004f418d36
Merge pull request #271 from Molecule-AI/fix/seo-builder-delegate-code-blockers
fix(reno-stars): SEO Builder delegates code blockers to Dev Leader, not human
2026-04-15 17:56:09 -07:00
Hongming Wang
472495c380
Merge pull request #270 from Molecule-AI/feat/workspace-transcript-endpoint
feat: GET /workspaces/:id/transcript — live agent session log
2026-04-15 17:55:41 -07:00
Hongming Wang
bd51ea6190
Merge pull request #292 from Molecule-AI/feat/reno-stars-social-publish-helpers
feat(reno-stars): social-publish skill with 7 battle-tested helpers
2026-04-15 17:53:58 -07:00
Hongming Wang
8dc833f306
Merge pull request #291 from Molecule-AI/feat/browser-automation-cdp-proxy-bundled
feat(browser-automation): bundle host-bridge CDP proxy for portable Chrome access
2026-04-15 17:53:31 -07:00
airenostars
f2ab9eb924 fix(reno-stars): SEO Builder delegates code blockers to Dev Leader, not human
Issue surfaced in SEO Builder Run 10 (2026-04-15):
- Marketing Leader found 2 code-level metadata blockers
  (white-rock page.tsx override + en.json description >160c)
- Telegram report listed them under "⚠️ ACTION ITEMS (human)"
- User: "it should automatically report to dev team instead of
  just asking CEO to do it"

Fix: when seo-builder finds a code-level blocker it can't fix via
DB, it delegates to the Dev Leader sibling workspace via A2A instead
of flagging for human. Only genuine human actions (Yelp email
verification, Google account-linked operations) stay in the human
bucket.

Also clarify marketing-leader/CLAUDE.md so the "DO NOT DELEGATE"
rule doesn't accidentally block this pattern — it's now explicit
that sibling handoff for scope mismatches is allowed (as opposed
to delegating down the hierarchy to spawn sub-agents, which stays
forbidden).
2026-04-15 17:47:27 -07:00
airenostars
66b8cbb7fa fix(transcript): validate workspace URL to prevent SSRF (#272)
`TranscriptHandler.Get` previously proxied `agent_card->>'url'` directly
to the outbound HTTP client with no validation. Since `agent_card` is
attacker-writable via /registry/register, a workspace-token holder
could point it at cloud metadata (169.254.169.254), link-local ranges,
or non-http schemes and pivot the platform container against internal
services (IMDS, Redis, Postgres, other containers on the Docker net).

Four required fixes per reviewer:

1. `validateWorkspaceURL(u *url.URL)` — runs before `httpClient.Do`:
   - scheme must be http/https (rejects file://, gopher://, ftp://)
   - cloud metadata hostname blocklist (GCP + Azure + plain "metadata")
   - IMDS IP blocklist (169.254.169.254)
   - IPv4/IPv6 link-local blocklist (169.254/16, fe80::/10, multicast)
   - IPv6 unique-local fd00::/8 blocklist
   - loopback + docker.internal still allowed for local dev

2. Query-param allowlist — `target.RawQuery = c.Request.URL.RawQuery`
   forwarded everything verbatim, letting a caller smuggle params the
   upstream transcript endpoint didn't intend to expose. Replaced with
   an allowlist of `since` and `limit`.

3. Sanitized error string — `fmt.Sprintf("workspace unreachable: %v", err)`
   leaked the actual internal host/IP via `net.OpError`. Now logs the
   real error server-side and returns a plain "workspace unreachable"
   to the caller.

4. 10 new regression test cases:
   - `TestTranscript_Rejects{CloudMetadataIP,NonHTTPScheme,MetadataHostname,LinkLocalIPv6}`
     exercise the handler end-to-end with each attack URL and assert
     400 before the HTTP client fires.
   - `TestValidateWorkspaceURL` table-drives the validator across
     localhost/public/docker-internal (allowed) + IMDS/GCP/Azure/file/
     gopher/link-local/multicast (rejected).
   - `TestTranscript_ProxyPropagatesAllowlistedQueryParams` asserts
     `secret=leak&cmd=rm` is stripped while `since=42&limit=7` pass
     through.

Also fixed a pre-existing test bug: `seedWorkspace` was issuing a real
SQL Exec against sqlmock with no expectation set, so the prior test
helpers silently failed in CI. Replaced with `expectWorkspaceURLLookup`
which programs the mock correctly. All 11 tests now pass.

Closes #272

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 17:46:55 -07:00
airenostars
f6922d9cb5 feat(reno-stars): social-publish skill with 7 battle-tested helpers
Add a new `social-publish` skill under the Marketing Leader template
containing verbatim copies of 7 puppeteer-core helper scripts that reliably
publish video posts to Facebook, Instagram, X, LinkedIn, TikTok, YouTube,
and Google Business Profile. Each helper encapsulates hours of debugging
from the 2026-04-15 incident (Lexical editor mirror selection, FB Reel
Next-button disambiguation, post-publish upsell dismissal, TikTok
beforeunload race, GBP iframe scoping, etc).

Rewrite the existing social-media-poster / monitor / engage skills to
delegate publishing to these helpers instead of freestyling puppeteer
per run. Mirror the same delegation note into the social-media-specialist
skill copies so both the Marketing Leader and its specialist agent follow
the same rule.

Not implemented as a platform plugin: the helpers are dom-specific to
Reno Stars Chrome sessions (profile path, account IDs, hardcoded URLs)
and belong in org-template content rather than a generic platform
capability.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 17:34:13 -07:00
airenostars
ff19c2ce26 feat(browser-automation): bundle host-bridge CDP proxy + connect helper
The plugin now ships everything a user needs to wire Chrome on their
host to workspaces inside Docker:

- host-bridge/cdp-proxy.cjs — rewrites the Host header so Chrome accepts
  DevTools Protocol connections from container-originated traffic, and
  forwards both HTTP (tab list, screenshots) and WebSocket upgrades.

- host-bridge/install-host-bridge.sh — one-command install on macOS
  (launchd user agent) or Linux (systemd --user unit). `uninstall`
  subcommand cleans up. No root required.

- skills/browser-automation/lib/connect.js — the mandatory helper
  consumers already use; re-exported here so the plugin is self-contained.

- SKILL.md — documents the one-time host setup and the existing
  defaultViewport:null + disconnect-not-close rules. The 2026-04-15
  social-media-poster incident (3h debug chasing phantom "sessions
  expired" errors on an 800x600 viewport) is captured inline.

Smoke-tested on macOS: install script registered the agent, proxy
listens on 0.0.0.0:9223, and a live workspace container
(ws-bee4d521-3d3) successfully reached Chrome via
host.docker.internal:9223.

This replaces ad-hoc per-user CDP proxies and makes the plugin
usable by any Molecule operator, not just the Reno Stars org.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 17:29:46 -07:00
Hongming Wang
7720489df9
Merge pull request #290 from Molecule-AI/chore/ci-e2e-api-concurrency-group
chore(ci): serialize e2e-api across runs to prevent docker collision
2026-04-15 17:29:40 -07:00
Hongming Wang
b231449eb4
Merge pull request #285 from Molecule-AI/fix/memory-tools-auth-headers
fix(memory-tools): #215-class — auth_headers on commit_memory + search_memory HTTP fallback
2026-04-15 17:29:24 -07:00
Hongming Wang
469d24c23a fix(tests): update memory fakes for auth_headers kwarg + activity overwrite
The #215-class fix in memory.py (859a60e) adds headers=_headers to the
direct-httpx commit_memory + search_memory paths, but 9 existing tests
in test_memory.py had FakeAsyncClient.post/get signatures like
`async def post(self, url, json):` with no headers kwarg. Python
raised TypeError: unexpected keyword argument 'headers' on every call,
commit_memory caught it and returned {success: False}, tests failed.

Fixes applied:

1. Add `headers=None` to every FakeAsyncClient.post + .get signature
   across test_memory.py. Uses replace_all so all 9+ fakes match.

2. For tests that capture a single captured["url"]:
   - test_commit_memory_uses_awareness_client_when_configured
   - test_commit_memory_uses_platform_fallback_without_awareness
   - test_commit_memory_httpx_201_success
   filter to only capture /memories URLs. Without the filter, the
   subsequent _record_memory_activity fire-and-forget post to /activity
   overwrites captured["url"] and the assertion fails.

3. For test_commit_memory_promoted_packet_logs_skill_promotion: bump
   expected captured["calls"] from 3 to 4. Pre-fix, the memory_write
   /activity call (from _record_memory_activity #125) was silently
   dropped because the fake rejected headers=; post-fix it succeeds
   and lands in the captured list alongside the skill_promotion
   /activity and /registry/heartbeat calls. Also extend that test's
   fake to accept /registry/heartbeat (was raising AssertionError).

Total: 36/36 memory tests pass. Full workspace-template suite 1189/1189.

This is strictly test-infrastructure work — zero production code
changed. CI never caught the break because the Mac mini runner has
been stuck for ~4 hours (tick-33/34/35/36 reports).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 17:29:15 -07:00
rabbitblood
48eca7264c fix(memory-tools): #215-class — auth_headers on commit_memory + search_memory HTTP fallback
Context: platform now gates `GET /workspaces/:id/memories` and
`POST /workspaces/:id/memories` behind workspace auth (post-#166 /
#167 AdminAuth wave). The `builtin_tools.memory` tool had three HTTP
call sites:

  1. commit_memory POST fallback (line 121)        ← NO auth_headers
  2. search_memory GET fallback (line 269)         ← NO auth_headers
  3. activity-log helper POST (line 371)           ← HAS auth_headers

Path 3 was already fixed. Paths 1 + 2 silently 401 every call, but the
tool's error-handling path returns `{"success": False}` without surfacing
the auth failure to the agent. Result: the agent sees an empty memory
backlog on every call and assumes there's nothing to do.

## Discovered today

Technical Researcher is the first workspace opted in to the idle-loop
pilot from #216 (reflection-on-completion pattern). The pilot fires
every 10 min, the agent calls `search_memory "research-backlog:..."` as
the first step, gets back an empty result, writes "tr-idle clean" to
memory, and stops. Clean-idle outcome every tick, 9 consecutive ticks.

Looking at TR's activity_logs response bodies:

    "Memory auth has failed on every tick this session — skipping the call"
    "tr-idle — step 2 done. Memory unavailable (auth token missing..."
    "tr-idle 04:15 — clean (memory auth still down, 3rd consecutive tick)"

The AGENT knew the memory calls were failing. The platform 401 error
was surfacing in the tool response, but our instrumentation wasn't
counting it as a defect — we saw "tr-idle clean" writes and assumed
the pilot was working as designed. It was actually silently broken.

## Fix

Import `platform_auth.auth_headers` lazily (same pattern as the
activity-log path already uses), attach `headers=_auth()` to both
httpx call sites. Matches the #225 fix for the register call.

## Not in this PR

- awareness_client.py also makes HTTP calls to a separate AWARENESS_URL
  service (not the platform), which may or may not need the same fix
  depending on that service's auth posture. Out of scope for this PR.

- TR's specific token problem: TR's `/configs/.auth_token` file is
  empty because it was re-provisioned via `apply_template: true`
  (recovery path from the failed-volume incident) and Phase 30.1
  only mints a token on FIRST register per workspace. This fix
  doesn't help TR until TR gets a fresh token — tracked separately.

## Test plan

- [x] Python syntax check on memory.py passes
- [ ] CI: all memory-related tests should still pass (the new code
      paths only add header passing, no shape change)
- [ ] Real-world verification: after TR gets a fresh token, idle-loop
      pilot should produce a dispatch within 10 min (seeded backlog
      already in place from this session)

## Related
- #215 / #225 — register call auth_headers fix (same pattern)
- #216 — TR idle-loop pilot (couldn't measure until this lands)
- #166 / #167 — platform AdminAuth wave that surfaced this gap
2026-04-15 17:26:26 -07:00
Hongming Wang
f2457ac287 chore(ci): serialize e2e-api across runs to prevent docker collision
Now that the Molecule-AI org has two self-hosted Apple-silicon runners
(`hongming-m1-mini` + `hongming-m1-mini-2`) servicing the same label set,
two CI runs could execute the e2e-api job concurrently. Each run starts
fixed-name docker containers (`molecule-ci-postgres`, `molecule-ci-redis`)
bound to host ports 15432/16379 — a collision means the second run fails
with "container name already in use" or "port already in use".

Adds a workflow-level `concurrency: e2e-api` group to the job so GitHub
Actions serializes e2e-api executions globally regardless of which runner
picks them up. `cancel-in-progress: false` ensures later runs queue
rather than cancelling the in-flight one (we want every PR's e2e check
to actually execute, not get skipped by a newer push).

Tradeoff: e2e-api is now effectively single-threaded across the whole
org. Measured duration is ~1-2 min per run, so the added serialization
latency is small relative to total CI wall time. All other jobs still
parallelize across both runners.
2026-04-15 17:06:41 -07:00