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
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,31 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Added

- Added `pr_comment_enabled` (default `true`). Set it to `false` to run the scan
without posting or updating the pull request comment. Scanning, the Socket
dashboard upload, `.socket.facts.json`, the high/critical job failure, and the
other notifiers are all unaffected; severity labels stay under the separate
`pr_labels_enabled` switch.
- Added `pr_comment_collapse_all` (default `false`), which collapses every
findings section including critical ones. Previously
`pr_comment_collapse_non_critical` always left critical findings expanded, so
a single critical finding forced the whole comment open.

### Fixed

- PR comment feature flags, including `pr_labels_enabled`, now accept string
values (`'false'`, `'0'`, `'no'`, `'off'`) as well as booleans. A Socket
dashboard config that supplied a flag as a string previously read as enabled,
because `bool("false")` is `True`.
- A blank PR comment flag now falls back to its documented default instead of
reading as off. The environment loader compared the raw value against the
single string `'true'`, so an action input forwarded from an unset workflow
variable arrived as `''` and disabled the flag, and `'1'`, `'yes'` and `'on'`
did not enable it. All the spellings the flags document now work from the
environment, a JSON config and a Socket dashboard config alike.

## [2.2.1] - 2026-07-30

### Fixed
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,12 @@ Socket Basics can also run locally or in other CI/CD environments:
Socket Basics delivers **beautifully formatted, actionable PR comments** with smart defaults — all enabled by default, zero configuration needed.

- 🔗 **Clickable File Links** — Jump directly to the vulnerable code in GitHub
- 📋 **Collapsible Sections** — Critical findings auto-expand, others collapse
- 📋 **Collapsible Sections** — Critical findings auto-expand, others collapse; `pr_comment_collapse_all: 'true'` collapses critical ones too
- 🎨 **Syntax Highlighting** — Language-aware code blocks
- 🏷️ **Auto-Labels** — PRs tagged with severity-based labels (e.g., `security: critical`)
- 🔴 **CVE Links & CVSS Scores** — One-click access to NVD with risk context
- 🚀 **Full Scan Link** — Report link prominently displayed at the top
- 🔇 **Fully Suppressible** — `pr_comment_enabled: 'false'` stops the comment entirely while the scan still runs, still uploads to the Socket dashboard, and still fails the job on high/critical findings

Every feature is customizable via GitHub Actions inputs, CLI flags, or environment variables.

Expand Down
18 changes: 18 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,11 @@ runs:
INPUT_WEBHOOK_URL: ${{ inputs.webhook_url }}
SOCKET_ADDITIONAL_PARAMS: ${{ inputs.socket_additional_params }}
SOCKET_TIER_1_ENABLED: ${{ inputs.socket_tier_1_enabled }}
INPUT_PR_COMMENT_ENABLED: ${{ inputs.pr_comment_enabled }}
INPUT_PR_COMMENT_LINKS_ENABLED: ${{ inputs.pr_comment_links_enabled }}
INPUT_PR_COMMENT_COLLAPSE_ENABLED: ${{ inputs.pr_comment_collapse_enabled }}
INPUT_PR_COMMENT_COLLAPSE_NON_CRITICAL: ${{ inputs.pr_comment_collapse_non_critical }}
INPUT_PR_COMMENT_COLLAPSE_ALL: ${{ inputs.pr_comment_collapse_all }}
INPUT_PR_COMMENT_CODE_FENCING_ENABLED: ${{ inputs.pr_comment_code_fencing_enabled }}
INPUT_PR_COMMENT_SHOW_RULE_NAMES: ${{ inputs.pr_comment_show_rule_names }}
INPUT_PR_LABELS_ENABLED: ${{ inputs.pr_labels_enabled }}
Expand Down Expand Up @@ -443,6 +445,15 @@ inputs:
description: "Generic webhook URL for WebhookNotifier"
required: false
default: ""
pr_comment_enabled:
description: >-
Post the findings comment on the pull request. Set to 'false' to run the
scan silently: findings are still uploaded to the Socket dashboard and the
action still fails the job on high/critical findings, but no comment is
posted or updated. Severity labels are controlled separately by
pr_labels_enabled.
required: false
default: "true"
pr_comment_links_enabled:
description: "Enable clickable file/line links in PR comments"
required: false
Expand All @@ -455,6 +466,13 @@ inputs:
description: "Auto-collapse non-critical findings (critical stays expanded)"
required: false
default: "true"
pr_comment_collapse_all:
description: >-
Collapse every findings section, including critical ones. Use this when
you want the comment to stay small no matter what it finds. Overrides

@lelia lelia Aug 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we either implement this behavior for the flat-table formatters or narrow the contract? Only OpenGrep and Tier 1 consume pr_comment_collapse_all. TruffleHog and Trivy Dockerfile findings remain expanded tables, and even SAST leaves one visible collapsed row per file, so the guide's claim that the comment becomes one line is not accurate. Please add formatter coverage for the remaining outputs, or describe this specifically as collapsing the currently collapsible SAST/Tier 1 sections and adjust the one-line wording.

pr_comment_collapse_non_critical.
required: false
default: "false"
pr_comment_code_fencing_enabled:
description: "Enable language-aware code fencing for trace output"
required: false
Expand Down
2 changes: 2 additions & 0 deletions docs/github-action.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,8 @@ jobs:

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'` collapses every section including critical ones.

📖 **[PR Comment Guide →](github-pr-comment-guide.md)** — Complete customization options, configuration examples, and reference table

## Enterprise Features
Expand Down
83 changes: 81 additions & 2 deletions docs/github-pr-comment-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,18 @@ pr_comment_collapse_enabled: 'false'
# Keep collapsible but expand everything
pr_comment_collapse_enabled: 'true'
pr_comment_collapse_non_critical: 'false'

# Keep collapsible and collapse everything, critical included
pr_comment_collapse_enabled: 'true'
pr_comment_collapse_all: 'true'
```

> [!NOTE]
> `pr_comment_collapse_non_critical` deliberately leaves critical findings
> expanded, so a single critical finding always opens the comment. Set
> `pr_comment_collapse_all: 'true'` when you want the comment to stay small no
> matter what it finds. It overrides `pr_comment_collapse_non_critical`.

---

### 3. Syntax Highlighting (`pr_comment_code_fencing_enabled`)
Expand Down Expand Up @@ -291,7 +301,47 @@ The logo is a 32px PNG rendered at 24x24 for retina-crisp display, with a transp

---

### 9. All-Clear Comment Updates
### 9. Turning the Comment Off (`pr_comment_enabled`)

**Default:** `true`

Set `pr_comment_enabled: 'false'` to run the scan without saying anything on the
PR. This is for teams who want to review finding quality in the Socket dashboard
first, without every PR growing a comment that developers have to scroll past.

```yaml
- uses: SocketDev/socket-basics@v2
with:
socket_security_api_key: ${{ secrets.SOCKET_SECURITY_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
pr_comment_enabled: 'false'
```

**What still happens when the comment is off:**

| Behavior | Still happens? |
|----------|----------------|
| Scanners run (SAST, secrets, containers) | ✅ Yes |
| Findings uploaded to the Socket dashboard | ✅ Yes |
| `.socket.facts.json` written | ✅ Yes |
| Job fails on high/critical findings | ✅ Yes |
| Other notifiers (Slack, Jira, webhook, ...) | ✅ Yes |
| Severity labels added to the PR | ✅ Yes, unless `pr_labels_enabled: 'false'` |
| Comment posted or updated | ❌ No |

Notifiers are the very last thing the run does — the scan finishes and the
findings are uploaded to Socket before any comment would be posted — so turning
the comment off cannot turn the dashboard off. Labels are a separate switch
(`pr_labels_enabled`) so you can keep or drop them independently.

> [!TIP]
> If you want to keep the comment but make it quieter, use
> `pr_comment_collapse_all: 'true'` instead. That collapses every section,
> including critical ones, so the comment is one line until someone opens it.

---

### 10. All-Clear Comment Updates

When a later Socket Basics run no longer has active findings for a previously-reported scanner section, the existing PR comment section is updated in place instead of being left stale or deleted.

Expand All @@ -313,9 +363,11 @@ When a later Socket Basics run no longer has active findings for a previously-re

| Option | Default | Type | Description |
|--------|---------|------|-------------|
| `pr_comment_enabled` | `true` | boolean | Post/update the findings comment on the PR |
| `pr_comment_links_enabled` | `true` | boolean | Enable clickable file/line links |
| `pr_comment_collapse_enabled` | `true` | boolean | Enable collapsible sections |
| `pr_comment_collapse_non_critical` | `true` | boolean | Auto-collapse non-critical findings |
| `pr_comment_collapse_non_critical` | `true` | boolean | Auto-collapse non-critical findings (critical stays expanded) |
| `pr_comment_collapse_all` | `false` | boolean | Collapse every section, critical included |
| `pr_comment_code_fencing_enabled` | `true` | boolean | Enable syntax highlighting |
| `pr_comment_show_rule_names` | `true` | boolean | Show explicit rule names |
| `pr_labels_enabled` | `true` | boolean | Add severity-based labels to PRs |
Expand All @@ -324,6 +376,18 @@ When a later Socket Basics run no longer has active findings for a previously-re
| `pr_label_medium` | `"security: medium"` | string | Label name for medium findings |
| `pr_label_low` | `"security: low"` | string | Label name for low findings |

### How boolean options are read

Every boolean above accepts `true`, `1`, `yes` and `on` for on, and `false`,
`0`, `no` and `off` for off, in any capitalization, whether it arrives as a
GitHub Action input, an environment variable, a `--config` JSON file, or a
Socket dashboard config.

A value that says nothing — blank, whitespace, or a word that is neither — falls
back to the default in the table. This matters for `pr_comment_enabled`: passing
it a workflow variable that turns out to be unset gives the action an empty
string, and that leaves the comment on rather than silently switching it off.

### Configuration Methods

**1. GitHub Actions (Recommended)**
Expand Down Expand Up @@ -395,6 +459,21 @@ pr_label_high: 'security'
pr_label_medium: 'security'
```

### Evaluation / Trial (Dashboard Only)

Review findings in the Socket dashboard without putting anything on the PR:
```yaml
pr_comment_enabled: 'false'
pr_labels_enabled: 'false'
```

### Quiet Comment (Everything Collapsed)

Keep a single collapsed comment even when there are critical findings:
```yaml
pr_comment_collapse_all: 'true'
```

---

## 🚀 Migration Guide
Expand Down
2 changes: 2 additions & 0 deletions scripts/preview_pr_comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def make_mock_config(
repo="SocketDev/example-app",
commit="a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
full_scan_url="https://socket.dev/dashboard/scan/12345",
collapse_all=False,
):
"""Build a mock config object matching the real pipeline shape."""
return MockConfig(
Expand All @@ -44,6 +45,7 @@ def make_mock_config(
pr_comment_links_enabled=True,
pr_comment_collapse_enabled=True,
pr_comment_collapse_non_critical=True,
pr_comment_collapse_all=collapse_all,
pr_comment_code_fencing_enabled=True,
pr_comment_show_rule_names=True,
full_scan_html_url=full_scan_url,
Expand Down
34 changes: 33 additions & 1 deletion socket_basics/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,38 @@

logger = logging.getLogger(__name__)

TRUE_STRINGS = ('true', '1', 'yes', 'on')
FALSE_STRINGS = ('false', '0', 'no', 'off')


def coerce_bool(value: Any, default: bool) -> bool:
"""Coerce a config value to a bool, tolerating the string forms.

Every layer that carries a flag delivers it differently: the environment
loader sees strings because GitHub Action inputs are always strings, a
Socket dashboard config can send either, and a JSON config sends real
booleans. ``bool("false")`` is ``True``, so a plain cast turns a disabled
flag back on.

A value that says nothing -- ``None``, an empty string, or a word that is
neither true nor false -- resolves to ``default`` rather than to ``False``.
An action input forwarded from an unset workflow variable arrives as an
empty string, and reading that as "off" silently disables a flag that
nobody asked to disable.
"""
if value is None:
return default
if isinstance(value, bool):
return value
if isinstance(value, str):
normalized = value.strip().lower()
if normalized in TRUE_STRINGS:
return True
if normalized in FALSE_STRINGS:
return False
return default
return bool(value)


def _normalize_path_parts(path_value: str | None) -> List[str] | None:
"""Normalize a path-like string into comparable POSIX-style path segments."""
Expand Down Expand Up @@ -812,7 +844,7 @@ def load_config_from_env() -> Dict[str, Any]:
env_value = os.getenv(env_var)
if env_value is not None:
if p_type == 'bool':
config[p_name] = env_value.lower() == 'true'
config[p_name] = coerce_bool(env_value, bool(default_value))
elif p_type == 'int':
try:
config[p_name] = int(env_value)
Expand Down
7 changes: 5 additions & 2 deletions socket_basics/core/connector/opengrep/github_pr.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def format_notifications(groups: Dict[str, List[Dict[str, Any]]], config=None) -
enable_links = flags['enable_links']
enable_collapse = flags['enable_collapse']
collapse_non_critical = flags['collapse_non_critical']
collapse_all = flags['collapse_all']
enable_code_fencing = flags['enable_code_fencing']
show_rule_names = flags['show_rule_names']
repository = flags['repository']
Expand Down Expand Up @@ -202,8 +203,10 @@ def format_notifications(groups: Dict[str, List[Dict[str, Any]]], config=None) -
if enable_collapse:
# Determine if this should be auto-expanded
has_critical = file_severities['critical'] > 0
# Auto-expand if: no collapse requested OR has critical findings
auto_expand = (not collapse_non_critical) or has_critical
# Auto-expand if: no collapse requested OR has critical findings.
# collapse_all wins over both so the comment can stay small
# even when a critical finding is present.
auto_expand = ((not collapse_non_critical) or has_critical) and not collapse_all

collapsible = helpers.create_collapsible_section(
display_path, # Don't use backticks in summary - they don't render in GitHub
Expand Down
6 changes: 5 additions & 1 deletion socket_basics/core/connector/socket_tier1/github_pr.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def format_notifications(components_list: List[Dict[str, Any]], config=None) ->
enable_links = flags['enable_links']
enable_collapse = flags['enable_collapse']
collapse_non_critical = flags['collapse_non_critical']
collapse_all = flags['collapse_all']
enable_code_fencing = flags['enable_code_fencing']
show_rule_names = flags['show_rule_names']
repository = flags['repository']
Expand Down Expand Up @@ -154,7 +155,10 @@ def format_notifications(components_list: List[Dict[str, Any]], config=None) ->

severity_summary = " | ".join(severity_parts) if severity_parts else "No issues"

open_attr = ' open' if (not collapse_non_critical or has_critical) else ''
# collapse_all wins over both so the comment can stay small even
# when a critical finding is present.
auto_expand = (not collapse_non_critical or has_critical) and not collapse_all
open_attr = ' open' if auto_expand else ''
content_lines.append(f"<details{open_attr}>")
content_lines.append(f"<summary><strong>{purl}</strong> ({severity_summary})</summary>")
content_lines.append("")
Expand Down
16 changes: 11 additions & 5 deletions socket_basics/core/notification/github_pr_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
from typing import Dict, Any, Optional, List, Tuple
from pathlib import Path

# coerce_bool lives in the config layer so the environment loader, a Socket
# dashboard config and these flags all read a string the same way.
from socket_basics.core.config import coerce_bool


# ============================================================================
# Severity Constants (shared across all scanners)
Expand Down Expand Up @@ -99,6 +103,7 @@ def get_feature_flags(config) -> Dict[str, Any]:
'enable_links': True,
'enable_collapse': True,
'collapse_non_critical': True,
'collapse_all': False,
'enable_code_fencing': True,
'show_rule_names': True,
'repository': '',
Expand All @@ -107,11 +112,12 @@ def get_feature_flags(config) -> Dict[str, Any]:
}

return {
'enable_links': config.get('pr_comment_links_enabled', True),
'enable_collapse': config.get('pr_comment_collapse_enabled', True),
'collapse_non_critical': config.get('pr_comment_collapse_non_critical', True),
'enable_code_fencing': config.get('pr_comment_code_fencing_enabled', True),
'show_rule_names': config.get('pr_comment_show_rule_names', True),
'enable_links': coerce_bool(config.get('pr_comment_links_enabled'), True),
'enable_collapse': coerce_bool(config.get('pr_comment_collapse_enabled'), True),
'collapse_non_critical': coerce_bool(config.get('pr_comment_collapse_non_critical'), True),
'collapse_all': coerce_bool(config.get('pr_comment_collapse_all'), False),
'enable_code_fencing': coerce_bool(config.get('pr_comment_code_fencing_enabled'), True),
'show_rule_names': coerce_bool(config.get('pr_comment_show_rule_names'), True),
'repository': config.repo if hasattr(config, 'repo') else '',
'commit_hash': config.commit_hash if hasattr(config, 'commit_hash') else '',
'full_scan_url': config.get('full_scan_html_url') if config else None
Expand Down
Loading