Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 82d300b

Browse files
Moved to new Diff endpoint and fix with commenting logic (#88)
* Version bump * Cherry picked 73e1ce2 back in * Cherry picked missing commit * Removed unneeded full scann processing
1 parent 43a9c2e commit 82d300b

File tree

8 files changed

+175
-38
lines changed

8 files changed

+175
-38
lines changed

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
66

77
[project]
88
name = "socketsecurity"
9-
version = "2.1.3"
9+
version = "2.1.9"
1010
requires-python = ">= 3.10"
1111
license = {"file" = "LICENSE"}
1212
dependencies = [

‎socketsecurity/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
__author__ = 'socket.dev'
2-
__version__ = '2.1.3'
2+
__version__ = '2.1.9'

‎socketsecurity/core/__init__.py‎

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -133,25 +133,40 @@ def create_sbom_output(self, diff: Diff) -> dict:
133133
@staticmethod
134134
def expand_brace_pattern(pattern: str) -> List[str]:
135135
"""
136-
Expands brace expressions (e.g., {a,b,c}) into separate patterns.
137-
"""
138-
brace_regex = re.compile(r"\{([^{}]+)\}")
139-
140-
# Expand all brace groups
141-
expanded_patterns = [pattern]
142-
while any("{" in p for p in expanded_patterns):
143-
new_patterns = []
144-
for pat in expanded_patterns:
145-
match = brace_regex.search(pat)
146-
if match:
147-
options = match.group(1).split(",") # Extract values inside {}
148-
prefix, suffix = pat[:match.start()], pat[match.end():]
149-
new_patterns.extend([prefix + opt + suffix for opt in options])
150-
else:
151-
new_patterns.append(pat)
152-
expanded_patterns = new_patterns
153-
154-
return expanded_patterns
136+
Recursively expands brace expressions (e.g., {a,b,c}) into separate patterns, supporting nested braces.
137+
"""
138+
def recursive_expand(pat: str) -> List[str]:
139+
stack = []
140+
for i, c in enumerate(pat):
141+
if c == '{':
142+
stack.append(i)
143+
elif c == '}' and stack:
144+
start = stack.pop()
145+
if not stack:
146+
# Found the outermost pair
147+
before = pat[:start]
148+
after = pat[i+1:]
149+
inner = pat[start+1:i]
150+
# Split on commas not inside nested braces
151+
options = []
152+
depth = 0
153+
last = 0
154+
for j, ch in enumerate(inner):
155+
if ch == '{':
156+
depth += 1
157+
elif ch == '}':
158+
depth -= 1
159+
elif ch == ',' and depth == 0:
160+
options.append(inner[last:j])
161+
last = j+1
162+
options.append(inner[last:])
163+
results = []
164+
for opt in options:
165+
expanded = before + opt + after
166+
results.extend(recursive_expand(expanded))
167+
return results
168+
return [pat]
169+
return recursive_expand(pattern)
155170

156171
@staticmethod
157172
def is_excluded(file_path: str, excluded_dirs: Set[str]) -> bool:
@@ -176,13 +191,7 @@ def find_files(self, path: str) -> List[str]:
176191
files: Set[str] = set()
177192

178193
# Get supported patterns from the API
179-
try:
180-
patterns = self.get_supported_patterns()
181-
except Exception as e:
182-
log.error(f"Error getting supported patterns from API: {e}")
183-
log.warning("Falling back to local patterns")
184-
from .utils import socket_globs as fallback_patterns
185-
patterns = fallback_patterns
194+
patterns = self.get_supported_patterns()
186195

187196
for ecosystem in patterns:
188197
if ecosystem in self.config.excluded_ecosystems:
@@ -642,7 +651,6 @@ def create_new_diff(
642651
try:
643652
new_scan_start = time.time()
644653
new_full_scan = self.create_full_scan(files_for_sending, params)
645-
new_full_scan.sbom_artifacts = self.get_sbom_data(new_full_scan.id)
646654
new_scan_end = time.time()
647655
log.info(f"Total time to create new full scan: {new_scan_end - new_scan_start:.2f}")
648656
except APIFailure as e:

‎socketsecurity/core/classes.py‎

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ class AlertCounts(TypedDict):
9797
low: int
9898

9999
@dataclass(kw_only=True)
100-
class Package(SocketArtifactLink):
100+
class Package():
101101
"""
102102
Represents a package detected in a Socket Security scan.
103103
@@ -106,16 +106,23 @@ class Package(SocketArtifactLink):
106106
"""
107107

108108
# Common properties from both artifact types
109-
id: str
109+
type: str
110110
name: str
111111
version: str
112-
type: str
112+
release: str
113+
diffType: str
114+
id: str
115+
author: List[str] = field(default_factory=list)
113116
score: SocketScore
114117
alerts: List[SocketAlert]
115-
author: List[str] = field(default_factory=list)
116118
size: Optional[int] = None
117119
license: Optional[str] = None
118120
namespace: Optional[str] = None
121+
topLevelAncestors: Optional[List[str]] = None
122+
direct: Optional[bool] = False
123+
manifestFiles: Optional[List[SocketManifestReference]] = None
124+
dependencies: Optional[List[str]] = None
125+
artifact: Optional[SocketArtifactLink] = None
119126

120127
# Package-specific fields
121128
license_text: str = ""
@@ -203,7 +210,9 @@ def from_diff_artifact(cls, data: dict) -> "Package":
203210
manifestFiles=ref.get("manifestFiles", []),
204211
dependencies=ref.get("dependencies"),
205212
artifact=ref.get("artifact"),
206-
namespace=data.get('namespace', None)
213+
namespace=data.get('namespace', None),
214+
release=ref.get("release", None),
215+
diffType=ref.get("diffType", None),
207216
)
208217

209218
class Issue:
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import markdown
2+
from bs4 import BeautifulSoup, NavigableString, Tag
3+
import string
4+
5+
6+
class Helper:
7+
@staticmethod
8+
def parse_gfm_section(html_content):
9+
"""
10+
Parse a GitHub-Flavored Markdown section containing a table and surrounding content.
11+
Returns a dict with "before_html", "columns", "rows_html", and "after_html".
12+
"""
13+
html = markdown.markdown(html_content, extensions=['extra'])
14+
soup = BeautifulSoup(html, "html.parser")
15+
16+
table = soup.find('table')
17+
if not table:
18+
# If no table, treat entire content as before_html
19+
return {"before_html": html, "columns": [], "rows_html": [], "after_html": ''}
20+
21+
# Collect HTML before the table
22+
before_parts = [str(elem) for elem in table.find_previous_siblings()]
23+
before_html = ''.join(reversed(before_parts))
24+
25+
# Collect HTML after the table
26+
after_parts = [str(elem) for elem in table.find_next_siblings()]
27+
after_html = ''.join(after_parts)
28+
29+
# Extract table headers
30+
headers = [th.get_text(strip=True) for th in table.find_all('th')]
31+
32+
# Extract table rows (skip header)
33+
rows_html = []
34+
for tr in table.find_all('tr')[1:]:
35+
cells = [str(td) for td in tr.find_all('td')]
36+
rows_html.append(cells)
37+
38+
return {
39+
"before_html": before_html,
40+
"columns": headers,
41+
"rows_html": rows_html,
42+
"after_html": after_html
43+
}
44+
45+
@staticmethod
46+
def parse_cell(html_td):
47+
"""Convert a table cell HTML into plain text or a dict for links/images."""
48+
soup = BeautifulSoup(html_td, "html.parser")
49+
a = soup.find('a')
50+
if a:
51+
cell = {"url": a.get('href', '')}
52+
img = a.find('img')
53+
if img:
54+
cell.update({
55+
"img_src": img.get('src', ''),
56+
"title": img.get('title', ''),
57+
"link_text": a.get_text(strip=True)
58+
})
59+
else:
60+
cell["link_text"] = a.get_text(strip=True)
61+
return cell
62+
return soup.get_text(strip=True)
63+
64+
@staticmethod
65+
def parse_html_parts(html_fragment):
66+
"""
67+
Convert an HTML fragment into a list of parts.
68+
Each part is either:
69+
- {"text": "..."}
70+
- {"link": "url", "text": "..."}
71+
- {"img_src": "url", "alt": "...", "title": "..."}
72+
"""
73+
soup = BeautifulSoup(html_fragment, 'html.parser')
74+
parts = []
75+
76+
def handle_element(elem):
77+
if isinstance(elem, NavigableString):
78+
text = str(elem).strip()
79+
if text and not all(ch in string.punctuation for ch in text):
80+
parts.append({"text": text})
81+
elif isinstance(elem, Tag):
82+
if elem.name == 'a':
83+
href = elem.get('href', '')
84+
txt = elem.get_text(strip=True)
85+
parts.append({"link": href, "text": txt})
86+
elif elem.name == 'img':
87+
parts.append({
88+
"img_src": elem.get('src', ''),
89+
"alt": elem.get('alt', ''),
90+
"title": elem.get('title', '')
91+
})
92+
else:
93+
# Recurse into children for nested tags
94+
for child in elem.children:
95+
handle_element(child)
96+
97+
for element in soup.contents:
98+
handle_element(element)
99+
100+
return parts
101+
102+
@staticmethod
103+
def section_to_json(section_result):
104+
"""
105+
Convert a parsed section into structured JSON.
106+
Returns {"before": [...], "table": [...], "after": [...]}.
107+
"""
108+
# Build JSON rows for the table
109+
table_rows = []
110+
cols = section_result.get('columns', [])
111+
for row_html in section_result.get('rows_html', []):
112+
cells = [Helper.parse_cell(cell_html) for cell_html in row_html]
113+
table_rows.append(dict(zip(cols, cells)))
114+
115+
return {
116+
"before": Helper.parse_html_parts(section_result.get('before_html', '')),
117+
"table": table_rows,
118+
"after": Helper.parse_html_parts(section_result.get('after_html', ''))
119+
}

‎socketsecurity/core/messages.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,8 @@ def create_security_comment_json(diff: Diff) -> dict:
292292
output = {
293293
"scan_failed": scan_failed,
294294
"new_alerts": [],
295-
"full_scan_id": diff.id
295+
"full_scan_id": diff.id,
296+
"diff_url": diff.diff_url
296297
}
297298
for alert in diff.new_alerts:
298299
alert: Issue

‎socketsecurity/output.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ def output_console_comments(self, diff_report: Diff, sbom_file_name: Optional[st
6666

6767
console_security_comment = Messages.create_console_security_alert_table(diff_report)
6868
self.logger.info("Security issues detected by Socket Security:")
69-
self.logger.info(console_security_comment)
69+
self.logger.info(f"Diff Url: {diff_report.diff_url}")
70+
self.logger.info(f"\n{console_security_comment}")
7071

7172
def output_console_json(self, diff_report: Diff, sbom_file_name: Optional[str] = None) -> None:
7273
"""Outputs JSON formatted results"""

‎socketsecurity/socketcli.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,15 +235,14 @@ def main_code():
235235
log.debug("Updated security comment with no new alerts")
236236

237237
# FIXME: diff.new_packages is never populated, neither is removed_packages
238-
if (len(diff.new_packages) == 0andlen(diff.removed_packages) ==0) or config.disable_overview:
238+
if (len(diff.new_packages) == 0) or config.disable_overview:
239239
if not update_old_overview_comment:
240240
new_overview_comment = False
241241
log.debug("No new/removed packages or Dependency Overview comment disabled")
242242
else:
243243
log.debug("Updated overview comment with no dependencies")
244244

245245
log.debug(f"Adding comments for {config.scm}")
246-
247246
scm.add_socket_comments(
248247
security_comment,
249248
overview_comment,

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /