Skip to content

Commit efff937

Browse files
committed
Merge remote-tracking branch 'origin/main' into lelia/generic-purl-type-parse-bug
# Conflicts: # .github/workflows/e2e-test.yml # CHANGELOG.md # pyproject.toml # socketsecurity/__init__.py # uv.lock
2 parents f7d5ab0 + 72bf0c2 commit efff937

7 files changed

Lines changed: 114 additions & 58 deletions

File tree

.github/workflows/e2e-test.yml

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ jobs:
9999
run: pip install uv
100100

101101
- name: Run Socket CLI
102+
id: run-cli
102103
env:
103104
SOCKET_SECURITY_API_KEY: ${{ secrets.SOCKET_CLI_API_TOKEN }}
104105
RETRY_PROBE: ${{ matrix.retry-probe }}
@@ -107,10 +108,10 @@ jobs:
107108
# Entries with retry-probe get up to 3 attempts: the probe exits 0
108109
# when the scan output looks complete, and a run that fails it is
109110
# re-run on the assumption of a transient backend failure. A
110-
# persistent failure still reaches the validate step, which fails
111-
# the job with full context. Retries are surfaced as warning
112-
# annotations so flake frequency stays visible instead of being
113-
# silently absorbed.
111+
# persistent incomplete result still reaches validation. Validation
112+
# only treats the explicit zero-project backend signature as
113+
# inconclusive; any other empty result remains a failure. Retries are
114+
# surfaced as warning annotations so flake frequency stays visible.
114115
max_attempts=3
115116
attempt=1
116117
while :; do
@@ -120,7 +121,8 @@ jobs:
120121
break
121122
fi
122123
if [ "$attempt" -ge "$max_attempts" ]; then
123-
echo "::warning title=e2e-${{ matrix.name }} incomplete results::output still fails the completeness probe after ${max_attempts} attempts; letting validation fail the job"
124+
echo "::warning title=e2e-${{ matrix.name }} incomplete results::output still fails the completeness probe after ${max_attempts} attempts; letting validation classify the result"
125+
echo "diagnostics=true" >> "$GITHUB_OUTPUT"
124126
break
125127
fi
126128
echo "::warning title=e2e-${{ matrix.name }} transient retry::attempt ${attempt} failed the completeness probe (suspected backend transient); retrying"
@@ -135,7 +137,7 @@ jobs:
135137
run: bash ${{ matrix.validate }}
136138

137139
- name: Upload diagnostics on failure
138-
if: failure()
140+
if: failure() || steps.run-cli.outputs.diagnostics == 'true'
139141
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
140142
with:
141143
name: e2e-${{ matrix.name }}-diagnostics-attempt${{ github.run_attempt }}
@@ -147,6 +149,7 @@ jobs:
147149
gl-*.json
148150
license_output.json
149151
if-no-files-found: ignore
152+
include-hidden-files: true
150153
retention-days: 14
151154

152155
# Branch protection requires the e2e-* checks, but the `e2e` job above is

.github/workflows/release.yml

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,17 +79,31 @@ jobs:
7979
env:
8080
VERSION: ${{ env.VERSION }}
8181
run: |
82-
for i in {1..30}; do
83-
if pip install socketsecurity==${VERSION}; then
82+
# The first lookup can race PyPI's Simple-index propagation, and a delayed
83+
# CDN purge can leave the index stale well after a successful upload.
84+
# pip caches HTTP responses by default, so without --no-cache-dir every
85+
# retry can reuse that initial stale response instead of checking whether
86+
# the release has appeared. Budget: 30 minutes.
87+
MAX_ATTEMPTS=60
88+
for i in $(seq 1 "$MAX_ATTEMPTS"); do
89+
if python -m pip install \
90+
--no-cache-dir \
91+
--index-url https://pypi.org/simple/ \
92+
"socketsecurity==${VERSION}"; then
8493
echo "Package ${VERSION} is now available and installable on PyPI"
85-
pip uninstall -y socketsecurity
86-
echo "success=true" >> $GITHUB_OUTPUT
94+
python -m pip uninstall -y socketsecurity
95+
echo "success=true" >> "$GITHUB_OUTPUT"
8796
exit 0
8897
fi
89-
echo "Attempt $i: Package not yet installable, waiting 20s... (${i}/30)"
90-
sleep 20
98+
if curl -s -f "https://pypi.org/pypi/socketsecurity/${VERSION}/json" > /dev/null; then
99+
echo "Release ${VERSION} exists on PyPI (JSON API) but is not in the Simple index yet - CDN propagation delay"
100+
fi
101+
if [ "$i" -lt "$MAX_ATTEMPTS" ]; then
102+
echo "Attempt $i: Package not yet installable, waiting 30s... (${i}/${MAX_ATTEMPTS})"
103+
sleep 30
104+
fi
91105
done
92-
echo "success=false" >> $GITHUB_OUTPUT
106+
echo "success=false" >> "$GITHUB_OUTPUT"
93107
exit 1
94108
95109
- name: Build & Push Docker

CHANGELOG.md

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,34 @@
88
fails, so the run exits through the CLI's API-error handling (exit code 3 by
99
default; `--disable-blocking` still exits 0) instead of writing empty
1010
GitLab dependency-scanning, license, and SARIF reports.
11-
- Requires `socketdev>=3.5.0`, which recognizes the full set of purl types
12-
(e.g. `generic`) and skips individual unparseable artifacts in the stream
13-
instead of failing the whole response.
11+
- The underlying stream-parse failure was fixed in `socketdev` 3.4.2 (already
12+
pinned at `3.5.0`): unrecognized purl types such as `generic` now resolve
13+
instead of raising, and individual unparseable artifacts are skipped rather
14+
than failing the whole response.
15+
16+
## 2.6.0
17+
18+
### Changed: pin all Python dependencies
19+
20+
- Pinned every runtime dependency in `pyproject.toml` to an exact version;
21+
several were previously unpinned or open ranges.
22+
- Replaced the `bs4` shim package with a direct, pinned `beautifulsoup4`
23+
dependency (the shim provided no version control over the actual library).
24+
- Pinned the bundled `socketdev` SDK to `3.5.0` (previously `>=3.3.0,<4.0.0`).
25+
- Docker images now install Python dependencies from the committed `uv.lock`
26+
with pip hash verification (`--require-hashes`), so image builds no longer
27+
resolve dependency versions from PyPI at build time. `pip check` validates
28+
the environment after install.
29+
- Pinned the `hatchling` build backend and the `uv` binary used in the
30+
Dockerfile.
1431

1532
### Changed: e2e reachability jobs retry transient empty results
1633

17-
- The e2e workflow now detects reachability runs that report success with no
18-
alerted components in the facts file — a transient backend condition — and
19-
retries the scan up to three times, uploading run artifacts on failure.
34+
- Reachability e2e runs that report success with no alerted components in the
35+
facts file are retried up to three times as a suspected transient backend
36+
failure. After retries, only the known zero-project backend signature is
37+
classified as inconclusive — any other empty result still fails — and e2e
38+
jobs upload their logs and reports as diagnostics on failure.
2039

2140
## 2.5.9
2241

Dockerfile

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ ENV PATH="/usr/local/go/bin:/usr/lib/go/bin:/root/.cargo/bin:${PATH}"
8686
ENV GOPATH="/go"
8787

8888
# Install uv
89-
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
89+
COPY --from=ghcr.io/astral-sh/uv:0.10.4 /uv /usr/local/bin/uv
9090

9191
# Install pyenv
9292
# pyenv lets us build/install arbitrary Python versions on demand. We install
@@ -111,14 +111,26 @@ RUN curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/p
111111
ln -s ~/.pyenv/bin/pyenv /bin/pyenv && \
112112
pyenv --version
113113

114+
# Install Python dependencies from the lockfile with hash verification so the
115+
# image never resolves loose versions from PyPI at build time.
116+
COPY pyproject.toml uv.lock /tmp/socket-cli-lock/
117+
# Index flags are passed explicitly (always production PyPI) so the
118+
# PIP_INDEX_URL/PIP_EXTRA_INDEX_URL ARGs used to point CLI/SDK preview installs
119+
# at TestPyPI don't leak into the locked dependency install via pip's env vars.
120+
RUN uv export --directory /tmp/socket-cli-lock --frozen --no-dev --no-emit-project \
121+
--format requirements-txt -o /tmp/socket-cli-lock/requirements.txt && \
122+
pip install --require-hashes --no-deps \
123+
--index-url https://pypi.org/simple --extra-index-url https://pypi.org/simple \
124+
-r /tmp/socket-cli-lock/requirements.txt
125+
114126
# Install CLI based on build mode
115127
RUN if [ "$USE_LOCAL_INSTALL" = "true" ]; then \
116128
echo "Using local development install"; \
117129
else \
118130
cli_installed=false; \
119131
for i in $(seq 1 10); do \
120132
echo "Attempt $i/10: Installing socketsecurity==$CLI_VERSION"; \
121-
if pip install --index-url ${PIP_INDEX_URL} --extra-index-url ${PIP_EXTRA_INDEX_URL} socketsecurity==$CLI_VERSION; then \
133+
if pip install --no-deps --index-url ${PIP_INDEX_URL} --extra-index-url ${PIP_EXTRA_INDEX_URL} socketsecurity==$CLI_VERSION; then \
122134
cli_installed=true; \
123135
break; \
124136
fi; \
@@ -131,6 +143,7 @@ RUN if [ "$USE_LOCAL_INSTALL" = "true" ]; then \
131143
echo "Failed to install socketsecurity==$CLI_VERSION after 10 attempts"; \
132144
exit 1; \
133145
fi; \
146+
pip check || exit 1; \
134147
if [ ! -z "$SDK_VERSION" ]; then \
135148
pip install --index-url ${PIP_INDEX_URL} --extra-index-url ${PIP_EXTRA_INDEX_URL} socketdev==${SDK_VERSION}; \
136149
fi; \
@@ -140,7 +153,7 @@ RUN if [ "$USE_LOCAL_INSTALL" = "true" ]; then \
140153
COPY . /app
141154
WORKDIR /app
142155
RUN if [ "$USE_LOCAL_INSTALL" = "true" ]; then \
143-
pip install --upgrade -e .; \
156+
pip install --no-deps -e . && pip check; \
144157
fi
145158

146159
# Create workspace directory with proper permissions

pyproject.toml

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[build-system]
22
requires = [
3-
"hatchling"
3+
"hatchling==1.31.0"
44
]
55
build-backend = "hatchling.build"
66

@@ -10,17 +10,17 @@ version = "2.6.2"
1010
requires-python = ">= 3.11"
1111
license = {"file" = "LICENSE"}
1212
dependencies = [
13-
'requests',
14-
'mdutils',
15-
'prettytable',
16-
'GitPython',
17-
'packaging',
18-
'python-dotenv',
19-
"socketdev>=3.5.0,<4.0.0",
20-
"bs4>=0.0.2",
21-
"markdown>=3.10",
22-
"brotli>=1.0.9; platform_python_implementation == 'CPython'",
23-
"brotlicffi>=1.0.9; platform_python_implementation != 'CPython'",
13+
"requests==2.34.2",
14+
"mdutils==1.8.1",
15+
"prettytable==3.18.0",
16+
"GitPython==3.1.57",
17+
"packaging==26.2",
18+
"python-dotenv==1.2.2",
19+
"socketdev==3.5.0",
20+
"beautifulsoup4==4.14.3",
21+
"markdown==3.10.2",
22+
"brotli==1.2.0; platform_python_implementation == 'CPython'",
23+
"brotlicffi==1.2.0.1; platform_python_implementation != 'CPython'",
2424
]
2525
readme = "README.md"
2626
description = "Socket Security CLI for CI/CD"

tests/e2e/validate-reachability.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,25 @@ if [ ! -f "$FACTS_PATH" ]; then
3434
fi
3535
echo "PASS: Reachability facts file present at $FACTS_PATH"
3636

37+
# The tier-1 backend intermittently returns the known fixture as one orphaned
38+
# component with zero projects, so Coana has no vulnerability to analyze even
39+
# though manifest upload, facts generation, and scan finalization all succeed.
40+
# After the workflow's bounded retries, classify only that explicit
41+
# upstream signature as inconclusive. Any other empty facts result still fails,
42+
# including the important regression case where Coana received a vulnerability
43+
# but the CLI lost its alerted component.
44+
if ! bash tests/e2e/reach-facts-probe.sh tests/e2e/fixtures/simple-npm; then
45+
if grep -q "Found 1 manifest files for reachability upload" "$LOG" && \
46+
grep -q "Found 0 projects across 0 ecosystems to analyze" "$LOG" && \
47+
grep -q "Filtered out 1 orphaned component" "$LOG"; then
48+
echo "::warning title=e2e-reachability inconclusive backend result::tier-1 returned the known zero-project/orphaned-component signature after retries; core reachability execution and finalization passed"
49+
echo "e2e-reachability: inconclusive after retries — known zero-project backend signature; diagnostics uploaded" >> "${GITHUB_STEP_SUMMARY:-/dev/null}"
50+
exit 0
51+
fi
52+
echo "FAIL: no components with alerts in .socket.facts.json and the known backend signature was not present"
53+
exit 1
54+
fi
55+
3756
# 3-4. Build SARIF from the facts file produced by the initial --reach run.
3857
# Avoid re-running reach + full scan here; duplicate API scans are slow and flaky in CI.
3958
uv run python -c "

uv.lock

Lines changed: 12 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)