refactor(util): move is_ipv4_literal out of pipelock.py into util.py
test / run tests/run_tests.py (pull_request) Successful in 25s

The classifier is a pure dotted-quad regex check — nothing
pipelock-specific about it. Pipelock now imports it from util.
test_pipelock_classify.py retargets at the new location.

Two manifest-accessor functions in pipelock.py
(pipelock_bottle_allowlist, pipelock_bottle_ssh_hostnames) look
generic but are 1-line wrappers used only internally; they stay
for now.
This commit is contained in:
2026-05-11 13:37:31 -04:00
parent ff962d2893
commit c62b3204a8
3 changed files with 17 additions and 16 deletions
+1 -13
View File
@@ -13,13 +13,13 @@ Image pin: ghcr.io/luckypipewrench/pipelock@sha256:<digest> for tag 2.3.0.
from __future__ import annotations
import os
import re
import subprocess
from dataclasses import dataclass
from pathlib import Path
from .log import die, info, warn
from .manifest import Manifest
from .util import is_ipv4_literal
# Pipelock image, pinned by digest. The digest is the multi-arch image
# index for ghcr.io/luckypipewrench/pipelock:2.3.0.
@@ -67,18 +67,6 @@ def pipelock_bottle_ssh_hostnames(manifest: Manifest, bottle_name: str) -> list[
return [e.Hostname for e in manifest.bottles[bottle_name].ssh if e.Hostname]
_IPV4_RE = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$")
def is_ipv4_literal(s: str) -> bool:
"""Pipelock's SSRF check fires on resolved IP, so an IP-literal
Hostname goes to ssrf.ip_allowlist while a hostname goes to
trusted_domains."""
if not s:
return False
return bool(_IPV4_RE.match(s))
def pipelock_bottle_ssh_trusted_domains(manifest: Manifest, bottle_name: str) -> list[str]:
return [h for h in pipelock_bottle_ssh_hostnames(manifest, bottle_name) if not is_ipv4_literal(h)]
+13
View File
@@ -6,6 +6,7 @@ claude_bottle/backend/docker/util.py."""
from __future__ import annotations
import os
import re
def expand_tilde(path: str) -> str:
@@ -16,3 +17,15 @@ def expand_tilde(path: str) -> str:
home = os.environ.get("HOME", "")
return home + path[1:]
return path
_IPV4_RE = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$")
def is_ipv4_literal(s: str) -> bool:
"""True iff `s` looks like a dotted-quad IPv4 literal. Does not
validate octet ranges; consumers that care about that should run
a stricter check. Empty input returns False."""
if not s:
return False
return bool(_IPV4_RE.match(s))
+3 -3
View File
@@ -1,10 +1,10 @@
"""Unit: is_ipv4_literal — the classifier that decides whether
bottle.ssh[].Hostname goes into ssrf.ip_allowlist (IPv4 literal) or
trusted_domains (hostname)."""
bottle.ssh[].Hostname goes into pipelock's ssrf.ip_allowlist (IPv4
literal) or trusted_domains (hostname)."""
import unittest
from claude_bottle.pipelock import is_ipv4_literal
from claude_bottle.util import is_ipv4_literal
class TestIPv4Classify(unittest.TestCase):