Skip to content
Merged
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
Empty file.
Empty file.
Empty file.
48 changes: 47 additions & 1 deletion packages/devqubit-engine/src/devqubit_engine/utils/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,51 @@ def capture_environment(include_pip: bool | None = None) -> dict[str, Any]:
return env


def _sanitize_git_url(url: str | None) -> str | None:
"""
Sanitize a git remote URL by removing embedded credentials.

Removes userinfo (user:password@) from URLs to prevent accidental
token/credential leakage in run records.

Parameters
----------
url : str or None
Git remote URL, possibly containing credentials.

Returns
-------
str or None
URL with credentials removed, or None if input was None.

Examples
--------
>>> _sanitize_git_url("https://token@github.com/org/repo.git")
'https://github.com/org/repo.git'
>>> _sanitize_git_url("https://user:pass@github.com/org/repo.git")
'https://github.com/org/repo.git'
>>> _sanitize_git_url("git@github.com:org/repo.git")
'git@github.com:org/repo.git'
"""
if not url:
return url

# Handle HTTPS URLs with credentials: https://user:pass@host/...
# Pattern matches: scheme://[userinfo@]host...
import re

# Match URLs with credentials in userinfo section
pattern = r"^(https?://)([^@]+@)(.+)$"
match = re.match(pattern, url)
if match:
scheme, _, rest = match.groups()
sanitized = f"{scheme}{rest}"
logger.debug("Sanitized credentials from git remote URL")
return sanitized

return url


def capture_git_provenance(cwd: str | None = None) -> dict[str, Any] | None:
"""
Capture git repository provenance information.
Expand Down Expand Up @@ -190,7 +235,8 @@ def _run_git(args: list[str]) -> str | None:
branch = _run_git(["rev-parse", "--abbrev-ref", "HEAD"])
status_output = _run_git(["status", "--porcelain"])
dirty = bool(status_output)
remote = _run_git(["config", "--get", "remote.origin.url"])
remote_raw = _run_git(["config", "--get", "remote.origin.url"])
remote = _sanitize_git_url(remote_raw)
describe = _run_git(["describe", "--tags", "--always", "--dirty"])

result = {
Expand Down
Empty file.
Empty file.
Empty file.
9 changes: 8 additions & 1 deletion packages/devqubit-ui/src/devqubit_ui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import logging
import os
from contextlib import asynccontextmanager
from importlib.metadata import PackageNotFoundError
from importlib.metadata import version as get_version
from pathlib import Path
from typing import AsyncGenerator

Expand Down Expand Up @@ -128,10 +130,15 @@ def create_app(
- ``app.state.store`` - Object store
- ``app.state.workspace`` - Workspace path string
"""
try:
_version = get_version("devqubit-ui")
except PackageNotFoundError:
_version = "0.0.0"

app = FastAPI(
title="devqubit UI",
description="Web interface for devqubit experiment tracking",
version="0.1.4",
version=_version,
lifespan=lifespan,
)

Expand Down
Empty file.
Empty file added src/devqubit/py.typed
Empty file.