fix: address all code review findings + remove exposed secrets

Code review fixes:
- 🟡 #1: Replace python3 with jq in Dockerfile template stages (~50MB → ~2MB)
- 🟡 #2: Add clone count verification to scripts/clone-manifest.sh
  (set -e + expected vs actual count check — fails build if any clone fails)
- 🟡 #3: Drop 'unsafe-eval' from CSP (not needed for Next.js production
  standalone builds, only dev mode). Updated test assertion.
- 🟡 #4: Remove broken pyproject.toml from workspace-template/ (it claimed
  to package as molecule-ai-workspace-runtime but the directory structure
  didn't match — the real package ships from the standalone repo)
- 🔵 #1: Add version-pinning TODO comment to manifest.json
- 🔵 #3: Add full repo URLs + test counts for SDK/MCP/CLI/runtime in CLAUDE.md

Security (GitGuardian alert):
- Removed Telegram bot token (8633739353:AA...) from template-molecule-dev
  pm/.env — replaced with ${TELEGRAM_BOT_TOKEN} placeholder
- Removed Claude OAuth token (sk-ant-oat01-...) from template-molecule-dev
  root .env — replaced with ${CLAUDE_CODE_OAUTH_TOKEN} placeholder
- Both tokens need immediate rotation by the operator

Tests: Platform middleware tests updated + all pass.
This commit is contained in:
Hongming Wang 2026-04-16 05:05:49 -07:00
parent 73865ee164
commit 510c40089f
8 changed files with 38 additions and 58 deletions

View File

@ -240,9 +240,11 @@ OPENAI_API_KEY=... bash scripts/test-team-e2e.sh # E2E: Multi-template
cd platform && go test -race ./... # 818 Go tests (handlers, registry, provisioner, CLI, delegation, org, channels, wsauth, middleware, scheduler, crypto, db — sqlmock + miniredis; +2 on 2026-04-15 tick-32 for YAML-injection runtime/model allowlist + TestSanitizeRuntime_Allowlist; +70 on 2026-04-15 overnight sweep across the security fix cluster; +6 on 2026-04-14 tick-8 for TestTenantGuard_*)
cd canvas && npm test # 482 Vitest tests (store, components, hydration, buildTree, secrets API, org template import, ConfirmDialog singleButton + 7 native-dialog replacements, WCAG critical batch, +12 on tick-32 for CookieConsent dialog + privacy-preserving default, +17 on tick-32 for PricingTable dispatch matrix + billing helper)
cd workspace-template && python -m pytest -v # 1179 pytest tests (adds platform_auth token store for Phase 30.1, memory_write activity logging, Hermes multi-provider registry, +10 on tick-32 for test_hermes_phase2_dispatch covering native Anthropic + native Gemini paths via auth_scheme dispatch; fixed env-var leak in test_hermes_providers fixture via snapshot/restore)
# SDK + MCP server tests now in standalone repos:
# github.com/Molecule-AI/molecule-sdk-python (pip install molecule-ai-sdk)
# github.com/Molecule-AI/molecule-mcp-server (npx @molecule-ai/mcp-server)
# SDK, MCP, CLI, and workspace runtime now in standalone repos:
# https://github.com/Molecule-AI/molecule-sdk-python pip install molecule-ai-sdk (132 tests)
# https://github.com/Molecule-AI/molecule-mcp-server npx @molecule-ai/mcp-server (97 tests)
# https://github.com/Molecule-AI/molecule-cli go install (Go TUI dashboard)
# https://github.com/Molecule-AI/molecule-ai-workspace-runtime pip install molecule-ai-workspace-runtime (shared adapter base)
```
### Integration Tests

View File

@ -1,4 +1,5 @@
{
"_comment": "TODO: pin refs to release tags (v1.0.0) before first customer deploy for reproducible builds. 'main' is OK while all repos are internal.",
"version": 1,
"plugins": [
{"name": "browser-automation", "repo": "Molecule-AI/molecule-ai-plugin-browser-automation", "ref": "main"},

View File

@ -12,7 +12,7 @@ RUN CGO_ENABLED=0 GOOS=linux go build -o /platform ./cmd/server
# Clone templates + plugins at build time from manifest.json
FROM alpine:3.20 AS templates
RUN apk add --no-cache git python3
RUN apk add --no-cache git jq
COPY manifest.json /manifest.json
COPY scripts/clone-manifest.sh /scripts/clone-manifest.sh
RUN chmod +x /scripts/clone-manifest.sh && /scripts/clone-manifest.sh /manifest.json /workspace-configs-templates /org-templates /plugins

View File

@ -35,7 +35,7 @@ RUN npm run build
# ── Stage 3: Clone templates + plugins from manifest.json ─────────────
FROM alpine:3.20 AS templates
RUN apk add --no-cache git python3
RUN apk add --no-cache git jq
COPY manifest.json /manifest.json
COPY scripts/clone-manifest.sh /scripts/clone-manifest.sh
RUN chmod +x /scripts/clone-manifest.sh && /scripts/clone-manifest.sh /manifest.json /workspace-configs-templates /org-templates /plugins

View File

@ -37,13 +37,12 @@ func SecurityHeaders() gin.HandlerFunc {
// Content-Type after Next() — but that's too late for headers.
//
// Simpler: apply a permissive CSP that allows Next.js to work.
// 'unsafe-inline' + 'unsafe-eval' are needed for Next.js dev
// hydration; in production Next.js uses nonces but we don't
// propagate them through the proxy. This is acceptable because
// the canvas is our own code, not user-generated content.
// 'unsafe-inline' is needed for Next.js standalone builds that
// inject inline scripts for hydration. 'unsafe-eval' was dropped
// after confirming production canvas renders without it.
c.Header("Content-Security-Policy",
"default-src 'self'; "+
"script-src 'self' 'unsafe-inline' 'unsafe-eval'; "+
"script-src 'self' 'unsafe-inline'; "+
"style-src 'self' 'unsafe-inline'; "+
"img-src 'self' data: blob:; "+
"connect-src 'self' ws: wss:; "+

View File

@ -56,7 +56,7 @@ func TestSecurityHeaders(t *testing.T) {
csp := w.Header().Get("Content-Security-Policy")
for _, fragment := range []string{
"default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval'",
"script-src 'self' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: blob:",
"connect-src 'self' ws: wss:",

View File

@ -1,12 +1,11 @@
#!/usr/bin/env bash
# clone-manifest.sh — clone all repos listed in manifest.json into their
# target directories. Replaces 33 hardcoded git-clone lines in Dockerfiles.
# target directories. Replaces hardcoded git-clone lines in Dockerfiles.
#
# Usage:
# ./scripts/clone-manifest.sh <manifest.json> <ws-templates-dir> <org-templates-dir> <plugins-dir>
#
# Example (Docker build stage):
# /scripts/clone-manifest.sh /manifest.json /workspace-configs-templates /org-templates /plugins
# Requires: git, jq (lighter than python3 — ~2MB vs ~50MB in Alpine)
set -euo pipefail
@ -15,26 +14,34 @@ WS_DIR="${2:?Missing workspace-templates dir}"
ORG_DIR="${3:?Missing org-templates dir}"
PLUGINS_DIR="${4:?Missing plugins dir}"
EXPECTED=0
CLONED=0
clone_category() {
local category="$1"
local target_dir="$2"
mkdir -p "$target_dir"
# Use python3 to parse JSON (jq may not be available in Docker)
python3 -c "
import json, sys
with open('$MANIFEST') as f:
m = json.load(f)
for entry in m.get('$category', []):
print(entry['name'], entry['repo'], entry.get('ref', 'main'))
" | while read -r name repo ref; do
local count
count=$(jq -r ".${category} | length" "$MANIFEST")
EXPECTED=$((EXPECTED + count))
local i=0
while [ "$i" -lt "$count" ]; do
local name repo ref
name=$(jq -r ".${category}[$i].name" "$MANIFEST")
repo=$(jq -r ".${category}[$i].repo" "$MANIFEST")
ref=$(jq -r ".${category}[$i].ref // \"main\"" "$MANIFEST")
echo " cloning $repo -> $target_dir/$name (ref=$ref)"
if [ "$ref" = "main" ]; then
git clone --depth=1 -q "https://github.com/${repo}.git" "$target_dir/$name"
else
git clone --depth=1 -q --branch "$ref" "https://github.com/${repo}.git" "$target_dir/$name"
fi
CLONED=$((CLONED + 1))
i=$((i + 1))
done
# Strip .git dirs to save space
@ -50,4 +57,10 @@ clone_category "org_templates" "$ORG_DIR"
echo "==> Cloning plugins..."
clone_category "plugins" "$PLUGINS_DIR"
echo "==> Done. $(find "$WS_DIR" "$ORG_DIR" "$PLUGINS_DIR" -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ') repos cloned."
# Verify all repos were cloned
if [ "$CLONED" -ne "$EXPECTED" ]; then
echo "::error::Expected $EXPECTED repos but only cloned $CLONED — some clones failed"
exit 1
fi
echo "==> Done. $CLONED/$EXPECTED repos cloned successfully."

View File

@ -1,35 +0,0 @@
[build-system]
requires = ["setuptools>=68.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "molecule-ai-workspace-runtime"
version = "0.1.0"
description = "Molecule AI workspace runtime — shared infrastructure for all agent adapters"
requires-python = ">=3.11"
license = {text = "BSL-1.1"}
readme = "README.md"
# Don't pin heavy deps — each adapter adds its own
dependencies = [
"a2a-sdk[http-server]>=0.3.25",
"httpx>=0.27.0",
"uvicorn>=0.30.0",
"starlette>=0.38.0",
"websockets>=12.0",
"pyyaml>=6.0",
"langchain-core>=0.3.0",
"opentelemetry-api>=1.24.0",
"opentelemetry-sdk>=1.24.0",
"opentelemetry-exporter-otlp-proto-http>=1.24.0",
"temporalio>=1.7.0",
]
[project.scripts]
molecule-runtime = "molecule_runtime.main:main_sync"
[tool.setuptools.packages.find]
where = ["."]
include = ["molecule_runtime*"]
[tool.setuptools.package-data]
"molecule_runtime" = ["py.typed"]