Extend get_validated_workspace_id() to all remaining unguarded URL positions:
- consolidation.py: _consolidate() — validates before GET/POST/DELETE to
/workspaces/{id}/memories endpoints. Graceful skip on failure (log + return).
- coordinator.py: get_children() — validates before /registry/{id}/peers.
Graceful skip (empty list) on failure.
- molecule_ai_status.py: set_status() — validates before /registry/heartbeat
and /workspaces/{id}/activity. Exits with descriptive error on failure.
With these three, every runtime use of WORKSPACE_ID in a URL path is now
validated. Remaining WORKSPACE_ID uses are:
- JSON body fields (not injection-risky): heartbeat, memory POST bodies
- Header values (X-Workspace-ID): lower risk, non-URL-injection
83 lines
2.5 KiB
Python
83 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Update workspace task status on the canvas.
|
|
|
|
Usage (from any script, cron job, or shell inside the container):
|
|
|
|
# Set current task (shows on canvas card)
|
|
python3 /app/molecule_ai_status.py "Running weekly SEO audit..."
|
|
|
|
# Clear task (removes banner from canvas)
|
|
python3 /app/molecule_ai_status.py ""
|
|
|
|
# Or use the shell alias:
|
|
molecule-monorepo-status "Analyzing competitor data..."
|
|
molecule-monorepo-status ""
|
|
|
|
The status appears as an amber banner on the workspace card in the canvas,
|
|
visible to the project owner in real-time.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
import httpx
|
|
|
|
from builtin_tools.validation import WorkspaceIdValidationError, get_validated_workspace_id
|
|
|
|
WORKSPACE_ID = os.environ.get("WORKSPACE_ID", "")
|
|
PLATFORM_URL = os.environ.get("PLATFORM_URL", "http://platform:8080")
|
|
|
|
|
|
def set_status(task: str):
|
|
"""Push current_task to platform via heartbeat."""
|
|
try:
|
|
try:
|
|
from builtin_tools.platform_auth import auth_headers as _auth
|
|
_headers = _auth()
|
|
except Exception:
|
|
_headers = {}
|
|
|
|
# --- Workspace ID validation (CWE-20 / CWE-88) -----------------------
|
|
try:
|
|
ws_id = get_validated_workspace_id(caller="molecule_ai_status.set_status")
|
|
except WorkspaceIdValidationError as e:
|
|
sys.stderr.write(f"molecule_ai_status: {e}\n")
|
|
return
|
|
|
|
httpx.post(
|
|
f"{PLATFORM_URL}/registry/heartbeat",
|
|
json={
|
|
"workspace_id": ws_id,
|
|
"current_task": task,
|
|
"active_tasks": 1 if task else 0,
|
|
"error_rate": 0,
|
|
"sample_error": "",
|
|
"uptime_seconds": 0,
|
|
},
|
|
headers=_headers,
|
|
timeout=5.0,
|
|
)
|
|
if task:
|
|
# Also log as activity for traceability
|
|
httpx.post(
|
|
f"{PLATFORM_URL}/workspaces/{ws_id}/activity",
|
|
json={
|
|
"activity_type": "task_update",
|
|
"source_id": ws_id,
|
|
"summary": task,
|
|
"status": "ok",
|
|
},
|
|
timeout=5.0,
|
|
)
|
|
except Exception as e:
|
|
print(f"molecule-monorepo-status: failed to update: {e}", file=sys.stderr)
|
|
|
|
|
|
if __name__ == "__main__": # pragma: no cover
|
|
if len(sys.argv) < 2:
|
|
print("Usage: molecule-monorepo-status 'task description'")
|
|
print(" molecule-monorepo-status '' # clear")
|
|
sys.exit(1)
|
|
|
|
set_status(sys.argv[1])
|