|
1 | 1 | # Troubleshooting |
2 | 2 |
|
3 | | -## API token scopes for scan comparisons |
| 3 | +## API token permissions |
4 | 4 |
|
5 | | -PR/MR runs compare the new scan against the repository's head scan. That comparison |
6 | | -first uses the diff-scans endpoints, which require an organization token with these |
7 | | -scopes in addition to whatever the scan itself needs: |
| 5 | +The CLI authenticates with a single Socket organization API token. Every run needs |
| 6 | +more than "create a scan" access, and a token that is missing a permission usually |
| 7 | +does **not** fail loudly — several paths degrade or fall back with only a warning. |
8 | 8 |
|
9 | | -- `diff-scans:create` |
10 | | -- `diff-scans:list` |
11 | | -- `full-scans:list` |
| 9 | +### What every run calls |
12 | 10 |
|
13 | | -If the token is missing them the scan still succeeds, so this is easy to miss. The only |
14 | | -signal is a warning, after which the CLI falls back to the older streaming comparison: |
| 11 | +These are exercised on any invocation, regardless of flags: |
| 12 | + |
| 13 | +| API call | Purpose | |
| 14 | +|:---|:---| |
| 15 | +| `GET organizations` | Resolve the org ID and slug from the token | |
| 16 | +| `GET report/supported` | Fetch supported manifest patterns (falls back to a bundled list on failure) | |
| 17 | +| `GET orgs/{org}/repos/{repo}` | Look up the repository | |
| 18 | +| `POST orgs/{org}/repos` | Create the repository — **only attempted if the lookup above fails** | |
| 19 | +| `POST orgs/{org}/full-scans` | Create the new scan | |
| 20 | + |
| 21 | +### What diff-producing runs add |
| 22 | + |
| 23 | +| API call | Purpose | |
| 24 | +|:---|:---| |
| 25 | +| `POST orgs/{org}/diff-scans/from-ids` | Create the comparison (preferred path) | |
| 26 | +| `GET orgs/{org}/diff-scans` | Resolve an existing comparison on a 409 | |
| 27 | +| `GET orgs/{org}/diff-scans/{id}?cached=true` | Poll for the computed comparison | |
| 28 | +| `GET orgs/{org}/full-scans/diff` | Legacy streaming comparison (fallback path) | |
| 29 | +| `GET orgs/{org}/full-scans/{id}` | Resolve a baseline for `--base-scan-id` / `--base-commit-sha` | |
| 30 | + |
| 31 | +The diff-scans path is the one with published scope names: `diff-scans:create`, |
| 32 | +`diff-scans:list` and `full-scans:list`. For the rest of the calls above, grant the |
| 33 | +token access to the corresponding resource; if you need the exact scope identifiers to |
| 34 | +provision a least-privilege token, ask Socket support rather than inferring them from |
| 35 | +the endpoint paths. |
| 36 | + |
| 37 | +### What individual flags add |
| 38 | + |
| 39 | +| Flag | Additional API calls | |
| 40 | +|:---|:---| |
| 41 | +| `--reach` | Manifest upload, `GET organizations` (plan check), full-scan tier-1 finalize | |
| 42 | +| `--generate-license` | `POST purl` | |
| 43 | +| `--enable-json` / `--enable-sarif` / `--enable-gitlab-security` on a full scan | `GET orgs/{org}/full-scans/{id}` (metadata), full-scan stream, and `POST license-metadata` for every package that carries a license | |
| 44 | +| `--sbom-file` | `GET orgs/{org}/export/cdx/{id}` | |
| 45 | + |
| 46 | +## `APIAccessDenied` on the scan comparison |
| 47 | + |
| 48 | +Any run that produces a diff — PR/MR events, **plain pushes on the default branch**, |
| 49 | +and `--enable-diff` / `--ignore-commit-files` runs without an SCM integration — first |
| 50 | +tries the diff-scans endpoints. A token without the `diff-scans:*` permissions logs a |
| 51 | +warning and silently continues on the older path: |
15 | 52 |
|
16 | 53 | ``` |
17 | 54 | Diff scan comparison failed with APIAccessDenied(Insufficient permissions), falling back to the streaming scan comparison |
18 | 55 | ``` |
19 | 56 |
|
20 | | -Grant the scopes to use the diff-scans path. It polls with short, bounded requests |
21 | | -rather than holding one connection open while the backend computes, which is what lets |
22 | | -large comparisons survive network idle timeouts — notably Azure NAT gateways, which |
23 | | -reap idle connections after four minutes and surface as an intermittent |
24 | | -`ConnectionResetError`. |
| 57 | +The scan still succeeds and the diff results are the same, so this is easy to miss. |
| 58 | + |
| 59 | +Note that this is *not* limited to PR/MR runs. A pipeline that only ever scans pushes |
| 60 | +(`--pr-number 0 --default-branch`) still hits it. |
| 61 | + |
| 62 | +**Which permission is missing.** The fallback path is `GET orgs/{org}/full-scans/diff`, |
| 63 | +a full-scans read. If you see the fallback produce results, your token already has |
| 64 | +full-scans read, and the missing grants are the two `diff-scans:*` permissions — not |
| 65 | +`full-scans:list`. If the fallback *also* fails, the gap is broader. |
| 66 | + |
| 67 | +**Why the diff-scans path is preferred.** It polls with short, bounded requests rather |
| 68 | +than holding one connection open while the backend computes, which is what lets large |
| 69 | +comparisons survive network idle timeouts — notably Azure NAT gateways, which reap idle |
| 70 | +connections after four minutes and surface as an intermittent `ConnectionResetError`. |
| 71 | +Falling back costs resilience, not correctness. |
25 | 72 |
|
26 | 73 | The two paths can take noticeably different amounts of time on the same repository, |
27 | 74 | because cached diff-scan responses always embed per-package license details while the |
28 | 75 | streaming comparison requests a lean payload. On a large dependency tree, compare the |
29 | 76 | `Diff scan comparison ready in ...` timing against the `Diff Report Gathered in ...` |
30 | 77 | total before assuming either path is at fault. |
31 | 78 |
|
| 79 | +## A missing repository permission can exit 2 |
| 80 | + |
| 81 | +If the repository lookup fails, the CLI assumes the repo does not exist yet and tries |
| 82 | +to create it. When the token cannot do either, the run exits with code **2** — which |
| 83 | +[the exit code table](../README.md#exit-codes) otherwise documents as a keyboard |
| 84 | +interrupt. That exit also bypasses `--disable-blocking` and `--exit-code-on-api-error`, |
| 85 | +so it fails the pipeline even when you have asked for infrastructure errors to be |
| 86 | +non-blocking. |
| 87 | + |
| 88 | +If a job dies with exit 2 and `Failed to create repository` or `API failure while |
| 89 | +creating repository` in the log, check the token's repository permissions before |
| 90 | +looking anywhere else. |
| 91 | + |
| 92 | +## Baseline scans that do not appear in the dashboard |
| 93 | + |
| 94 | +On the first scan of a repository or branch there is no head scan to diff against, so |
| 95 | +the CLI creates a **temporary empty baseline** and compares to that: |
| 96 | + |
| 97 | +``` |
| 98 | +No previous scan found - creating empty baseline scan |
| 99 | +Comparing scans - Head scan ID: <baseline>, New scan ID: <real scan> |
| 100 | +``` |
| 101 | + |
| 102 | +The baseline is created as a temporary scan and is deliberately not set as the |
| 103 | +repository head or default branch, so it does not show up as a repository scan in the |
| 104 | +dashboard. Looking it up by ID will come up empty. This is expected — the scan to look |
| 105 | +at is the "New scan ID". |
| 106 | + |
| 107 | +Because everything is new relative to an empty baseline, the first run also reports |
| 108 | +every package as added and zero as unchanged. |
| 109 | + |
| 110 | +## Reachability scan IDs are not full-scan IDs |
| 111 | + |
| 112 | +With `--reach`, the CLI logs an extra identifier before the normal scan flow starts: |
| 113 | + |
| 114 | +``` |
| 115 | +Reachability scan ID: <tier-1 reachability scan ID> |
| 116 | +``` |
| 117 | + |
| 118 | +That value is `tier1ReachabilityScanId`, read out of `.socket.facts.json`. It |
| 119 | +identifies the reachability analysis run, not a Socket full scan, and will not resolve |
| 120 | +in the dashboard's scan views. The full scan to look at is the "New scan ID" reported |
| 121 | +by the comparison step that follows. |
| 122 | + |
32 | 123 | ## Common gotchas |
33 | 124 |
|
34 | 125 | - In diff scope, `--strict-blocking` uses a stricter alert set (`new + unchanged`) for blocking checks and diff-based output selection. |
|
0 commit comments