Skip to content

feat(tools): add SSRF prevention for git clone URLs#505

Merged
Aureliolo merged 7 commits intomainfrom
feat/ssrf-git-allowlist
Mar 17, 2026
Merged

feat(tools): add SSRF prevention for git clone URLs#505
Aureliolo merged 7 commits intomainfrom
feat/ssrf-git-allowlist

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Add host/IP validation with async DNS resolution to GitCloneTool to prevent SSRF via agent-controlled clone URLs
  • Block private, loopback, link-local, CGNAT, TEST-NET, multicast, reserved, and other RFC-reserved IP ranges by default
  • Add GitCloneNetworkPolicy frozen Pydantic model with configurable hostname_allowlist (case-insensitive, normalized, deduplicated), block_private_ips master switch, and dns_resolution_timeout
  • Extract git_url_validator.py from git_tools.py — scheme check, hostname extraction, IP blocklist, DNS validation
  • Fail-closed on all error paths: DNS timeout, NXDOMAIN, empty results, unparseable IPs, unexpected exceptions
  • IPv6-mapped IPv4 unwrapping prevents ::ffff:127.0.0.1 bypass
  • All resolved IPs must be public (prevents DNS rebinding with mixed results)
  • Add git_clone field to RootConfig for YAML-level configuration

Closes #221

Review coverage

Pre-reviewed by 11 agents (code-reviewer, python-reviewer, security-reviewer, test-analyzer, silent-failure-hunter, async-concurrency-reviewer, conventions-enforcer, logging-audit, type-design-analyzer, issue-resolution-verifier, docs-consistency). 19 findings addressed in second commit.

Test plan

  • 96 new unit tests in test_git_url_validator.py covering all validator functions
  • 9 clone integration tests in test_git_clone.py (local clone, SSRF blocking, allowlist bypass, scheme wiring)
  • Hypothesis property-based tests for all blocked IPv4/IPv6 ranges + non-blocked IPv4
  • Full suite: 8645 passed, 94%+ coverage
  • mypy strict: 0 issues across 1032 files
  • ruff lint + format: clean

🤖 Generated with Claude Code

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 16, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the security of Git clone operations by introducing robust Server-Side Request Forgery (SSRF) prevention. It ensures that git clone URLs are thoroughly validated against a comprehensive list of blocked private and reserved IP ranges, utilizing asynchronous DNS resolution to prevent malicious redirection. The new system is highly configurable, allowing administrators to define allowed hostnames and adjust security parameters, thereby safeguarding against potential exploits while maintaining operational flexibility.

Highlights

  • SSRF Prevention for Git Clone URLs: Implemented host/IP validation with asynchronous DNS resolution within the GitCloneTool to prevent Server-Side Request Forgery (SSRF) attacks via agent-controlled clone URLs.
  • Comprehensive IP Blocking: Blocked private, loopback, link-local, CGNAT, TEST-NET, multicast, and other RFC-reserved IP ranges by default, including unwrapping IPv6-mapped IPv4 addresses.
  • Configurable Network Policy: Introduced a GitCloneNetworkPolicy Pydantic model allowing configuration of a hostname_allowlist (case-insensitive, normalized), a block_private_ips master switch, and a dns_resolution_timeout.
  • Code Refactoring and Modularity: Extracted URL validation logic into a new dedicated module, git_url_validator.py, separating scheme checks, hostname extraction, IP blocklisting, and DNS validation from the main git_tools.py.
  • Fail-Closed Security Posture: Ensured that all error paths, including DNS timeouts, NXDOMAIN, empty results, unparseable IPs, and unexpected exceptions, result in a blocked clone operation.
  • Configuration Integration: Added a git_clone field to the RootConfig for YAML-level configuration of the new network policy.
Changelog
  • CLAUDE.md
    • Updated the description of the tools/ directory to include git clone SSRF prevention (git_url_validator).
    • Added new Git-related event constants (GIT_COMMAND_START, GIT_CLONE_URL_REJECTED, GIT_CLONE_SSRF_BLOCKED, GIT_CLONE_DNS_FAILED) to the list of event names.
  • src/synthorg/config/defaults.py
    • Added a default empty dictionary for git_clone configuration.
  • src/synthorg/config/schema.py
    • Imported GitCloneNetworkPolicy from synthorg.tools.git_url_validator.
    • Added a git_clone field of type GitCloneNetworkPolicy to the RootConfig with a default factory and description.
  • src/synthorg/observability/events/git.py
    • Added new constants GIT_CLONE_SSRF_BLOCKED and GIT_CLONE_DNS_FAILED for observability events related to Git clone operations.
  • src/synthorg/tools/init.py
    • Imported GitCloneNetworkPolicy.
    • Exported GitCloneNetworkPolicy in the __all__ list.
  • src/synthorg/tools/git_tools.py
    • Removed the internal _is_allowed_clone_url function and _ALLOWED_CLONE_SCHEMES constant, delegating this logic to the new git_url_validator module.
    • Imported ALLOWED_CLONE_SCHEMES, GitCloneNetworkPolicy, is_allowed_clone_scheme, and validate_clone_url_host from synthorg.tools.git_url_validator.
    • Modified the GitCloneTool constructor to accept an optional network_policy argument, defaulting to GitCloneNetworkPolicy().
    • Updated the GitCloneTool.execute method's docstring to reflect the new SSRF prevention via hostname/IP validation.
    • Replaced the call to _is_allowed_clone_url with is_allowed_clone_scheme for initial URL scheme validation.
    • Added a new step in GitCloneTool.execute to call validate_clone_url_host for SSRF prevention, returning an error if validation fails.
  • src/synthorg/tools/git_url_validator.py
    • Added a new file git_url_validator.py to encapsulate Git clone URL validation and SSRF prevention logic.
    • Defined ALLOWED_CLONE_SCHEMES for valid Git URL protocols.
    • Defined _BLOCKED_NETWORKS containing a comprehensive list of IPv4 and IPv6 private, loopback, link-local, and reserved IP ranges.
    • Implemented GitCloneNetworkPolicy as a Pydantic model for configuring hostname allowlists, private IP blocking, and DNS resolution timeouts, with normalization and deduplication of allowlist entries.
    • Implemented _is_blocked_ip to check if an IP address falls within any blocked network, handling IPv6-mapped IPv4 addresses.
    • Implemented _extract_hostname to reliably extract hostnames from various Git URL formats (standard, SCP-like, IPv6 literals).
    • Implemented is_allowed_clone_scheme to validate Git URL schemes, rejecting local paths and flag injection attempts.
    • Implemented _dns_failure for consistent logging of DNS resolution errors.
    • Implemented _resolve_and_check for asynchronous DNS resolution and validation of all resolved IPs against the blocklist, failing closed on errors or private IPs.
    • Implemented validate_clone_url_host as the main entry point for host validation, incorporating allowlist bypass, literal IP checks, and DNS resolution.
  • tests/unit/tools/git/conftest.py
    • Updated the allow_local_clone fixture to monkeypatch both is_allowed_clone_scheme and validate_clone_url_host to allow local file paths for testing purposes.
  • tests/unit/tools/git/test_git_clone.py
    • Added a new test file test_git_clone.py for integration tests of GitCloneTool.
    • Included tests for cloning local repositories, cloning with depth, blocking directory traversal, handling invalid URLs, and cloning specific branches.
    • Added a dedicated TestGitCloneToolSsrf class with integration tests for SSRF prevention, covering blocked loopback and private IPs, allowlisted hosts, and blocked file schemes.
  • tests/unit/tools/git/test_git_tools.py
    • Removed the TestGitCloneTool class and TestCloneUrlValidation class, as their tests were moved to the new test_git_clone.py and test_git_url_validator.py files respectively.
  • tests/unit/tools/git/test_git_url_validator.py
    • Added a new test file test_git_url_validator.py for comprehensive unit and property-based testing of the new URL validation logic.
    • Included tests for _extract_hostname covering various URL formats.
    • Included tests for _is_blocked_ip covering blocked and public IPv4/IPv6 addresses, including IPv6-mapped IPv4.
    • Included tests for is_allowed_clone_scheme covering allowed and blocked URL schemes.
    • Included tests for GitCloneNetworkPolicy covering defaults, custom values, immutability, timeout bounds, and allowlist normalization/deduplication.
    • Included tests for validate_clone_url_host with mocked DNS resolution, covering public/private hosts, literal IPs, allowlist bypass, DNS timeouts, NXDOMAIN, empty results, and mixed DNS results.
    • Implemented Hypothesis property-based tests to ensure all blocked IPv4/IPv6 ranges are consistently detected and non-blocked IPv4 addresses are never flagged.
Activity
  • The pull request was pre-reviewed by 11 automated agents (code-reviewer, python-reviewer, security-reviewer, test-analyzer, silent-failure-hunter, async-concurrency-reviewer, conventions-enforcer, logging-audit, type-design-analyzer, issue-resolution-verifier, docs-consistency).
  • 19 findings identified during the initial agent review were addressed in a subsequent commit.
  • Extensive testing was performed, including 96 new unit tests for the URL validator, 9 clone integration tests, Hypothesis property-based tests for IP ranges, achieving 94%+ code coverage.
  • Mypy strict checks passed with 0 issues across 1032 files.
  • Ruff lint and format checks were clean.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 16, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: edeb2799-f7ff-4ddb-9183-5eed8a28fa67

📥 Commits

Reviewing files that changed from the base of the PR and between de1a6ac and 6862a67.

📒 Files selected for processing (11)
  • CLAUDE.md
  • src/synthorg/config/defaults.py
  • src/synthorg/config/schema.py
  • src/synthorg/observability/events/git.py
  • src/synthorg/tools/__init__.py
  • src/synthorg/tools/git_tools.py
  • src/synthorg/tools/git_url_validator.py
  • tests/unit/tools/git/conftest.py
  • tests/unit/tools/git/test_git_clone.py
  • tests/unit/tools/git/test_git_tools.py
  • tests/unit/tools/git/test_git_url_validator.py

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • SSRF protection for git clone with a configurable network policy and new default config option.
  • Tools

    • Added a built-in git URL validator for clone operations; applies SSRF host/IP checks and masks credentials in messages.
  • Observability

    • New events for clone SSRF blocked, DNS resolution failures, and SSRF disabled.
  • Documentation

    • Updated OpenAPI export note, expanded logging/event guidance, and documented the new SSRF prevention tool.
  • Tests

    • Large new test suite for URL validation and SSRF; related test adjustments and removals.

Walkthrough

Adds SSRF-resistant git clone validation: new git_url_validator module and GitCloneNetworkPolicy, integrates host/IP checks into GitCloneTool (configurable via network_policy), exports the policy, adds observability events, updates config/schema/defaults, and adds extensive tests and docs updates.

Changes

Cohort / File(s) Summary
SSRF Validation Module
src/synthorg/tools/git_url_validator.py
New module implementing scheme checks, hostname extraction (URL, SCP, IPv6), private/reserved IP blocklist, async DNS resolution with timeout, Pydantic GitCloneNetworkPolicy, and public APIs is_allowed_clone_scheme and validate_clone_url_host.
Git Clone Tool Integration
src/synthorg/tools/git_tools.py
GitCloneTool ctor adds optional network_policy; replaces internal scheme/URL checks with is_allowed_clone_scheme, masks credentials consistently, and calls validate_clone_url_host to block SSRF/DNS failures before executing clones.
Config & Schema
src/synthorg/config/defaults.py, src/synthorg/config/schema.py
Added top-level git_clone default and RootConfig.git_clone: GitCloneNetworkPolicy with default_factory and descriptive metadata.
Observability & Exports
src/synthorg/observability/events/git.py, src/synthorg/tools/__init__.py
Added event constants GIT_CLONE_SSRF_BLOCKED, GIT_CLONE_DNS_FAILED, GIT_CLONE_SSRF_DISABLED; exported GitCloneNetworkPolicy from synthorg.tools.
Tests
tests/unit/tools/git/test_git_url_validator.py, tests/unit/tools/git/test_git_clone.py, tests/unit/tools/git/conftest.py, tests/unit/tools/git/test_git_tools.py
Added comprehensive validator unit tests (incl. Hypothesis-driven cases) and new GitCloneTool integration tests for SSRF scenarios; conftest patched to use public validators; removed older GitCloneTool test class.
Docs / Logging Guidance
CLAUDE.md
Updated OpenAPI export note, expanded logging event names, and added git_url_validator to tools documentation.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client/User
    participant Tool as GitCloneTool
    participant Scheme as is_allowed_clone_scheme
    participant HostVal as validate_clone_url_host
    participant DNS as DNS Resolver
    participant GitExec as git executor

    Client->>Tool: clone_repo(url, ...)
    Tool->>Scheme: validate scheme
    alt scheme invalid
        Scheme-->>Tool: blocked
        Tool-->>Client: Error (invalid scheme)
    else scheme allowed
        Scheme-->>Tool: allowed
        Tool->>HostVal: validate_clone_url_host(url, policy)
        HostVal->>HostVal: extract hostname
        alt literal IP
            HostVal->>HostVal: check IP blocklist
        else domain name
            HostVal->>DNS: resolve hostname (timeout)
            DNS-->>HostVal: IPs / error
            HostVal->>HostVal: check resolved IPs vs blocklist
        end
        alt blocked / DNS failure
            HostVal-->>Tool: validation failed (SSRF/DNS)
            Tool-->>Client: Error (SSRF/DNS)
        else allowed
            HostVal-->>Tool: validation passed
            Tool->>GitExec: execute git clone
            GitExec-->>Client: Clone complete
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.59% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding SSRF prevention for git clone URLs, which is the primary objective of the PR.
Description check ✅ Passed The description comprehensively covers the changeset, including SSRF prevention mechanisms, network policy configuration, test coverage, and issue resolution.
Linked Issues check ✅ Passed The PR fully implements all objectives from issue #221: host/IP validation with deny-listing of reserved ranges, configurable hostname allowlist, and integrated network validation in GitCloneTool.
Out of Scope Changes check ✅ Passed All changes are directly scoped to SSRF prevention for git clone URLs: new validator module, policy configuration, integration into GitCloneTool, event logging, and comprehensive test coverage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ssrf-git-allowlist
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/ssrf-git-allowlist
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces robust SSRF prevention for git clone URLs. The core logic is encapsulated in a new git_url_validator.py module, which performs scheme validation, hostname extraction, and asynchronous DNS resolution to ensure clone targets do not resolve to private or reserved IP addresses. The implementation is comprehensive, handling edge cases like IPv6-mapped IPv4 addresses and failing closed on all error paths. The new functionality is configurable through a GitCloneNetworkPolicy model and is thoroughly covered by an extensive suite of unit, integration, and property-based tests. The changes are well-structured and significantly improve the security posture of the git tooling.

Comment on lines +117 to +119
if normalized != self.hostname_allowlist:
object.__setattr__(self, "hostname_allowlist", normalized)
return self
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

While using object.__setattr__ on a frozen Pydantic model is a known pattern, a more idiomatic approach in Pydantic v2 is to return a new model instance with the updated value. This aligns better with the principles of immutability by creating a new object instead of mutating one in place.

You can achieve this using self.model_copy(update={...}) and returning the new instance when a change is needed.

Suggested change
if normalized != self.hostname_allowlist:
object.__setattr__(self, "hostname_allowlist", normalized)
return self
if normalized != self.hostname_allowlist:
return self.model_copy(update={"hostname_allowlist": normalized})
return self

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/config/schema.py`:
- Around line 537-540: The RootConfig class docstring's Attributes section is
missing the new git_clone field; update the docstring for RootConfig to include
an entry for git_clone describing its type (GitCloneNetworkPolicy) and purpose
(Git clone SSRF prevention network policy), similar to how trust, promotion, and
coordination are documented so readers see the new Field defined as git_clone:
GitCloneNetworkPolicy = Field(default_factory=GitCloneNetworkPolicy,
description="Git clone SSRF prevention network policy").

In `@src/synthorg/tools/git_tools.py`:
- Around line 679-683: The rejection path currently logs the raw clone URL (in
the block using is_allowed_clone_scheme and GIT_CLONE_URL_REJECTED via
logger.warning), which can leak credentials; replace the raw url in the log with
a redacted value that contains only scheme+host (no userinfo or path). Reuse the
same sanitizer used in git_url_validator.py (the function that produces the
scheme+host redaction used around line 322) to produce the redacted URL, and
pass that redacted string to logger.warning instead of the original url.

In `@src/synthorg/tools/git_url_validator.py`:
- Around line 60-68: The _BLOCKED_NETWORKS denylist is missing the IPv6
multicast block ff00::/8; update the _BLOCKED_NETWORKS definition in
git_url_validator.py to include ipaddress.IPv6Network("ff00::/8") alongside the
other IPv6 networks (e.g., near the lines with ipaddress.IPv6Network("fc00::/7")
and "fe80::/10") so IPv6 multicast literals/responses are correctly treated as
denied.
- Around line 214-217: The current SCP-like check rejects any URL containing
"::", which inadvertently blocks bracketed IPv6 hosts like
git@[2001:db8::1]:repo.git; update the boolean expression (the return statement
that currently reads "@" in url and ":" in url and "::" not in url and "://" not
in url) to allow "::" when it appears inside a bracketed IPv6 literal (e.g.
detect '@[' and a closing ']' before the path colon) — e.g. permit URLs where
'@[' is present and a matching ']' occurs prior to the first ':' — while still
rejecting unbracketed "::" and "://"; reference the same SCP-like check and keep
behavior consistent with _extract_hostname which already supports bracketed
IPv6.

In `@tests/unit/tools/git/test_git_clone.py`:
- Around line 134-142: Remove the redundant parentheses around the URL literal
passed to tool.execute in the arguments dict: change the value for the "url" key
from ("https://internal-git.example.com/repo.git") to
"https://internal-git.example.com/repo.git" so the test remains the same but
without unnecessary grouping.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8be66d2c-3eb0-4874-9cc8-d8d470d6d744

📥 Commits

Reviewing files that changed from the base of the PR and between bd85a8d and 35a267d.

📒 Files selected for processing (11)
  • CLAUDE.md
  • src/synthorg/config/defaults.py
  • src/synthorg/config/schema.py
  • src/synthorg/observability/events/git.py
  • src/synthorg/tools/__init__.py
  • src/synthorg/tools/git_tools.py
  • src/synthorg/tools/git_url_validator.py
  • tests/unit/tools/git/conftest.py
  • tests/unit/tools/git/test_git_clone.py
  • tests/unit/tools/git/test_git_tools.py
  • tests/unit/tools/git/test_git_url_validator.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Do not use from __future__ import annotations in Python code. Python 3.14 has PEP 649 native lazy annotations.
Use except A, B: (no parentheses) for exception syntax on Python 3.14. Do not use except (A, B):.
Add type hints to all public functions and classes. Use mypy strict mode.
Use Google-style docstrings on all public classes and functions. Docstrings are enforced by ruff D rules.
Create new objects for immutability. Never mutate existing objects. For non-Pydantic internal collections, use copy.deepcopy() at construction and wrap with MappingProxyType for read-only enforcement.
Use frozen Pydantic models for config and identity. Use separate mutable-via-copy models with model_copy(update=...) for runtime state that evolves.
Do not mix static config fields with mutable runtime fields in one Pydantic model.
Use Pydantic v2 conventions: @computed_field for derived values, NotBlankStr for identifier/name fields (including optional and tuple variants), and model_validator/ConfigDict.
Use asyncio.TaskGroup for fan-out/fan-in parallel operations in new code. Prefer structured concurrency over bare create_task.
Keep function bodies under 50 lines and files under 800 lines.
Handle errors explicitly. Never silently swallow exceptions.
Validate user input, external API responses, and config files at system boundaries.
Use ruff check and ruff format for Python linting and formatting (88-character line length).
Use mypy with strict mode for type-checking all Python code.
Python version must be 3.14 or higher. PEP 649 provides native lazy annotations.

Files:

  • tests/unit/tools/git/test_git_tools.py
  • src/synthorg/observability/events/git.py
  • src/synthorg/config/defaults.py
  • src/synthorg/config/schema.py
  • src/synthorg/tools/__init__.py
  • tests/unit/tools/git/test_git_url_validator.py
  • tests/unit/tools/git/test_git_clone.py
  • tests/unit/tools/git/conftest.py
  • src/synthorg/tools/git_url_validator.py
  • src/synthorg/tools/git_tools.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, and @pytest.mark.slow to mark test cases.
Prefer @pytest.mark.parametrize for testing similar cases with multiple inputs.
Use Hypothesis for property-based testing in Python with @given + @settings. Control via HYPOTHESIS_PROFILE env var (dev: 1000 examples, ci: 200 examples).

Files:

  • tests/unit/tools/git/test_git_tools.py
  • tests/unit/tools/git/test_git_url_validator.py
  • tests/unit/tools/git/test_git_clone.py
  • tests/unit/tools/git/conftest.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: Every module with business logic must import logger with from synthorg.observability import get_logger then logger = get_logger(__name__). Never use import logging, logging.getLogger(), or print() in application code.
Always use logger as the variable name for loggers. Never use _logger or log.
Use event name constants from domain-specific modules under synthorg.observability.events for all logging calls. Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.
Use structured logging with logger.info(EVENT, key=value) syntax. Never use format string logging like logger.info('msg %s', val).
All error paths must log at WARNING or ERROR level with context before raising.
All state transitions must log at INFO level.
Use DEBUG level for object creation, internal flow, and entry/exit of key functions.
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small as aliases. Use test-provider, test-small-001, etc. in tests.
Library API reference is auto-generated from docstrings via mkdocstrings + Griffe in docs/api/. Use Google-style docstrings for public APIs.
Use Pydantic BaseModel for all data models. Frozen models for config/identity. Mutable-via-copy models for runtime state.

Files:

  • src/synthorg/observability/events/git.py
  • src/synthorg/config/defaults.py
  • src/synthorg/config/schema.py
  • src/synthorg/tools/__init__.py
  • src/synthorg/tools/git_url_validator.py
  • src/synthorg/tools/git_tools.py
src/synthorg/observability/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Observability includes structured logging via get_logger(__name__), correlation tracking, and log sinks.

Files:

  • src/synthorg/observability/events/git.py
src/synthorg/tools/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Tool registry supports built-in tools (file_system/, git, sandbox/, code_runner), MCP bridge, role-based access, approval tool (request_human_approval).

Files:

  • src/synthorg/tools/__init__.py
  • src/synthorg/tools/git_url_validator.py
  • src/synthorg/tools/git_tools.py
🧠 Learnings (17)
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/tools/**/*.py : Tool registry supports built-in tools (file_system/, git, sandbox/, code_runner), MCP bridge, role-based access, approval tool (request_human_approval).

Applied to files:

  • tests/unit/tools/git/test_git_tools.py
  • src/synthorg/config/schema.py
  • src/synthorg/tools/__init__.py
  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` for all logging calls. Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to web/src/{components,views,styles}/**/* : Vue dashboard uses PrimeVue components + Tailwind CSS styling. Global CSS in styles/, theme configuration in styles/.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/security/**/*.py : Security module includes SecOps agent, rule engine (soft-allow/hard-deny), audit log, output scanner, risk classifier, autonomy levels (4 strategies), timeout policies.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to web/src/components/**/*.vue : Vue dashboard uses TypeScript. Organize components by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.651Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.651Z
Learning: Security architecture is documented in `docs/security.md` (comprehensive guide covering SecOps agent, rule engine, audit log, output scanning, risk classification, autonomy levels, timeout policies).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to docker/{Dockerfile*,compose.yml} : Docker: Backend uses 3-stage build (builder → setup → distroless runtime), Chainguard Python, non-root (UID 65532), CIS-hardened. Web uses nginxinc/nginx-unprivileged, Vue 3 SPA with PrimeVue + Tailwind CSS, SPA routing, API/WebSocket proxy to backend.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger` (not `_logger`, not `log`).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : Structured kwargs in logging: always `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/**/*.py : Every module with business logic must import logger with `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging`, `logging.getLogger()`, or `print()` in application code.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/**/*.py : Use structured logging with `logger.info(EVENT, key=value)` syntax. Never use format string logging like `logger.info('msg %s', val)`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : All state transitions must log at INFO.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability includes structured logging via `get_logger(__name__)`, correlation tracking, and log sinks.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/**/*.py : All state transitions must log at INFO level.

Applied to files:

  • CLAUDE.md
🧬 Code graph analysis (4)
src/synthorg/config/schema.py (1)
src/synthorg/tools/git_url_validator.py (1)
  • GitCloneNetworkPolicy (74-119)
src/synthorg/tools/__init__.py (1)
src/synthorg/tools/git_url_validator.py (1)
  • GitCloneNetworkPolicy (74-119)
tests/unit/tools/git/test_git_url_validator.py (1)
src/synthorg/tools/git_url_validator.py (5)
  • GitCloneNetworkPolicy (74-119)
  • _extract_hostname (154-194)
  • _is_blocked_ip (125-151)
  • is_allowed_clone_scheme (197-217)
  • validate_clone_url_host (300-352)
src/synthorg/tools/git_tools.py (1)
src/synthorg/tools/git_url_validator.py (3)
  • GitCloneNetworkPolicy (74-119)
  • is_allowed_clone_scheme (197-217)
  • validate_clone_url_host (300-352)
🔇 Additional comments (14)
src/synthorg/observability/events/git.py (1)

11-12: LGTM!

New event constants follow the established naming convention (git.clone.*) and are properly typed as Final[str].

CLAUDE.md (2)

135-135: LGTM!

Documentation accurately reflects the new git_url_validator module added for SSRF prevention.


199-199: LGTM!

Event constants list correctly updated with the new GIT_CLONE_SSRF_BLOCKED and GIT_CLONE_DNS_FAILED events from events.git.

src/synthorg/config/schema.py (1)

35-35: LGTM!

Import correctly placed with other tools-related imports.

src/synthorg/config/defaults.py (1)

43-43: LGTM!

Default entry follows the established pattern. The empty dict will be merged with GitCloneNetworkPolicy's defaults via the schema's default_factory.

src/synthorg/tools/__init__.py (2)

31-31: LGTM!

Import correctly placed and follows the established module organization.


61-61: LGTM!

Export added in correct alphabetical position within __all__.

tests/unit/tools/git/test_git_clone.py (3)

1-12: LGTM!

Clean module setup with appropriate imports and timeout configuration.


18-91: LGTM!

Comprehensive test coverage for GitCloneTool basic operations: local clone, shallow clone with depth, directory traversal prevention, invalid URL rejection, and branch-specific cloning.


97-153: LGTM!

SSRF prevention tests effectively cover key scenarios: loopback blocking, private IP blocking, allowlist bypass verification, and file:// scheme rejection. The allowlist test correctly validates that SSRF checks pass (no "blocked"/"ssrf" in output) while allowing the clone to fail for other reasons.

tests/unit/tools/git/test_git_tools.py (1)

11-12: LGTM!

Good documentation explaining why GitCloneTool is still imported despite clone-specific tests being moved to test_git_clone.py. The tool correctly remains in _ALL_GIT_TOOL_CLASSES for property-based tests.

tests/unit/tools/git/conftest.py (1)

176-200: LGTM!

Fixture correctly updated to bypass both the scheme check (is_allowed_clone_scheme) and the new SSRF host validation (validate_clone_url_host) for local file:// clone tests. The async signature of _allow_all_hosts correctly matches the production function's signature.

src/synthorg/tools/git_tools.py (1)

712-717: SSRF validation ordering looks correct.

Running host/IP validation right before clone execution (after cheap local checks) keeps the flow fail-closed without unnecessary DNS work.

tests/unit/tools/git/test_git_url_validator.py (1)

617-617: Remove hardcoded @settings(max_examples=200) to respect Hypothesis profiles.

Per coding guidelines, HYPOTHESIS_PROFILE environment variable should control example counts (dev: 1000, ci: 200), not hardcoded decorators. Remove @settings(max_examples=200) from lines 617, 631, and 641.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/synthorg/tools/git_url_validator.py (1)

45-47: ⚠️ Potential issue | 🟠 Major

Credential redaction is incomplete for non-HTTP URL forms.

Line [46] only redacts https?://...@. If hostname extraction fails later (Line [344]), inputs like ssh://token@/repo.git can still be logged with userinfo intact.

🔒 Proposed hardening
-_CREDENTIAL_RE: Final[re.Pattern[str]] = re.compile(r"(https?://)[^@/]+@")
+_URL_USERINFO_RE: Final[re.Pattern[str]] = re.compile(
+    r"((?:https?|ssh)://)[^@/]+@"
+)
+_SCP_USERINFO_RE: Final[re.Pattern[str]] = re.compile(r"^([^@/:]+)@")
+
+def _redact_clone_url(url: str) -> str:
+    """Redact userinfo in clone URL logs."""
+    redacted = _URL_USERINFO_RE.sub(r"\1***@", url)
+    if "://" not in redacted:
+        redacted = _SCP_USERINFO_RE.sub("***@", redacted)
+    return redacted
-        redacted = _CREDENTIAL_RE.sub(r"\1***@", url)
+        redacted = _redact_clone_url(url)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/tools/git_url_validator.py` around lines 45 - 47, The current
credential redaction regex _CREDENTIAL_RE only matches http(s) schemes; broaden
it to detect userinfo for any URL scheme (e.g., match any scheme like
ssh://user@ or token@) by updating _CREDENTIAL_RE to accept arbitrary scheme
prefixes before the userinfo, and ensure the sanitization path used as a
fallback when hostname extraction fails (the code around the hostname extraction
at ~line 344) applies this regex so inputs like ssh://token@/repo.git are
redacted before logging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/unit/tools/git/test_git_url_validator.py`:
- Around line 602-619: The test duplicates production IPv4 blocklist
(_ALL_BLOCKED_V4) causing drift; instead import and reuse the canonical
_BLOCKED_NETWORKS from the production module and replace the local
_ALL_BLOCKED_V4 tuple with a reference or derived tuple from that imported
constant (e.g., tuple(_BLOCKED_NETWORKS) or similar) so tests always reflect
current production ranges; update any tests that iterate over _ALL_BLOCKED_V4 to
use the imported _BLOCKED_NETWORKS symbol (or a frozen copy) and remove the
duplicated literal list.

---

Duplicate comments:
In `@src/synthorg/tools/git_url_validator.py`:
- Around line 45-47: The current credential redaction regex _CREDENTIAL_RE only
matches http(s) schemes; broaden it to detect userinfo for any URL scheme (e.g.,
match any scheme like ssh://user@ or token@) by updating _CREDENTIAL_RE to
accept arbitrary scheme prefixes before the userinfo, and ensure the
sanitization path used as a fallback when hostname extraction fails (the code
around the hostname extraction at ~line 344) applies this regex so inputs like
ssh://token@/repo.git are redacted before logging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d683ebf7-46d2-4d99-9c97-67c7820414b3

📥 Commits

Reviewing files that changed from the base of the PR and between 35a267d and 049d6a2.

📒 Files selected for processing (7)
  • CLAUDE.md
  • src/synthorg/config/schema.py
  • src/synthorg/observability/events/git.py
  • src/synthorg/tools/git_tools.py
  • src/synthorg/tools/git_url_validator.py
  • tests/unit/tools/git/test_git_clone.py
  • tests/unit/tools/git/test_git_url_validator.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Do not use from __future__ import annotations in Python code. Python 3.14 has PEP 649 native lazy annotations.
Use except A, B: (no parentheses) for exception syntax on Python 3.14. Do not use except (A, B):.
Add type hints to all public functions and classes. Use mypy strict mode.
Use Google-style docstrings on all public classes and functions. Docstrings are enforced by ruff D rules.
Create new objects for immutability. Never mutate existing objects. For non-Pydantic internal collections, use copy.deepcopy() at construction and wrap with MappingProxyType for read-only enforcement.
Use frozen Pydantic models for config and identity. Use separate mutable-via-copy models with model_copy(update=...) for runtime state that evolves.
Do not mix static config fields with mutable runtime fields in one Pydantic model.
Use Pydantic v2 conventions: @computed_field for derived values, NotBlankStr for identifier/name fields (including optional and tuple variants), and model_validator/ConfigDict.
Use asyncio.TaskGroup for fan-out/fan-in parallel operations in new code. Prefer structured concurrency over bare create_task.
Keep function bodies under 50 lines and files under 800 lines.
Handle errors explicitly. Never silently swallow exceptions.
Validate user input, external API responses, and config files at system boundaries.
Use ruff check and ruff format for Python linting and formatting (88-character line length).
Use mypy with strict mode for type-checking all Python code.
Python version must be 3.14 or higher. PEP 649 provides native lazy annotations.

Files:

  • src/synthorg/config/schema.py
  • tests/unit/tools/git/test_git_url_validator.py
  • src/synthorg/tools/git_tools.py
  • tests/unit/tools/git/test_git_clone.py
  • src/synthorg/tools/git_url_validator.py
  • src/synthorg/observability/events/git.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: Every module with business logic must import logger with from synthorg.observability import get_logger then logger = get_logger(__name__). Never use import logging, logging.getLogger(), or print() in application code.
Always use logger as the variable name for loggers. Never use _logger or log.
Use event name constants from domain-specific modules under synthorg.observability.events for all logging calls. Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.
Use structured logging with logger.info(EVENT, key=value) syntax. Never use format string logging like logger.info('msg %s', val).
All error paths must log at WARNING or ERROR level with context before raising.
All state transitions must log at INFO level.
Use DEBUG level for object creation, internal flow, and entry/exit of key functions.
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small as aliases. Use test-provider, test-small-001, etc. in tests.
Library API reference is auto-generated from docstrings via mkdocstrings + Griffe in docs/api/. Use Google-style docstrings for public APIs.
Use Pydantic BaseModel for all data models. Frozen models for config/identity. Mutable-via-copy models for runtime state.

Files:

  • src/synthorg/config/schema.py
  • src/synthorg/tools/git_tools.py
  • src/synthorg/tools/git_url_validator.py
  • src/synthorg/observability/events/git.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, and @pytest.mark.slow to mark test cases.
Prefer @pytest.mark.parametrize for testing similar cases with multiple inputs.
Use Hypothesis for property-based testing in Python with @given + @settings. Control via HYPOTHESIS_PROFILE env var (dev: 1000 examples, ci: 200 examples).

Files:

  • tests/unit/tools/git/test_git_url_validator.py
  • tests/unit/tools/git/test_git_clone.py
src/synthorg/tools/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Tool registry supports built-in tools (file_system/, git, sandbox/, code_runner), MCP bridge, role-based access, approval tool (request_human_approval).

Files:

  • src/synthorg/tools/git_tools.py
  • src/synthorg/tools/git_url_validator.py
src/synthorg/observability/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Observability includes structured logging via get_logger(__name__), correlation tracking, and log sinks.

Files:

  • src/synthorg/observability/events/git.py
🧠 Learnings (22)
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/tools/**/*.py : Tool registry supports built-in tools (file_system/, git, sandbox/, code_runner), MCP bridge, role-based access, approval tool (request_human_approval).

Applied to files:

  • src/synthorg/config/schema.py
  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to **/*.py : Use frozen Pydantic models for config and identity. Use separate mutable-via-copy models with `model_copy(update=...)` for runtime state that evolves.

Applied to files:

  • src/synthorg/tools/git_url_validator.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to **/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).

Applied to files:

  • src/synthorg/tools/git_url_validator.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to **/*.py : Config vs runtime state: frozen Pydantic models for config/identity; separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/tools/git_url_validator.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/**/*.py : Use Pydantic BaseModel for all data models. Frozen models for config/identity. Mutable-via-copy models for runtime state.

Applied to files:

  • src/synthorg/tools/git_url_validator.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to **/*.py : Create new objects for immutability. Never mutate existing objects. For non-Pydantic internal collections, use `copy.deepcopy()` at construction and wrap with `MappingProxyType` for read-only enforcement.

Applied to files:

  • src/synthorg/tools/git_url_validator.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to web/src/{components,views,styles}/**/* : Vue dashboard uses PrimeVue components + Tailwind CSS styling. Global CSS in styles/, theme configuration in styles/.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/security/**/*.py : Security module includes SecOps agent, rule engine (soft-allow/hard-deny), audit log, output scanner, risk classifier, autonomy levels (4 strategies), timeout policies.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to web/src/components/**/*.vue : Vue dashboard uses TypeScript. Organize components by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.651Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.651Z
Learning: Security architecture is documented in `docs/security.md` (comprehensive guide covering SecOps agent, rule engine, audit log, output scanning, risk classification, autonomy levels, timeout policies).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to docker/{Dockerfile*,compose.yml} : Docker: Backend uses 3-stage build (builder → setup → distroless runtime), Chainguard Python, non-root (UID 65532), CIS-hardened. Web uses nginxinc/nginx-unprivileged, Vue 3 SPA with PrimeVue + Tailwind CSS, SPA routing, API/WebSocket proxy to backend.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : Structured kwargs in logging: always `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger` (not `_logger`, not `log`).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/**/*.py : Every module with business logic must import logger with `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging`, `logging.getLogger()`, or `print()` in application code.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` for all logging calls. Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/**/*.py : Use structured logging with `logger.info(EVENT, key=value)` syntax. Never use format string logging like `logger.info('msg %s', val)`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to src/synthorg/**/*.py : All state transitions must log at INFO.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability includes structured logging via `get_logger(__name__)`, correlation tracking, and log sinks.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/**/*.py : All state transitions must log at INFO level.

Applied to files:

  • CLAUDE.md
🧬 Code graph analysis (4)
src/synthorg/config/schema.py (1)
src/synthorg/tools/git_url_validator.py (1)
  • GitCloneNetworkPolicy (82-127)
tests/unit/tools/git/test_git_url_validator.py (1)
src/synthorg/tools/git_url_validator.py (5)
  • GitCloneNetworkPolicy (82-127)
  • _extract_hostname (162-202)
  • _is_blocked_ip (133-159)
  • is_allowed_clone_scheme (205-233)
  • validate_clone_url_host (318-376)
src/synthorg/tools/git_tools.py (1)
src/synthorg/tools/git_url_validator.py (3)
  • GitCloneNetworkPolicy (82-127)
  • is_allowed_clone_scheme (205-233)
  • validate_clone_url_host (318-376)
tests/unit/tools/git/test_git_clone.py (2)
src/synthorg/tools/git_tools.py (7)
  • GitCloneTool (596-720)
  • execute (86-104)
  • execute (205-248)
  • execute (314-360)
  • execute (454-498)
  • execute (556-590)
  • execute (661-720)
src/synthorg/tools/git_url_validator.py (1)
  • GitCloneNetworkPolicy (82-127)
🔇 Additional comments (7)
src/synthorg/observability/events/git.py (1)

11-13: Looks good: SSRF/DNS event constants are clear and consistent.

These constants fit the existing git event naming scheme and support the new observability paths cleanly.

src/synthorg/tools/git_url_validator.py (1)

133-376: Strong fail-closed SSRF validation flow.

Blocked-range checks, IPv6-mapped IPv4 handling, DNS timeout/error handling, and “all resolved IPs must be public” logic are implemented defensively.

tests/unit/tools/git/test_git_clone.py (1)

18-153: Good integration coverage for clone + SSRF paths.

The added cases exercise both normal clone flows and SSRF policy behaviors, including blocked/private literals and allowlist bypass.

src/synthorg/config/schema.py (1)

422-423: Config/schema integration is clean.

RootConfig.git_clone is properly documented and wired with default_factory=GitCloneNetworkPolicy, matching the new network policy surface.

Also applies to: 538-541

tests/unit/tools/git/test_git_url_validator.py (1)

26-598: Great test depth for SSRF and DNS edge cases.

The suite covers literal IPs, DNS failures, mixed DNS answers, IPv6 paths, and property-based guarantees well.

Also applies to: 622-662

src/synthorg/tools/git_tools.py (1)

612-613: SSRF enforcement is integrated in the right execution point.

Running cheap local validations first and DNS-based host checks before git clone is a solid ordering for both performance and safety.

Also applies to: 657-659, 668-670, 680-720

CLAUDE.md (1)

135-135: Docs update is aligned with the feature changes.

The tool-registry and logging guidance now reflect the new git clone SSRF validation/event model.

Also applies to: 199-199

Comment on lines +121 to +127
@model_validator(mode="after")
def _normalize_allowlist(self) -> Self:
"""Lowercase and deduplicate allowlist entries."""
normalized = tuple(dict.fromkeys(h.lower() for h in self.hostname_allowlist))
if normalized != self.hostname_allowlist:
object.__setattr__(self, "hostname_allowlist", normalized)
return self
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify frozen-model mutation pattern in validator implementation.
rg -n --type py 'object\.__setattr__\(|model_validator\(mode="after"\)|field_validator\(' src/synthorg/tools/git_url_validator.py

Repository: Aureliolo/synthorg

Length of output: 175


🏁 Script executed:

cat -n src/synthorg/tools/git_url_validator.py | head -150

Repository: Aureliolo/synthorg

Length of output: 6729


Use field_validator for field normalization instead of mutating a frozen model.

Line 126 mutates the frozen model via object.__setattr__, which violates the immutability principle. A field_validator normalizes the field during construction without post-hoc mutation.

♻️ Proposed refactor
-from typing import Final, Self
+from typing import Final
 ...
-from pydantic import BaseModel, ConfigDict, Field, model_validator
+from pydantic import BaseModel, ConfigDict, Field, field_validator
 ...
-    `@model_validator`(mode="after")
-    def _normalize_allowlist(self) -> Self:
-        """Lowercase and deduplicate allowlist entries."""
-        normalized = tuple(dict.fromkeys(h.lower() for h in self.hostname_allowlist))
-        if normalized != self.hostname_allowlist:
-            object.__setattr__(self, "hostname_allowlist", normalized)
-        return self
+    `@field_validator`("hostname_allowlist")
+    `@classmethod`
+    def _normalize_allowlist(
+        cls,
+        value: tuple[NotBlankStr, ...],
+    ) -> tuple[NotBlankStr, ...]:
+        """Lowercase and deduplicate allowlist entries."""
+        return tuple(dict.fromkeys(h.lower() for h in value))

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/unit/tools/git/test_git_url_validator.py`:
- Around line 601-647: Add a symmetric property test for IPv6 non-blocked
addresses: create a new test method named test_non_blocked_ipv6_never_flagged
under TestValidateCloneUrlHostProperties that uses Hypothesis with
st.ip_addresses(v=6).filter(lambda ip: not any(ip in net for net in
_ALL_BLOCKED_V6)), calls assert _is_blocked_ip(str(ip)) is False, and reduce
examples (e.g. `@settings`(max_examples=50)) to avoid heavy filtering; this
mirrors test_non_blocked_ipv4_never_flagged and references the existing
_ALL_BLOCKED_V6 and _is_blocked_ip symbols.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fea07877-6e0a-4225-9cf5-766c7d3cd09b

📥 Commits

Reviewing files that changed from the base of the PR and between 049d6a2 and b3f9654.

📒 Files selected for processing (2)
  • src/synthorg/tools/git_url_validator.py
  • tests/unit/tools/git/test_git_url_validator.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Do not use from __future__ import annotations in Python code. Python 3.14 has PEP 649 native lazy annotations.
Use except A, B: (no parentheses) for exception syntax on Python 3.14. Do not use except (A, B):.
Add type hints to all public functions and classes. Use mypy strict mode.
Use Google-style docstrings on all public classes and functions. Docstrings are enforced by ruff D rules.
Create new objects for immutability. Never mutate existing objects. For non-Pydantic internal collections, use copy.deepcopy() at construction and wrap with MappingProxyType for read-only enforcement.
Use frozen Pydantic models for config and identity. Use separate mutable-via-copy models with model_copy(update=...) for runtime state that evolves.
Do not mix static config fields with mutable runtime fields in one Pydantic model.
Use Pydantic v2 conventions: @computed_field for derived values, NotBlankStr for identifier/name fields (including optional and tuple variants), and model_validator/ConfigDict.
Use asyncio.TaskGroup for fan-out/fan-in parallel operations in new code. Prefer structured concurrency over bare create_task.
Keep function bodies under 50 lines and files under 800 lines.
Handle errors explicitly. Never silently swallow exceptions.
Validate user input, external API responses, and config files at system boundaries.
Use ruff check and ruff format for Python linting and formatting (88-character line length).
Use mypy with strict mode for type-checking all Python code.
Python version must be 3.14 or higher. PEP 649 provides native lazy annotations.

Files:

  • src/synthorg/tools/git_url_validator.py
  • tests/unit/tools/git/test_git_url_validator.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: Every module with business logic must import logger with from synthorg.observability import get_logger then logger = get_logger(__name__). Never use import logging, logging.getLogger(), or print() in application code.
Always use logger as the variable name for loggers. Never use _logger or log.
Use event name constants from domain-specific modules under synthorg.observability.events for all logging calls. Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.
Use structured logging with logger.info(EVENT, key=value) syntax. Never use format string logging like logger.info('msg %s', val).
All error paths must log at WARNING or ERROR level with context before raising.
All state transitions must log at INFO level.
Use DEBUG level for object creation, internal flow, and entry/exit of key functions.
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small as aliases. Use test-provider, test-small-001, etc. in tests.
Library API reference is auto-generated from docstrings via mkdocstrings + Griffe in docs/api/. Use Google-style docstrings for public APIs.
Use Pydantic BaseModel for all data models. Frozen models for config/identity. Mutable-via-copy models for runtime state.

Files:

  • src/synthorg/tools/git_url_validator.py
src/synthorg/tools/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Tool registry supports built-in tools (file_system/, git, sandbox/, code_runner), MCP bridge, role-based access, approval tool (request_human_approval).

Files:

  • src/synthorg/tools/git_url_validator.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, and @pytest.mark.slow to mark test cases.
Prefer @pytest.mark.parametrize for testing similar cases with multiple inputs.
Use Hypothesis for property-based testing in Python with @given + @settings. Control via HYPOTHESIS_PROFILE env var (dev: 1000 examples, ci: 200 examples).

Files:

  • tests/unit/tools/git/test_git_url_validator.py
🧠 Learnings (6)
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to **/*.py : Use frozen Pydantic models for config and identity. Use separate mutable-via-copy models with `model_copy(update=...)` for runtime state that evolves.

Applied to files:

  • src/synthorg/tools/git_url_validator.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to **/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).

Applied to files:

  • src/synthorg/tools/git_url_validator.py
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to **/*.py : Config vs runtime state: frozen Pydantic models for config/identity; separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/tools/git_url_validator.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to src/**/*.py : Use Pydantic BaseModel for all data models. Frozen models for config/identity. Mutable-via-copy models for runtime state.

Applied to files:

  • src/synthorg/tools/git_url_validator.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to **/*.py : Create new objects for immutability. Never mutate existing objects. For non-Pydantic internal collections, use `copy.deepcopy()` at construction and wrap with `MappingProxyType` for read-only enforcement.

Applied to files:

  • src/synthorg/tools/git_url_validator.py
📚 Learning: 2026-03-16T20:59:42.650Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:59:42.650Z
Learning: Applies to **/*.py : Use Pydantic v2 conventions: `computed_field` for derived values, `NotBlankStr` for identifier/name fields (including optional and tuple variants), and `model_validator`/`ConfigDict`.

Applied to files:

  • src/synthorg/tools/git_url_validator.py
🧬 Code graph analysis (1)
tests/unit/tools/git/test_git_url_validator.py (1)
src/synthorg/tools/git_url_validator.py (5)
  • GitCloneNetworkPolicy (82-127)
  • _extract_hostname (162-202)
  • _is_blocked_ip (133-159)
  • is_allowed_clone_scheme (205-233)
  • validate_clone_url_host (318-376)
🔇 Additional comments (14)
tests/unit/tools/git/test_git_url_validator.py (6)

1-21: LGTM!

Imports are well-organized, and the module-level timeout marker is appropriate for async DNS tests. Importing _BLOCKED_NETWORKS from the production module ensures test data stays in sync with production blocklists.


27-91: LGTM!

Comprehensive hostname extraction tests covering standard URLs, SCP-like syntax, IPv6 literals, and edge cases. Parametrization with descriptive IDs aids readability.


97-217: LGTM!

Excellent coverage of blocked IP ranges including loopback, private, CGNAT, link-local, multicast, reserved IPv4/IPv6 ranges, and the critical IPv6-mapped IPv4 bypass vectors. The fail-closed test for unparseable inputs verifies secure default behavior.


223-275: LGTM!

Comprehensive scheme validation covering allowed remote protocols, SCP-like syntax with IPv6 literals, and blocked patterns including flag injection, ext:: protocol, file:// scheme, and insecure http.


281-348: LGTM!

Thorough coverage of the GitCloneNetworkPolicy model including defaults, immutability enforcement, timeout bounds, inf/nan rejection, and allowlist normalization with deduplication. The NotBlankStr validation tests ensure empty/whitespace entries are properly rejected.


354-598: LGTM!

Comprehensive async validation tests covering:

  • Public/private IP resolution
  • DNS rebinding with mixed results (one private blocks all)
  • Literal IP handling (IPv4, IPv6, IPv6-mapped)
  • Allowlist bypass with case-insensitive matching
  • Fail-closed behavior for all DNS error paths (timeout, NXDOMAIN, empty results, unexpected exceptions)
  • SCP-like URL validation
  • Master switch (block_private_ips=False) behavior

The mock pattern using monkeypatch.setattr on loop.getaddrinfo is appropriate for async DNS testing.

src/synthorg/tools/git_url_validator.py (8)

1-36: LGTM!

The module docstring appropriately documents the TOCTOU limitation with a note on network-level egress controls for high-security deployments. Logger setup follows project conventions with get_logger(__name__) and event constants from the observability module.


38-76: LGTM!

Comprehensive IP blocklist covering all RFC-reserved ranges:

  • IPv4: loopback, private, CGNAT, link-local, TEST-NETs, multicast, reserved, broadcast
  • IPv6: loopback, unspecified, NAT64, discard, Teredo, documentation, 6to4, ULA, link-local, multicast

The IPv6 multicast block (ff00::/8) is now included, addressing the prior review concern.


121-127: Consider using field_validator for cleaner normalization.

The object.__setattr__ pattern in model_validator(mode="after") works but circumvents the frozen model's immutability semantics during construction. A field_validator normalizes the field before the model is frozen, avoiding the workaround.

♻️ Proposed refactor
-from typing import Final, Self
+from typing import Final
 ...
-from pydantic import BaseModel, ConfigDict, Field, model_validator
+from pydantic import BaseModel, ConfigDict, Field, field_validator
 ...
-    `@model_validator`(mode="after")
-    def _normalize_allowlist(self) -> Self:
-        """Lowercase and deduplicate allowlist entries."""
-        normalized = tuple(dict.fromkeys(h.lower() for h in self.hostname_allowlist))
-        if normalized != self.hostname_allowlist:
-            object.__setattr__(self, "hostname_allowlist", normalized)
-        return self
+    `@field_validator`("hostname_allowlist", mode="after")
+    `@classmethod`
+    def _normalize_allowlist(
+        cls,
+        value: tuple[NotBlankStr, ...],
+    ) -> tuple[NotBlankStr, ...]:
+        """Lowercase and deduplicate allowlist entries."""
+        return tuple(dict.fromkeys(h.lower() for h in value))

133-159: LGTM!

Correct fail-closed behavior for unparseable IPs with appropriate logging. The IPv6-mapped IPv4 unwrapping at line 156-157 prevents ::ffff:127.0.0.1 bypass attacks.


162-202: LGTM!

Robust hostname extraction supporting standard URLs, SCP-like syntax, and IPv6 literals with proper bracket handling. Returns None for malformed inputs enabling fail-closed validation upstream.


205-233: LGTM!

Scheme validation correctly:

  • Blocks flag injection (-cfoo=bar)
  • Allows https:// and ssh:// schemes
  • Supports SCP-like syntax with bracketed IPv6 literals (git@[2001:db8::1]:repo.git)
  • Rejects ext:: protocol by detecting unbracketed ::

The prior review concern about SCP-like IPv6 rejection is now addressed.


239-312: LGTM!

DNS resolution is correctly implemented with:

  • Timeout handling via asyncio.wait_for
  • Fail-closed behavior for all error paths (timeout, NXDOMAIN, unexpected exceptions, empty results)
  • All-must-pass validation preventing DNS rebinding with mixed public/private results
  • Appropriate logging levels (WARNING for expected failures, ERROR with exc_info for unexpected)

318-376: LGTM!

Main validator correctly implements the validation flow:

  1. Hostname extraction with credential redaction for safe logging
  2. Allowlist bypass (early return for internal Git servers)
  3. Master switch audit logging when block_private_ips=False
  4. Literal IP handling without DNS overhead
  5. DNS resolution fallback for hostnames

The credential redaction pattern at line 338 ensures sensitive tokens don't leak into logs.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 16, 2026

Codecov Report

❌ Patch coverage is 98.30508% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.11%. Comparing base (8dd2524) to head (6862a67).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/synthorg/tools/git_url_validator.py 98.11% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #505      +/-   ##
==========================================
+ Coverage   93.08%   93.11%   +0.02%     
==========================================
  Files         502      503       +1     
  Lines       24202    24312     +110     
  Branches     2313     2330      +17     
==========================================
+ Hits        22529    22637     +108     
- Misses       1327     1328       +1     
- Partials      346      347       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/unit/tools/git/test_git_url_validator.py`:
- Around line 354-366: The helper functions _dns_result and _dns_result_v6 use
numeric socket family/type/protocol magic numbers; replace these with the socket
module constants (e.g., socket.AF_INET, socket.SOCK_STREAM or socket.SOCK_DGRAM
as appropriate, and socket.IPPROTO_TCP/IPPROTO_UDP or the protocol used in
tests) so the returned tuples read [(socket.AF_INET, socket.SOCK_STREAM,
socket.IPPROTO_TCP, "", (addr, 0))] and [(socket.AF_INET6, socket.SOCK_STREAM,
socket.IPPROTO_TCP, "", (addr, 0, 0, 0))] to improve readability while
preserving behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3dfc12ac-97b6-4672-824c-f7340c60e882

📥 Commits

Reviewing files that changed from the base of the PR and between b3f9654 and 0f321a0.

📒 Files selected for processing (1)
  • tests/unit/tools/git/test_git_url_validator.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Do not use from __future__ import annotations in Python code. Python 3.14 has PEP 649 native lazy annotations.
Use except A, B: (no parentheses) for exception syntax on Python 3.14. Do not use except (A, B):.
Add type hints to all public functions and classes. Use mypy strict mode.
Use Google-style docstrings on all public classes and functions. Docstrings are enforced by ruff D rules.
Create new objects for immutability. Never mutate existing objects. For non-Pydantic internal collections, use copy.deepcopy() at construction and wrap with MappingProxyType for read-only enforcement.
Use frozen Pydantic models for config and identity. Use separate mutable-via-copy models with model_copy(update=...) for runtime state that evolves.
Do not mix static config fields with mutable runtime fields in one Pydantic model.
Use Pydantic v2 conventions: @computed_field for derived values, NotBlankStr for identifier/name fields (including optional and tuple variants), and model_validator/ConfigDict.
Use asyncio.TaskGroup for fan-out/fan-in parallel operations in new code. Prefer structured concurrency over bare create_task.
Keep function bodies under 50 lines and files under 800 lines.
Handle errors explicitly. Never silently swallow exceptions.
Validate user input, external API responses, and config files at system boundaries.
Use ruff check and ruff format for Python linting and formatting (88-character line length).
Use mypy with strict mode for type-checking all Python code.
Python version must be 3.14 or higher. PEP 649 provides native lazy annotations.

Files:

  • tests/unit/tools/git/test_git_url_validator.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, and @pytest.mark.slow to mark test cases.
Prefer @pytest.mark.parametrize for testing similar cases with multiple inputs.
Use Hypothesis for property-based testing in Python with @given + @settings. Control via HYPOTHESIS_PROFILE env var (dev: 1000 examples, ci: 200 examples).

Files:

  • tests/unit/tools/git/test_git_url_validator.py
🧬 Code graph analysis (1)
tests/unit/tools/git/test_git_url_validator.py (1)
src/synthorg/tools/git_url_validator.py (5)
  • GitCloneNetworkPolicy (82-127)
  • _extract_hostname (162-202)
  • _is_blocked_ip (133-159)
  • is_allowed_clone_scheme (205-233)
  • validate_clone_url_host (318-376)
🔇 Additional comments (8)
tests/unit/tools/git/test_git_url_validator.py (8)

1-22: LGTM!

Imports are well-organized. The module-level timeout marker and importing _BLOCKED_NETWORKS from production to derive test data (preventing drift) are good practices.


27-92: LGTM!

Comprehensive parametrized tests for hostname extraction covering standard URLs, SCP-like syntax, and edge cases with descriptive test IDs.


97-218: LGTM!

Thorough coverage of blocked IP ranges including loopback, private, CGNAT, link-local, multicast, and reserved ranges for both IPv4 and IPv6. The IPv6-mapped IPv4 unwrapping and fail-closed behavior for unparseable IPs are properly tested.


223-276: LGTM!

Good coverage of scheme validation including security-sensitive patterns like flag injection (-cfoo=bar) and ext:: protocol blocking.


281-349: LGTM!

Comprehensive tests for the GitCloneNetworkPolicy model including defaults, bounds validation, immutability, inf/nan rejection, and allowlist normalization/deduplication behavior.


372-599: Comprehensive async test coverage for SSRF prevention.

Excellent coverage of:

  • DNS rebinding attacks with mocked resolution
  • Literal IP handling (IPv4, IPv6, IPv6-mapped IPv4)
  • AWS metadata endpoint (169.254.169.254)
  • Fail-closed behavior for all DNS error paths (timeout, NXDOMAIN, empty results, unexpected exceptions)
  • Mixed DNS results requiring all resolved IPs to be public
  • Allowlist bypass and case-insensitive matching

601-670: LGTM!

Property-based tests correctly derive blocked ranges from the production _BLOCKED_NETWORKS constant, preventing drift. The IPv6 non-blocked test properly excludes IPv6-mapped IPv4 addresses that map to blocked ranges, with a reduced example count (max_examples=50) to account for filter overhead.


368-371: The async test methods in this class are correctly implemented without explicit @pytest.mark.asyncio decorators. The project is properly configured with asyncio_mode = "auto" in pyproject.toml, which automatically detects and runs async test functions without requiring manual markers. This approach aligns with the project's coding guidelines.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/unit/tools/git/test_git_url_validator.py`:
- Around line 9-10: Remove the hardcoded max_examples values from the Hypothesis
settings decorators in tests/unit/tools/git/test_git_url_validator.py so the
HYPOTHESIS_PROFILE environment variable controls example counts; locate the four
occurrences of `@settings`(max_examples=...) (used on the test functions
referenced around lines 628, 642, 652, 669), and either remove the max_examples
argument or replace the decorator with `@settings`() (or omit the decorator if not
needed) so Hypothesis profiles (dev/ci) determine the number of examples; keep
the existing hypothesis imports (given, settings, strategies as st) intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ed35e578-aedf-4740-bc29-f99619228c88

📥 Commits

Reviewing files that changed from the base of the PR and between 0f321a0 and de1a6ac.

📒 Files selected for processing (1)
  • tests/unit/tools/git/test_git_url_validator.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Do not use from __future__ import annotations in Python code. Python 3.14 has PEP 649 native lazy annotations.
Use except A, B: (no parentheses) for exception syntax on Python 3.14. Do not use except (A, B):.
Add type hints to all public functions and classes. Use mypy strict mode.
Use Google-style docstrings on all public classes and functions. Docstrings are enforced by ruff D rules.
Create new objects for immutability. Never mutate existing objects. For non-Pydantic internal collections, use copy.deepcopy() at construction and wrap with MappingProxyType for read-only enforcement.
Use frozen Pydantic models for config and identity. Use separate mutable-via-copy models with model_copy(update=...) for runtime state that evolves.
Do not mix static config fields with mutable runtime fields in one Pydantic model.
Use Pydantic v2 conventions: @computed_field for derived values, NotBlankStr for identifier/name fields (including optional and tuple variants), and model_validator/ConfigDict.
Use asyncio.TaskGroup for fan-out/fan-in parallel operations in new code. Prefer structured concurrency over bare create_task.
Keep function bodies under 50 lines and files under 800 lines.
Handle errors explicitly. Never silently swallow exceptions.
Validate user input, external API responses, and config files at system boundaries.
Use ruff check and ruff format for Python linting and formatting (88-character line length).
Use mypy with strict mode for type-checking all Python code.
Python version must be 3.14 or higher. PEP 649 provides native lazy annotations.

Files:

  • tests/unit/tools/git/test_git_url_validator.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, and @pytest.mark.slow to mark test cases.
Prefer @pytest.mark.parametrize for testing similar cases with multiple inputs.
Use Hypothesis for property-based testing in Python with @given + @settings. Control via HYPOTHESIS_PROFILE env var (dev: 1000 examples, ci: 200 examples).

Files:

  • tests/unit/tools/git/test_git_url_validator.py
🧬 Code graph analysis (1)
tests/unit/tools/git/test_git_url_validator.py (1)
src/synthorg/tools/git_url_validator.py (5)
  • GitCloneNetworkPolicy (82-127)
  • _extract_hostname (162-202)
  • _is_blocked_ip (133-159)
  • is_allowed_clone_scheme (205-233)
  • validate_clone_url_host (318-376)
🔇 Additional comments (1)
tests/unit/tools/git/test_git_url_validator.py (1)

610-616: Good drift-proof test data setup.

Deriving _ALL_BLOCKED_V4 / _ALL_BLOCKED_V6 from production _BLOCKED_NETWORKS keeps property tests aligned with runtime policy.

Comment on lines +9 to +10
from hypothesis import given, settings
from hypothesis import strategies as st
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify this file no longer hardcodes per-test max_examples.
rg -n --type=py '@settings\(max_examples=' tests/unit/tools/git/test_git_url_validator.py

Repository: Aureliolo/synthorg

Length of output: 204


Remove hardcoded max_examples and rely on HYPOTHESIS_PROFILE environment variable.

Lines 628, 642, 652, and 669 use hardcoded @settings(max_examples=...) decorators, which bypass the HYPOTHESIS_PROFILE environment variable. Per coding guidelines, Hypothesis profile control should determine example counts (dev: 1000 examples, ci: 200 examples).

♻️ Proposed change
-from hypothesis import given, settings
+from hypothesis import given
 from hypothesis import strategies as st
@@
-    `@settings`(max_examples=200)
     def test_blocked_ipv4_always_detected(self, ip: ipaddress.IPv4Address) -> None:
@@
-    `@settings`(max_examples=200)
     def test_blocked_ipv6_always_detected(self, ip: ipaddress.IPv6Address) -> None:
@@
-    `@settings`(max_examples=200)
     def test_non_blocked_ipv4_never_flagged(self, ip: ipaddress.IPv4Address) -> None:
@@
-    `@settings`(max_examples=50)
     def test_non_blocked_ipv6_never_flagged(self, ip: ipaddress.IPv6Address) -> None:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/tools/git/test_git_url_validator.py` around lines 9 - 10, Remove
the hardcoded max_examples values from the Hypothesis settings decorators in
tests/unit/tools/git/test_git_url_validator.py so the HYPOTHESIS_PROFILE
environment variable controls example counts; locate the four occurrences of
`@settings`(max_examples=...) (used on the test functions referenced around lines
628, 642, 652, 669), and either remove the max_examples argument or replace the
decorator with `@settings`() (or omit the decorator if not needed) so Hypothesis
profiles (dev/ci) determine the number of examples; keep the existing hypothesis
imports (given, settings, strategies as st) intact.

Block git clone to private/loopback/link-local IPs with async DNS
resolution to prevent SSRF via agent-controlled URLs. Adds
GitCloneNetworkPolicy config model with hostname allowlist for
legitimate internal Git servers. All resolved IPs must be public;
fails closed on DNS errors.

- Extract git_url_validator.py from git_tools.py (scheme check,
  hostname extraction, IP blocklist, async DNS validation)
- Add GIT_CLONE_SSRF_BLOCKED / GIT_CLONE_DNS_FAILED event constants
- GitCloneTool accepts network_policy param, runs SSRF check after
  all cheap local validations (scheme, branch, directory)
- Add git_clone field to RootConfig for YAML-level configuration
- 76 new tests including Hypothesis property-based IP range coverage
Pre-reviewed by 11 agents, 19 findings addressed:

- Expand _BLOCKED_NETWORKS with CGNAT, TEST-NETs, multicast,
  reserved, broadcast, NAT64, discard, and documentation ranges
- Rename private API to public: is_allowed_clone_scheme,
  ALLOWED_CLONE_SCHEMES (cross-module contract)
- Use NotBlankStr for hostname_allowlist entries, normalize to
  lowercase and deduplicate at construction via model_validator
- Add logging on fail-closed paths: unparseable IP, hostname
  extraction failure, unexpected DNS exceptions (catch-all)
- Document TOCTOU gap in module docstring and at call site
- Extract _dns_failure helper to keep _resolve_and_check < 50 lines
- Move clone tool tests to test_git_clone.py (file size < 800)
- Add tests: IPv6 literal IPs, SCP literal IPs, bracket edge cases,
  inf/nan timeout rejection, allowlist normalization, scheme wiring,
  AF_INET6 DNS results, unexpected DNS exceptions
- Update CLAUDE.md: package structure, fix phantom GIT_OPERATION_START,
  add new SSRF event constants to logging section
- Redact credentials from clone URLs before logging (git_tools,
  git_url_validator) to prevent leaking userinfo in structured logs
- Add 6to4 (2002::/16), Teredo (2001::/32), IPv6 multicast (ff00::/8)
  to blocked networks — closes SSRF bypass via IPv6 tunneling ranges
- Fix SCP-like scheme check to allow bracketed IPv6 literals
  (git@[2001:db8::1]:repo.git) while still blocking ext:: protocol
- Log WARNING when block_private_ips=False is active (new
  GIT_CLONE_SSRF_DISABLED event) for audit trail on security bypasses
- Log unexpected DNS exceptions at ERROR with exc_info traceback
  instead of WARNING through the generic _dns_failure helper
- Add git_clone to RootConfig docstring Attributes section
- Remove redundant parentheses in test_git_clone.py
- Extend parametrized + Hypothesis property tests for new blocked
  ranges and SCP IPv6 scheme acceptance
…ts (#221)

- Broaden _CREDENTIAL_RE to redact userinfo from any scheme://
  URL (not just http/https) — covers ssh://token@host leakage
- Derive _ALL_BLOCKED_V4 and _ALL_BLOCKED_V6 from production
  _BLOCKED_NETWORKS constant to prevent test/production drift
)

Add symmetric test_non_blocked_ipv6_never_flagged property test that
mirrors the IPv4 counterpart.  Filter excludes IPv6-mapped IPv4
addresses (::ffff:x.x.x.x) that map to blocked IPv4 ranges, since
_is_blocked_ip unwraps these before checking.
Add isinstance guard for IPv6Address.ipv4_mapped access to satisfy
mypy union-attr check (Hypothesis ip_addresses returns IPv4 | IPv6).
…est helpers (#221)

Use socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP instead
of raw 2, 1, 6 in _dns_result and _dns_result_v6 test helpers.
@Aureliolo Aureliolo force-pushed the feat/ssrf-git-allowlist branch from de1a6ac to 6862a67 Compare March 17, 2026 06:35
@Aureliolo Aureliolo merged commit 492dd0d into main Mar 17, 2026
25 of 27 checks passed
@Aureliolo Aureliolo deleted the feat/ssrf-git-allowlist branch March 17, 2026 06:35
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 17, 2026 06:35 — with GitHub Actions Inactive
Aureliolo added a commit that referenced this pull request Mar 17, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.3.1](v0.3.0...v0.3.1)
(2026-03-17)


### Features

* **api:** RFC 9457 Phase 2 — ProblemDetail and content negotiation
([#496](#496))
([30f7c49](30f7c49))
* **cli:** verify container image signatures and SLSA provenance on pull
([#492](#492))
([bef272d](bef272d)),
closes [#491](#491)
* **engine:** implement context budget management in execution loops
([#520](#520))
([181eb8a](181eb8a)),
closes [#416](#416)
* implement settings persistence layer (DB-backed config)
([#495](#495))
([4bd99f7](4bd99f7)),
closes [#450](#450)
* **memory:** implement dual-mode archival in memory consolidation
([#524](#524))
([4603c9e](4603c9e)),
closes [#418](#418)
* migrate config consumers to read through SettingsService
([#510](#510))
([32f553d](32f553d))
* **settings:** implement settings change subscriptions for service
hot-reload ([#526](#526))
([53f908e](53f908e)),
closes [#503](#503)
* **settings:** register API config in SettingsService with 2-phase init
([#518](#518))
([29f7481](29f7481))
* **tools:** add SSRF prevention for git clone URLs
([#505](#505))
([492dd0d](492dd0d))
* **tools:** wire RootConfig.git_clone to GitCloneTool instantiation
([#519](#519))
([b7d8172](b7d8172))


### Bug Fixes

* **api:** replace JWT query parameter with one-time ticket for
WebSocket auth
([#493](#493))
([22a25f6](22a25f6)),
closes [#343](#343)


### Documentation

* add uv cache lock contention handling to worktree skill
([#500](#500))
([bd85a8d](bd85a8d))
* document RFC 9457 dual response formats in OpenAPI schema
([#506](#506))
([8dd2524](8dd2524))


### Maintenance

* upgrade jsdom from 28 to 29
([#499](#499))
([1ea2249](1ea2249))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add host/IP allowlisting for git clone URLs (SSRF prevention)

1 participant