Complete guide to integrating Socket Basics into your GitHub Actions workflows for automated security scanning.
- Quick Start
- Performance and Caching
- Basic Configuration
- Enterprise Features
- Advanced Workflows
- Configuration Reference
- Troubleshooting
Add Socket Basics to your workflow in 3 steps:
- Create workflow file at
.github/workflows/security-scan.yml - Add required secrets to your repository
- Configure scanning options
name: Security Scan
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
jobs:
security-scan:
permissions:
issues: write
contents: read
pull-requests: write
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Run Socket Basics
uses: SocketDev/socket-basics@v3.4.1
env:
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
socket_security_api_key: ${{ secrets.SOCKET_SECURITY_API_KEY }}On a Socket Enterprise organization with Socket Basics enabled, this is the whole workflow: the API key loads your scanning configuration from the Socket Dashboard. Dashboard settings take precedence over scanner inputs you add to with:, including dashboard fields you leave blank. See Dashboard Settings and Workflow Inputs.
Without dashboard configuration, every scanner input defaults to off, so turn on the scanners you want with the inputs under Common Scanning Options.
When you reference uses: SocketDev/socket-basics@v3.4.1, GitHub Actions pulls the
pre-built image referenced by action.yml. The historical multi-stage
Docker build still matters for maintainers because it determines what lands in the
published image:
| Improvement | Benefit |
|---|---|
Multi-stage stages (trivy, trufflehog, etc.) |
GitHub's runner cache can reuse unchanged tool layers across runs |
python:3.12-slim base |
~850 MB smaller final image → faster layer pulls on cold runners |
--mount=type=cache for apt / uv / npm |
Faster repeated builds locally and on self-hosted runners with a persistent cache |
On standard GitHub-hosted runners (ephemeral, no persistent Docker cache between jobs), users mainly benefit from pulling a ready-made image instead of rebuilding Socket Basics from source in every workflow run.
Starting with v2, the action pulls a pre-built image from GHCR rather than
building from source on every run. Pinning to a specific version tag (e.g. @v3.4.1)
means the action starts in seconds — the image is built, integration-tested, and
published before the release tag is ever created.
If you run socket-basics in other CI systems (Jenkins, GitLab, CircleCI, etc.) or
as a standalone docker run, pull the pre-built image directly:
docker pull ghcr.io/socketdev/socket-basics:3.4.1See Local Docker Installation for usage examples.
Socket Basics is a security tool. Its own supply-chain integrity matters — if the action itself is compromised or ships a bad release, every repo running it is immediately affected. We've seen this happen across the ecosystem:
- Floating tags (
@v2,:latest) auto-update on every new release. A single bad push silently reaches all users with no review gate. (We do publish:latest/:latest-heavyDocker aliases as an onboarding convenience, but treat them as exactly that — production pipelines should pin an exact version or digest.) - Version tags (
@v3.4.1) are better, but tags are mutable by default. A tag can be deleted and recreated pointing at a different commit. There are documented cases of this happening — maliciously and accidentally. - Commit SHAs are the only truly immutable reference. A SHA cannot be reassigned. Combined with Dependabot, you get automated upgrades with a human review gate at zero ongoing maintenance cost.
We don't publish a floating major tag (v2) for the action. Docker image
version tags are immutable registry-side (enforced by an immutable-tag rule),
with latest/latest-heavy as the only floating aliases — but SHA/digest
pinning is still the recommendation for defence in depth.
Two supported approaches, both managed by Dependabot:
Strategy 1 — Commit SHA pin + Dependabot (recommended)
The only truly immutable reference. Dependabot keeps it current automatically.
- name: Run Socket Basics
# Dependabot keeps this SHA up to date — see .github/dependabot.yml setup below.
uses: SocketDev/socket-basics@<sha> # v3.4.1
with:
socket_security_api_key: ${{ secrets.SOCKET_SECURITY_API_KEY }}Get the SHA for any release:
git ls-remote https://github.com/SocketDev/socket-basics refs/tags/v3.4.1Strategy 2 — Version tag pin + Dependabot
Acceptable if you trust that tags are immutable (they are — socket-basics enforces tag protection rules). SHA pinning is still preferable for defence in depth.
- uses: SocketDev/socket-basics@v3.4.1
with:
socket_security_api_key: ${{ secrets.SOCKET_SECURITY_API_KEY }}Dependabot setup (works for both strategies)
Add or extend .github/dependabot.yml in your repo:
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"Dependabot opens a PR for each new release, updating the SHA or version tag
and keeping the # v3.4.1 comment in sync. You review, approve, and merge
on your own schedule — automated upgrades with a human gate.
Comparison
| Strategy | Immutable? | Auto-updates | Review gate |
|---|---|---|---|
@v2 floating tag |
❌ (not published) | — | — |
@v3.4.1 + Dependabot |
✅ (tag protection enforced) | Yes (weekly PR) | Yes |
@<sha> + Dependabot |
✅ always | Yes (weekly PR) | Yes |
Socket Basics requires the following permissions to post PR comments and create issues:
permissions:
issues: write # Severity labels are managed through the issues API
contents: read # Read repository contents
pull-requests: write # Post and update the findings commentInclude these in your workflow's jobs.<job_id>.permissions section.
github_token (required for PR comments and labels)
- GitHub token for posting PR comments and API access
- Use
${{ secrets.GITHUB_TOKEN }}(automatically provided) - Also set
GITHUB_PR_NUMBERin the stepenv:as shown in the Quick Start. Onpull_requestevents the number is also read from the event payload, so the env entry is a belt-and-braces default that keeps the same workflow working onissue_commentand other triggers.
socket_security_api_key (required to upload results and load dashboard configuration)
- Without it the scan still runs and comments on the PR, but nothing reaches the Socket dashboard.
socket_org (optional)
- Discovered from the API key when the key has the
socket-basicsscope. Set it explicitly when configuring the scan fromwith:inputs instead of the dashboard. - Outside the action the same value is
SOCKET_ORGor--socket-org. See the name mapping for the equivalents of every input.
SAST (Static Analysis):
- uses: SocketDev/socket-basics@v3.4.1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
# Enable SAST for specific languages
python_sast_enabled: 'true'
javascript_sast_enabled: 'true'
go_sast_enabled: 'true'
java_sast_enabled: 'true'
# Or enable all languages
all_languages_enabled: 'true'Secret Scanning:
- uses: SocketDev/socket-basics@v3.4.1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
secret_scanning_enabled: 'true'
# Optional: exclude paths (directory names, file names, or globs)
trufflehog_exclude_dir: 'node_modules,vendor,dist,**/appsettings.*.json'
# Optional: report unverified secrets too (default: verified and unknown)
trufflehog_show_unverified: 'true'Secret verification runs on every scan and requires network egress. By default only verified and unknown results are reported. Verified secrets are critical and blocking; unknown results are low severity and nonblocking. TruffleHog confirms each candidate against third-party validation endpoints, and a runner that cannot reach one reports the candidate as
unknowninstead of silently dropping it. Settrufflehog_show_unverified: 'true'to include candidates that were checked but not confirmed as valid as well.
Container Scanning:
- uses: SocketDev/socket-basics@v3.4.1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
# Listing images or Dockerfiles auto-enables the matching Trivy scan.
# Images must be reachable from the job: public, pullable after an earlier
# `docker login` step, or built earlier in the same job.
container_images: 'nginx:1.27.4,ghcr.io/your-org/api:1.4.2'
dockerfiles: 'Dockerfile,docker/Dockerfile.prod'
# Optional: Trivy vulnerability scanning for supported language ecosystems
trivy_vuln_enabled: 'true'dockerfiles checks each Dockerfile for misconfigurations with trivy config.
It does not report vulnerabilities in the base image or in packages the build
installs. For those, build the image and list it in container_images, which
runs trivy image. Both inputs take literal, comma-separated values. Dockerfile
paths are relative to the repository root, wildcards are not expanded, and a
path that does not exist is skipped with a Dockerfile not found warning.
Note
Trivy is bundled in the pre-built action image (a Socket-built distribution, rebuilt from unmodified upstream source and pinned by digest), so these inputs need no extra setup. Only a native install needs the version guidance in Trivy (Container Scanning).
Socket Tier 1 Reachability:
- uses: SocketDev/socket-basics@v3.4.1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
socket_tier_1_enabled: 'true'- uses: SocketDev/socket-basics@v3.4.1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
python_sast_enabled: 'true'
# Enable tabular console output
console_tabular_enabled: 'true'
# Or enable JSON output
console_json_enabled: 'true'
# Enable verbose logging for debugging
verbose: 'true'By default the scanners run against the entire repository, so every PR
re-reports the whole repo's existing findings. To report only on what the PR
changed — the way Socket SCA Pull Request alerts behave — use the
changed_files input. This scopes SAST/OpenGrep, secret, and container scans to
the changed files and dramatically reduces PR finding volume.
name: Socket Basics (PR diff-only)
on:
pull_request:
jobs:
socket-basics:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# Required so the PR base branch is available for the diff
fetch-depth: 0
- name: Run Socket Basics (changed files only)
uses: SocketDev/socket-basics@v3.4.1
env:
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
# Diff-only: scope all scanners to files changed in this PR
changed_files: 'auto'
python_sast_enabled: 'true'
javascript_sast_enabled: 'true'
secret_scanning_enabled: 'true'changed_files accepts:
auto— the PR base diff in CI; staged changes only in a local run with no PR contextpr— the PR base diff, and nothing elsecurrent-commit— files in theHEADcommit- a commit hash — files changed in that commit
- a comma-separated file list — e.g.
src/app.py,src/utils.js
Important
For auto/pr modes, check out with fetch-depth: 0 so the base branch is
available to diff against. Deletions are excluded, so a delete-only PR scans
nothing rather than falling back to the whole repo. To scan an explicit file
list regardless of git state, use the scan_files input instead.
Note
When the diff cannot be resolved — the checkout is unreadable, or the base
branch is missing (most commonly a shallow clone without fetch-depth: 0) —
Socket Basics fails with a configuration error naming the underlying git
error. It does not scan.
This is deliberate. Diff-only scoping is an explicit instruction, and if it cannot be honored there is no honest result to report:
- Skipping the scanners would exit green having scanned zero files. A passing check that inspected nothing is worse than a failing one, and a warning buried in a run log is not something anyone acts on.
- Silently scanning everything would do the expensive thing on every PR — precisely what asking for a diff scope was avoiding. On a large repository that is a slow or OOM-prone check, and it reports pre-existing findings rather than the PR's own, so a checkout misconfiguration surfaces as large PR comments on every PR until corrected.
If the error appears on every PR, the cause is almost always a missing
fetch-depth: 0 — fix the checkout rather than sizing up the runner. Shallow
checkouts get a more specific error naming that fix directly, including the
no merge base shape where the base tip was fetched without connecting
history.
To scan anyway, set scan_all: true. That widens an unresolvable scope to
a full-repository scan with a warning instead of failing. Every enabled scanner
widens consistently on that failure path. scan_all does not override a scope
that resolved successfully: the changed files remain authoritative, and a
genuinely empty diff still skips the scoped scanners.
A genuinely empty diff (e.g. a delete-only PR) is a successful resolution and still skips the scanners — only a failed resolution errors. The resolved file count is logged on every scoped run, so an empty diff and a failed lookup are always distinguishable in the logs.
Diff-only mode logs what it did. Look for these lines in the step output:
INFO Resolved PR diff base to 'origin/main'
INFO Diff-only scan scoping requested (changed_files=auto): resolved 12 changed file(s)
INFO Diff-only scan scoping active: 12 scan target(s) from 12 changed file(s)
If the scope could not be applied, the run fails and says why:
| Error or warning you will see | What to do |
|---|---|
this checkout is shallow ... Set 'fetch-depth: 0' |
Add fetch-depth: 0 to actions/checkout |
no pull request base was found |
The trigger is not pull_request, so there is no base. Use changed_files: 'current-commit' or an explicit file list |
is not a git repository |
Run actions/checkout before the scan step |
git refused to read ... even with ... safe.directory |
The checkout is damaged or incomplete. Re-run actions/checkout, or pass an explicit file list |
the scope could not be resolved |
Fix the preceding Git error, or set scan_all: true (CLI: --scan-all) to opt into a full-repository fallback |
A successful empty diff is logged separately as genuinely empty and skips the scoped scanners; it is never conflated with a resolution failure.
You do not need git config --global --add safe.directory for this. The scan
runs as root inside a container over a workspace owned by the runner user, and
git normally refuses that with detected dubious ownership. Git subprocesses
mark the explicitly selected workspace as a command-scoped safe.directory, so
no config files are changed and no workflow change is needed. Setting
safe.directory in a workflow step would not have helped anyway, because it
writes the runner's git config rather than the container's.
changed_files is honored identically from the action input, the
INPUT_CHANGED_FILES environment variable, the --changed-files CLI flag, a
--config JSON file, and a Socket dashboard config. scan_all is only the
fail-open fallback when one of those requests cannot be resolved; it does not
override a successfully resolved scope. It reaches the scan the same five ways
— the scan_all input, INPUT_SCAN_ALL=true, the --scan-all CLI flag, a
--config JSON key, or dashboard config — and --no-scan-all turns it back
off for a single CLI run.
Socket Basics automatically posts enhanced PR comments with smart defaults that work out of the box — clickable file links, collapsible sections, syntax highlighting, CVE links, CVSS scores, and auto-labels are all enabled by default.
To run the scan without commenting on the PR at all, set pr_comment_enabled: 'false'. The scan still runs, findings are still uploaded to the Socket dashboard, and the job still fails on high/critical findings — only the comment is suppressed. If you want a quieter comment rather than no comment, pr_comment_collapse_all: 'true' closes the SAST and Socket Tier 1 sections, critical findings included.
📖 PR Comment Guide → — Complete customization options, configuration examples, and reference table
Socket Basics Enterprise features require a Socket Enterprise subscription.
Configure Socket Basics centrally from the Socket Dashboard:
Setup:
- Log in to Socket Dashboard
- Navigate to Settings → Socket Basics
- Configure scanning policies, notification channels, and rule sets
- Save your configuration
Enable in workflow:
- uses: SocketDev/socket-basics@v3.4.1
env:
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
# Dashboard configuration (Enterprise required)
socket_org: 'your-org-slug'
socket_security_api_key: ${{ secrets.SOCKET_SECURITY_API_KEY }}Note
You can also pass credentials using environment variables instead of the with: section:
- uses: SocketDev/socket-basics@v3.4.1
env:
SOCKET_SECURITY_API_KEY: ${{ secrets.SOCKET_SECURITY_API_KEY }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}Both approaches work identically. Use whichever fits your workflow style.
Your workflow will automatically use the settings configured in the dashboard.
When the dashboard configuration loads, it takes precedence over the action's
with: inputs. The action passes every input to the scanner as an INPUT_*
environment variable, and dashboard settings are applied on top of environment
variables (see Configuration Precedence).
The dashboard configuration loads when all three of these are true:
- The organization is on a Socket Enterprise plan.
- Socket Basics is enabled for the organization.
- The API key has the
socket-basicsscope.
Fields you leave blank in the dashboard still count. The dashboard sends a blank
field as an empty value and a switched-off toggle as false, and either one
replaces what the workflow set. This covers the SAST language and rule settings,
secret scanning, Socket Tier 1, the Container Images and Dockerfiles
fields, console output and verbose logging. For example, if the Dockerfiles
field is blank, a workflow that sets dockerfiles: 'Dockerfile' scans no
Dockerfiles.
The exceptions:
- A blank
<language>_enabled_rulesor<language>_disabled_rulesfield is skipped, so the workflow's value or the action's default list applies. - A blank notification field (Slack, Jira, Microsoft Teams, generic webhook, Microsoft Sentinel, Sumo Logic) falls back to the workflow's value, and so does a blank GitHub token. A filled-in dashboard field still wins.
- These inputs have no dashboard field, so the workflow's value applies:
changed_files,scan_files,scan_all,use_custom_sast_rules,custom_sast_rule_path,sast_ignore_overrides,trivy_vuln_enabledand the PR comment and label inputs.
To check which configuration a run used, look for one of these lines in the step log:
| Log line | What it means |
|---|---|
Loaded Socket Basics API configuration (overrides environment defaults) |
The dashboard configuration loaded and takes precedence over your with: inputs |
Loaded Socket plan metadata (free/non-enterprise mode; no dashboard overrides) |
No dashboard configuration loaded, so your with: inputs apply |
Error loading Socket Basics config: ... |
The dashboard configuration could not be loaded, so your with: inputs apply. The message says why. |
Dashboard values apply to every repository in the organization, so put a value
in the dashboard when it is the same everywhere. A Dockerfile path that does not
exist in a repository is skipped with a Dockerfile not found warning, so one
list of common paths can serve every repository.
For values that differ per repository, such as Dockerfile paths or images built earlier in the job, pass CLI flags. CLI flags are applied after the dashboard configuration, but the action does not accept them, so run the action's image directly as a step:
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Socket Basics
uses: docker://ghcr.io/socketdev/socket-basics:3.4.1
env:
SOCKET_SECURITY_API_KEY: ${{ secrets.SOCKET_SECURITY_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
with:
args: --dockerfiles Dockerfile,docker/Dockerfile.prod --images myapp:${{ github.sha }}Only the flags you pass take precedence. Every other setting still comes from the dashboard. Running the image directly differs from the action in two ways:
- The action's default rule lists (
<language>_enabled_rulesinaction.yml) don't apply. Where the dashboard's rule-list fields are blank, SAST uses the image's own defaults fromconnectors.yaml, which include more rules for some languages. - The step isn't tied to an action version, so update the image tag yourself
when you upgrade. To pin by digest, append
@sha256:<digest>to the image.docker buildx imagetools inspect ghcr.io/socketdev/socket-basics:3.4.1prints the digest.
See the Parameters Reference for every CLI flag.
All notification integrations require Socket Enterprise.
Slack Notifications:
- uses: SocketDev/socket-basics@v3.4.1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
socket_org: ${{ secrets.SOCKET_ORG }}
socket_security_api_key: ${{ secrets.SOCKET_SECURITY_API_KEY }}
python_sast_enabled: 'true'
# Slack webhook (Enterprise required)
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}Jira Issue Creation:
- uses: SocketDev/socket-basics@v3.4.1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
socket_org: ${{ secrets.SOCKET_ORG }}
socket_security_api_key: ${{ secrets.SOCKET_SECURITY_API_KEY }}
python_sast_enabled: 'true'
# Jira integration (Enterprise required)
jira_url: 'https://your-org.atlassian.net'
jira_email: ${{ secrets.JIRA_EMAIL }}
jira_api_token: ${{ secrets.JIRA_API_TOKEN }}
jira_project: 'SEC'Microsoft Teams:
- uses: SocketDev/socket-basics@v3.4.1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
socket_org: ${{ secrets.SOCKET_ORG }}
socket_security_api_key: ${{ secrets.SOCKET_SECURITY_API_KEY }}
python_sast_enabled: 'true'
# MS Teams webhook (Enterprise required)
msteams_webhook_url: ${{ secrets.MSTEAMS_WEBHOOK_URL }}Generic Webhook:
- uses: SocketDev/socket-basics@v3.4.1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
socket_org: ${{ secrets.SOCKET_ORG }}
socket_security_api_key: ${{ secrets.SOCKET_SECURITY_API_KEY }}
python_sast_enabled: 'true'
# Generic webhook (Enterprise required)
webhook_url: ${{ secrets.WEBHOOK_URL }}SIEM Integration:
- uses: SocketDev/socket-basics@v3.4.1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
socket_org: ${{ secrets.SOCKET_ORG }}
socket_security_api_key: ${{ secrets.SOCKET_SECURITY_API_KEY }}
python_sast_enabled: 'true'
# Microsoft Sentinel (Enterprise required)
ms_sentinel_workspace_id: ${{ secrets.MS_SENTINEL_WORKSPACE_ID }}
ms_sentinel_shared_key: ${{ secrets.MS_SENTINEL_SHARED_KEY }}
# Sumo Logic (Enterprise required)
sumologic_endpoint: ${{ secrets.SUMOLOGIC_ENDPOINT }}name: Comprehensive Security Scan
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches: [main, develop]
jobs:
security-scan:
permissions:
issues: write
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Run Socket Basics
uses: SocketDev/socket-basics@v3.4.1
env:
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
socket_org: ${{ secrets.SOCKET_ORG }}
socket_security_api_key: ${{ secrets.SOCKET_SECURITY_API_KEY }}
# Enable multiple languages (javascript_sast_enabled covers TypeScript)
python_sast_enabled: 'true'
javascript_sast_enabled: 'true'
go_sast_enabled: 'true'
# Security scans
secret_scanning_enabled: 'true'
socket_tier_1_enabled: 'true'
# Notifications (Enterprise)
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}name: Weekly Security Audit
on:
schedule:
# Run every Monday at 9 AM UTC
- cron: '0 9 * * 1'
workflow_dispatch: # Allow manual trigger
jobs:
security-audit:
permissions:
issues: write
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Run Full Security Scan
uses: SocketDev/socket-basics@v3.4.1
env:
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
socket_org: ${{ secrets.SOCKET_ORG }}
socket_security_api_key: ${{ secrets.SOCKET_SECURITY_API_KEY }}
# Scan all supported languages
all_languages_enabled: 'true'
# Enable all security features
secret_scanning_enabled: 'true'
socket_tier_1_enabled: 'true'
# Verbose output for audit trail
verbose: 'true'
console_tabular_enabled: 'true'
# Send to multiple channels (Enterprise)
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
jira_url: ${{ secrets.JIRA_URL }}
jira_email: ${{ secrets.JIRA_EMAIL }}
jira_api_token: ${{ secrets.JIRA_API_TOKEN }}
jira_project: 'SEC'Note
The pre-built GitHub Action bundles Trivy (a Socket-built distribution, rebuilt from unmodified upstream source and pinned by digest), so the container-scanning inputs below work out of the box with no Trivy install step.
Important
When your organization's dashboard configuration loads, its Container
Images and Dockerfiles fields take precedence over container_images
and dockerfiles in this example, even when those fields are blank. Pass the
values as CLI flags instead, as shown in
Setting Values Per Repository.
name: Container Security
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches: [main]
paths:
- 'Dockerfile*'
- 'docker/**'
jobs:
container-scan:
permissions:
issues: write
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Build Docker Image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Socket Basics (image + Dockerfile scan)
uses: SocketDev/socket-basics@v3.4.1
env:
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
socket_security_api_key: ${{ secrets.SOCKET_SECURITY_API_KEY }}
# The image built above is visible to the action through the runner's
# Docker daemon; registry images must be pullable from the job.
container_images: 'myapp:${{ github.sha }}'
dockerfiles: 'Dockerfile'For repositories with multiple Dockerfiles across different directories, you can automatically discover them instead of manually listing each path.
Important
This example passes the discovered paths through the dockerfiles input. When
your organization's dashboard configuration loads, the dashboard's
Dockerfiles field takes precedence over that input, even when the field is
blank, and the discovered paths are never scanned. In that case, replace the
Run Socket Basics step in the security-scan job with:
- name: Run Socket Basics
uses: docker://ghcr.io/socketdev/socket-basics:3.4.1
env:
SOCKET_SECURITY_API_KEY: ${{ secrets.SOCKET_SECURITY_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
with:
args: --dockerfiles ${{ needs.discover-dockerfiles.outputs.dockerfiles }} --verboseSetting Values Per Repository covers how this step differs from the action.
name: Security Scan with Dockerfile Auto-Discovery
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches: [main]
jobs:
discover-dockerfiles:
runs-on: ubuntu-latest
outputs:
dockerfiles: ${{ steps.discover.outputs.dockerfiles }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Discover Dockerfiles
id: discover
run: |
DOCKERFILES=$(find . -type d \( \
-name node_modules -o -name vendor -o -name .git -o \
-name test -o -name tests -o -name testing -o -name __tests__ -o \
-name fixture -o -name fixtures -o -name testdata -o \
-name example -o -name examples -o -name sample -o -name samples -o \
-name dist -o -name build -o -name out -o -name target -o \
-name venv -o -name .venv -o -name .cache \
\) -prune -o \
-type f \( -name 'Dockerfile' -o -name 'Dockerfile.*' -o -name '*.dockerfile' \) \
-print | sed 's|^./||' | paste -sd ',' -)
echo "Discovered Dockerfiles: $DOCKERFILES"
echo "dockerfiles=$DOCKERFILES" >> $GITHUB_OUTPUT
security-scan:
needs: discover-dockerfiles
if: needs.discover-dockerfiles.outputs.dockerfiles != ''
permissions:
issues: write
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Run Socket Basics
uses: SocketDev/socket-basics@v3.4.1
env:
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
# Discovered Dockerfiles feed Trivy-backed misconfiguration scanning,
# which is bundled in the pre-built action image.
dockerfiles: ${{ needs.discover-dockerfiles.outputs.dockerfiles }}
verbose: 'true'How it works:
-
Discovery job uses
findto locate Dockerfiles matching common patterns:Dockerfile(exact match)Dockerfile.*(e.g.,Dockerfile.prod,Dockerfile.dev)*.dockerfile(e.g.,backend.dockerfile)
-
Excluded directories prevent scanning test fixtures and build artifacts:
- Package managers:
node_modules,vendor,venv - Test directories:
test,tests,__tests__,fixtures - Build outputs:
dist,build,out,target
- Package managers:
-
Scan job receives discovered paths via job output and skips if none found
Customizing discovery patterns (edit the find expression in the discovery step):
# Only scan production Dockerfiles
-type f -name 'Dockerfile.prod' -print
# Add custom exclusions
-name custom_test_dir -o -name legacy -o \Use custom rules from your repository by setting use_custom_sast_rules and
custom_sast_rule_path. This path is resolved relative to GITHUB_WORKSPACE
in GitHub Actions.
name: Security Scan with Custom SAST Rules
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
security-scan:
permissions:
issues: write
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Run Socket Basics
uses: SocketDev/socket-basics@v3.4.1
env:
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
# Enable SAST languages you expect to run.
python_sast_enabled: 'true'
javascript_sast_enabled: 'true'
# Enable custom rules from repository path.
use_custom_sast_rules: 'true'
custom_sast_rule_path: '.socket/rules'
# Optional: to avoid allowlist exclusions, run all rules for enabled languages.
all_rules_enabled: 'true'
# Optional: enable specific bundled or custom rule IDs.
javascript_enabled_rules: 'eval-usage,prototype-pollution'
# Ignore one or more SAST rules globally or for exact repo-relative files
sast_ignore_overrides: 'js-sql-injection:index.js'Important behavior:
- When the dashboard configuration loads, its SAST language toggles,
all_rules_enabledand rule lists take precedence over thesewith:values, including fields left blank. A blank rule list is the exception and is skipped.use_custom_sast_rules,custom_sast_rule_pathandsast_ignore_overrideshave no dashboard field and come from the workflow. See Dashboard Settings and Workflow Inputs. <language>_enabled_rulesis an allowlist and can suppress custom rule IDs.all_rules_enabled: 'true'disables allowlist filtering for enabled languages.
sast_ignore_overrides supports:
rule_idto ignore a SAST rule everywhere in the reporule_id:pathto ignore a SAST rule for one exact repo-relative file
Examples:
js-sql-injectionjs-sql-injection:index.jsjs-sql-injection:src/unsafe/demo.jsjs-express-async-no-error-handler,js-sql-injection:index.js,js-missing-helmet
Notes:
- Paths must be exact repo-relative paths using
/separators after normalization. - Windows-style input such as
src\\unsafe\\demo.jsis accepted and normalized automatically. - Globs and directory-prefix matching are not supported in this first version.
- A
rule_id:pathentry is an exactrule_id AND pathmatch. If the path does not match, Socket Basics will not fall back to a rule-only ignore. - Broad dashboard rule disables such as
<language>_disabled_rulesstill ignore that rule everywhere in the repo. If both are configured, the broad disabled-rule behavior can make it look like a narrow path override matched when it did not. - In
.socket.facts.json, ignored alerts includeactionReasonso you can see whether the ignore came fromsast_ignore_overrideordisabled_rule.
See action.yml for the complete list of inputs.
Every input has a CLI flag and environment-variable equivalent; the name mapping lists them side by side.
Core Configuration:
socket_org— Socket organization slug (Enterprise)socket_security_api_key— Socket Security API key (Enterprise)github_token— GitHub token (required for PR comments and labels)verbose— Enable verbose logging in the step logconsole_tabular_enabled— Tabular console output in the step logconsole_json_enabled— JSON console output in the step log- The action always scans
GITHUB_WORKSPACE(there is noworkspaceinput); narrow the scope with the inputs below.
Scan Scope:
changed_files— Diff-only mode (auto,pr,current-commit, a commit hash, or a file list)scan_files— Explicit comma-separated file listscan_all— Fail-open fallback when achanged_filesscope cannot be resolved (CLI:--scan-all/--no-scan-all)
SAST Languages:
all_languages_enabled— Enable all languagespython_sast_enabled,javascript_sast_enabled(covers TypeScript)go_sast_enabled,golang_sast_enabledjava_sast_enabled,php_sast_enabled,ruby_sast_enabledcsharp_sast_enabled,dotnet_sast_enabledc_sast_enabled,cpp_sast_enabledkotlin_sast_enabled,scala_sast_enabled,swift_sast_enabledrust_sast_enabled,elixir_sast_enabled,erlang_sast_enabledall_rules_enabled— Run every bundled rule for the enabled languages (disables the per-language allowlists)opengrep_notification_method— Route SAST findings to one notifier (e.g.console,slack)
Rule Configuration (per language):
<language>_enabled_rules— Comma-separated rules to enable<language>_disabled_rules— Comma-separated rules to disableuse_custom_sast_rules— Enable custom SAST rule discovery from repo filescustom_sast_rule_path— Relative path to custom SAST rule directorysast_ignore_overrides— Comma-separatedrule_idorrule_id:pathSAST ignore overrides
Security Scanning:
secret_scanning_enabled— Enable secret scanningdisable_all_secrets— Turn every secret-scanning feature offtrufflehog_exclude_dir— Comma-separated paths to exclude (directory names, file names, or globs)trufflehog_show_unverified— Include unverified secrets alongside the verified and unknown results reported by defaulttrufflehog_notification_method— Route secret findings to one notifier (notification_methodis a deprecated alias)socket_tier_1_enabled— Socket Tier 1 reachabilitysocket_additional_params— Extra arguments forsocket scan reach
Container Scanning:
container_images— Comma-separated images to scan (auto-enables image scanning)dockerfiles— Comma-separated Dockerfiles to scan (auto-enables Dockerfile scanning)trivy_disabled_rules— Trivy rules to disabletrivy_image_scanning_disabled— Disable image scanningtrivy_vuln_enabled— Enable vulnerability scanning for supported language ecosystemstrivy_notification_method— Route container findings to one notifier
Note
Container scanning is backed by Trivy, bundled in the pre-built GitHub Action image (a Socket-built distribution, pinned by digest) — these inputs work without any extra setup.
Notifications (Enterprise Required):
slack_webhook_url— Slack webhookjira_url,jira_email,jira_api_token,jira_project— Jira config (serverandprojectare deprecated aliases)msteams_webhook_url— MS Teams webhookwebhook_url— Generic webhookms_sentinel_workspace_id,ms_sentinel_shared_key(aliasms_sentinel_key) — MS Sentinelsumologic_endpoint— Sumo Logic
PR Comments and Labels:
pr_comment_enabled,pr_comment_collapse_all,pr_labels_enabled,pr_label_<severity>and the otherpr_comment_*inputs — see the PR Comment Guide
Storage (environment variables, not inputs):
S3 upload is configured through the step env: block, not with::
SOCKET_S3_ENABLED, SOCKET_S3_BUCKET, SOCKET_S3_ACCESS_KEY, SOCKET_S3_SECRET_KEY,
and optionally SOCKET_S3_REGION and SOCKET_S3_ENDPOINT. See
S3 Upload Configuration.
Every with: input is delivered to the container as an INPUT_<NAME>
environment variable (the input name upper-cased), so the two forms below are
equivalent and either can be used in the step env: block:
env:
INPUT_PYTHON_SAST_ENABLED: 'true'
INPUT_SECRET_SCANNING_ENABLED: 'true'
SOCKET_ORG: ${{ secrets.SOCKET_ORG }}
SOCKET_SECURITY_API_KEY: ${{ secrets.SOCKET_SECURITY_API_KEY }}Plain (un-prefixed) names are accepted only for credentials, notifier endpoints
and a few GitHub context values (SOCKET_ORG, SOCKET_SECURITY_API_KEY,
GITHUB_TOKEN, SLACK_WEBHOOK_URL, JIRA_URL, ...). A scanner toggle such as
PYTHON_SAST_ENABLED without the INPUT_ prefix is ignored. The full list is
in the name mapping.
Problem: Scanner reports no files found.
Solution: Ensure actions/checkout runs before Socket Basics:
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - Must be first
- uses: SocketDev/socket-basics@v3.4.1Problem: Security findings don't appear as PR comments.
Solutions:
- Verify
github_tokenis provided - Check workflow permissions:
permissions:
contents: read
pull-requests: write
issues: write # only needed for severity labels- Make sure the run knows which PR it is on. Pass the number explicitly, as the Quick Start does, so it works on every trigger:
env:
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}- Check that
pr_comment_enabledhas not been set to'false'(in the workflow or in the Socket dashboard configuration). The scan then runs and uploads results but deliberately posts nothing. - Nothing is posted when there are no findings above the reporting threshold.
Look for
GithubPRNotifierlines in the step log to see what the notifier decided and why.
Problem: The action is green but no full scan appears in the dashboard.
Solutions:
- Provide
socket_security_api_key. Without it the scan runs locally only. - Check the log for
No Socket organization configured. Either setsocket_orgor use an API key with thesocket-basicsscope so the organization can be discovered from the key. - Check the token scopes:
full-scansis required to upload;socket-basicsis required to load dashboard configuration.Insufficient permissionsin the log means a scope is missing. pr_comment_enabled: 'false'does not affect the upload; results still reach the dashboard.
Problem: Container image scanning fails.
Solutions:
- For private images, add authentication:
- name: Login to Registry
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdinProblem: A scanner turned on in with: doesn't run, or Dockerfile and image
scanning reports nothing even though dockerfiles or container_images is set.
Likely cause: The dashboard configuration loaded and took precedence over
those inputs, blank fields included. When the Dockerfiles and Container
Images fields are blank, Trivy doesn't load, so the log has no
Successfully loaded connector: trivy line. If trivy_vuln_enabled is on,
Trivy loads for the filesystem scan and logs
No Dockerfiles specified, skipping Trivy Dockerfile scanning instead.
How to confirm: Look for Loaded Socket Basics API configuration (overrides environment defaults) in the step log, then check the matching fields on the
dashboard's Socket Basics settings page.
Solutions:
- Set the value in the dashboard if it is the same for every repository.
- Pass it as a CLI flag by running the image directly, as shown in Setting Values Per Repository.
Problem: Dashboard configuration or notifications not working.
Solutions:
- Verify Socket Enterprise subscription is active
- Check that
socket_organdsocket_security_api_keyare set correctly - Confirm API key has required permissions in Socket Dashboard
Problem: A rule_id:path override appears to ignore findings outside the specified file.
Likely cause: The rule is also disabled more broadly in dashboard settings or other config through <language>_disabled_rules.
How to confirm:
- Open the generated
.socket.facts.json - Find the ignored alert and inspect
actionReason actionReason: "sast_ignore_override"means the exact path override matchedactionReason: "disabled_rule"means the finding was ignored by a broad rule disable instead
Additional signal: If the configured path does not exist under the workspace, Socket Basics logs a warning and does not fall back to rule-only matching.
Problem: The action is slow, runs out of memory, posts very large PR
comments, or produces a huge .socket.facts.json.
all_languages_enabled over a whole monorepo runs every SAST rule set against
every file on every PR. Independently of findings, the SAST connector also
records one component per non-gitignored file in the workspace, so the
facts file grows with the file count, and repositories that commit media,
vendored code or generated assets produce very large uploads.
Solutions:
- Scope PR runs to the diff.
changed_files: 'auto'(withfetch-depth: 0) makes each PR report only its own changes and is the single biggest win. - Enable only the languages the repository actually uses instead of
all_languages_enabled. - Keep generated and vendored trees out of git or list them in
.gitignore; ignored files are excluded from the inventory.trufflehog_exclude_diraffects secret scanning only. - For multi-project monorepos, run one job per project with
scan_files/changed_files, or use the Docker image with a per-project--workspace(see Local Docker Installation). - Use a self-hosted runner with more memory only after the above.
Problem: GitHub API rate limit exceeded.
Solution: Use a personal access token with higher limits:
with:
github_token: ${{ secrets.GITHUB_PAT }}Next Steps:
- Pre-Commit Hook Setup — Catch issues before commit
- Local Installation — Run scans from your terminal
- Parameters Reference — Every CLI flag, action input and environment variable, with the mapping between them


