Skip to content

Commit 26f32d2

Browse files
committed
fix: harden diff baseline resolution
1 parent f69bf4e commit 26f32d2

7 files changed

Lines changed: 228 additions & 93 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@
2020
commit instead of failing the run, and logs which commit was used and how far
2121
back it is. Squash merges, rebases, and multi-commit pushes all leave a merge
2222
base unscanned even when default-branch scanning is configured correctly. The
23-
run still fails when no scanned ancestor is reachable.
23+
lookup follows paginated scan history and the run still fails when no scanned
24+
ancestor is reachable or the exact-commit lookup itself fails.
2425
- Implicit diff baselines are selected from the same workspace, scan type,
25-
repository, and default branch. A baseline lookup that fails is reported as an
26-
API error instead of resolving to an empty baseline, and temporary scans are
27-
skipped when selecting one.
26+
repository, and default branch, including when no workspace is supplied. A
27+
baseline lookup that fails is reported as an API error instead of resolving to
28+
an empty baseline, and temporary scans are skipped when selecting one.
2829
## 2.8.1
2930

3031
### Changed: bump pinned @coana-tech/cli to 15.10.40

README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,21 +44,22 @@ socketcli --enable-gitlab-security --gitlab-security-file gl-dependency-scanning
4444

4545
### PR scan diffed against the merge base
4646

47-
By default, PR scans are diffed against the repository's latest head scan. To diff against
48-
the exact commit your PR branched from instead, pass the merge base as the baseline:
47+
By default, PR scans are diffed against the repository's latest matching head scan. To
48+
prefer the commit your PR branched from as the baseline, pass the merge base:
4949

5050
```bash
5151
BASE_SHA=$(git merge-base origin/main HEAD)
5252
socketcli --pr-number 123 --base-commit-sha "$BASE_SHA"
5353
```
5454

55-
> **Requirement:** `--base-commit-sha` only works if Socket already has a full scan for that
56-
> exact commit. In practice this means your CI must run `socketcli` on **every commit that
57-
> lands on your default branch** — not just some of them. If merges can land without a scan
58-
> (skipped/canceled builds, `[skip ci]`, path-filtered pipelines), the PR scan will fail with
59-
> exit code 3 rather than silently diff against the wrong baseline. See
55+
> The CLI uses the exact commit's newest matching full scan when one exists. Otherwise, it
56+
> searches up to 100 first-parent commits in the local checkout and uses the nearest scanned
57+
> ancestor, with a warning that the diff is wider than the merge base. Run `socketcli`
58+
> regularly on your default branch and ensure PR checkouts contain enough history for that
59+
> walk. The run fails with the configured API-error exit code only when no scanned ancestor
60+
> is reachable (or when the scan lookup itself fails). See
6061
> [`docs/cli-reference.md`](https://github.com/SocketDev/socket-python-cli/blob/main/docs/cli-reference.md)
61-
> for the full requirements and a backfill pattern that makes PR jobs self-sufficient.
62+
> for the full behavior and an optional exact-baseline backfill pattern.
6263
6364
A specific full scan ID also works: `--base-scan-id <id>`.
6465

docs/ci-cd.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -347,11 +347,14 @@ generator rather than a static YAML file:
347347
BASE_SHA=$(git merge-base "origin/${TARGET}" HEAD)
348348
```
349349

350-
- **Emit the backfill step conditionally from the generator.** The generator is the
351-
natural place for the "does a baseline scan exist?" check
350+
- **If an exact baseline is required, emit a backfill step conditionally from the
351+
generator.** The generator is the natural place for the "does an exact baseline
352+
scan exist?" check
352353
(`GET /orgs/{org}/full-scans?repo=<repo>&commit_hash=$BASE_SHA&per_page=1`): only
353354
emit the baseline-scan step when it returns nothing. The emitted pipeline then shows
354-
in the UI whether a backfill will run.
355+
in the UI whether a backfill will run. Without a backfill, the CLI automatically
356+
uses the nearest scanned first-parent ancestor within 100 commits and warns that
357+
the diff is wider.
355358

356359
- **Keep the backfill inside one command step.** The checkout-base → scan →
357360
checkout-PR sequence must not be split across steps — steps can land on different
@@ -360,17 +363,18 @@ generator rather than a static YAML file:
360363
checkout: `git worktree add /tmp/socket-base "$BASE_SHA"` then
361364
`socketcli --target-path /tmp/socket-base --branch "$TARGET" --disable-blocking`.
362365

363-
- **Soft-fail infra errors, not findings.** A missing baseline (or any API error)
364-
exits with code 3 (`--exit-code-on-api-error` to change it); real findings exit 1.
366+
- **Soft-fail infra errors, not findings.** No reachable scanned ancestor (or any API
367+
error) exits with code 3 (`--exit-code-on-api-error` to change it); real findings exit 1.
365368
[`soft_fail: [{exit_status: 3}]`](https://buildkite.com/docs/pipelines/configure/step-types/command-step)
366369
on the PR scan step keeps infra errors from blocking merges while security findings
367370
still do.
368371

369372
- **["Cancel intermediate builds"](https://buildkite.com/docs/pipelines/configure/canceling-builds#cancel-running-intermediate-builds)
370-
on the default branch is the main source of baseline gaps.** Canceled builds never
371-
scan their commit, so merge-base lookups for PRs based on those commits fail. The
372-
conditional backfill step above is the remedy; there is no per-step exemption from
373-
build cancellation in Buildkite. If you need strict scan-once semantics for
373+
on the default branch is a common source of exact-baseline gaps.** Canceled builds
374+
never scan their commit, so these PRs fall back to an older scanned ancestor. Use
375+
the conditional backfill step above when an exact merge-base comparison is required;
376+
there is no per-step exemption from build cancellation in Buildkite. If you need
377+
strict scan-once semantics for
374378
concurrent backfills of the same merge base, serialize the backfill step with a
375379
[concurrency group](https://buildkite.com/docs/pipelines/configure/workflows/controlling-concurrency)
376380
keyed on the merge-base SHA.

docs/cli-reference.md

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -256,24 +256,22 @@ If you don't want to provide the Socket API Token every time then you can use th
256256
| `--commit-message` | False | *auto* | Commit message (auto-detected from git) |
257257
| `--commit-sha` | False | *auto* | Commit SHA (auto-detected from git) |
258258
| `--base-scan-id` | False | | Full scan ID to diff against, overriding the repository's head scan as the baseline. Mutually exclusive with `--base-commit-sha` |
259-
| `--base-commit-sha`| False | | Commit SHA to diff against, overriding the repository's head scan as the baseline. The most recent full scan for that commit is used; the CLI errors (exit code 3, or `--exit-code-on-api-error`) if no scan exists for it. Mutually exclusive with `--base-scan-id` |
259+
| `--base-commit-sha`| False | | Commit SHA to prefer as the diff baseline, overriding the repository's head scan. The CLI uses its most recent matching full scan or the nearest scanned first-parent ancestor within 100 local commits. It errors (exit code 3, or `--exit-code-on-api-error`) if no scanned ancestor is reachable. Mutually exclusive with `--base-scan-id` |
260260

261-
> **Diffing against the merge base** — by default, PR scans are diffed against the repository's *latest* head scan, which may include newer default-branch commits than your PR branched from. To diff against the exact commit your PR is based on, compute the merge base and pass it as the baseline:
261+
> **Diffing against the merge base** — by default, PR scans are diffed against the repository's latest matching head scan, which may include newer default-branch commits than your PR branched from. To prefer the commit your PR is based on, compute the merge base and pass it as the baseline:
262262
>
263263
> ```shell
264264
> BASE_SHA=$(git merge-base origin/main HEAD)
265265
> socketcli --pr-number 123 --base-commit-sha "$BASE_SHA"
266266
> ```
267267
>
268-
> **Requirement: a full scan must already exist for the merge-base commit.** `--base-commit-sha` does not create a scan of that commit; it looks up an existing one. That lookup only succeeds if your CI runs `socketcli` on **every commit that lands on your default branch** — every merge and direct push, not just periodic or latest-only scans. Common ways commits slip through without a scan:
268+
> `--base-commit-sha` does not create a scan of that commit. The CLI first looks for the newest non-temporary scan matching the repository, workspace, scan type, and exact commit. If the exact commit was not scanned, it walks up to 100 first-parent commits from that SHA in the local checkout and uses the nearest matching scanned ancestor. It logs a warning with the selected commit and distance because this produces a wider diff than the merge base.
269269
>
270-
> - CI settings that cancel or skip intermediate builds when newer commits land (e.g. Buildkite's ["cancel intermediate builds"](https://buildkite.com/docs/pipelines/configure/canceling-builds#cancel-running-intermediate-builds))
271-
> - `[skip ci]` commits, path-filtered pipelines, or failed/canceled scan steps
272-
> - merge-base commits that predate your Socket rollout
270+
> Run `socketcli` regularly on the default branch so recent ancestors have scans. PR checkouts must also retain the merge base and enough first-parent history; shallow clones can shorten the search. Gaps are expected when CI cancels intermediate builds, commits use `[skip ci]`, pipelines are path-filtered, or the merge base predates your Socket rollout.
273271
>
274-
> If no scan exists for the commit, the CLI **fails** (exit code 3, or your `--exit-code-on-api-error` value; exit 0 with `--disable-blocking`) instead of silently falling back to the head scan — a wrong baseline would misreport which alerts the PR introduces. Don't adopt this flag without default-branch scan coverage in place; you'll fail PR builds on lookup misses.
272+
> If no scanned ancestor is reachable within the local 100-commit walk, the CLI **fails** (exit code 3, or your `--exit-code-on-api-error` value; exit 0 with `--disable-blocking`) instead of silently falling back to the repository head. API or permission failures also fail rather than being treated as a missing exact scan.
275273
>
276-
> **Backfill pattern** — if your default-branch coverage has gaps, the PR job can create the missing baseline itself before scanning:
274+
> **Optional exact-baseline backfill** — if the wider ancestor fallback is not acceptable, the PR job can create the missing exact baseline before scanning:
277275
>
278276
> ```shell
279277
> BASE_SHA=$(git merge-base origin/main HEAD)
@@ -285,7 +283,7 @@ If you don't want to provide the Socket API Token every time then you can use th
285283
> socketcli --pr-number 123 --base-commit-sha "$BASE_SHA"
286284
> ```
287285
>
288-
> Run the baseline step with `--disable-blocking` (findings on the default branch must not fail the PR job) and an explicit `--branch`, since branch auto-detection is unreliable at a detached HEAD.
286+
> Run the baseline step with `--disable-blocking` (findings on the default branch must not fail the PR job) and an explicit `--branch`, since branch auto-detection is unreliable at a detached HEAD. Without this step, the CLI automatically uses the nearest scanned ancestor.
289287
>
290288
> Buildkite users with dynamically generated pipelines: see [Merge-base baselines in Buildkite](ci-cd.md#merge-base-baselines-in-buildkite-dynamic-pipelines) for generation-time vs. step-time guidance.
291289

socketsecurity/config.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -587,9 +587,9 @@ def create_argument_parser() -> argparse.ArgumentParser:
587587
metavar="<sha>",
588588
default=None,
589589
help="Commit SHA to diff the new scan against, overriding the repository's head "
590-
"scan as the baseline. The most recent full scan matching this commit (e.g. "
591-
"the merge base from 'git merge-base origin/main HEAD') is used; the CLI "
592-
"errors if no scan exists for it. Mutually exclusive with --base-scan-id."
590+
"scan as the baseline. The CLI uses the most recent matching full scan, or "
591+
"the nearest scanned first-parent ancestor within 100 local commits when "
592+
"the commit itself was not scanned. Mutually exclusive with --base-scan-id."
593593
)
594594

595595
# Path and File options

0 commit comments

Comments
 (0)