Skip to content

Commit 468ea97

Browse files
leliaclaude
andcommitted
Document the API token permissions a run actually needs
The only scope guidance in the repo covered the diff-scans comparison path, described it as PR/MR-only, and listed `full-scans:list` alongside the two `diff-scans:*` scopes. The comparison also runs on plain pushes, and the streaming fallback is itself a full-scans read, so a reader hitting the `APIAccessDenied` warning was pointed at the wrong permission and told the condition did not apply to them. Replace it with the full picture: what every run calls, what diff-producing runs add, and what each flag adds. Scope names are only published for the diff-scans path, so the rest are described by endpoint rather than guessed at. Also document three outcomes that look like failures and are not: the temporary empty baseline on a first scan, the reachability scan ID being a `tier1ReachabilityScanId` rather than a full scan ID, and a repository permission gap exiting 2 — a code the exit table documents as a keyboard interrupt, and which bypasses `--disable-blocking` and `--exit-code-on-api-error`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 7c95310 commit 468ea97

5 files changed

Lines changed: 158 additions & 16 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Documentation: API token permissions
6+
7+
- Documented the API calls a run actually makes, so a token can be provisioned without
8+
trial and error: what every run calls, what diff-producing runs add, and what each
9+
flag adds. Only the diff-scans scope names are published, so the remaining calls are
10+
described by endpoint, with a pointer to support for exact scope identifiers.
11+
- Corrected the scan-comparison guidance. The `APIAccessDenied` fallback was documented
12+
as a PR/MR-only condition, but it applies to any run that produces a diff, including
13+
plain pushes on the default branch. The guidance also listed `full-scans:list`
14+
alongside the two `diff-scans:*` scopes, which points readers at a permission the
15+
fallback path demonstrably already has.
16+
- Documented three outcomes that are expected but read as failures: the temporary empty
17+
baseline created on a repository's first scan, which does not appear in the dashboard;
18+
the reachability scan ID being a `tier1ReachabilityScanId` rather than a full scan ID,
19+
so it does not resolve in the dashboard's scan views; and a repository permission gap
20+
exiting `2`, a code the exit table otherwise documents as a keyboard interrupt and
21+
which bypasses both `--disable-blocking` and `--exit-code-on-api-error`.
22+
- Documented all four accepted API token environment variables in priority order, and
23+
corrected a reference to `$SOCKET_API_TOKEN` in the GitLab requirements section that
24+
did not match the documented `SOCKET_SECURITY_API_TOKEN`.
325
## 2.9.7
426

527
### Changed: bump pinned @coana-tech/cli to 15.10.51

‎README.md‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ pip install socketsecurity
1818
export SOCKET_SECURITY_API_TOKEN="<token>"
1919
```
2020

21+
The token needs more than scan-creation access, and some paths fall back with only a
22+
warning when a permission is missing. See
23+
[`docs/troubleshooting.md#api-token-permissions`](https://github.com/SocketDev/socket-python-cli/blob/main/docs/troubleshooting.md#api-token-permissions).
24+
2125
### 3) Run a basic scan
2226

2327
```bash
@@ -221,7 +225,7 @@ Minimal pattern:
221225
|------|---------|
222226
| `0` | Clean scan — no blocking issues (or `--disable-blocking` set) |
223227
| `1` | Blocking security finding(s) detected |
224-
| `2` | Scan interrupted (SIGINT / Ctrl+C) |
228+
| `2` | Scan interrupted (SIGINT / Ctrl+C), or a repository lookup/creation failure |
225229
| `3` | Infrastructure or API error (timeout, network failure, unexpected error) |
226230

227231
`--exit-code-on-api-error <N>` remaps the infrastructure-error code (`3`) to any
@@ -246,6 +250,10 @@ precedence matters:
246250
set.** It changes the infra-error code (and the generic-error code); it never
247251
touches the security-finding code (`1`).
248252

253+
- **Neither flag covers exit `2`.** A repository lookup/creation failure (typically a
254+
token permission gap) exits `2` directly and is not remapped by either flag. See
255+
[`docs/troubleshooting.md#a-missing-repository-permission-can-exit-2`](https://github.com/SocketDev/socket-python-cli/blob/main/docs/troubleshooting.md#a-missing-repository-permission-can-exit-2).
256+
249257
So for the common "don't let Socket outages block my pipeline, but still fail on
250258
real findings" goal, use `--exit-code-on-api-error` **without** `--disable-blocking`:
251259

‎docs/ci-cd.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,3 +546,9 @@ Prebuilt examples in this repo:
546546
the console table, including native Buildkite job logs, and in GitHub/GitLab
547547
security comments when that SCM adapter is configured. Findings without a known
548548
patched release leave the console cell blank and omit the comment field.
549+
- A token missing the `diff-scans:*` permissions does not fail the build — it logs
550+
`Diff scan comparison failed with APIAccessDenied(Insufficient permissions)` and falls
551+
back to a less resilient comparison path. This affects PR/MR events and the
552+
`--scm api` / `--enable-diff` paths; since 2.9.0 an SCM-integrated branch push
553+
creates a full scan instead and never reaches it. See
554+
[`troubleshooting.md#api-token-permissions`](troubleshooting.md#api-token-permissions).

‎docs/cli-reference.md‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,21 @@ If you don't want to provide the Socket API Token every time then you can use th
232232
|:------------|:---------|:--------|:----------------------------------------------------------------------------------|
233233
| `--api-token` | False | | Socket Security API token (can also be set via SOCKET_SECURITY_API_TOKEN env var) |
234234
235+
The token is read from the first of these that is set, then from `--api-token`:
236+
237+
1. `SOCKET_SECURITY_API_KEY`
238+
2. `SOCKET_SECURITY_API_TOKEN`
239+
3. `SOCKET_API_KEY`
240+
4. `SOCKET_API_TOKEN`
241+
242+
`SOCKET_SECURITY_API_TOKEN` is the documented name; the others are accepted for
243+
compatibility. Prefer the documented name in new pipelines.
244+
245+
> **Token permissions:** the CLI needs more than scan-creation access, and several
246+
> paths degrade with only a warning when a permission is missing rather than failing.
247+
> See [`troubleshooting.md#api-token-permissions`](troubleshooting.md#api-token-permissions)
248+
> for the full list of API calls a run makes and which flags add to it.
249+
235250
#### Repository
236251
| Parameter | Required | Default | Description |
237252
|:-----------------|:---------|:--------|:------------------------------------------------------------------------------------------------------------------|
@@ -898,7 +913,7 @@ When `--enable-gitlab-security` (or `--enable-json` / `--enable-sarif`) is used
898913
### Requirements
899914
900915
- **GitLab Version**: GitLab 12.0 or later (for Security Dashboard support)
901-
- **Socket API Token**: Set via `$SOCKET_API_TOKEN` environment variable or `--api-token` parameter
916+
- **Socket API Token**: Set via `SOCKET_SECURITY_API_TOKEN` environment variable or `--api-token` parameter (see [Authentication](#authentication) for the other accepted variable names)
902917
- **CI/CD Artifacts**: Reports must be uploaded as `dependency_scanning` artifacts
903918
904919
### Troubleshooting

‎docs/troubleshooting.md‎

Lines changed: 105 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,125 @@
11
# Troubleshooting
22

3-
## API token scopes for scan comparisons
3+
## API token permissions
44

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.
88

9-
- `diff-scans:create`
10-
- `diff-scans:list`
11-
- `full-scans:list`
9+
### What every run calls
1210

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:
1552

1653
```
1754
Diff scan comparison failed with APIAccessDenied(Insufficient permissions), falling back to the streaming scan comparison
1855
```
1956

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.
2572

2673
The two paths can take noticeably different amounts of time on the same repository,
2774
because cached diff-scan responses always embed per-package license details while the
2875
streaming comparison requests a lean payload. On a large dependency tree, compare the
2976
`Diff scan comparison ready in ...` timing against the `Diff Report Gathered in ...`
3077
total before assuming either path is at fault.
3178

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+
32123
## Common gotchas
33124

34125
- In diff scope, `--strict-blocking` uses a stricter alert set (`new + unchanged`) for blocking checks and diff-based output selection.

0 commit comments

Comments
 (0)