Skip to content

Commit 81387c8

Browse files
committed
Merge branch 'main' into svn-ssh-non-onteractive
2 parents f04d773 + fdf22cc commit 81387c8

15 files changed

Lines changed: 221 additions & 28 deletions

CHANGELOG.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
Release 0.14.0 (unreleased)
22
===========================
33

4+
* Warn when a project URL uses a plaintext transport scheme (#1229)
45
* Documentation and threat-model clarifications for existing release attestation support (#1208)
56
* Report SVN externals fetched during update (#1220)
67
* Use ``.cdx.json`` as the default extension for CycloneDX SBOM reports (#1118)
@@ -19,7 +20,7 @@ Release 0.14.0 (unreleased)
1920
* Fix arbitrary file write via malicious tar/zip symlink (#1152)
2021
* Prevent SSH command injection (#1152)
2122
* Allow manifests with no ``projects`` key so ``dfetch add`` can bootstrap empty manifest (#1197)
22-
* Run ``svn+ssh://`` connections in non-interactive mode to prevent hanging (#0)
23+
* Run ``svn+ssh://`` connections in non-interactive mode to prevent hanging (#1230)
2324

2425
Release 0.13.0 (released 2026-03-30)
2526
====================================

dfetch/manifest/project.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,13 +321,38 @@
321321
import copy
322322
from collections.abc import Sequence
323323
from dataclasses import dataclass, field
324+
from urllib.parse import urlsplit, urlunsplit
324325

325326
from typing_extensions import Required, TypedDict
326327

328+
from dfetch.log import get_logger
327329
from dfetch.manifest.remote import Remote
328330
from dfetch.manifest.version import Version
329331
from dfetch.util.util import always_str_list, str_if_possible
330332

333+
logger = get_logger(__name__)
334+
335+
_PLAINTEXT_SCHEMES = frozenset({"http", "git", "svn"})
336+
337+
338+
def plaintext_warning(url: str) -> str:
339+
"""Return a warning string if *url* uses a plaintext transport, else empty string."""
340+
parsed = urlsplit(url)
341+
scheme = parsed.scheme.lower()
342+
if scheme not in _PLAINTEXT_SCHEMES:
343+
return ""
344+
host = parsed.hostname or ""
345+
try:
346+
port = parsed.port
347+
except ValueError:
348+
port = None
349+
netloc = f"{host}:{port}" if isinstance(port, int) else host
350+
redacted_url = urlunsplit((scheme, netloc, parsed.path, "", ""))
351+
return (
352+
f"Project URL '{redacted_url}' uses plaintext transport ({scheme}://). "
353+
"Use https:// or SSH (e.g. svn+ssh://) to prevent interception."
354+
)
355+
331356

332357
@dataclass
333358
class Integrity:

dfetch/project/subproject.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from collections.abc import Callable, Sequence
77

88
from dfetch.log import get_logger
9-
from dfetch.manifest.project import ProjectEntry
9+
from dfetch.manifest.project import ProjectEntry, plaintext_warning
1010
from dfetch.manifest.version import Version
1111
from dfetch.project.abstract_check_reporter import AbstractCheckReporter
1212
from dfetch.project.metadata import Dependency, InvalidMetadataError, Metadata
@@ -129,6 +129,8 @@ def update(
129129
f"Fetching {to_fetch}",
130130
enabled=self._show_animations,
131131
):
132+
if warning := plaintext_warning(self.__project.remote_url):
133+
logger.print_warning_line(self.__project.name, warning)
132134
actually_fetched, dependency = self._fetch_impl(to_fetch)
133135
self._log_project(f"Fetched {actually_fetched}")
134136

@@ -213,6 +215,8 @@ def check_for_update(
213215
with logger.status(
214216
self.__project.name, "Checking", enabled=self._show_animations
215217
):
218+
if warning := plaintext_warning(self.__project.remote_url):
219+
logger.print_warning_line(self.__project.name, warning)
216220
latest_version = self._check_for_newer_version()
217221

218222
if not latest_version:

dfetch/vcs/svn.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,13 @@ def _raise_if_ssh_host_key_error(url: str, exc: SubprocessCommandError) -> None:
4545
"""Raise a helpful RuntimeError if *exc* looks like an SSH host-key failure."""
4646
stderr_lower = exc.stderr.lower()
4747
if any(msg in stderr_lower for msg in _SSH_HOST_KEY_MSGS):
48+
parsed = urlparse(url)
49+
host_only = parsed.hostname or url
4850
target = _ssh_target_from_url(url)
4951
raise RuntimeError(
5052
f"SSH host key verification failed while connecting to '{url}'.\n"
5153
"Add the host to your known hosts file, for example by running:\n"
52-
f" ssh-keyscan {target} >> ~/.ssh/known_hosts\n"
54+
f" ssh-keyscan {host_only} >> ~/.ssh/known_hosts\n"
5355
"Or test the SSH connection manually:\n"
5456
f" ssh -T {target}"
5557
) from exc
@@ -134,7 +136,7 @@ def list_of_tags(self) -> list[str]:
134136
)
135137
except SubprocessCommandError as exc:
136138
_raise_if_ssh_host_key_error(self._remote, exc)
137-
raise
139+
return []
138140
return [
139141
str(tag).strip("/\r") for tag in result.stdout.decode().split("\n") if tag
140142
]
@@ -205,7 +207,11 @@ def is_svn(self) -> bool:
205207
"""Check if is SVN."""
206208
try:
207209
with in_directory(self._path):
208-
run_on_cmdline(logger, ["svn", "info", "--non-interactive"])
210+
run_on_cmdline(
211+
logger,
212+
["svn", "info", "--non-interactive"],
213+
env=_extend_env_for_non_interactive_mode(),
214+
)
209215
return True
210216
except (SubprocessCommandError, RuntimeError):
211217
return False
@@ -222,6 +228,7 @@ def externals(self) -> list[External]:
222228
"svn:externals",
223229
"-R",
224230
],
231+
env=_extend_env_for_non_interactive_mode(),
225232
)
226233
repo_root = SvnRepo.get_info_from_target()["Repository Root"]
227234
return SvnRepo._parse_externals(
@@ -518,7 +525,9 @@ def create_diff(
518525
)
519526

520527
with in_directory(self._path):
521-
patch_text = run_on_cmdline(logger, cmd).stdout
528+
patch_text = run_on_cmdline(
529+
logger, cmd, env=_extend_env_for_non_interactive_mode()
530+
).stdout
522531

523532
if not patch_text.strip():
524533
return Patch.empty().convert_type(PatchType.SVN)
@@ -537,6 +546,7 @@ def get_username(self) -> str:
537546
"author",
538547
self._path,
539548
],
549+
env=_extend_env_for_non_interactive_mode(),
540550
)
541551
return str(result.stdout.decode().strip())
542552
except SubprocessCommandError:

doc/explanation/security.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ to reproduce a deterministic dependency state.
9696
allow exfiltration risks if upstream sources are compromised or intentionally
9797
malicious.
9898

99+
.. note::
100+
101+
dfetch warns during dependency update/check operations when a project URL uses a plaintext
102+
transport scheme (``http://``, ``git://``, or ``svn://``). Use ``https://``
103+
or SSH (e.g. ``svn+ssh://``) to protect dependency fetches against interception.
104+
99105
Threat Models
100106
-------------
101107

example/dfetch.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ manifest:
77
default: true # Set it as default
88

99
- name: sourceforge
10-
url-base: svn+ssh://svn.code.sf.net/p/
10+
url-base: svn://svn.code.sf.net/p/
1111

1212
projects:
1313

features/check-svn-repo.feature

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Feature: Checking dependencies from a svn repository
1111
1212
remotes:
1313
- name: cunit
14-
url-base: svn://svn.code.sf.net/p/cunit/code
14+
url-base: https://svn.code.sf.net/p/cunit/code
1515
1616
projects:
1717
- name: cunit-svn-rev-only
@@ -44,7 +44,7 @@ Feature: Checking dependencies from a svn repository
4444
4545
remotes:
4646
- name: cutter
47-
url-base: svn://svn.code.sf.net/p/cutter/svn/cutter
47+
url-base: https://svn.code.sf.net/p/cutter/svn/cutter
4848
4949
projects:
5050
- name: cutter-svn-tag
@@ -69,7 +69,7 @@ Feature: Checking dependencies from a svn repository
6969
7070
remotes:
7171
- name: cunit
72-
url-base: svn://svn.code.sf.net/p/cunit/code
72+
url-base: https://svn.code.sf.net/p/cunit/code
7373
default: true
7474
7575
projects:
@@ -152,7 +152,7 @@ Feature: Checking dependencies from a svn repository
152152
153153
remotes:
154154
- name: cutter
155-
url-base: svn://svn.code.sf.net/p/cutter/svn/cutter
155+
url-base: https://svn.code.sf.net/p/cutter/svn/cutter
156156
157157
projects:
158158
- name: cutter-svn-tag

features/fetch-svn-repo.feature

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ Feature: Fetching dependencies from a svn repository
1717
1818
remotes:
1919
- name: cunit
20-
url-base: svn://svn.code.sf.net/p/cunit/code
20+
url-base: https://svn.code.sf.net/p/cunit/code
2121
default: true
2222
2323
- name: cutter
24-
url-base: svn://svn.code.sf.net/p/cutter/svn/cutter
24+
url-base: https://svn.code.sf.net/p/cutter/svn/cutter
2525
2626
projects:
2727
- name: cunit-svn-rev-only

features/freeze-projects.feature

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Feature: Freeze dependencies
4444
projects:
4545
- name: cunit-svn
4646
vcs: svn
47-
url: svn://svn.code.sf.net/p/cunit/code
47+
url: https://svn.code.sf.net/p/cunit/code
4848
4949
"""
5050
And all projects are updated
@@ -59,7 +59,7 @@ Feature: Freeze dependencies
5959
branch: trunk
6060
revision: '176'
6161
vcs: svn
62-
url: svn://svn.code.sf.net/p/cunit/code
62+
url: https://svn.code.sf.net/p/cunit/code
6363
6464
"""
6565

features/list-projects.feature

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ Feature: List dependencies
5858
5959
projects:
6060
- name: cutter-svn-tag
61-
url: svn://svn.code.sf.net/p/cutter/svn/cutter
61+
url: https://svn.code.sf.net/p/cutter/svn/cutter
6262
tag: 1.1.7
6363
vcs: svn
6464
src: acmacros
@@ -71,7 +71,7 @@ Feature: List dependencies
7171
Dfetch (0.13.0)
7272
cutter-svn-tag:
7373
- remote : <none>
74-
remote url : svn://svn.code.sf.net/p/cutter/svn/cutter
74+
remote url : https://svn.code.sf.net/p/cutter/svn/cutter
7575
branch : <none>
7676
tag : 1.1.7
7777
last fetch : 29/12/2024, 20:09:21

0 commit comments

Comments
 (0)