Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## 2.7.0

### Changed: improve monorepo scan diagnostics and guidance

- Added aggregate scan configuration, manifest-count, baseline-selection, and
fallback diagnostics without listing submitted manifest paths.
- Clarified monorepo scan scoping, workspace flags, CI path filters, and timeout
behavior, with a changed-workspace GitHub Actions example.

### Fixed: apply configured exit codes to API failures

- Full-scan and streamed-diff API failures now use the configured infrastructure
error exit code instead of the security-finding exit code.

## 2.6.8

### Changed: bump pinned @coana-tech/cli to 15.10.25
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,10 @@ value — e.g. a Buildkite
code, or `0` to swallow infra errors. Exit `3` is a Socket convention, not an
industry standard.

This mapping applies to errors the CLI receives and handles. An external process
supervisor (for example GNU `timeout`) can terminate the CLI before it handles an
error, so the supervisor's exit status (commonly 124 or 137) takes precedence.

### How these options interact

The two flags that affect exit codes can cancel each other out, so the order of
Expand Down
218 changes: 218 additions & 0 deletions docs/ci-cd.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,224 @@ Equivalent JSON:
SOCKET_SECURITY_API_TOKEN: ${{ secrets.SOCKET_SECURITY_API_TOKEN }}
```

#### GitHub Actions: scan changed monorepo workspaces independently

GitHub Actions `paths` filters only decide whether a workflow starts. They do not
change `socketcli` discovery or upload scope. For a merge gate, it is usually safer
to start a small selector job on every PR update, then create one scan job per
affected logical workspace. This also avoids a required check remaining pending
when GitHub skips the entire workflow because of a top-level path filter.

Define a repository variable named `SOCKET_MONOREPO_WORKSPACES_JSON`. Its value is
an array with one stable workspace name, one or more scan roots, and the path globs
that should select that workspace. Fill these placeholders with the repository's
real layout; list a shared/root lockfile in every workspace it affects.

```json
[
{
"name": "<stable-workspace-name>",
"sub_paths": ["<repo-relative-scan-root>"],
"watch_globs": ["<repo-relative-changed-file-glob>"]
}
]
```

Also define `SOCKETCLI_VERSION` as the exact package version validated for the
workflow. The workflow below logs that version, uses full Git history for reliable
base/head selection, creates one matrix job (and therefore one graph and baseline)
per selected workspace, and fails closed on CLI/API/timeout failures. It uses API
SCM mode plus `--enable-diff` because parallel `--scm github` jobs can race while
updating the same PR comments; the matrix checks and report links are the gate.

```yaml
name: Socket Security

on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches: [main]

permissions:
contents: read

jobs:
select-workspaces:
runs-on: ubuntu-latest
outputs:
count: ${{ steps.select.outputs.count }}
matrix: ${{ steps.select.outputs.matrix }}
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
persist-credentials: false

- id: select
name: Select changed workspaces
env:
WORKSPACES_JSON: ${{ vars.SOCKET_MONOREPO_WORKSPACES_JSON }}
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
shell: bash
run: |
python - <<'PY'
import fnmatch
import json
import os
import re
import subprocess

workspaces = json.loads(os.environ["WORKSPACES_JSON"])
if not isinstance(workspaces, list):
raise SystemExit("SOCKET_MONOREPO_WORKSPACES_JSON must be a JSON array")

base = os.environ["BASE_SHA"]
head = os.environ["HEAD_SHA"]
if not base or set(base) == {"0"}:
base = subprocess.check_output(
["git", "rev-parse", f"{head}^"], text=True
).strip()
changed_output = subprocess.check_output(
["git", "diff", "--name-only", "-z", base, head]
)
changed = [
item.decode("utf-8", "surrogateescape")
for item in changed_output.split(b"\0")
if item
]

selected = []
for workspace in workspaces:
name = workspace.get("name", "")
sub_paths = workspace.get("sub_paths") or []
watch_globs = workspace.get("watch_globs") or []
if not re.fullmatch(r"[A-Za-z0-9._-]+", name):
raise SystemExit(f"Invalid workspace name: {name!r}")
if not sub_paths or any(
not isinstance(path, str)
or path.startswith("/")
or ".." in path.split("/")
for path in sub_paths
):
raise SystemExit(f"Invalid sub_paths for workspace {name!r}")
if not watch_globs:
watch_globs = [
pattern
for path in sub_paths
for pattern in (
["*"]
if path.strip("/") in ("", ".")
else [path.rstrip("/"), f"{path.rstrip('/')}/*"]
)
]
if any(
fnmatch.fnmatchcase(path, pattern)
for path in changed
for pattern in watch_globs
):
selected.append({"name": name, "sub_paths": sub_paths})

matrix = json.dumps({"include": selected}, separators=(",", ":"))
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output:
output.write(f"count={len(selected)}\n")
output.write(f"matrix={matrix}\n")
PY

scan-workspace:
needs: select-workspaces
if: needs.select-workspaces.outputs.count != '0'
timeout-minutes: 20
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.select-workspaces.outputs.matrix) }}
name: Socket scan (${{ matrix.name }})
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
persist-credentials: false

- uses: actions/setup-python@v6
with:
python-version: '3.12'

- name: Install pinned Socket CLI
env:
SOCKETCLI_VERSION: ${{ vars.SOCKETCLI_VERSION }}
run: |
python -m pip install "socketsecurity==$SOCKETCLI_VERSION"
socketcli --version

- name: Scan workspace
env:
SOCKET_SECURITY_API_KEY: ${{ secrets.SOCKET_SECURITY_API_KEY }}
PR_NUMBER: ${{ github.event.pull_request.number || 0 }}
WORKSPACE_NAME: ${{ matrix.name }}
SUB_PATHS_JSON: ${{ toJSON(matrix.sub_paths) }}
shell: bash
run: |
set +e
args=(
--target-path "$GITHUB_WORKSPACE"
--workspace-name "$WORKSPACE_NAME"
--enable-diff
--pr-number "$PR_NUMBER"
--exit-code-on-api-error 3
--report-link-file socket-report-link.txt
--summary-file socket-summary.txt
)
while IFS= read -r sub_path; do
args+=(--sub-path "$sub_path")
done < <(jq -r '.[]' <<<"$SUB_PATHS_JSON")

socketcli "${args[@]}" 2>&1 | tee socket-output.log
code=${PIPESTATUS[0]}

{
echo "## Socket scan: $WORKSPACE_NAME"
if [ -s socket-report-link.txt ]; then
echo "[View the report]($(cat socket-report-link.txt))"
fi
if [ -s socket-summary.txt ]; then
echo '```'
cat socket-summary.txt
echo '```'
fi
} >> "$GITHUB_STEP_SUMMARY"

exit "$code"

socket-security:
if: always()
needs: [select-workspaces, scan-workspace]
runs-on: ubuntu-latest
steps:
- name: Enforce matrix result
env:
SELECT_RESULT: ${{ needs.select-workspaces.result }}
SCAN_RESULT: ${{ needs.scan-workspace.result }}
run: |
test "$SELECT_RESULT" = success
[[ "$SCAN_RESULT" = success || "$SCAN_RESULT" = skipped ]]
```

Each configuration object may intentionally contain several `sub_paths` when
those directories are one logical dependency graph. To split backend resolution,
use separate objects with different `name` values. Add `--workspace <name>` only
when the Socket organization requires API workspace association; it is not a scan
scope control.

The job has an explicit 20-minute total budget. Tune that value from observed
workspace-level latency after the split; a five-minute cap can still be too close
to a slow request plus local startup. The CLI's `--timeout` is different: it
defaults to 1,200 seconds **per API request**. If an operator adds GNU `timeout`,
that process supervisor can terminate the CLI before it maps an error through
`--exit-code-on-api-error`; without `--preserve-status`, GNU reports 124 after its
initial timeout signal or 137 if `SIGKILL` is involved.

### Buildkite

```yaml
Expand Down
34 changes: 28 additions & 6 deletions docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,26 @@ Pre-configured workflow files are in [`../workflows/`](../workflows/).

> **Note:** If you're looking to associate a scan with a named Socket workspace (e.g. because your repo is identified as `org/repo`), see the [`--workspace` flag](#repository) instead. The `--workspace-name` flag described in this section is an unrelated monorepo feature.

The Socket CLI supports scanning specific workspaces within monorepo structures while preserving git context from the repository root. This is useful for organizations that maintain multiple applications or services in a single repository.
The Socket CLI supports scanning selected directories within a monorepo while preserving git context from the repository root. Scan scope is controlled by `--target-path` and `--sub-path`; CI workflow path filters and the CLI's changed-file detection do not narrow the manifests uploaded after a scan starts.

### Key Features

- **Multiple Sub-paths**: Specify multiple `--sub-path` options to scan different directories within your monorepo
- **Combined Workspace**: All sub-paths are scanned together as a single workspace in Socket
- **Target path**: Supplies repository/Git context and is the discovery root when no `--sub-path` is present
- **Multiple Sub-paths**: Restrict discovery to those directories, but combine every repeated `--sub-path` into one upload and one server-side dependency graph
- **Git Context Preserved**: Repository metadata (commits, branches, etc.) comes from the main target-path
- **Workspace Naming**: Use `--workspace-name` to differentiate scans from different parts of your monorepo
- **Workspace Naming**: Use a stable, unique `--workspace-name` for each independently scanned logical workspace; it suffixes the repository slug and therefore gives that workspace its own repository head/baseline

`--workspace` is different: it sends Socket organization workspace context with the full-scan API request. It does not narrow client-side filesystem discovery, split the upload into independent scans, or change the repository suffix. Backend policy/routing for that workspace remains server-owned.

> **Performance consequence:** If the goal is smaller independently resolvable graphs, run one CLI invocation per logical workspace, with a distinct `--workspace-name`. Adding several unrelated directories to one command with repeated `--sub-path` flags still asks the backend to resolve one combined graph.

Normal scan logs include the effective repository and Socket workspace context,
repository-relative discovery roots, aggregate manifest count, and selected baseline.
Individual manifest paths remain opt-in through `--save-submitted-files-list`.

### Usage Examples

**Scan multiple frontend and backend workspaces:**
**Scan several directories that belong to one logical application:**
```bash
socketcli --target-path /path/to/monorepo \
--sub-path frontend \
Expand All @@ -89,6 +97,19 @@ This will:
- Create a repository in Socket named like `my-repo-mobile-web`
- Preserve git context (commits, branch info) from the repository root

**Create independent frontend and backend scans:**
```bash
socketcli --target-path /path/to/monorepo \
--sub-path frontend \
--workspace-name frontend

socketcli --target-path /path/to/monorepo \
--sub-path backend \
--workspace-name backend
```

These are two full-scan uploads, two server-side graphs, and two repository head/baseline sequences. In CI they can run as separate matrix jobs. See [GitHub Actions: scan changed monorepo workspaces independently](ci-cd.md#github-actions-scan-changed-monorepo-workspaces-independently).

**Generate GitLab Security Dashboard report:**
```bash
socketcli --enable-gitlab-security \
Expand Down Expand Up @@ -138,6 +159,7 @@ This will simultaneously generate:

- Both `--sub-path` and `--workspace-name` must be specified together
- `--sub-path` can be used multiple times to include multiple directories
- Repeated `--sub-path` values are combined into one scan; they do not create independent workspace scans
- All specified sub-paths must exist within the target-path

## Usage
Expand Down Expand Up @@ -373,7 +395,7 @@ The launcher can be tuned via the `SOCKET_CLI_COANA_LAUNCHER` environment variab
| `--strict-blocking` | False | False | Fail on ANY security policy violations (blocking severity), not just new ones. Only works in diff mode. See [Strict Blocking Mode](#strict-blocking-mode) for details. |
| `--enable-diff` | False | False | Enable diff mode even when using `--integration api` (forces diff mode without SCM integration) |
| `--scm` | False | api | Source control management type |
| `--timeout` | False | | Timeout in seconds for API requests |
| `--timeout` | False | 1200 | Timeout in seconds for each API request. This is not a total CLI runtime limit and does not limit local discovery, Git, or reachability analysis. |

#### Plugins

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ build-backend = "hatchling.build"

[project]
name = "socketsecurity"
version = "2.6.8"
version = "2.7.0"
requires-python = ">= 3.11"
license = {"file" = "LICENSE"}
dependencies = [
Expand Down
2 changes: 1 addition & 1 deletion socketsecurity/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__author__ = 'socket.dev'
__version__ = '2.6.8'
__version__ = '2.7.0'
USER_AGENT = f'SocketPythonCLI/{__version__}'
Loading
Loading