Skip to content
Merged
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
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- uses: actions/checkout@v4

- name: Run Socket Basics
uses: SocketDev/socket-basics@v1.0.2
uses: SocketDev/socket-basics@v1.0.3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
socket_security_api_key: ${{ secrets.SOCKET_SECURITY_API_KEY }}
Expand Down Expand Up @@ -106,7 +106,7 @@ Configure scanning policies, notification channels, and rule sets for your entir

**Dashboard-Configured (Enterprise):**
```yaml
- uses: SocketDev/socket-basics@v1.0.2
- uses: SocketDev/socket-basics@v1.0.3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
socket_security_api_key: ${{ secrets.SOCKET_SECURITY_API_KEY }}
Expand All @@ -115,7 +115,7 @@ Configure scanning policies, notification channels, and rule sets for your entir

**CLI-Configured:**
```yaml
- uses: SocketDev/socket-basics@v1.0.2
- uses: SocketDev/socket-basics@v1.0.3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
python_sast_enabled: 'true'
Expand All @@ -129,7 +129,7 @@ Configure scanning policies, notification channels, and rule sets for your entir

```bash
# Build with version tag
docker build -t socketdev/socket-basics:1.0.2 .
docker build -t socketdev/socket-basics:1.0.3 .

# Run scan
docker run --rm -v "$PWD:/workspace" socketdev/socket-basics:1.0.3 \
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "socket_basics"
version = "1.0.2"
version = "1.0.3"
description = "Socket Basics with integrated SAST, secret scanning, and container analysis"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
7 changes: 0 additions & 7 deletions socket_basics/connectors.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ connectors:
enables:
- python_sast_enabled
- javascript_sast_enabled
- typescript_sast_enabled
- go_sast_enabled
- golang_sast_enabled
- java_sast_enabled
Expand Down Expand Up @@ -56,12 +55,6 @@ connectors:
type: bool
default: false
group: "SAST Javascript"
- name: typescript_sast_enabled
option: --typescript
description: "Enable TypeScript SAST scanning"
env_variable: INPUT_TYPESCRIPT_SAST_ENABLED
type: bool
default: false
- name: go_sast_enabled
option: --go
description: "Enable Go SAST scanning"
Expand Down
206 changes: 119 additions & 87 deletions socket_basics/core/connector/opengrep/github_pr.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
def _get_github_pr_result_limit() -> int:
"""Get the result limit for GitHub PR notifications."""
try:
notifications_yaml = Path(__file__).parent.parent.parent / 'notifications.yaml'
notifications_yaml = Path(__file__).parent.parent.parent.parent / 'notifications.yaml'
with open(notifications_yaml, 'r') as f:
config = yaml.safe_load(f)
return config.get('settings', {}).get('result_limits', {}).get('github_pr', 100)
Expand All @@ -30,124 +30,156 @@ def format_notifications(groups: Dict[str, List[Dict[str, Any]]], config=None) -

# Map subtypes to friendly display names
subtype_names = {
'sast-python': 'SAST Python',
'sast-javascript': 'SAST JavaScript',
'sast-golang': 'SAST Go',
'sast-java': 'SAST Java',
'sast-php': 'SAST PHP',
'sast-ruby': 'SAST Ruby',
'sast-csharp': 'SAST C#',
'sast-dotnet': 'SAST .NET',
'sast-c': 'SAST C',
'sast-cpp': 'SAST C++',
'sast-kotlin': 'SAST Kotlin',
'sast-scala': 'SAST Scala',
'sast-swift': 'SAST Swift',
'sast-rust': 'SAST Rust',
'sast-python': 'Socket SAST Python',
'sast-javascript': 'Socket SAST JavaScript',
'sast-golang': 'Socket SAST Go',
'sast-java': 'Socket SAST Java',
'sast-php': 'Socket SAST PHP',
'sast-ruby': 'Socket SAST Ruby',
'sast-csharp': 'Socket SAST C#',
'sast-dotnet': 'Socket SAST .NET',
'sast-c': 'Socket SAST C',
'sast-cpp': 'Socket SAST C++',
'sast-kotlin': 'Socket SAST Kotlin',
'sast-scala': 'Socket SAST Scala',
'sast-swift': 'Socket SAST Swift',
'sast-rust': 'Socket SAST Rust',
}

severity_order = {'critical': 0, 'high': 1, 'medium': 2, 'low': 3}
severity_emoji = {
'critical': '🔴',
'high': '🟠',
'medium': '🟡',
'low': '⚪'
}

for subtype, items in groups.items():
rows = []
# Group findings by file path, then by rule within each file
file_groups = {} # {file_path: {rule_id: [(severity, start, end, code_snippet), ...]}}
severity_counts = {'critical': 0, 'high': 0, 'medium': 0, 'low': 0}

for item in items:
c = item['component']
a = item['alert']
props = a.get('props', {}) or {}
full_path = props.get('filePath', a.get('location', {}).get('path')) or '-'
rule_id = props.get('ruleId', a.get('title', ''))
severity = a.get('severity', '').lower()
start_line = props.get('startLine', '')
end_line = props.get('endLine', '')
code_snippet = props.get('codeSnippet', '') or ''

try:
file_name = Path(full_path).name
except Exception:
file_name = full_path
# Count by severity
if severity in severity_counts:
severity_counts[severity] += 1

# Format code snippets with <pre> tags and <br> for line breaks
code_snippet = props.get('codeSnippet', '') or ''
if code_snippet:
# Use <pre> tags for better code formatting as requested
code_formatted = code_snippet.replace('\n', '<br>')
if len(code_formatted) > 200:
code_formatted = code_formatted[:200] + '...'
code_snippet = f"<pre>{code_formatted}</pre>"
else:
code_snippet = '-'
# Group by file path
if full_path not in file_groups:
file_groups[full_path] = {}

severity = a.get('severity', '').lower()
rows.append((
severity_order.get(severity, 4),
[
f"**{props.get('ruleId', a.get('title', ''))}**",
f"*{a.get('severity', '')}*",
f"`{file_name}`",
f"`{full_path}`",
f"Lines {props.get('startLine','')}-{props.get('endLine','')}",
code_snippet
]
))

# Sort by severity and extract rows
rows.sort(key=lambda x: x[0])
rows = [row[1] for row in rows]
# Group by rule within file
if rule_id not in file_groups[full_path]:
file_groups[full_path][rule_id] = []

file_groups[full_path][rule_id].append({
'severity': severity,
'start_line': start_line,
'end_line': end_line,
'code_snippet': code_snippet
})

# Apply truncation
# result_limit = _get_github_pr_result_limit()
total_results = len(rows)
was_truncated = False
#
# if total_results > result_limit:
# logger.info(f"Truncating GitHub PR OpenGrep results from {total_results} to {result_limit} (prioritized by severity)")
# rows = rows[:result_limit]
# was_truncated = True
# Build content in requested format
display_name = subtype_names.get(subtype, f"Socket {subtype.upper()}")

# Create markdown table for this subtype
display_name = subtype_names.get(subtype, subtype.upper())
if not rows:
content = f"No {display_name} issues found."
if not file_groups:
content = f"✅ No issues found."
else:
headers = ['Rule', 'Severity', 'File', 'Path', 'Lines', 'Code']
header_row = '| ' + ' | '.join(headers) + ' |'
separator_row = '| ' + ' | '.join(['---'] * len(headers)) + ' |'
content_rows = []
for row in rows:
content_rows.append('| ' + ' | '.join(str(cell) for cell in row) + ' |')
content_lines = []

content = '\n'.join([header_row, separator_row] + content_rows)
# Add summary
content_lines.append("### Summary")
content_lines.append(f"{severity_emoji.get('critical', '🔴')} Critical: {severity_counts['critical']} | "
f"{severity_emoji.get('high', '🟠')} High: {severity_counts['high']} | "
f"{severity_emoji.get('medium', '🟡')} Medium: {severity_counts['medium']} | "
f"{severity_emoji.get('low', '⚪')} Low: {severity_counts['low']}")
content_lines.append("")
content_lines.append("### Details")
content_lines.append("")

# Add truncation notice if needed
# if was_truncated:
# content += f"\n\n⚠️ **Results truncated to {result_limit} highest severity findings** (total: {total_results}). See full scan URL for complete results."
# Sort files by highest severity finding in each file
file_severity_list = []
for file_path in file_groups.keys():
# Find the highest severity (lowest number) in this file
min_severity = 999
for rule_id, locations in file_groups[file_path].items():
for loc in locations:
sev = severity_order.get(loc['severity'], 4)
if sev < min_severity:
min_severity = sev
file_severity_list.append((min_severity, file_path))

# Sort by severity first, then by file path
file_severity_list.sort(key=lambda x: (x[0], x[1]))

for _, file_path in file_severity_list:
try:
file_name = Path(file_path).name
except Exception:
file_name = file_path

# File header
content_lines.append(f"#### `{file_path}`")
content_lines.append("")

# Sort rules by severity within file
rules_in_file = []
for rule_id, locations in file_groups[file_path].items():
# Get highest severity for this rule
min_severity = min(severity_order.get(loc['severity'], 4) for loc in locations)
rules_in_file.append((min_severity, rule_id, locations))

rules_in_file.sort(key=lambda x: x[0])

# Output each rule with its locations
for _, rule_id, locations in rules_in_file:
# Get severity from first location (they should all be same rule)
rule_severity = locations[0]['severity']
emoji = severity_emoji.get(rule_severity, '⚪')

content_lines.append(f"**{rule_id}** ")
content_lines.append(f"{emoji} *{rule_severity.upper()}*")
content_lines.append("")

# Output each location with code snippet
for loc in locations:
content_lines.append(f"**Lines {loc['start_line']}:{loc['end_line']}**")
if loc['code_snippet']:
# Format code snippet in code block
content_lines.append("```")
content_lines.append(loc['code_snippet'])
content_lines.append("```")
content_lines.append("")

content = '\n'.join(content_lines)

# Build title with repo/branch/commit info from config
title_parts = ["Socket Security Results"]
# Build title
title_parts = [display_name]
if config:
if config.repo:
title_parts.append(config.repo)
if config.branch:
title_parts.append(config.branch)
if config.commit_hash:
title_parts.append(config.commit_hash)
title_parts.append(config.commit_hash[:8]) # Short hash

title = " - ".join(title_parts)

# Count total findings for summary
total_findings = total_results if not was_truncated else total_results

# Add summary section with scanner findings
summary_content = f"""## Summary

| Scanner | Findings |
|---------|----------|
| {display_name} | {total_findings} |

## Details

{content}"""

# Wrap content with HTML comment markers for section updates
wrapped_content = f"""<!-- {subtype} start -->
# {title}

{summary_content}
{content}
<!-- {subtype} end -->"""

tables.append({
Expand Down
4 changes: 2 additions & 2 deletions socket_basics/core/connector/opengrep/ms_sentinel.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
def _get_ms_sentinel_result_limit() -> int:
"""Get the result limit for MS Sentinel notifications."""
try:
notifications_yaml = Path(__file__).parent.parent.parent / 'notifications.yaml'
notifications_yaml = Path(__file__).parent.parent.parent.parent / 'notifications.yaml'
with open(notifications_yaml, 'r') as f:
config = yaml.safe_load(f)
return config.get('settings', {}).get('result_limits', {}).get('ms_sentinel', 500)
return config.get('settings', {}).get('result_limits', {}).get('ms_sentinel', 100)
except Exception as e:
logger.warning(f"Could not load MS Sentinel result limit from notifications.yaml: {e}, using default 500")
return 500
Expand Down
Loading