molecule-ai-workspace-runtime/tests/test_workspace_id_validation.py
Molecule AI Infra-Runtime-BE d4b9bff5d0 fix(a2a_cli): validate WORKSPACE_ID in discover() before X-Workspace-ID header
PR #32 wrapped all platform URL construction sites with
get_validated_workspace_id() but missed a2a_cli.discover(), which
passed the raw unvalidated WORKSPACE_ID in the X-Workspace-ID header.
All other functions (peers, info) had try/except guards added.

discover() now calls get_validated_workspace_id() upfront and returns
None (printing the error) if validation fails — consistent with the
best-effort error handling pattern used elsewhere in the module.

Tests: 2 new cases in TestA2aCliDiscoverValidation covering empty
and slash-injected WORKSPACE_ID values.

Follow-up to: PR #32 (fix/908-add-namespace-param-commit-memory)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 01:35:37 +00:00

186 lines
7.8 KiB
Python

"""Regression tests for WORKSPACE_ID validation (issue #14, CWE-20)."""
import os
import re
import pytest
class TestValidateWorkspaceId:
"""validate_workspace_id() must reject injection characters."""
@pytest.fixture(autouse=True)
def _reset_cache(self, monkeypatch):
"""Clear module-level caches and env before each test."""
import molecule_runtime.platform_auth as pa_mod
pa_mod._validated_workspace_id = None
monkeypatch.delenv("WORKSPACE_ID", raising=False)
def test_rejects_empty_string(self, monkeypatch):
import molecule_runtime.platform_auth as pa_mod
monkeypatch.setenv("WORKSPACE_ID", "")
with pytest.raises(ValueError, match="empty"):
pa_mod.validate_workspace_id(os.environ["WORKSPACE_ID"])
def test_rejects_whitespace_only(self, monkeypatch):
import molecule_runtime.platform_auth as pa_mod
monkeypatch.setenv("WORKSPACE_ID", " ")
with pytest.raises(ValueError, match="invalid characters"):
pa_mod.validate_workspace_id(os.environ["WORKSPACE_ID"])
def test_rejects_slash(self, monkeypatch):
import molecule_runtime.platform_auth as pa_mod
monkeypatch.setenv("WORKSPACE_ID", "ws/foo")
with pytest.raises(ValueError, match="invalid characters"):
pa_mod.validate_workspace_id(os.environ["WORKSPACE_ID"])
def test_rejects_double_dot(self, monkeypatch):
import molecule_runtime.platform_auth as pa_mod
monkeypatch.setenv("WORKSPACE_ID", "ws..foo")
with pytest.raises(ValueError, match="invalid characters"):
pa_mod.validate_workspace_id(os.environ["WORKSPACE_ID"])
def test_rejects_hash_fragment(self, monkeypatch):
import molecule_runtime.platform_auth as pa_mod
monkeypatch.setenv("WORKSPACE_ID", "ws#foo")
with pytest.raises(ValueError, match="invalid characters"):
pa_mod.validate_workspace_id(os.environ["WORKSPACE_ID"])
def test_rejects_question_mark(self, monkeypatch):
import molecule_runtime.platform_auth as pa_mod
monkeypatch.setenv("WORKSPACE_ID", "ws?foo")
with pytest.raises(ValueError, match="invalid characters"):
pa_mod.validate_workspace_id(os.environ["WORKSPACE_ID"])
def test_rejects_backslash(self, monkeypatch):
import molecule_runtime.platform_auth as pa_mod
monkeypatch.setenv("WORKSPACE_ID", "ws\\foo")
with pytest.raises(ValueError, match="invalid characters"):
pa_mod.validate_workspace_id(os.environ["WORKSPACE_ID"])
def test_rejects_newline(self, monkeypatch):
import molecule_runtime.platform_auth as pa_mod
monkeypatch.setenv("WORKSPACE_ID", "ws\nfoo")
with pytest.raises(ValueError, match="invalid characters"):
pa_mod.validate_workspace_id(os.environ["WORKSPACE_ID"])
def test_accepts_valid_uuid(self, monkeypatch):
import molecule_runtime.platform_auth as pa_mod
monkeypatch.setenv("WORKSPACE_ID", "53255246-1f34-432c-87e5-ae07f888f905")
assert pa_mod.validate_workspace_id("53255246-1f34-432c-87e5-ae07f888f905") == "53255246-1f34-432c-87e5-ae07f888f905"
def test_accepts_simple_alphanumeric(self, monkeypatch):
import molecule_runtime.platform_auth as pa_mod
monkeypatch.setenv("WORKSPACE_ID", "test-workspace")
assert pa_mod.validate_workspace_id("test-workspace") == "test-workspace"
def test_rejects_uppercase(self, monkeypatch):
import molecule_runtime.platform_auth as pa_mod
monkeypatch.setenv("WORKSPACE_ID", "Test-Workspace")
with pytest.raises(ValueError, match="invalid characters"):
pa_mod.validate_workspace_id(os.environ["WORKSPACE_ID"])
def test_rejects_starts_with_hyphen(self, monkeypatch):
import molecule_runtime.platform_auth as pa_mod
monkeypatch.setenv("WORKSPACE_ID", "-test-workspace")
with pytest.raises(ValueError, match="invalid characters"):
pa_mod.validate_workspace_id(os.environ["WORKSPACE_ID"])
def test_cache_returns_same_result(self, monkeypatch):
"""Second call returns cached result without re-validation."""
import molecule_runtime.platform_auth as pa_mod
monkeypatch.setenv("WORKSPACE_ID", "my-workspace-123")
first = pa_mod.validate_workspace_id("my-workspace-123")
second = pa_mod.validate_workspace_id("my-workspace-123")
assert first == second == "my-workspace-123"
class TestWorkspaceIdConstantInApproval:
"""approval.py imports WORKSPACE_ID from platform_auth."""
def test_approval_imports_validated_id(self, monkeypatch):
"""approval.py uses platform_auth's validated WORKSPACE_ID constant."""
import molecule_runtime.builtin_tools.approval as approval_mod
from molecule_runtime.platform_auth import WORKSPACE_ID
# Verify it's the same object
assert approval_mod.WORKSPACE_ID is WORKSPACE_ID
# Verify the value contains no injection chars
assert "\n" not in approval_mod.WORKSPACE_ID
assert "/" not in approval_mod.WORKSPACE_ID
assert "\\" not in approval_mod.WORKSPACE_ID
assert ".." not in approval_mod.WORKSPACE_ID
class TestWorkspaceIdConstantInDelegation:
"""delegation.py imports WORKSPACE_ID from platform_auth."""
def test_delegation_imports_validated_id(self, monkeypatch):
"""delegation.py uses platform_auth's validated WORKSPACE_ID constant."""
import molecule_runtime.builtin_tools.delegation as delegation_mod
from molecule_runtime.platform_auth import WORKSPACE_ID
assert delegation_mod.WORKSPACE_ID is WORKSPACE_ID
assert "\n" not in delegation_mod.WORKSPACE_ID
assert "/" not in delegation_mod.WORKSPACE_ID
assert "\\" not in delegation_mod.WORKSPACE_ID
assert ".." not in delegation_mod.WORKSPACE_ID
class TestA2aCliDiscoverValidation:
"""a2a_cli.discover() validates WORKSPACE_ID before hitting the platform.
Since builtin_tools is only stubbed when the full test suite loads
(conftest must import other modules first to trigger the stubs), we
test the validation contract indirectly:
1. get_validated_workspace_id() raises WorkspaceIdValidationError for bad input
2. discover() is the only a2a_cli function that calls get_validated_workspace_id()
in the X-Workspace-ID header path — covered by (1)
"""
@pytest.fixture(autouse=True)
def _reset_cache(self, monkeypatch):
"""Clear validation caches and env before each test."""
import molecule_runtime.platform_auth as pa_mod
import molecule_runtime.builtin_tools.validation as val_mod
pa_mod._validated_workspace_id = None
val_mod._cached_workspace_id = None
val_mod._cached_validated = False
monkeypatch.delenv("WORKSPACE_ID", raising=False)
def test_validated_ws_id_raises_on_empty(self):
"""get_validated_workspace_id() must raise on empty WORKSPACE_ID."""
from molecule_runtime.builtin_tools.validation import (
WorkspaceIdValidationError,
get_validated_workspace_id,
)
with pytest.raises(WorkspaceIdValidationError, match="empty"):
get_validated_workspace_id(caller="test")
def test_validated_ws_id_raises_on_slash(self, monkeypatch):
"""get_validated_workspace_id() must raise when WORKSPACE_ID contains /."""
import molecule_runtime.builtin_tools.validation as val_mod
val_mod._cached_workspace_id = None
val_mod._cached_validated = False
monkeypatch.setenv("WORKSPACE_ID", "ws/foo")
from molecule_runtime.builtin_tools.validation import (
WorkspaceIdValidationError,
get_validated_workspace_id,
)
with pytest.raises(WorkspaceIdValidationError, match="invalid"):
get_validated_workspace_id(caller="test")