Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/agentex/lib/core/tracing/code_revision.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

from agentex.lib.utils.logging import make_logger

__all__ = ("COMMIT_SHA_KEY", "enable", "disable", "is_enabled", "commit_sha")
__all__ = ("COMMIT_SHA_KEY", "enable", "disable", "is_enabled", "commit_sha", "is_git_object_name")

logger = make_logger(__name__)

Expand All @@ -31,6 +31,12 @@
# git's own 7-character minimum.
_GIT_SHA_RE = re.compile(r"[0-9a-fA-F]{7,64}")


def is_git_object_name(value: str) -> bool:
"""Whether ``value`` is a full or abbreviated git SHA-1/SHA-256 object name."""
return _GIT_SHA_RE.fullmatch(value.strip()) is not None


_COMMIT_SHA_ENV = "AGENT_COMMIT_SHA"
# Fallback only: automatic, and only usable when it happens to be SHA-shaped.
_AGENT_VERSION_ENV = "AGENT_VERSION"
Expand Down
3 changes: 3 additions & 0 deletions src/agentex/lib/environment_variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class EnvVarKeys(str, Enum):
AGENT_ID = "AGENT_ID"
AGENT_VERSION = "AGENT_VERSION"
AGENT_COMMIT_SHA = "AGENT_COMMIT_SHA"
AGENT_SOURCE_REPO = "AGENT_SOURCE_REPO"
AGENT_API_KEY = "AGENT_API_KEY"
# ACP Configuration
ACP_URL = "ACP_URL"
Expand Down Expand Up @@ -74,6 +75,8 @@ class EnvironmentVariables(BaseModel):
# `adk.code_revision.enable()`, which also refuses a value that is not a git
# object name. See agentex.lib.core.tracing.code_revision.
AGENT_COMMIT_SHA: str | None = None
# Git remote the agent was built from (any URL form; normalized to host/path on use).
AGENT_SOURCE_REPO: str | None = None
AGENT_API_KEY: str | None = None
ACP_TYPE: str | None = "async"
AGENT_INPUT_TYPE: str | None = None
Expand Down
3 changes: 2 additions & 1 deletion src/agentex/lib/utils/build_provenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ def normalize_remote(url: Optional[str]) -> Optional[str]:
"""Strip credentials and scheme from a remote, returning ``host/path``."""
if not url:
return None
candidate = url.strip()
# Query strings and fragments never name a repo, but they do carry tokens.
candidate = url.strip().split("?", 1)[0].split("#", 1)[0]
# scp-like syntax: git@host:org/repo(.git) — no scheme, host/path split on ':'
if "://" not in candidate and ":" in candidate and "/" not in candidate.split(":", 1)[0]:
candidate = candidate.split("@", 1)[-1].replace(":", "/", 1)
Expand Down
33 changes: 26 additions & 7 deletions src/agentex/lib/utils/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

from agentex.lib.utils.logging import make_logger
from agentex.lib.environment_variables import EnvironmentVariables
from agentex.lib.utils.build_provenance import normalize_remote
from agentex.lib.core.tracing.code_revision import is_git_object_name

logger = make_logger(__name__)

Expand All @@ -20,6 +22,29 @@ def get_auth_principal(env_vars: EnvironmentVariables):
except Exception:
return None


def build_registration_metadata(env_vars: EnvironmentVariables, agent_card=None) -> dict:
"""Deployment id, source provenance, and agent card; keys appear only when known."""
metadata: dict = {}
if env_vars.AGENTEX_DEPLOYMENT_ID:
metadata["deployment_id"] = env_vars.AGENTEX_DEPLOYMENT_ID
commit = (env_vars.AGENT_COMMIT_SHA or "").strip()
if commit:
if is_git_object_name(commit):
metadata["commit_sha"] = commit
else:
logger.warning(
"AGENT_COMMIT_SHA=%r is not a git commit SHA; commit_sha omitted from registration.",
commit,
)
repo = normalize_remote(env_vars.AGENT_SOURCE_REPO)
Comment on lines +31 to +40

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 New Fields Break Test Stubs

The new unconditional accesses to AGENT_COMMIT_SHA and AGENT_SOURCE_REPO break the existing register_agent tests. Their environment stub in tests/lib/test_agent_card.py defines neither attribute, so all three tests raise AttributeError before making their mocked HTTP requests. Update the fixture or read these optional fields defensively so the test suite can pass.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agentex/lib/utils/registration.py
Line: 31-40

Comment:
**New Fields Break Test Stubs**

The new unconditional accesses to `AGENT_COMMIT_SHA` and `AGENT_SOURCE_REPO` break the existing `register_agent` tests. Their environment stub in `tests/lib/test_agent_card.py` defines neither attribute, so all three tests raise `AttributeError` before making their mocked HTTP requests. Update the fixture or read these optional fields defensively so the test suite can pass.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Claude Code Fix in Codex

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ee05394 by extending the EnvVars stub in test_agent_card.py with the two new fields. The accesses stay unconditional on purpose: EnvironmentVariables declares both fields, and a defensive getattr would hide a real typo in the model.

🤖 — posted via Claude Code

if repo:
metadata["source_repo"] = repo
Comment on lines +40 to +42

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Repository Credentials Can Leak

If AGENT_SOURCE_REPO contains a credential in its query string or fragment, such as https://github.com/org/repo?access_token=SECRET, normalize_remote preserves that suffix. The credential is then sent in source_repo and included in the successful-registration log. Strip query strings and fragments before adding the repository to the metadata.

How this was verified: The environment value reaches the registration payload through normalize_remote, whose string processing never removes ? or # suffixes.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agentex/lib/utils/registration.py
Line: 40-42

Comment:
**Repository Credentials Can Leak**

If `AGENT_SOURCE_REPO` contains a credential in its query string or fragment, such as `https://github.com/org/repo?access_token=SECRET`, `normalize_remote` preserves that suffix. The credential is then sent in `source_repo` and included in the successful-registration log. Strip query strings and fragments before adding the repository to the metadata.

**How this was verified:** The environment value reaches the registration payload through `normalize_remote`, whose string processing never removes `?` or `#` suffixes.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Claude Code Fix in Codex

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. Fixed at the layer that promises it: normalize_remote now drops the query string and fragment before any other processing (ee05394), with a table row for ?access_token=SECRET#frag. Same fix pushed to the Gitea copy of the normalizer in scaleapi/sgp#5720.

🤖 — posted via Claude Code

if agent_card is not None:
metadata["agent_card"] = agent_card.model_dump() if hasattr(agent_card, "model_dump") else agent_card
return metadata


async def register_agent(env_vars: EnvironmentVariables, agent_card=None):
"""Register this agent with the Agentex server"""
if not env_vars.AGENTEX_BASE_URL:
Expand All @@ -33,13 +58,7 @@ async def register_agent(env_vars: EnvironmentVariables, agent_card=None):
or f"Generic description for agent: {env_vars.AGENT_NAME}"
)

# Registration metadata carries the deployment id and agent card.
registration_metadata: dict = {}
if env_vars.AGENTEX_DEPLOYMENT_ID:
registration_metadata["deployment_id"] = env_vars.AGENTEX_DEPLOYMENT_ID
if agent_card is not None:
card_data = agent_card.model_dump() if hasattr(agent_card, "model_dump") else agent_card
registration_metadata["agent_card"] = card_data
registration_metadata = build_registration_metadata(env_vars, agent_card)

# Prepare registration data
registration_data = {
Expand Down
2 changes: 2 additions & 0 deletions tests/lib/test_agent_card.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,8 @@ def mock_env_vars(self):
"AGENT_ID": None,
"AGENT_INPUT_TYPE": None,
"AGENT_API_KEY": None,
"AGENT_COMMIT_SHA": None,
"AGENT_SOURCE_REPO": None,
"AGENTEX_DEPLOYMENT_ID": None,
})()
return mock
Expand Down
1 change: 1 addition & 0 deletions tests/lib/test_build_provenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def _write(root: Path, rel: str, content: str = "x") -> None:
("https://github.com/scaleapi/Repo.git", "github.com/scaleapi/Repo"),
("https://x-token:secret@GitHub.com/scaleapi/Repo", "github.com/scaleapi/Repo"),
("ssh://git@gitlab.com/group/sub/proj.git", "gitlab.com/group/sub/proj"),
("https://github.com/scaleapi/Repo.git?access_token=SECRET#frag", "github.com/scaleapi/Repo"),
("", None),
(None, None),
],
Expand Down
49 changes: 49 additions & 0 deletions tests/lib/utils/test_registration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""Registration metadata: what an agent reports about itself at startup."""

from __future__ import annotations

import pytest

from agentex.lib.utils.registration import build_registration_metadata
from agentex.lib.environment_variables import EnvironmentVariables

SHA = "b362b171a9c4e1f09d8e7a6b5c4d3e2f1a0b9c8d"


def _env(**overrides) -> EnvironmentVariables:
return EnvironmentVariables(AGENT_NAME="sample-agent", ACP_URL="http://agent", **overrides)


def test_nothing_known_yields_empty_metadata():
assert build_registration_metadata(_env()) == {}


def test_commit_and_repo_reported_when_set():
env = _env(AGENT_COMMIT_SHA=SHA, AGENT_SOURCE_REPO="git@github.com:scaleapi/Demo.git")
assert build_registration_metadata(env) == {
"commit_sha": SHA,
"source_repo": "github.com/scaleapi/Demo",
}


@pytest.mark.parametrize("value", ["latest", "v1.2.3", "rocket_mock_agent-" + SHA, "abc", " "])
def test_non_commit_values_are_omitted_not_forwarded(value):
"""A field named for a commit never holds an image tag, same rule as __commit_sha__."""
assert "commit_sha" not in build_registration_metadata(_env(AGENT_COMMIT_SHA=value))


def test_repo_normalization_strips_scheme_and_credentials():
env = _env(AGENT_SOURCE_REPO="https://x-token:secret@GitHub.com/scaleapi/Demo.git")
assert build_registration_metadata(env)["source_repo"] == "github.com/scaleapi/Demo"


def test_deployment_id_and_agent_card_still_reported():
class Card:
def model_dump(self):
return {"name": "sample"}

env = _env(AGENTEX_DEPLOYMENT_ID="dep-1")
assert build_registration_metadata(env, Card()) == {
"deployment_id": "dep-1",
"agent_card": {"name": "sample"},
}
Loading