socketsecurity 2.4.2__py3-none-any.whl → 2.4.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,3 @@
1
1
  __author__ = 'socket.dev'
2
- __version__ = '2.4.2'
2
+ __version__ = '2.4.4'
3
3
  USER_AGENT = f'SocketPythonCLI/{__version__}'
socketsecurity/config.py CHANGED
@@ -55,6 +55,50 @@ def load_cli_config_file(config_path: str) -> dict:
55
55
  return scoped
56
56
  return data
57
57
 
58
+ def normalize_exclude_paths(value) -> Optional[List[str]]:
59
+ """Normalize a --exclude-paths value into a clean list of patterns.
60
+
61
+ Accepts a comma-separated string (CLI) or a list/tuple (e.g. a JSON/TOML --config file
62
+ value), so config-file-supplied patterns flow through the same validation as CLI ones.
63
+ """
64
+ if not value:
65
+ return None
66
+ if isinstance(value, str):
67
+ items = value.split(",")
68
+ elif isinstance(value, (list, tuple)):
69
+ items = value
70
+ else:
71
+ return None
72
+ cleaned = [str(p).strip() for p in items if str(p).strip()]
73
+ return cleaned or None
74
+
75
+
76
+ def validate_exclude_paths(patterns: List[str]) -> None:
77
+ """Validate --exclude-paths patterns (mirrors Node's assertValidExcludePaths).
78
+
79
+ Patterns are scan-root-relative globs. Reject the cases coana's --exclude-dirs / fast-glob
80
+ cannot honor: negation, absolute paths, ``..`` traversal, and degenerate match-everything.
81
+ Exits with code 1 on the first invalid pattern.
82
+ """
83
+ # Degenerate match-everything forms, compared against the trailing-slash-stripped pattern
84
+ # (so "**/" reduces to "**" and is rejected, matching Node's stripTrailingSlash + check).
85
+ degenerate = {"", ".", "**", "./**", "/**"}
86
+ for p in patterns:
87
+ norm = (p or "").strip().replace("\\", "/")
88
+ if norm.startswith("!"):
89
+ logging.error(f"--exclude-paths: negation patterns are not supported: {p!r}")
90
+ exit(1)
91
+ if norm.startswith("/"):
92
+ logging.error(f"--exclude-paths: patterns must be scan-root relative (no leading '/'): {p!r}")
93
+ exit(1)
94
+ if norm == ".." or norm.startswith("../") or "/../" in norm or norm.endswith("/.."):
95
+ logging.error(f"--exclude-paths: '..' path traversal is not allowed: {p!r}")
96
+ exit(1)
97
+ if norm.rstrip("/") in degenerate:
98
+ logging.error(f"--exclude-paths: pattern would exclude everything: {p!r}")
99
+ exit(1)
100
+
101
+
58
102
  @dataclass
59
103
  class PluginConfig:
60
104
  enabled: bool = False
@@ -106,6 +150,7 @@ class CliConfig:
106
150
  include_module_folders: bool = False
107
151
  repo_is_public: bool = False
108
152
  excluded_ecosystems: list[str] = field(default_factory=lambda: [])
153
+ exclude_paths: Optional[List[str]] = None
109
154
  version: str = __version__
110
155
  jira_plugin: PluginConfig = field(default_factory=PluginConfig)
111
156
  slack_plugin: PluginConfig = field(default_factory=PluginConfig)
@@ -167,6 +212,12 @@ class CliConfig:
167
212
 
168
213
  args = parser.parse_args(args_list)
169
214
 
215
+ if args.reach_exclude_paths:
216
+ logging.warning(
217
+ "--reach-exclude-paths is deprecated; use --exclude-paths instead. "
218
+ "It is still honored and unioned with --exclude-paths."
219
+ )
220
+
170
221
  # Get API token from env or args (check multiple env var names)
171
222
  api_token = (
172
223
  os.getenv("SOCKET_SECURITY_API_KEY") or
@@ -258,6 +309,7 @@ class CliConfig:
258
309
  'reach_lazy_mode': args.reach_lazy_mode,
259
310
  'reach_ecosystems': args.reach_ecosystems.split(',') if args.reach_ecosystems else None,
260
311
  'reach_exclude_paths': args.reach_exclude_paths.split(',') if args.reach_exclude_paths else None,
312
+ 'exclude_paths': normalize_exclude_paths(args.exclude_paths),
261
313
  'reach_skip_cache': args.reach_skip_cache,
262
314
  'reach_min_severity': args.reach_min_severity,
263
315
  'reach_output_file': args.reach_output_file,
@@ -361,6 +413,10 @@ class CliConfig:
361
413
  logging.error("--sarif-reachability potentially/reachable-or-potentially requires --sarif-scope full")
362
414
  exit(1)
363
415
 
416
+ # Validate --exclude-paths patterns up front (mirrors Node's assertValidExcludePaths).
417
+ if config_args.get("exclude_paths"):
418
+ validate_exclude_paths(config_args["exclude_paths"])
419
+
364
420
  # Validate that only_facts_file requires reach
365
421
  if args.only_facts_file and not args.reach:
366
422
  logging.error("--only-facts-file requires --reach to be specified")
@@ -570,6 +626,15 @@ def create_argument_parser() -> argparse.ArgumentParser:
570
626
  help="List of ecosystems to exclude from analysis (JSON array string)"
571
627
  )
572
628
 
629
+ path_group.add_argument(
630
+ "--exclude-paths",
631
+ dest="exclude_paths",
632
+ metavar="<list>",
633
+ help="Comma-separated paths/globs to exclude from BOTH manifest discovery and "
634
+ "reachability analysis (e.g. 'tests/**,packages/legacy,*.spec.ts'). "
635
+ "Supersedes --reach-exclude-paths."
636
+ )
637
+
573
638
  # Branch and Scan Configuration
574
639
  config_group = parser.add_argument_group('Branch and Scan Configuration')
575
640
  config_group.add_argument(
@@ -919,7 +984,8 @@ def create_argument_parser() -> argparse.ArgumentParser:
919
984
  "--reach-exclude-paths",
920
985
  dest="reach_exclude_paths",
921
986
  metavar="<list>",
922
- help="Paths to exclude from reachability analysis (comma-separated)"
987
+ help="[DEPRECATED: use --exclude-paths] Paths to exclude from reachability analysis "
988
+ "(comma-separated). Still honored and unioned with --exclude-paths."
923
989
  )
924
990
  reachability_group.add_argument(
925
991
  "--reach-min-severity",
@@ -213,6 +213,67 @@ class Core:
213
213
  return True
214
214
  return False
215
215
 
216
+ @staticmethod
217
+ def _exclude_glob_to_regex(pattern: str) -> str:
218
+ """Translate a micromatch-style glob into an anchored regex string.
219
+
220
+ Mirrors the Node CLI's --exclude-paths matcher (src/commands/scan/exclude-paths.mts):
221
+ patterns are matched against scan-root-relative POSIX paths, case-sensitively, where
222
+ ``*`` does NOT cross ``/`` and ``**`` DOES. Patterns are anchored at the scan root, so
223
+ ``tests`` matches ``tests`` (not ``src/tests``); use ``**/tests`` to match at any depth.
224
+ """
225
+ i, n = 0, len(pattern)
226
+ out = ["^"]
227
+ while i < n:
228
+ c = pattern[i]
229
+ if c == "*":
230
+ if i + 1 < n and pattern[i + 1] == "*":
231
+ if i + 2 < n and pattern[i + 2] == "/":
232
+ out.append("(?:[^/]+/)*") # '**/' -> zero or more path segments
233
+ i += 3
234
+ else:
235
+ out.append(".*") # '**' at end / before non-slash -> any, incl '/'
236
+ i += 2
237
+ else:
238
+ out.append("[^/]*") # '*' -> within a single path segment
239
+ i += 1
240
+ elif c == "?":
241
+ out.append("[^/]")
242
+ i += 1
243
+ else:
244
+ out.append(re.escape(c))
245
+ i += 1
246
+ out.append("$")
247
+ return "".join(out)
248
+
249
+ @staticmethod
250
+ def compile_exclude_paths(patterns: Optional[List[str]]) -> List["re.Pattern"]:
251
+ """Compile --exclude-paths globs into anchored regexes (compiled once per scan).
252
+
253
+ Each pattern ``P`` is expanded the way Node feeds fast-glob's ``ignore``: ``P`` (a file-
254
+ or dir-shaped exact match) plus ``P/**`` (its subtree), unless ``P`` already ends with
255
+ ``/**``. Validation of the patterns happens earlier, in CliConfig.from_args.
256
+ """
257
+ compiled: List["re.Pattern"] = []
258
+ for raw in patterns or []:
259
+ p = (raw or "").strip().replace("\\", "/").rstrip("/")
260
+ if not p:
261
+ continue
262
+ globs = [p] if p.endswith("/**") else [p, f"{p}/**"]
263
+ compiled.extend(re.compile(Core._exclude_glob_to_regex(g)) for g in globs)
264
+ return compiled
265
+
266
+ @staticmethod
267
+ def path_matches_exclude_regexes(rel_path: str, regexes: List["re.Pattern"]) -> bool:
268
+ rp = rel_path.replace(os.sep, "/").replace("\\", "/")
269
+ return any(r.match(rp) for r in regexes)
270
+
271
+ @staticmethod
272
+ def matches_exclude_paths(file_path: str, base_path: str, patterns: List[str]) -> bool:
273
+ """Convenience matcher (compiles patterns per call); used in tests/ad-hoc checks."""
274
+ rel_path = os.path.relpath(file_path, base_path).replace(os.sep, "/")
275
+ return Core.path_matches_exclude_regexes(rel_path, Core.compile_exclude_paths(patterns))
276
+
216
277
  def save_submitted_files_list(self, files: List[str], output_path: str) -> None:
217
278
  """
218
279
  Save the list of submitted file names to a JSON file for debugging.
@@ -336,6 +397,17 @@ class Core:
336
397
  start_time = time.time()
337
398
  files: Set[str] = set()
338
399
 
400
+ # Unified --exclude-paths: filter discovered manifests by the same paths/globs that are
401
+ # forwarded to coana's --exclude-dirs. Only consulted when the user supplied the flag.
402
+ # Patterns are anchored to `path` (the scan root this pass walks), matching coana's
403
+ # target and the Node CLI's fast-glob cwd. NOTE: when scanning multiple --sub-path
404
+ # targets, find_files runs once per sub-path, so a pattern like `tests` anchors to each
405
+ # sub-path independently (Node anchors all patterns to a single scan-root cwd). This only
406
+ # differs for the multi-target full-scan + --exclude-paths combo; the reach flow is
407
+ # single-target, so it matches Node there.
408
+ exclude_paths = getattr(self.cli_config, "exclude_paths", None) if self.cli_config else None
409
+ exclude_regexes = Core.compile_exclude_paths(exclude_paths) if exclude_paths else []
410
+
339
411
  # Get supported patterns from the API
340
412
  patterns = self.get_supported_patterns()
341
413
 
@@ -365,8 +437,15 @@ class Core:
365
437
 
366
438
  for glob_file in glob_files:
367
439
  glob_file_str = str(glob_file)
368
- if os.path.isfile(glob_file_str) and not Core.is_excluded(glob_file_str, self.config.excluded_dirs):
369
- files.add(glob_file_str.replace("\\", "/"))
440
+ if not os.path.isfile(glob_file_str):
441
+ continue
442
+ if Core.is_excluded(glob_file_str, self.config.excluded_dirs):
443
+ continue
444
+ if exclude_regexes:
445
+ rel = os.path.relpath(glob_file_str, path)
446
+ if Core.path_matches_exclude_regexes(rel, exclude_regexes):
447
+ continue
448
+ files.add(glob_file_str.replace("\\", "/"))
370
449
 
371
450
  glob_end = time.time()
372
451
  log.debug(f"Globbing took {glob_end - glob_start:.4f} seconds")
@@ -388,7 +388,15 @@ def main_code():
388
388
  timeout=config.reach_analysis_timeout,
389
389
  memory_limit=config.reach_analysis_memory_limit,
390
390
  ecosystems=config.reach_ecosystems,
391
- exclude_paths=config.reach_exclude_paths,
391
+ # Union the deprecated --reach-exclude-paths with the unified --exclude-paths
392
+ # and forward verbatim to coana's --exclude-dirs. Patterns are scan-root
393
+ # relative; coana resolves --exclude-dirs relative to its `run` target, which
394
+ # here is `.` == cwd == scan root, so passthrough is correct. If a nested
395
+ # target is ever supported, re-anchor patterns to the target first (see Node's
396
+ # pathRelativeToTarget in exclude-paths.mts).
397
+ exclude_paths=(
398
+ (config.reach_exclude_paths or []) + (config.exclude_paths or [])
399
+ ) or None,
392
400
  min_severity=config.reach_min_severity,
393
401
  skip_cache=config.reach_skip_cache or False,
394
402
  disable_analytics=config.reach_disable_analytics or False,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: socketsecurity
3
- Version: 2.4.2
3
+ Version: 2.4.4
4
4
  Summary: Socket Security CLI for CI/CD
5
5
  Project-URL: Homepage, https://socket.dev
6
6
  Author-email: Douglas Coburn <douglas@socket.dev>
@@ -43,7 +43,7 @@ Requires-Dist: packaging
43
43
  Requires-Dist: prettytable
44
44
  Requires-Dist: python-dotenv
45
45
  Requires-Dist: requests
46
- Requires-Dist: socketdev<4.0.0,>=3.1.2
46
+ Requires-Dist: socketdev<4.0.0,>=3.2.0
47
47
  Provides-Extra: dev
48
48
  Requires-Dist: hatch; extra == 'dev'
49
49
  Requires-Dist: pre-commit; extra == 'dev'
@@ -1,10 +1,10 @@
1
1
  LICENSE,sha256=42PY8FVn2DIlllI33b2wn91DeorIVqORc6Q9uw11DUU,1066
2
- socketsecurity/__init__.py,sha256=wgvl_hj9zKTBESVZibEgCtDqwqse_ncYE2-ZMojkdsM,94
3
- socketsecurity/config.py,sha256=NhnSLh5x7DgmmRX_xG4HlD6cDlXDXqhn2YeNu-xkwv0,39491
2
+ socketsecurity/__init__.py,sha256=OmNsArjDwCgclnJjgfTPOzi-UN_YKlbrAaz_Rq2ZLhY,94
3
+ socketsecurity/config.py,sha256=m0gxrEAUGHkUVgWGGkWh0p8TUDU26EaPuSIhLTDpxdA,42464
4
4
  socketsecurity/fossa_compat.py,sha256=NO_bRlZkuzDNel2C-6lLfPbWAYwLta5gYWAVhtRaJAc,17596
5
5
  socketsecurity/output.py,sha256=mCcf8bvmxHFgB3XSHYIWNsBa9WZ6SkrM9OftFhzZ2pw,17394
6
- socketsecurity/socketcli.py,sha256=H-NEyZAwMG2cVMd0cYUnQaloDXtdW8GQx9jZGMtYlGA,42086
7
- socketsecurity/core/__init__.py,sha256=EWiwQNQOr_t9Eafm5afhWtaxQj6p4BmI49viEcJNJGU,76279
6
+ socketsecurity/socketcli.py,sha256=idEfWoVkODD0i0uuuRGaVUPWtuXM8CGCb4ZjIHnH3l0,42729
7
+ socketsecurity/core/__init__.py,sha256=MYeXn4bAcGFZ36m8-RsYzWM7NkFZhGewA51OTu2hwtE,80386
8
8
  socketsecurity/core/alert_selection.py,sha256=_lEm5sslT52bsM1YmdRWVaHTSq3U27465juOk99u-wY,8660
9
9
  socketsecurity/core/classes.py,sha256=s33Z434aj2HXt1o6fDbGP3RqreW2KXBcOdjPbOT5F4w,17510
10
10
  socketsecurity/core/cli_client.py,sha256=S9Ba-oCpx7QlGXLp5C77oarTv_uBU39biEHVMJJep1k,2548
@@ -34,8 +34,8 @@ socketsecurity/plugins/teams.py,sha256=49Q4zxx_S5oGOoB2D0XqneAV0e5M3YpumO9XL6SxW
34
34
  socketsecurity/plugins/webhook.py,sha256=IG-eM3irrewWfieuyxBNUcJ9rqPBYjvM3bK42S3e0a0,439
35
35
  socketsecurity/plugins/formatters/__init__.py,sha256=9FQBrh1Ju30Tlo4d2ao91-qQaRdcEX0A3Gu_0kk-Kno,145
36
36
  socketsecurity/plugins/formatters/slack.py,sha256=6G5vH87akgPYxJ1XJWLgTozuTOOmk8m9VAAh677rc4A,9531
37
- socketsecurity-2.4.2.dist-info/METADATA,sha256=UkDpjdPXLHn3SAAeP9xnNq1NRY3MUpujfOS4jAZ_xNk,11687
38
- socketsecurity-2.4.2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
39
- socketsecurity-2.4.2.dist-info/entry_points.txt,sha256=89ullhKkHRQUMGAXard1jWN2xNDL3qleyKOTAqnIyzY,103
40
- socketsecurity-2.4.2.dist-info/licenses/LICENSE,sha256=42PY8FVn2DIlllI33b2wn91DeorIVqORc6Q9uw11DUU,1066
41
- socketsecurity-2.4.2.dist-info/RECORD,,
37
+ socketsecurity-2.4.4.dist-info/METADATA,sha256=cLWn3Fug6IC5n3LN8eMRHmoawAFboYSCEbGIg2_3IME,11687
38
+ socketsecurity-2.4.4.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
39
+ socketsecurity-2.4.4.dist-info/entry_points.txt,sha256=89ullhKkHRQUMGAXard1jWN2xNDL3qleyKOTAqnIyzY,103
40
+ socketsecurity-2.4.4.dist-info/licenses/LICENSE,sha256=42PY8FVn2DIlllI33b2wn91DeorIVqORc6Q9uw11DUU,1066
41
+ socketsecurity-2.4.4.dist-info/RECORD,,