Skip to content

Commit 994c938

Browse files
committed
Apply audit remediation
1 parent 7f305a6 commit 994c938

4 files changed

Lines changed: 48 additions & 2 deletions

File tree

.github/workflows/monthly_snapshot_audit.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@ name: Monthly Snapshot Audit
55
- cron: "25 2 1 * *"
66
workflow_dispatch:
77

8+
concurrency:
9+
group: ${{ github.workflow }}-${{ github.ref_name }}
10+
cancel-in-progress: false
11+
812
jobs:
913
monthly-snapshot-audit:
1014
if: github.ref_name != 'logs'
1115
runs-on:
16+
timeout-minutes: 60
1217
- self-hosted
1318
- Linux
1419
- X64

.github/workflows/publish-hk-snapshot-artifacts.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,14 @@ on:
9595
default: longbridgequant
9696
type: string
9797

98+
concurrency:
99+
group: ${{ github.workflow }}-${{ github.ref_name }}
100+
cancel-in-progress: false
101+
98102
jobs:
99103
build-and-publish:
100104
runs-on: ubuntu-latest
105+
timeout-minutes: 60
101106
permissions:
102107
contents: read
103108
id-token: write

src/hk_equity_snapshot_pipelines/input_sources.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
from __future__ import annotations
22

33
import argparse
4+
import shutil
45
import shlex
56
import subprocess
67
from collections.abc import Callable
78
from dataclasses import dataclass
89
from pathlib import Path
910
from urllib.parse import urlparse
10-
from urllib.request import urlretrieve
11+
from urllib.request import Request, urlopen
1112

1213
from .evidence_uri_policy import SENSITIVE_EVIDENCE_URI_MARKERS
1314

1415
SUPPORTED_SNAPSHOT_SUFFIXES = frozenset({".csv"})
16+
DEFAULT_REMOTE_COPY_TIMEOUT_SECONDS = 60
1517

1618
CopyFn = Callable[[str, Path], None]
1719

@@ -47,7 +49,9 @@ def _default_gcs_copy(source: str, target: Path) -> None:
4749

4850

4951
def _default_https_copy(source: str, target: Path) -> None:
50-
urlretrieve(source, target) # noqa: S310 - operator-supplied HTTPS data source URL.
52+
request = Request(source, headers={"User-Agent": "hk-equity-snapshot-pipelines"})
53+
with urlopen(request, timeout=DEFAULT_REMOTE_COPY_TIMEOUT_SECONDS) as response, target.open("wb") as output: # noqa: S310 - operator-supplied HTTPS data source URL.
54+
shutil.copyfileobj(response, output)
5155

5256

5357
def _source_suffix(source: str, *, allowed_suffixes: frozenset[str], default_suffix: str) -> str:

tests/test_input_sources.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import pytest
88

99
from hk_equity_snapshot_pipelines.input_sources import (
10+
DEFAULT_REMOTE_COPY_TIMEOUT_SECONDS,
11+
_default_https_copy,
1012
resolve_hk_snapshot_inputs,
1113
resolve_input_source,
1214
source_needs_gcloud,
@@ -68,6 +70,36 @@ def fake_copy(source: str, target: Path) -> None:
6870
assert resolved.exists()
6971

7072

73+
def test_default_https_copy_uses_timeout_and_streams_response(monkeypatch, tmp_path):
74+
calls: list[tuple[str, float]] = []
75+
76+
class FakeResponse:
77+
def __init__(self) -> None:
78+
self._payload = b"symbol,sector\n00001,Financials\n"
79+
80+
def __enter__(self) -> "FakeResponse":
81+
return self
82+
83+
def __exit__(self, *args: object) -> None:
84+
return None
85+
86+
def read(self, size: int = -1) -> bytes:
87+
payload, self._payload = self._payload, b""
88+
return payload
89+
90+
def fake_urlopen(request, *, timeout: float):
91+
calls.append((request.full_url, timeout))
92+
return FakeResponse()
93+
94+
monkeypatch.setattr("hk_equity_snapshot_pipelines.input_sources.urlopen", fake_urlopen)
95+
target = tmp_path / "factor_snapshot.csv"
96+
97+
_default_https_copy("https://example.com/factor_snapshot.csv", target)
98+
99+
assert target.read_text(encoding="utf-8") == "symbol,sector\n00001,Financials\n"
100+
assert calls == [("https://example.com/factor_snapshot.csv", DEFAULT_REMOTE_COPY_TIMEOUT_SECONDS)]
101+
102+
71103
def test_resolve_input_source_rejects_secret_like_remote_uri(tmp_path):
72104
with pytest.raises(ValueError, match="must not contain token"):
73105
resolve_input_source(

0 commit comments

Comments
 (0)