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
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,25 @@ 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`.

## [2.2.1] - 2026-07-30

### Fixed
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
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
71 changes: 69 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 Down Expand Up @@ -395,6 +447,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
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
40 changes: 35 additions & 5 deletions socket_basics/core/notification/github_pr_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,34 @@ def wrap_pr_comment_section(
# Configuration Helper
# ============================================================================

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

Flags reach us as real booleans from the environment loader, but a Socket
dashboard config can supply them as strings. ``bool("false")`` is ``True``,
so a plain cast would silently turn a disabled flag back on.

Args:
value: Raw config value (bool, string, None, ...)
default: Value to use when nothing usable was provided

Returns:
The resolved boolean
"""
if value is None:
return default
if isinstance(value, bool):
return value
if isinstance(value, str):
normalized = value.strip().lower()
if normalized in ('true', '1', 'yes', 'on'):
return True
if normalized in ('false', '0', 'no', 'off'):
return False
return default
return bool(value)


def get_feature_flags(config) -> Dict[str, Any]:
"""Extract PR comment feature flags from config object.

Expand All @@ -99,6 +127,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 +136,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
24 changes: 22 additions & 2 deletions socket_basics/core/notification/github_pr_notifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from urllib.parse import quote

from socket_basics.core.notification.base import BaseNotifier
from socket_basics.core.notification.github_pr_helpers import coerce_bool
from socket_basics.core.config import get_github_token, get_github_repository, get_github_pr_number

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -36,8 +37,9 @@ def __init__(self, params: Dict[str, Any] | None = None):

def notify(self, facts: Dict[str, Any]) -> None:
notifications = facts.get('notifications', []) or []
labels_enabled = self.config.get('pr_labels_enabled', True)

labels_enabled = coerce_bool(self.config.get('pr_labels_enabled'), True)
comment_enabled = coerce_bool(self.config.get('pr_comment_enabled'), True)
Comment thread
jdalton marked this conversation as resolved.

if not isinstance(notifications, list):
logger.error('GithubPRNotifier: only supports new format - list of dicts with title/content')
return
Expand All @@ -57,6 +59,24 @@ def notify(self, facts: Dict[str, Any]) -> None:
notification_section_types = self._extract_section_types_from_notifications(valid_notifications)
facts_section_types = self._infer_section_types_from_facts(facts)

# Comment suppression: the scan has already run and the findings have
# already been uploaded to the Socket dashboard by the time notifiers
# execute, so this only silences the PR comment. Severity labels stay
# under pr_labels_enabled so the two can be turned off independently.
if not comment_enabled:
logger.info(
'GithubPRNotifier: PR comments disabled (pr_comment_enabled=false); '
'%d finding section(s) were scanned and uploaded to the Socket dashboard '
'but no comment will be posted or updated',
len(valid_notifications),
)
if labels_enabled:
pr_number = self._get_pr_number()
if pr_number:
labels = self._determine_pr_labels(valid_notifications)
self._reconcile_pr_labels(pr_number, labels)
return

if not valid_notifications:
pr_number = self._get_pr_number()
if pr_number:
Expand Down
12 changes: 12 additions & 0 deletions socket_basics/notifications.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ notifiers:
option: --github-api-url
env_variable: GITHUB_API_URL
type: str
- name: pr_comment_enabled
option: --pr-comment
env_variable: INPUT_PR_COMMENT_ENABLED
type: bool
default: true
description: "Post/update the findings comment on the PR (scanning and dashboard upload are unaffected)"
- name: pr_comment_links_enabled
option: --pr-comment-links
env_variable: INPUT_PR_COMMENT_LINKS_ENABLED
Expand All @@ -108,6 +114,12 @@ notifiers:
type: bool
default: true
description: "Auto-collapse non-critical findings (critical stays expanded)"
- name: pr_comment_collapse_all
option: --pr-comment-collapse-all
env_variable: INPUT_PR_COMMENT_COLLAPSE_ALL
type: bool
default: false
description: "Collapse every findings section, including critical ones"
- name: pr_comment_code_fencing_enabled
option: --pr-comment-code-fencing
env_variable: INPUT_PR_COMMENT_CODE_FENCING_ENABLED
Expand Down
Loading