PRD 0003: Bottle Backend abstraction #5
@@ -13,13 +13,13 @@ Image pin: ghcr.io/luckypipewrench/pipelock@sha256:<digest> for tag 2.3.0.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import subprocess
|
import subprocess
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .log import die, info, warn
|
from .log import die, info, warn
|
||||||
from .manifest import Manifest
|
from .manifest import Manifest
|
||||||
|
from .util import is_ipv4_literal
|
||||||
|
|
||||||
# Pipelock image, pinned by digest. The digest is the multi-arch image
|
# Pipelock image, pinned by digest. The digest is the multi-arch image
|
||||||
# index for ghcr.io/luckypipewrench/pipelock:2.3.0.
|
# 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]
|
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]:
|
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)]
|
return [h for h in pipelock_bottle_ssh_hostnames(manifest, bottle_name) if not is_ipv4_literal(h)]
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ claude_bottle/backend/docker/util.py."""
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
def expand_tilde(path: str) -> str:
|
def expand_tilde(path: str) -> str:
|
||||||
@@ -16,3 +17,15 @@ def expand_tilde(path: str) -> str:
|
|||||||
home = os.environ.get("HOME", "")
|
home = os.environ.get("HOME", "")
|
||||||
return home + path[1:]
|
return home + path[1:]
|
||||||
return path
|
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))
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
"""Unit: is_ipv4_literal — the classifier that decides whether
|
"""Unit: is_ipv4_literal — the classifier that decides whether
|
||||||
bottle.ssh[].Hostname goes into ssrf.ip_allowlist (IPv4 literal) or
|
bottle.ssh[].Hostname goes into pipelock's ssrf.ip_allowlist (IPv4
|
||||||
trusted_domains (hostname)."""
|
literal) or trusted_domains (hostname)."""
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from claude_bottle.pipelock import is_ipv4_literal
|
from claude_bottle.util import is_ipv4_literal
|
||||||
|
|
||||||
|
|
||||||
class TestIPv4Classify(unittest.TestCase):
|
class TestIPv4Classify(unittest.TestCase):
|
||||||
|
|||||||
Reference in New Issue
Block a user