feat(delivery): git workspaces + SSRF host check (v2 phase A3-2)
The platform's side of D6: git runs on the host, in a gitdir that is never mounted into a container, so repository credentials never reach the model. - adapters/git_workspace.py: LocalGitWorkspaceAdapter implementing RepoWorkspacePort. Bare shared gitdir + a dev work tree mounted at /workspace + one linked worktree per task under work/.worktrees/<id> (excluded via the gitdir's info/exclude, not the project's .gitignore). Explicit --git-dir/--work-tree, core.hooksPath=/dev/null, fsmonitor off, quotePath off, GIT_CONFIG_NOSYSTEM, a private HOME per project, prompts off, one asyncio.Lock per project. Token via GIT_ASKPASS reading a 0600 file; SSH key as a 0600 temp file with a pinned known_hosts. Everything deleted in a finally; only a *path* ever travels through the environment. - The bug this phase existed to find: --git-dir=<shared> does not address a linked worktree, so `git commit` in a task worktree committed straight to `dev`. Operations now address <gitdir>/worktrees/<task_id> explicitly — and never read the worktree's .git file, which a repository can rewrite. - domain/platform/rules.py: is_blocked_address (pure) + assert_remote_host_allowed (resolves, refuses if any address is loopback/private/link-local/metadata). Shared by the git remote test and, later, every deploy target. - ProjectExternalGit domain model + repository; container_delivery wires the real adapter behind GIT_WORKSPACE=local with a resolver that joins the project, its repo and its (decrypted) credentials. Tests: 394 API (+48) against real git and a local bare repo; ruff and mypy clean. Report: docs/plan/reports/agent-3-phase-2.md
This commit is contained in:
@@ -54,3 +54,20 @@ class ProjectGitCredential:
|
||||
encrypted_password: str
|
||||
id: str = field(default_factory=new_id)
|
||||
created_at: datetime = field(default_factory=utcnow)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ProjectExternalGit:
|
||||
"""Credentials for a *user-supplied* git remote (v2 ``git_source=external``).
|
||||
|
||||
Separate from :class:`ProjectGitCredential`, which is the platform's own
|
||||
Gitea identity. Keeping them apart means switching a project to an external
|
||||
remote can never accidentally reuse — or expose — the platform token.
|
||||
"""
|
||||
|
||||
project_id: str
|
||||
auth_type: str = "none" # none | token | ssh_key
|
||||
username: str | None = None
|
||||
secret_encrypted: str | None = None
|
||||
known_hosts: str | None = None
|
||||
updated_at: datetime = field(default_factory=utcnow)
|
||||
|
||||
Reference in New Issue
Block a user