From cd3052fc8825fdc198f8a66bc8eb83e21c8c335a Mon Sep 17 00:00:00 2001 From: Bryant Liu Date: Wed, 8 Jul 2026 00:57:44 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E2=9C=A8=20(assembly):=20Add=20=5FAGENT=5F?= =?UTF-8?q?ID=5FRE=20regex=20for=20agent=5Fid=20validation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/core/assembly.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agent_assembly/core/assembly.py b/agent_assembly/core/assembly.py index 0ffaf0b5..3bf5a538 100644 --- a/agent_assembly/core/assembly.py +++ b/agent_assembly/core/assembly.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +import re import sys from dataclasses import dataclass, field from threading import Lock @@ -42,6 +43,7 @@ ENV_CONTROL_PLANE_URL = "AA_CONTROL_PLANE_URL" _DEFAULT_AGENT_ID = "agent-assembly-default" +_AGENT_ID_RE = re.compile(r"^[A-Za-z0-9_.-]{1,128}$") _VALID_RUNTIME_MODES = {"auto", "ebpf", "proxy", "sdk-only"} _VALID_ENFORCEMENT_MODES: frozenset[EnforcementMode] = frozenset({"enforce", "observe", "disabled"}) _INIT_LOCK = Lock() From b1048b2f6b13ca18f8964cb172cd9b5a7aa3945f Mon Sep 17 00:00:00 2001 From: Bryant Liu Date: Wed, 8 Jul 2026 00:58:11 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E2=9C=A8=20(assembly):=20Add=20=5Fvalidate?= =?UTF-8?q?=5Fagent=5Fid=20helper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/core/assembly.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/agent_assembly/core/assembly.py b/agent_assembly/core/assembly.py index 3bf5a538..e264f4b5 100644 --- a/agent_assembly/core/assembly.py +++ b/agent_assembly/core/assembly.py @@ -50,6 +50,20 @@ _ACTIVE_CONTEXT: AssemblyContext | None = None +def _validate_agent_id(agent_id: str) -> str: + """Validate agent_id format before it reaches socket-path interpolation (AAASM-4301). + + Only allows [A-Za-z0-9_.-] up to 128 chars. Reject anything else with a clear ValueError + so callers get a fast, actionable error instead of a downstream FileNotFoundError from + a malformed socket path. + """ + if not _AGENT_ID_RE.fullmatch(agent_id): + raise ValueError( + f"invalid agent_id: {agent_id!r}; must match {_AGENT_ID_RE.pattern}" + ) + return agent_id + + class RuntimePatch(Protocol): """Internal monkey-patch mechanism used by framework adapters. From 0de5e0a14c885efbbe538de0a8b964a977e28ce0 Mon Sep 17 00:00:00 2001 From: Bryant Liu Date: Wed, 8 Jul 2026 00:58:42 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=90=9B=20(assembly):=20Enforce=20agen?= =?UTF-8?q?t=5Fid=20validation=20in=20init=5Fassembly=20(AAASM-4301)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent_assembly/core/assembly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent_assembly/core/assembly.py b/agent_assembly/core/assembly.py index e264f4b5..05be85de 100644 --- a/agent_assembly/core/assembly.py +++ b/agent_assembly/core/assembly.py @@ -220,7 +220,7 @@ def init_assembly( if spawned_by_tool is None: spawned_by_tool = _spawn.spawned_by_tool - resolved_agent_id = agent_id or _DEFAULT_AGENT_ID + resolved_agent_id = _validate_agent_id(agent_id or _DEFAULT_AGENT_ID) global _ACTIVE_CONTEXT with _INIT_LOCK: From ad30d22b9f75a13b5e04f8fdd1b5481180a6b639 Mon Sep 17 00:00:00 2001 From: Bryant Liu Date: Wed, 8 Jul 2026 01:33:08 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E2=9C=85=20(assembly):=20Add=20parametrize?= =?UTF-8?q?d=20tests=20for=20=5Fvalidate=5Fagent=5Fid=20(AAASM-4301)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/unit/test_assembly.py | 42 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/test/unit/test_assembly.py b/test/unit/test_assembly.py index b087f5d4..3aeed993 100644 --- a/test/unit/test_assembly.py +++ b/test/unit/test_assembly.py @@ -698,3 +698,45 @@ def test_init_assembly_no_warning_for_loopback_http_with_api_key( agent_id="agent-loopback", ) context.shutdown() + + +# AAASM-4301: validate_agent_id enforces a strict character-set + length cap +# on the agent_id argument before it ever reaches socket-path interpolation. + + +@pytest.mark.parametrize( + "agent_id", + [ + "agent-assembly-default", # the module default + "default", + "agent-1", + "my_agent_2026", + "a.b.c", + "A", # single char, upper + "a", # single char, lower + "0", # single char, digit + "a" * 128, # max length + ], +) +def test_validate_agent_id_accepts_well_formed_values(agent_id: str) -> None: + """Well-formed agent_ids match the regex and are returned unchanged.""" + assert core_assembly._validate_agent_id(agent_id) == agent_id + + +@pytest.mark.parametrize( + "agent_id", + [ + "", # empty string + "a" * 129, # one char over the length cap + "../etc/passwd", # path-traversal attempt + "has space", # whitespace + "has/slash", # path separator + "has$special", # shell metacharacter + "unicode-α", # non-ASCII alpha + "trailing\n", # newline + ], +) +def test_validate_agent_id_rejects_malformed_values(agent_id: str) -> None: + """Malformed agent_ids raise ValueError with the regex pattern in the message.""" + with pytest.raises(ValueError, match=r"invalid agent_id"): + core_assembly._validate_agent_id(agent_id) From 1a573cf53a6011baa77351d3717786cac96a7e46 Mon Sep 17 00:00:00 2001 From: Bryant Liu Date: Wed, 8 Jul 2026 01:34:32 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E2=9C=85=20(assembly):=20Add=20init=5Fasse?= =?UTF-8?q?mbly=20regression=20tests=20for=20agent=5Fid=20validation=20(AA?= =?UTF-8?q?ASM-4301)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/unit/test_assembly.py | 50 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/test/unit/test_assembly.py b/test/unit/test_assembly.py index 3aeed993..ac7e2d91 100644 --- a/test/unit/test_assembly.py +++ b/test/unit/test_assembly.py @@ -740,3 +740,53 @@ def test_validate_agent_id_rejects_malformed_values(agent_id: str) -> None: """Malformed agent_ids raise ValueError with the regex pattern in the message.""" with pytest.raises(ValueError, match=r"invalid agent_id"): core_assembly._validate_agent_id(agent_id) + + +def test_init_assembly_rejects_malformed_agent_id( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """AAASM-4301 regression: malformed agent_id fails fast at init_assembly entry. + + Without this validation, a path-traversal-like agent_id would be silently + interpolated into the runtime socket path, causing a downstream FileNotFoundError + when the SDK tries to connect. The validator raises ValueError up-front so the + caller gets a clear, actionable error. + """ + monkeypatch.setattr(core_assembly, "_register_adapters", lambda **kwargs: []) + monkeypatch.setattr( + core_assembly, + "_start_network_layer", + lambda **kwargs: ("sdk-only", core_assembly._noop_shutdown), + ) + + with pytest.raises(ValueError, match=r"invalid agent_id"): + init_assembly( + gateway_url="http://localhost:8080", + api_key="test-api-key", + agent_id="../attack", + ) + + +def test_init_assembly_accepts_default_agent_id( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """The module-level _DEFAULT_AGENT_ID must satisfy the validator. + + Guards against a future change that alters _DEFAULT_AGENT_ID to a value the + regex rejects, which would break every caller of init_assembly() (no args). + """ + monkeypatch.setattr(core_assembly, "_register_adapters", lambda **kwargs: []) + monkeypatch.setattr( + core_assembly, + "_start_network_layer", + lambda **kwargs: ("sdk-only", core_assembly._noop_shutdown), + ) + + context = init_assembly( + gateway_url="http://localhost:8080", + api_key="test-api-key", + ) + try: + assert context.client.agent_id == core_assembly._DEFAULT_AGENT_ID + finally: + context.shutdown()