Skip to content

Commit 1f418da

Browse files
authored
Added back in transitive logic and fixed format of integration messages (#6)
1 parent d011df3 commit 1f418da

25 files changed

+1432
-552
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
- uses: actions/checkout@v4
2424

2525
- name: Run Socket Basics
26-
uses: SocketDev/[email protected].2
26+
uses: SocketDev/[email protected].3
2727
with:
2828
github_token: ${{ secrets.GITHUB_TOKEN }}
2929
socket_security_api_key: ${{ secrets.SOCKET_SECURITY_API_KEY }}
@@ -106,7 +106,7 @@ Configure scanning policies, notification channels, and rule sets for your entir
106106

107107
**Dashboard-Configured (Enterprise):**
108108
```yaml
109-
- uses: SocketDev/[email protected].2
109+
- uses: SocketDev/[email protected].3
110110
with:
111111
github_token: ${{ secrets.GITHUB_TOKEN }}
112112
socket_security_api_key: ${{ secrets.SOCKET_SECURITY_API_KEY }}
@@ -115,7 +115,7 @@ Configure scanning policies, notification channels, and rule sets for your entir
115115

116116
**CLI-Configured:**
117117
```yaml
118-
- uses: SocketDev/[email protected].2
118+
- uses: SocketDev/[email protected].3
119119
with:
120120
github_token: ${{ secrets.GITHUB_TOKEN }}
121121
python_sast_enabled: 'true'
@@ -129,7 +129,7 @@ Configure scanning policies, notification channels, and rule sets for your entir
129129

130130
```bash
131131
# Build with version tag
132-
docker build -t socketdev/socket-basics:1.0.2 .
132+
docker build -t socketdev/socket-basics:1.0.3 .
133133
134134
# Run scan
135135
docker run --rm -v "$PWD:/workspace" socketdev/socket-basics:1.0.3 \

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "socket_basics"
3-
version = "1.0.2"
3+
version = "1.0.3"
44
description = "Socket Basics with integrated SAST, secret scanning, and container analysis"
55
readme = "README.md"
66
requires-python = ">=3.10"

socket_basics/connectors.yaml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ connectors:
2020
enables:
2121
- python_sast_enabled
2222
- javascript_sast_enabled
23-
- typescript_sast_enabled
2423
- go_sast_enabled
2524
- golang_sast_enabled
2625
- java_sast_enabled
@@ -56,12 +55,6 @@ connectors:
5655
type: bool
5756
default: false
5857
group: "SAST Javascript"
59-
- name: typescript_sast_enabled
60-
option: --typescript
61-
description: "Enable TypeScript SAST scanning"
62-
env_variable: INPUT_TYPESCRIPT_SAST_ENABLED
63-
type: bool
64-
default: false
6558
- name: go_sast_enabled
6659
option: --go
6760
description: "Enable Go SAST scanning"

socket_basics/core/connector/opengrep/github_pr.py

Lines changed: 119 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
def _get_github_pr_result_limit() -> int:
1616
"""Get the result limit for GitHub PR notifications."""
1717
try:
18-
notifications_yaml = Path(__file__).parent.parent.parent / 'notifications.yaml'
18+
notifications_yaml = Path(__file__).parent.parent.parent.parent / 'notifications.yaml'
1919
with open(notifications_yaml, 'r') as f:
2020
config = yaml.safe_load(f)
2121
return config.get('settings', {}).get('result_limits', {}).get('github_pr', 100)
@@ -30,124 +30,156 @@ def format_notifications(groups: Dict[str, List[Dict[str, Any]]], config=None) -
3030

3131
# Map subtypes to friendly display names
3232
subtype_names = {
33-
'sast-python': 'SAST Python',
34-
'sast-javascript': 'SAST JavaScript',
35-
'sast-golang': 'SAST Go',
36-
'sast-java': 'SAST Java',
37-
'sast-php': 'SAST PHP',
38-
'sast-ruby': 'SAST Ruby',
39-
'sast-csharp': 'SAST C#',
40-
'sast-dotnet': 'SAST .NET',
41-
'sast-c': 'SAST C',
42-
'sast-cpp': 'SAST C++',
43-
'sast-kotlin': 'SAST Kotlin',
44-
'sast-scala': 'SAST Scala',
45-
'sast-swift': 'SAST Swift',
46-
'sast-rust': 'SAST Rust',
33+
'sast-python': 'Socket SAST Python',
34+
'sast-javascript': 'Socket SAST JavaScript',
35+
'sast-golang': 'Socket SAST Go',
36+
'sast-java': 'Socket SAST Java',
37+
'sast-php': 'Socket SAST PHP',
38+
'sast-ruby': 'Socket SAST Ruby',
39+
'sast-csharp': 'Socket SAST C#',
40+
'sast-dotnet': 'Socket SAST .NET',
41+
'sast-c': 'Socket SAST C',
42+
'sast-cpp': 'Socket SAST C++',
43+
'sast-kotlin': 'Socket SAST Kotlin',
44+
'sast-scala': 'Socket SAST Scala',
45+
'sast-swift': 'Socket SAST Swift',
46+
'sast-rust': 'Socket SAST Rust',
4747
}
4848

4949
severity_order = {'critical': 0, 'high': 1, 'medium': 2, 'low': 3}
50+
severity_emoji = {
51+
'critical': '🔴',
52+
'high': '🟠',
53+
'medium': '🟡',
54+
'low': '⚪'
55+
}
5056

5157
for subtype, items in groups.items():
52-
rows = []
58+
# Group findings by file path, then by rule within each file
59+
file_groups = {} # {file_path: {rule_id: [(severity, start, end, code_snippet), ...]}}
60+
severity_counts = {'critical': 0, 'high': 0, 'medium': 0, 'low': 0}
61+
5362
for item in items:
5463
c = item['component']
5564
a = item['alert']
5665
props = a.get('props', {}) or {}
5766
full_path = props.get('filePath', a.get('location', {}).get('path')) or '-'
67+
rule_id = props.get('ruleId', a.get('title', ''))
68+
severity = a.get('severity', '').lower()
69+
start_line = props.get('startLine', '')
70+
end_line = props.get('endLine', '')
71+
code_snippet = props.get('codeSnippet', '') or ''
5872

59-
try:
60-
file_name = Path(full_path).name
61-
except Exception:
62-
file_name = full_path
73+
# Count by severity
74+
if severity in severity_counts:
75+
severity_counts[severity] += 1
6376

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

75-
severity = a.get('severity', '').lower()
76-
rows.append((
77-
severity_order.get(severity, 4),
78-
[
79-
f"**{props.get('ruleId', a.get('title', ''))}**",
80-
f"*{a.get('severity', '')}*",
81-
f"`{file_name}`",
82-
f"`{full_path}`",
83-
f"Lines {props.get('startLine','')}-{props.get('endLine','')}",
84-
code_snippet
85-
]
86-
))
87-
88-
# Sort by severity and extract rows
89-
rows.sort(key=lambda x: x[0])
90-
rows = [row[1] for row in rows]
81+
# Group by rule within file
82+
if rule_id not in file_groups[full_path]:
83+
file_groups[full_path][rule_id] = []
84+
85+
file_groups[full_path][rule_id].append({
86+
'severity': severity,
87+
'start_line': start_line,
88+
'end_line': end_line,
89+
'code_snippet': code_snippet
90+
})
9191

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

102-
# Create markdown table for this subtype
103-
display_name = subtype_names.get(subtype, subtype.upper())
104-
if not rows:
105-
content = f"No {display_name} issues found."
95+
if not file_groups:
96+
content = f"✅ No issues found."
10697
else:
107-
headers = ['Rule', 'Severity', 'File', 'Path', 'Lines', 'Code']
108-
header_row = '| ' + ' | '.join(headers) + ' |'
109-
separator_row = '| ' + ' | '.join(['---'] * len(headers)) + ' |'
110-
content_rows = []
111-
for row in rows:
112-
content_rows.append('| ' + ' | '.join(str(cell) for cell in row) + ' |')
98+
content_lines = []
11399

114-
content = '\n'.join([header_row, separator_row] + content_rows)
100+
# Add summary
101+
content_lines.append("### Summary")
102+
content_lines.append(f"{severity_emoji.get('critical', '🔴')} Critical: {severity_counts['critical']} | "
103+
f"{severity_emoji.get('high', '🟠')} High: {severity_counts['high']} | "
104+
f"{severity_emoji.get('medium', '🟡')} Medium: {severity_counts['medium']} | "
105+
f"{severity_emoji.get('low', '⚪')} Low: {severity_counts['low']}")
106+
content_lines.append("")
107+
content_lines.append("### Details")
108+
content_lines.append("")
115109

116-
# Add truncation notice if needed
117-
# if was_truncated:
118-
# content += f"\n\n⚠️ **Results truncated to {result_limit} highest severity findings** (total: {total_results}). See full scan URL for complete results."
110+
# Sort files by highest severity finding in each file
111+
file_severity_list = []
112+
for file_path in file_groups.keys():
113+
# Find the highest severity (lowest number) in this file
114+
min_severity = 999
115+
for rule_id, locations in file_groups[file_path].items():
116+
for loc in locations:
117+
sev = severity_order.get(loc['severity'], 4)
118+
if sev < min_severity:
119+
min_severity = sev
120+
file_severity_list.append((min_severity, file_path))
121+
122+
# Sort by severity first, then by file path
123+
file_severity_list.sort(key=lambda x: (x[0], x[1]))
124+
125+
for _, file_path in file_severity_list:
126+
try:
127+
file_name = Path(file_path).name
128+
except Exception:
129+
file_name = file_path
130+
131+
# File header
132+
content_lines.append(f"#### `{file_path}`")
133+
content_lines.append("")
134+
135+
# Sort rules by severity within file
136+
rules_in_file = []
137+
for rule_id, locations in file_groups[file_path].items():
138+
# Get highest severity for this rule
139+
min_severity = min(severity_order.get(loc['severity'], 4) for loc in locations)
140+
rules_in_file.append((min_severity, rule_id, locations))
141+
142+
rules_in_file.sort(key=lambda x: x[0])
143+
144+
# Output each rule with its locations
145+
for _, rule_id, locations in rules_in_file:
146+
# Get severity from first location (they should all be same rule)
147+
rule_severity = locations[0]['severity']
148+
emoji = severity_emoji.get(rule_severity, '⚪')
149+
150+
content_lines.append(f"**{rule_id}** ")
151+
content_lines.append(f"{emoji} *{rule_severity.upper()}*")
152+
content_lines.append("")
153+
154+
# Output each location with code snippet
155+
for loc in locations:
156+
content_lines.append(f"**Lines {loc['start_line']}:{loc['end_line']}**")
157+
if loc['code_snippet']:
158+
# Format code snippet in code block
159+
content_lines.append("```")
160+
content_lines.append(loc['code_snippet'])
161+
content_lines.append("```")
162+
content_lines.append("")
163+
164+
content = '\n'.join(content_lines)
119165

120-
# Build title with repo/branch/commit info from config
121-
title_parts = ["Socket Security Results"]
166+
# Build title
167+
title_parts = [display_name]
122168
if config:
123169
if config.repo:
124170
title_parts.append(config.repo)
125171
if config.branch:
126172
title_parts.append(config.branch)
127173
if config.commit_hash:
128-
title_parts.append(config.commit_hash)
174+
title_parts.append(config.commit_hash[:8]) # Short hash
129175

130176
title = " - ".join(title_parts)
131177

132-
# Count total findings for summary
133-
total_findings = total_results if not was_truncated else total_results
134-
135-
# Add summary section with scanner findings
136-
summary_content = f"""## Summary
137-
138-
| Scanner | Findings |
139-
|---------|----------|
140-
| {display_name} | {total_findings} |
141-
142-
## Details
143-
144-
{content}"""
145-
146178
# Wrap content with HTML comment markers for section updates
147179
wrapped_content = f"""<!-- {subtype} start -->
148180
# {title}
149181
150-
{summary_content}
182+
{content}
151183
<!-- {subtype} end -->"""
152184

153185
tables.append({

socket_basics/core/connector/opengrep/ms_sentinel.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
def _get_ms_sentinel_result_limit() -> int:
1616
"""Get the result limit for MS Sentinel notifications."""
1717
try:
18-
notifications_yaml = Path(__file__).parent.parent.parent / 'notifications.yaml'
18+
notifications_yaml = Path(__file__).parent.parent.parent.parent / 'notifications.yaml'
1919
with open(notifications_yaml, 'r') as f:
2020
config = yaml.safe_load(f)
21-
return config.get('settings', {}).get('result_limits', {}).get('ms_sentinel', 500)
21+
return config.get('settings', {}).get('result_limits', {}).get('ms_sentinel', 100)
2222
except Exception as e:
2323
logger.warning(f"Could not load MS Sentinel result limit from notifications.yaml: {e}, using default 500")
2424
return 500

0 commit comments

Comments
 (0)