Skip to content

Commit ebf39ca

Browse files
leliaclaude
andcommitted
Merge main into the ruff/QoL branch and move the release to 2.8.1
main shipped 2.8.0 (#325 monorepo scan diagnostics and API exit codes, #331 Slack severity normalization and the socketdev 3.6.0 bump) while this branch was open, so the 2.7.3 it targeted is now behind the published release. Conflict resolution, in every case keeping main's shipped behaviour and this branch's formatting and fixes: - slack.py: kept the "middle" -> "medium" normalization from #331, restyled to double quotes. Losing it would have silently reintroduced the wrong Slack severity counts. - socketcli.py: kept #325's _log_scan_mode_fallback diagnostics and the force_api_mode branch; this branch's side was the older logic reformatted. - core/__init__.py: kept #325's _log_scan_configuration helper and its call sites, and the switch from sys.exit to raise so API failures reach the configured infrastructure exit code. Kept this branch's contextlib.suppress cleanup and modern annotations, and modernized the helper's own signature since this branch drops the typing.List/Optional imports. - version-check.yml: already assembled from main plus the enforcement gating and this branch's both-quote version strip. - CHANGELOG: this branch's section is now 2.8.1, above the shipped 2.8.0. uv.lock regenerated from the merged pyproject with the pinned uv 0.12.8. ruff check and ruff format are both clean; 584 passed, 2 skipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2 parents d947eb2 + 860b8ea commit ebf39ca

14 files changed

Lines changed: 693 additions & 26 deletions

File tree

CHANGELOG.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Changelog
22

3-
## 2.7.3
3+
## 2.8.1
44

55
### Fixed: credentials could appear in debug log output
66

@@ -56,6 +56,40 @@
5656
- `config.py` logged through the root logger, so its warnings and errors ignored
5757
the configured log level and format. They now use the `socketcli` logger like
5858
the rest of the CLI.
59+
## 2.8.0
60+
61+
### Changed: improve monorepo scan diagnostics and guidance
62+
63+
- Added aggregate scan configuration, manifest-count, baseline-selection, and
64+
fallback diagnostics without listing submitted manifest paths.
65+
- Clarified monorepo scan scoping, workspace flags, CI path filters, and timeout
66+
behavior, with a changed-workspace GitHub Actions example.
67+
68+
### Changed: bump socketdev to 3.6.0
69+
70+
- Bumped the pinned SDK (`socketdev`) from `3.5.0` to `3.6.0`. Its package-type
71+
enum gained ten members — `alpm`, `chrome`, `clawhub`, `edge-extension`,
72+
`firefox-extension`, `qpkg`, `socket`, `swid`, `vscode` and
73+
`vscode-extension` — so artifacts of those types are now reported under their
74+
own type instead of falling back to `unknown`.
75+
76+
### Fixed: apply configured exit codes to API failures
77+
78+
- Full-scan and streamed-diff API failures now use the configured infrastructure
79+
error exit code instead of the security-finding exit code.
80+
81+
### Fixed: mid-severity findings were dropped from the Slack summary
82+
83+
- The Slack reachability formatter keyed every severity lookup on `medium`,
84+
but the API sends `middle`. A mid-severity finding therefore missed all of
85+
them at once: it was not counted, so the summary always read `Medium: 0`; it
86+
was excluded from `total_findings`, which can drive the "and N more" count
87+
negative; and it sorted at the default order of 4, below `low`, so it was the
88+
first thing truncated when the Slack block limit was reached.
89+
- Severity is now normalized to one spelling when an alert is read, matching
90+
how the GitLab and PR-comment paths already handle both forms. The findings
91+
themselves were always listed; only the counts, ordering and truncation were
92+
wrong.
5993

6094
## 2.7.2
6195

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,10 @@ value — e.g. a Buildkite
229229
code, or `0` to swallow infra errors. Exit `3` is a Socket convention, not an
230230
industry standard.
231231

232+
This mapping applies to errors the CLI receives and handles. An external process
233+
supervisor (for example GNU `timeout`) can terminate the CLI before it handles an
234+
error, so the supervisor's exit status (commonly 124 or 137) takes precedence.
235+
232236
### How these options interact
233237

234238
The two flags that affect exit codes can cancel each other out, so the order of

docs/ci-cd.md

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,239 @@ Equivalent JSON:
7171
SOCKET_SECURITY_API_TOKEN: ${{ secrets.SOCKET_SECURITY_API_TOKEN }}
7272
```
7373
74+
#### GitHub Actions: scan changed monorepo workspaces independently
75+
76+
GitHub Actions `paths` filters only decide whether a workflow starts. They do not
77+
change `socketcli` discovery or upload scope. For a merge gate, it is usually safer
78+
to start a small selector job on every PR update, then create one scan job per
79+
affected logical workspace. This also avoids a required check remaining pending
80+
when GitHub skips the entire workflow because of a top-level path filter.
81+
82+
This pattern produces one dashboard entry per logical workspace, which is what
83+
gives each component its own alerts, baseline, and policy. It is also the layout
84+
that grows the dashboard's repository list. See
85+
[Choosing a scan layout](cli-reference.md#choosing-a-scan-layout) for when that
86+
trade-off is worth making.
87+
88+
Define a repository variable named `SOCKET_MONOREPO_WORKSPACES_JSON`. Its value is
89+
an array with one stable workspace name, one or more scan roots, and the path globs
90+
that should select that workspace. Fill these placeholders with the repository's
91+
real layout. A workspace definition selects directory roots; shared root manifests,
92+
lockfiles, and cross-directory path dependencies outside those roots are not included
93+
automatically.
94+
95+
```json
96+
[
97+
{
98+
"name": "<stable-workspace-name>",
99+
"sub_paths": ["<repo-relative-scan-root>"],
100+
"watch_globs": ["<repo-relative-changed-file-glob>"]
101+
}
102+
]
103+
```
104+
105+
Each `sub_paths` value must be a directory, not an individual manifest or lockfile.
106+
Using `.` includes the entire target path. Do not use this changed-workspace pattern
107+
until the directory boundaries preserve every shared input needed to resolve each
108+
logical graph. If root workspace metadata governs most or all of the repository, a
109+
smaller coverage-preserving split may not be representable with `--sub-path` alone.
110+
111+
Also define `SOCKETCLI_VERSION` as the exact package version validated for the
112+
workflow. The workflow below logs that version, uses full Git history for reliable
113+
base/head selection, creates one matrix job (and therefore one graph and baseline)
114+
per selected workspace, and fails closed on CLI/API/timeout failures. It uses API
115+
SCM mode plus `--enable-diff` because parallel `--scm github` jobs can race while
116+
updating the same PR comments; the matrix checks and report links are the gate.
117+
118+
```yaml
119+
name: Socket Security
120+
121+
on:
122+
pull_request:
123+
types: [opened, synchronize, reopened]
124+
push:
125+
branches: [main]
126+
127+
permissions:
128+
contents: read
129+
130+
jobs:
131+
select-workspaces:
132+
runs-on: ubuntu-latest
133+
outputs:
134+
count: ${{ steps.select.outputs.count }}
135+
matrix: ${{ steps.select.outputs.matrix }}
136+
steps:
137+
- uses: actions/checkout@v5
138+
with:
139+
fetch-depth: 0
140+
persist-credentials: false
141+
142+
- id: select
143+
name: Select changed workspaces
144+
env:
145+
WORKSPACES_JSON: ${{ vars.SOCKET_MONOREPO_WORKSPACES_JSON }}
146+
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
147+
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
148+
shell: bash
149+
run: |
150+
python - <<'PY'
151+
import fnmatch
152+
import json
153+
import os
154+
import re
155+
import subprocess
156+
157+
workspaces = json.loads(os.environ["WORKSPACES_JSON"])
158+
if not isinstance(workspaces, list):
159+
raise SystemExit("SOCKET_MONOREPO_WORKSPACES_JSON must be a JSON array")
160+
161+
base = os.environ["BASE_SHA"]
162+
head = os.environ["HEAD_SHA"]
163+
if not base or set(base) == {"0"}:
164+
base = subprocess.check_output(
165+
["git", "rev-parse", f"{head}^"], text=True
166+
).strip()
167+
changed_output = subprocess.check_output(
168+
["git", "diff", "--name-only", "-z", base, head]
169+
)
170+
changed = [
171+
item.decode("utf-8", "surrogateescape")
172+
for item in changed_output.split(b"\0")
173+
if item
174+
]
175+
176+
selected = []
177+
for workspace in workspaces:
178+
name = workspace.get("name", "")
179+
sub_paths = workspace.get("sub_paths") or []
180+
watch_globs = workspace.get("watch_globs") or []
181+
if not re.fullmatch(r"[A-Za-z0-9._-]+", name):
182+
raise SystemExit(f"Invalid workspace name: {name!r}")
183+
if not sub_paths or any(
184+
not isinstance(path, str)
185+
or path.startswith("/")
186+
or ".." in path.split("/")
187+
for path in sub_paths
188+
):
189+
raise SystemExit(f"Invalid sub_paths for workspace {name!r}")
190+
if not watch_globs:
191+
watch_globs = [
192+
pattern
193+
for path in sub_paths
194+
for pattern in (
195+
["*"]
196+
if path.strip("/") in ("", ".")
197+
else [path.rstrip("/"), f"{path.rstrip('/')}/*"]
198+
)
199+
]
200+
if any(
201+
fnmatch.fnmatchcase(path, pattern)
202+
for path in changed
203+
for pattern in watch_globs
204+
):
205+
selected.append({"name": name, "sub_paths": sub_paths})
206+
207+
matrix = json.dumps({"include": selected}, separators=(",", ":"))
208+
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output:
209+
output.write(f"count={len(selected)}\n")
210+
output.write(f"matrix={matrix}\n")
211+
PY
212+
213+
scan-workspace:
214+
needs: select-workspaces
215+
if: needs.select-workspaces.outputs.count != '0'
216+
timeout-minutes: 20
217+
strategy:
218+
fail-fast: false
219+
matrix: ${{ fromJSON(needs.select-workspaces.outputs.matrix) }}
220+
name: Socket scan (${{ matrix.name }})
221+
runs-on: ubuntu-latest
222+
steps:
223+
- uses: actions/checkout@v5
224+
with:
225+
fetch-depth: 0
226+
persist-credentials: false
227+
228+
- uses: actions/setup-python@v6
229+
with:
230+
python-version: '3.12'
231+
232+
- name: Install pinned Socket CLI
233+
env:
234+
SOCKETCLI_VERSION: ${{ vars.SOCKETCLI_VERSION }}
235+
run: |
236+
python -m pip install "socketsecurity==$SOCKETCLI_VERSION"
237+
socketcli --version
238+
239+
- name: Scan workspace
240+
env:
241+
SOCKET_SECURITY_API_KEY: ${{ secrets.SOCKET_SECURITY_API_KEY }}
242+
PR_NUMBER: ${{ github.event.pull_request.number || 0 }}
243+
WORKSPACE_NAME: ${{ matrix.name }}
244+
SUB_PATHS_JSON: ${{ toJSON(matrix.sub_paths) }}
245+
shell: bash
246+
run: |
247+
set +e
248+
args=(
249+
--target-path "$GITHUB_WORKSPACE"
250+
--workspace-name "$WORKSPACE_NAME"
251+
--enable-diff
252+
--pr-number "$PR_NUMBER"
253+
--exit-code-on-api-error 3
254+
--report-link-file socket-report-link.txt
255+
--summary-file socket-summary.txt
256+
)
257+
while IFS= read -r sub_path; do
258+
args+=(--sub-path "$sub_path")
259+
done < <(jq -r '.[]' <<<"$SUB_PATHS_JSON")
260+
261+
socketcli "${args[@]}" 2>&1 | tee socket-output.log
262+
code=${PIPESTATUS[0]}
263+
264+
{
265+
echo "## Socket scan: $WORKSPACE_NAME"
266+
if [ -s socket-report-link.txt ]; then
267+
echo "[View the report]($(cat socket-report-link.txt))"
268+
fi
269+
if [ -s socket-summary.txt ]; then
270+
echo '```'
271+
cat socket-summary.txt
272+
echo '```'
273+
fi
274+
} >> "$GITHUB_STEP_SUMMARY"
275+
276+
exit "$code"
277+
278+
socket-security:
279+
if: always()
280+
needs: [select-workspaces, scan-workspace]
281+
runs-on: ubuntu-latest
282+
steps:
283+
- name: Enforce matrix result
284+
env:
285+
SELECT_RESULT: ${{ needs.select-workspaces.result }}
286+
SCAN_RESULT: ${{ needs.scan-workspace.result }}
287+
run: |
288+
test "$SELECT_RESULT" = success
289+
[[ "$SCAN_RESULT" = success || "$SCAN_RESULT" = skipped ]]
290+
```
291+
292+
Each configuration object may intentionally contain several `sub_paths` when
293+
those directories are one logical dependency graph. To split backend resolution,
294+
use separate objects with different `name` values. Add `--workspace <name>` only
295+
when the Socket organization requires API workspace association; it is not a scan
296+
scope control. Use `--save-submitted-files-list` in a non-required canary to verify
297+
the exact manifests selected before adopting workspace-level scans as a merge gate.
298+
299+
The job has an explicit 20-minute total budget. Tune that value from observed
300+
workspace-level latency after the split; a five-minute cap can still be too close
301+
to a slow request plus local startup. The CLI's `--timeout` is different: it
302+
defaults to 1,200 seconds **per API request**. If an operator adds GNU `timeout`,
303+
that process supervisor can terminate the CLI before it maps an error through
304+
`--exit-code-on-api-error`; without `--preserve-status`, GNU reports 124 after its
305+
initial timeout signal or 137 if `SIGKILL` is involved.
306+
74307
### Buildkite
75308

76309
```yaml

0 commit comments

Comments
 (0)