socketsecurity 2.2.15__tar.gz → 2.2.22__tar.gz

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.
Files changed (85) hide show
  1. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/Dockerfile +2 -1
  2. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/PKG-INFO +30 -3
  3. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/README.md +27 -1
  4. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/pyproject.toml +4 -3
  5. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/__init__.py +1 -1
  6. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/config.py +101 -1
  7. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/core/git_interface.py +1 -2
  8. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/core/helper/__init__.py +2 -1
  9. socketsecurity-2.2.22/socketsecurity/core/tools/reachability.py +238 -0
  10. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/socketcli.py +135 -1
  11. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/uv.lock +41 -5
  12. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/.github/CODEOWNERS +0 -0
  13. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/.github/PULL_REQUEST_TEMPLATE/bug-fix.md +0 -0
  14. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/.github/PULL_REQUEST_TEMPLATE/feature.md +0 -0
  15. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/.github/PULL_REQUEST_TEMPLATE/improvement.md +0 -0
  16. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  17. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/.github/workflows/docker-stable.yml +0 -0
  18. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/.github/workflows/pr-preview.yml +0 -0
  19. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/.github/workflows/release.yml +0 -0
  20. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/.github/workflows/version-check.yml +0 -0
  21. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/.gitignore +0 -0
  22. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/.hooks/sync_version.py +0 -0
  23. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/.pre-commit-config.yaml +0 -0
  24. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/.python-version +0 -0
  25. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/LICENSE +0 -0
  26. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/Makefile +0 -0
  27. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/docs/README.md +0 -0
  28. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/pytest.ini +0 -0
  29. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/scripts/build_container.sh +0 -0
  30. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/scripts/deploy-test-docker.sh +0 -0
  31. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/scripts/deploy-test-pypi.sh +0 -0
  32. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/scripts/run.sh +0 -0
  33. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/core/__init__.py +0 -0
  34. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/core/classes.py +0 -0
  35. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/core/cli_client.py +0 -0
  36. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/core/exceptions.py +0 -0
  37. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/core/lazy_file_loader.py +0 -0
  38. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/core/logging.py +0 -0
  39. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/core/messages.py +0 -0
  40. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/core/resource_utils.py +0 -0
  41. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/core/scm/__init__.py +0 -0
  42. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/core/scm/base.py +0 -0
  43. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/core/scm/client.py +0 -0
  44. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/core/scm/github.py +0 -0
  45. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/core/scm/gitlab.py +0 -0
  46. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/core/scm_comments.py +0 -0
  47. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/core/socket_config.py +0 -0
  48. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/core/utils.py +0 -0
  49. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/output.py +0 -0
  50. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/plugins/__init__.py +0 -0
  51. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/plugins/base.py +0 -0
  52. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/plugins/jira.py +0 -0
  53. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/plugins/manager.py +0 -0
  54. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/plugins/slack.py +0 -0
  55. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/plugins/teams.py +0 -0
  56. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/socketsecurity/plugins/webhook.py +0 -0
  57. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/__init__.py +0 -0
  58. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/core/conftest.py +0 -0
  59. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/core/create_diff_input.json +0 -0
  60. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/core/test_diff_generation.py +0 -0
  61. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/core/test_package_and_alerts.py +0 -0
  62. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/core/test_sdk_methods.py +0 -0
  63. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/core/test_supporting_methods.py +0 -0
  64. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/data/fullscans/create_response.json +0 -0
  65. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/data/fullscans/diff/stream_diff.json +0 -0
  66. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/data/fullscans/diff/stream_diff_full.json +0 -0
  67. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/data/fullscans/head_scan/metadata.json +0 -0
  68. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/data/fullscans/head_scan/stream_scan.json +0 -0
  69. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/data/fullscans/head_scan/stream_scan_full.json +0 -0
  70. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/data/fullscans/new_scan/metadata.json +0 -0
  71. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/data/fullscans/new_scan/stream_scan.json +0 -0
  72. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/data/repos/repo_info_error.json +0 -0
  73. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/data/repos/repo_info_no_head.json +0 -0
  74. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/data/repos/repo_info_success.json +0 -0
  75. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/data/settings/security-policy.json +0 -0
  76. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/unit/__init__.py +0 -0
  77. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/unit/test_cli_config.py +0 -0
  78. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/unit/test_client.py +0 -0
  79. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/unit/test_config.py +0 -0
  80. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/unit/test_gitlab_auth.py +0 -0
  81. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/unit/test_gitlab_auth_fallback.py +0 -0
  82. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/tests/unit/test_output.py +0 -0
  83. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/workflows/bitbucket-pipelines.yml +0 -0
  84. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/workflows/github-actions.yml +0 -0
  85. {socketsecurity-2.2.15 → socketsecurity-2.2.22}/workflows/gitlab-ci.yml +0 -0
@@ -6,7 +6,8 @@ ARG PIP_INDEX_URL=https://pypi.org/simple
6
6
  ARG PIP_EXTRA_INDEX_URL=https://pypi.org/simple
7
7
 
8
8
  RUN apk update \
9
- && apk add --no-cache git nodejs npm yarn
9
+ && apk add --no-cache git nodejs npm yarn \
10
+ && npm install @coana-tech/cli -g
10
11
 
11
12
  # Install CLI with retries for TestPyPI propagation (10 attempts, 30s each = 5 minutes total)
12
13
  RUN for i in $(seq 1 10); do \
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: socketsecurity
3
- Version: 2.2.15
3
+ Version: 2.2.22
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>
@@ -33,13 +33,14 @@ Classifier: Intended Audience :: Developers
33
33
  Classifier: Programming Language :: Python :: 3.11
34
34
  Classifier: Programming Language :: Python :: 3.12
35
35
  Requires-Python: >=3.10
36
+ Requires-Dist: bs4>=0.0.2
36
37
  Requires-Dist: gitpython
37
38
  Requires-Dist: mdutils
38
39
  Requires-Dist: packaging
39
40
  Requires-Dist: prettytable
40
41
  Requires-Dist: python-dotenv
41
42
  Requires-Dist: requests
42
- Requires-Dist: socketdev<4.0.0,>=3.0.6
43
+ Requires-Dist: socketdev<4.0.0,>=3.0.16
43
44
  Provides-Extra: dev
44
45
  Requires-Dist: hatch; extra == 'dev'
45
46
  Requires-Dist: pre-commit; extra == 'dev'
@@ -150,7 +151,11 @@ socketcli [-h] [--api-token API_TOKEN] [--repo REPO] [--repo-is-public] [--branc
150
151
  [--save-manifest-tar SAVE_MANIFEST_TAR] [--files FILES] [--sub-path SUB_PATH] [--workspace-name WORKSPACE_NAME]
151
152
  [--excluded-ecosystems EXCLUDED_ECOSYSTEMS] [--default-branch] [--pending-head] [--generate-license] [--enable-debug]
152
153
  [--enable-json] [--enable-sarif] [--disable-overview] [--exclude-license-details] [--allow-unverified] [--disable-security-issue]
153
- [--ignore-commit-files] [--disable-blocking] [--enable-diff] [--scm SCM] [--timeout TIMEOUT] [--include-module-folders] [--version]
154
+ [--ignore-commit-files] [--disable-blocking] [--enable-diff] [--scm SCM] [--timeout TIMEOUT] [--include-module-folders]
155
+ [--reach] [--reach-version REACH_VERSION] [--reach-analysis-timeout REACH_ANALYSIS_TIMEOUT]
156
+ [--reach-analysis-memory-limit REACH_ANALYSIS_MEMORY_LIMIT] [--reach-ecosystems REACH_ECOSYSTEMS] [--reach-exclude-paths REACH_EXCLUDE_PATHS]
157
+ [--reach-min-severity {low,medium,high,critical}] [--reach-skip-cache] [--reach-disable-analytics] [--reach-output-file REACH_OUTPUT_FILE]
158
+ [--only-facts-file] [--version]
154
159
  ````
155
160
 
156
161
  If you don't want to provide the Socket API Token every time then you can use the environment variable `SOCKET_SECURITY_API_KEY`
@@ -216,6 +221,28 @@ If you don't want to provide the Socket API Token every time then you can use th
216
221
  | --allow-unverified | False | False | Allow unverified packages |
217
222
  | --disable-security-issue | False | False | Disable security issue checks |
218
223
 
224
+ #### Reachability Analysis
225
+ | Parameter | Required | Default | Description |
226
+ |:---------------------------------|:---------|:--------|:---------------------------------------------------------------------------------------------------------------------------|
227
+ | --reach | False | False | Enable reachability analysis to identify which vulnerable functions are actually called by your code |
228
+ | --reach-version | False | latest | Version of @coana-tech/cli to use for analysis |
229
+ | --reach-analysis-timeout | False | 1200 | Timeout in seconds for the reachability analysis (default: 1200 seconds / 20 minutes) |
230
+ | --reach-analysis-memory-limit | False | 4096 | Memory limit in MB for the reachability analysis (default: 4096 MB / 4 GB) |
231
+ | --reach-ecosystems | False | | Comma-separated list of ecosystems to analyze (e.g., "npm,pypi"). If not specified, all supported ecosystems are analyzed |
232
+ | --reach-exclude-paths | False | | Comma-separated list of file paths or patterns to exclude from reachability analysis |
233
+ | --reach-min-severity | False | | Minimum severity level for reporting reachability results (low, medium, high, critical) |
234
+ | --reach-skip-cache | False | False | Skip cache and force fresh reachability analysis |
235
+ | --reach-disable-analytics | False | False | Disable analytics collection during reachability analysis |
236
+ | --reach-output-file | False | .socket.facts.json | Path where reachability analysis results should be saved |
237
+ | --only-facts-file | False | False | Submit only the .socket.facts.json file to an existing scan (requires --reach and a prior scan) |
238
+
239
+ **Reachability Analysis Requirements:**
240
+ - `npm` - Required to install and run @coana-tech/cli
241
+ - `npx` - Required to execute @coana-tech/cli
242
+ - `uv` - Required for Python environment management
243
+
244
+ The CLI will automatically install @coana-tech/cli if not present. Use `--reach` to enable reachability analysis during a full scan, or use `--only-facts-file` with `--reach` to submit reachability results to an existing scan.
245
+
219
246
  #### Advanced Configuration
220
247
  | Parameter | Required | Default | Description |
221
248
  |:-------------------------|:---------|:--------|:----------------------------------------------------------------------|
@@ -94,7 +94,11 @@ socketcli [-h] [--api-token API_TOKEN] [--repo REPO] [--repo-is-public] [--branc
94
94
  [--save-manifest-tar SAVE_MANIFEST_TAR] [--files FILES] [--sub-path SUB_PATH] [--workspace-name WORKSPACE_NAME]
95
95
  [--excluded-ecosystems EXCLUDED_ECOSYSTEMS] [--default-branch] [--pending-head] [--generate-license] [--enable-debug]
96
96
  [--enable-json] [--enable-sarif] [--disable-overview] [--exclude-license-details] [--allow-unverified] [--disable-security-issue]
97
- [--ignore-commit-files] [--disable-blocking] [--enable-diff] [--scm SCM] [--timeout TIMEOUT] [--include-module-folders] [--version]
97
+ [--ignore-commit-files] [--disable-blocking] [--enable-diff] [--scm SCM] [--timeout TIMEOUT] [--include-module-folders]
98
+ [--reach] [--reach-version REACH_VERSION] [--reach-analysis-timeout REACH_ANALYSIS_TIMEOUT]
99
+ [--reach-analysis-memory-limit REACH_ANALYSIS_MEMORY_LIMIT] [--reach-ecosystems REACH_ECOSYSTEMS] [--reach-exclude-paths REACH_EXCLUDE_PATHS]
100
+ [--reach-min-severity {low,medium,high,critical}] [--reach-skip-cache] [--reach-disable-analytics] [--reach-output-file REACH_OUTPUT_FILE]
101
+ [--only-facts-file] [--version]
98
102
  ````
99
103
 
100
104
  If you don't want to provide the Socket API Token every time then you can use the environment variable `SOCKET_SECURITY_API_KEY`
@@ -160,6 +164,28 @@ If you don't want to provide the Socket API Token every time then you can use th
160
164
  | --allow-unverified | False | False | Allow unverified packages |
161
165
  | --disable-security-issue | False | False | Disable security issue checks |
162
166
 
167
+ #### Reachability Analysis
168
+ | Parameter | Required | Default | Description |
169
+ |:---------------------------------|:---------|:--------|:---------------------------------------------------------------------------------------------------------------------------|
170
+ | --reach | False | False | Enable reachability analysis to identify which vulnerable functions are actually called by your code |
171
+ | --reach-version | False | latest | Version of @coana-tech/cli to use for analysis |
172
+ | --reach-analysis-timeout | False | 1200 | Timeout in seconds for the reachability analysis (default: 1200 seconds / 20 minutes) |
173
+ | --reach-analysis-memory-limit | False | 4096 | Memory limit in MB for the reachability analysis (default: 4096 MB / 4 GB) |
174
+ | --reach-ecosystems | False | | Comma-separated list of ecosystems to analyze (e.g., "npm,pypi"). If not specified, all supported ecosystems are analyzed |
175
+ | --reach-exclude-paths | False | | Comma-separated list of file paths or patterns to exclude from reachability analysis |
176
+ | --reach-min-severity | False | | Minimum severity level for reporting reachability results (low, medium, high, critical) |
177
+ | --reach-skip-cache | False | False | Skip cache and force fresh reachability analysis |
178
+ | --reach-disable-analytics | False | False | Disable analytics collection during reachability analysis |
179
+ | --reach-output-file | False | .socket.facts.json | Path where reachability analysis results should be saved |
180
+ | --only-facts-file | False | False | Submit only the .socket.facts.json file to an existing scan (requires --reach and a prior scan) |
181
+
182
+ **Reachability Analysis Requirements:**
183
+ - `npm` - Required to install and run @coana-tech/cli
184
+ - `npx` - Required to execute @coana-tech/cli
185
+ - `uv` - Required for Python environment management
186
+
187
+ The CLI will automatically install @coana-tech/cli if not present. Use `--reach` to enable reachability analysis during a full scan, or use `--only-facts-file` with `--reach` to submit reachability results to an existing scan.
188
+
163
189
  #### Advanced Configuration
164
190
  | Parameter | Required | Default | Description |
165
191
  |:-------------------------|:---------|:--------|:----------------------------------------------------------------------|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "socketsecurity"
9
- version = "2.2.15"
9
+ version = "2.2.22"
10
10
  requires-python = ">= 3.10"
11
11
  license = {"file" = "LICENSE"}
12
12
  dependencies = [
@@ -16,7 +16,8 @@ dependencies = [
16
16
  'GitPython',
17
17
  'packaging',
18
18
  'python-dotenv',
19
- 'socketdev>=3.0.6,<4.0.0'
19
+ 'socketdev>=3.0.16,<4.0.0',
20
+ "bs4>=0.0.2",
20
21
  ]
21
22
  readme = "README.md"
22
23
  description = "Socket Security CLI for CI/CD"
@@ -158,4 +159,4 @@ docstring-code-format = false
158
159
  docstring-code-line-length = "dynamic"
159
160
 
160
161
  [tool.hatch.build.targets.wheel]
161
- include = ["socketsecurity", "LICENSE"]
162
+ include = ["socketsecurity", "LICENSE"]
@@ -1,3 +1,3 @@
1
1
  __author__ = 'socket.dev'
2
- __version__ = '2.2.15'
2
+ __version__ = '2.2.22'
3
3
  USER_AGENT = f'SocketPythonCLI/{__version__}'
@@ -62,7 +62,19 @@ class CliConfig:
62
62
  save_manifest_tar: Optional[str] = None
63
63
  sub_paths: List[str] = field(default_factory=list)
64
64
  workspace_name: Optional[str] = None
65
-
65
+ # Reachability Flags
66
+ reach: bool = False
67
+ reach_version: Optional[str] = None
68
+ reach_analysis_memory_limit: Optional[int] = None
69
+ reach_analysis_timeout: Optional[int] = None
70
+ reach_disable_analytics: bool = False
71
+ reach_ecosystems: Optional[List[str]] = None
72
+ reach_exclude_paths: Optional[List[str]] = None
73
+ reach_skip_cache: bool = False
74
+ reach_min_severity: Optional[str] = None
75
+ reach_output_file: Optional[str] = None
76
+ only_facts_file: bool = False
77
+
66
78
  @classmethod
67
79
  def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
68
80
  parser = create_argument_parser()
@@ -110,6 +122,17 @@ class CliConfig:
110
122
  'save_manifest_tar': args.save_manifest_tar,
111
123
  'sub_paths': args.sub_paths or [],
112
124
  'workspace_name': args.workspace_name,
125
+ 'reach': args.reach,
126
+ 'reach_version': args.reach_version,
127
+ 'reach_analysis_timeout': args.reach_analysis_timeout,
128
+ 'reach_analysis_memory_limit': args.reach_analysis_memory_limit,
129
+ 'reach_disable_analytics': args.reach_disable_analytics,
130
+ 'reach_ecosystems': args.reach_ecosystems.split(',') if args.reach_ecosystems else None,
131
+ 'reach_exclude_paths': args.reach_exclude_paths.split(',') if args.reach_exclude_paths else None,
132
+ 'reach_skip_cache': args.reach_skip_cache,
133
+ 'reach_min_severity': args.reach_min_severity,
134
+ 'reach_output_file': args.reach_output_file,
135
+ 'only_facts_file': args.only_facts_file,
113
136
  'version': __version__
114
137
  }
115
138
  try:
@@ -141,6 +164,11 @@ class CliConfig:
141
164
  logging.error("--workspace-name requires --sub-path to be specified")
142
165
  exit(1)
143
166
 
167
+ # Validate that only_facts_file requires reach
168
+ if args.only_facts_file and not args.reach:
169
+ logging.error("--only-facts-file requires --reach to be specified")
170
+ exit(1)
171
+
144
172
  return cls(**config_args)
145
173
 
146
174
  def to_dict(self) -> dict:
@@ -474,6 +502,78 @@ def create_argument_parser() -> argparse.ArgumentParser:
474
502
  help="Enabling including module folders like node_modules"
475
503
  )
476
504
 
505
+ # Reachability Configuration
506
+ reachability_group = parser.add_argument_group('Reachability Analysis')
507
+ reachability_group.add_argument(
508
+ "--reach",
509
+ dest="reach",
510
+ action="store_true",
511
+ help="Enable reachability analysis"
512
+ )
513
+ reachability_group.add_argument(
514
+ "--reach-version",
515
+ dest="reach_version",
516
+ metavar="<version>",
517
+ help="Specific version of @coana-tech/cli to use (e.g., '1.2.3')"
518
+ )
519
+ reachability_group.add_argument(
520
+ "--reach-timeout",
521
+ dest="reach_analysis_timeout",
522
+ type=int,
523
+ metavar="<seconds>",
524
+ help="Timeout for reachability analysis in seconds"
525
+ )
526
+ reachability_group.add_argument(
527
+ "--reach-memory-limit",
528
+ dest="reach_analysis_memory_limit",
529
+ type=int,
530
+ metavar="<mb>",
531
+ help="Memory limit for reachability analysis in MB"
532
+ )
533
+ reachability_group.add_argument(
534
+ "--reach-ecosystems",
535
+ dest="reach_ecosystems",
536
+ metavar="<list>",
537
+ help="Ecosystems to analyze for reachability (comma-separated, e.g., 'npm,pypi')"
538
+ )
539
+ reachability_group.add_argument(
540
+ "--reach-exclude-paths",
541
+ dest="reach_exclude_paths",
542
+ metavar="<list>",
543
+ help="Paths to exclude from reachability analysis (comma-separated)"
544
+ )
545
+ reachability_group.add_argument(
546
+ "--reach-min-severity",
547
+ dest="reach_min_severity",
548
+ metavar="<level>",
549
+ help="Minimum severity level for reachability analysis (info, low, moderate, high, critical)"
550
+ )
551
+ reachability_group.add_argument(
552
+ "--reach-skip-cache",
553
+ dest="reach_skip_cache",
554
+ action="store_true",
555
+ help="Skip cache usage for reachability analysis"
556
+ )
557
+ reachability_group.add_argument(
558
+ "--reach-disable-analytics",
559
+ dest="reach_disable_analytics",
560
+ action="store_true",
561
+ help="Disable analytics sharing for reachability analysis"
562
+ )
563
+ reachability_group.add_argument(
564
+ "--reach-output-file",
565
+ dest="reach_output_file",
566
+ metavar="<path>",
567
+ default=".socket.facts.json",
568
+ help="Output file path for reachability analysis results (default: .socket.facts.json)"
569
+ )
570
+ reachability_group.add_argument(
571
+ "--only-facts-file",
572
+ dest="only_facts_file",
573
+ action="store_true",
574
+ help="Submit only the .socket.facts.json file when creating full scan (requires --reach)"
575
+ )
576
+
477
577
  parser.add_argument(
478
578
  '--version',
479
579
  action='version',
@@ -97,8 +97,7 @@ class Git:
97
97
  else:
98
98
  # Try to get branch name from git properties
99
99
  try:
100
- self.branch = self.head.reference
101
- urllib.parse.unquote(str(self.branch))
100
+ self.branch = urllib.parse.unquote(str(self.head.reference))
102
101
  log.debug(f"Branch detected from git reference: {self.branch}")
103
102
  except Exception as error:
104
103
  log.debug(f"Failed to get branch from git reference: {error}")
@@ -1,5 +1,6 @@
1
1
  import markdown
2
- from bs4 import BeautifulSoup, NavigableString, Tag
2
+ from bs4 import BeautifulSoup, Tag
3
+ from bs4.element import NavigableString
3
4
  import string
4
5
 
5
6
 
@@ -0,0 +1,238 @@
1
+ from socketdev import socketdev
2
+ from typing import List, Optional, Dict, Any
3
+ import os
4
+ import subprocess
5
+ import json
6
+ import pathlib
7
+ import logging
8
+ import sys
9
+
10
+ log = logging.getLogger(__name__)
11
+
12
+
13
+ class ReachabilityAnalyzer:
14
+ def __init__(self, sdk: socketdev, api_token: str):
15
+ self.sdk = sdk
16
+ self.api_token = api_token
17
+
18
+ def _ensure_coana_cli_installed(self, version: Optional[str] = None) -> str:
19
+ """
20
+ Check if @coana-tech/cli is installed, and install/update it if needed.
21
+
22
+ Args:
23
+ version: Specific version to install (e.g., '1.2.3'). If None, updates to latest.
24
+
25
+ Returns:
26
+ str: The package specifier to use with npx
27
+ """
28
+ # Determine the package specifier
29
+ package_spec = f"@coana-tech/cli@{version}" if version else "@coana-tech/cli"
30
+
31
+ # If a specific version is requested, check if it's already installed
32
+ if version:
33
+ try:
34
+ check_cmd = ["npm", "list", "-g", "@coana-tech/cli", "--depth=0"]
35
+ result = subprocess.run(
36
+ check_cmd,
37
+ capture_output=True,
38
+ text=True,
39
+ timeout=10
40
+ )
41
+
42
+ # If npm list succeeds and mentions the specific version, it's installed
43
+ if result.returncode == 0 and f"@coana-tech/cli@{version}" in result.stdout:
44
+ log.debug(f"@coana-tech/cli@{version} is already installed globally")
45
+ return package_spec
46
+
47
+ except Exception as e:
48
+ log.debug(f"Could not check for existing @coana-tech/cli installation: {e}")
49
+
50
+ # Install or update the package
51
+ if version:
52
+ log.info(f"Installing reachability analysis plugin (@coana-tech/cli@{version})...")
53
+ else:
54
+ log.info("Updating reachability analysis plugin (@coana-tech/cli) to latest version...")
55
+ log.info("This may take a moment...")
56
+
57
+ try:
58
+ install_cmd = ["npm", "install", "-g", package_spec]
59
+ log.debug(f"Installing with command: {' '.join(install_cmd)}")
60
+
61
+ result = subprocess.run(
62
+ install_cmd,
63
+ capture_output=True,
64
+ text=True,
65
+ timeout=300 # 5 minute timeout for installation
66
+ )
67
+
68
+ if result.returncode != 0:
69
+ log.warning(f"Global installation failed, npx will download on demand")
70
+ log.debug(f"Install stderr: {result.stderr}")
71
+ else:
72
+ log.info("Reachability analysis plugin installed successfully")
73
+
74
+ except subprocess.TimeoutExpired:
75
+ log.warning("Installation timed out, npx will download on demand")
76
+ except Exception as e:
77
+ log.warning(f"Could not install globally: {e}, npx will download on demand")
78
+
79
+ return package_spec
80
+
81
+
82
+ def run_reachability_analysis(
83
+ self,
84
+ org_slug: str,
85
+ target_directory: str,
86
+ tar_hash: Optional[str] = None,
87
+ output_path: str = ".socket.facts.json",
88
+ timeout: Optional[int] = None,
89
+ memory_limit: Optional[int] = None,
90
+ ecosystems: Optional[List[str]] = None,
91
+ exclude_paths: Optional[List[str]] = None,
92
+ min_severity: Optional[str] = None,
93
+ skip_cache: bool = False,
94
+ disable_analytics: bool = False,
95
+ repo_name: Optional[str] = None,
96
+ branch_name: Optional[str] = None,
97
+ version: Optional[str] = None,
98
+ ) -> Dict[str, Any]:
99
+ """
100
+ Run reachability analysis.
101
+
102
+ Args:
103
+ org_slug: Socket organization slug
104
+ target_directory: Directory to analyze
105
+ tar_hash: Tar hash from manifest upload or existing scan (optional)
106
+ output_path: Output file path for results
107
+ timeout: Analysis timeout in seconds
108
+ memory_limit: Memory limit in MB
109
+ ecosystems: List of ecosystems to analyze (e.g., ['npm', 'pypi'])
110
+ exclude_paths: Paths to exclude from analysis
111
+ min_severity: Minimum severity level (info, low, moderate, high, critical)
112
+ skip_cache: Skip cache usage
113
+ disable_analytics: Disable analytics sharing
114
+ repo_name: Repository name
115
+ branch_name: Branch name
116
+ version: Specific version of @coana-tech/cli to use
117
+
118
+ Returns:
119
+ Dict containing scan_id and report_path
120
+ """
121
+ # Ensure @coana-tech/cli is installed
122
+ cli_package = self._ensure_coana_cli_installed(version)
123
+
124
+ # Build CLI command arguments
125
+ cmd = ["npx", cli_package, "run", target_directory]
126
+
127
+ # Add required arguments
128
+ output_dir = str(pathlib.Path(output_path).parent)
129
+ cmd.extend([
130
+ "--output-dir", output_dir,
131
+ "--socket-mode", output_path,
132
+ "--disable-report-submission"
133
+ ])
134
+
135
+ # Add conditional arguments
136
+ if timeout:
137
+ cmd.extend(["--analysis-timeout", str(timeout)])
138
+
139
+ if memory_limit:
140
+ cmd.extend(["--memory-limit", str(memory_limit)])
141
+
142
+ if disable_analytics:
143
+ cmd.append("--disable-analytics-sharing")
144
+
145
+ # KEY POINT: Only add manifest tar hash if we have one
146
+ if tar_hash:
147
+ cmd.extend(["--run-without-docker", "--manifests-tar-hash", tar_hash])
148
+
149
+ if ecosystems:
150
+ cmd.extend(["--purl-types"] + ecosystems)
151
+
152
+ if exclude_paths:
153
+ cmd.extend(["--exclude-dirs"] + exclude_paths)
154
+
155
+ if min_severity:
156
+ cmd.extend(["--min-severity", min_severity])
157
+
158
+ if skip_cache:
159
+ cmd.append("--skip-cache-usage")
160
+
161
+ # Set up environment variables
162
+ env = os.environ.copy()
163
+
164
+ # Required environment variables for Coana CLI
165
+ env["SOCKET_ORG_SLUG"] = org_slug
166
+ env["SOCKET_CLI_API_TOKEN"] = self.api_token
167
+
168
+ # Optional environment variables
169
+ if repo_name:
170
+ env["SOCKET_REPO_NAME"] = repo_name
171
+
172
+ if branch_name:
173
+ env["SOCKET_BRANCH_NAME"] = branch_name
174
+
175
+ # Execute CLI
176
+ log.info("Running reachability analysis...")
177
+ log.debug(f"Reachability command: {' '.join(cmd)}")
178
+ log.debug(f"Environment: SOCKET_ORG_SLUG={org_slug}, SOCKET_REPO_NAME={repo_name or 'not set'}, SOCKET_BRANCH_NAME={branch_name or 'not set'}")
179
+
180
+ try:
181
+ # Run with output streaming to stderr (don't capture output)
182
+ result = subprocess.run(
183
+ cmd,
184
+ env=env,
185
+ cwd=os.getcwd(),
186
+ stdout=sys.stderr, # Send stdout to stderr so user sees it
187
+ stderr=sys.stderr, # Send stderr to stderr
188
+ timeout=timeout + 60 if timeout else None # Add buffer to subprocess timeout
189
+ )
190
+
191
+ if result.returncode != 0:
192
+ log.error(f"Reachability analysis failed with exit code {result.returncode}")
193
+ raise Exception(f"Reachability analysis failed with exit code {result.returncode}")
194
+
195
+ # Extract scan ID from output file
196
+ scan_id = self._extract_scan_id(output_path)
197
+
198
+ log.info(f"Reachability analysis completed successfully")
199
+ if scan_id:
200
+ log.info(f"Scan ID: {scan_id}")
201
+
202
+ return {
203
+ "scan_id": scan_id,
204
+ "report_path": output_path,
205
+ "tar_hash_used": tar_hash
206
+ }
207
+
208
+ except subprocess.TimeoutExpired:
209
+ log.error(f"Reachability analysis timed out after {timeout} seconds")
210
+ raise Exception(f"Reachability analysis timed out after {timeout} seconds")
211
+ except Exception as e:
212
+ log.error(f"Failed to run reachability analysis: {str(e)}")
213
+ raise Exception(f"Failed to run reachability analysis: {str(e)}")
214
+
215
+ def _extract_scan_id(self, facts_file_path: str) -> Optional[str]:
216
+ """
217
+ Extract tier1ReachabilityScanId from the socket facts JSON file.
218
+
219
+ Args:
220
+ facts_file_path: Path to the .socket.facts.json file
221
+
222
+ Returns:
223
+ Optional[str]: The scan ID if found, None otherwise
224
+ """
225
+ try:
226
+ if not os.path.exists(facts_file_path):
227
+ log.warning(f"Facts file not found: {facts_file_path}")
228
+ return None
229
+
230
+ with open(facts_file_path, 'r') as f:
231
+ facts = json.load(f)
232
+
233
+ scan_id = facts.get('tier1ReachabilityScanId')
234
+ return scan_id.strip() if scan_id else None
235
+
236
+ except (json.JSONDecodeError, IOError) as e:
237
+ log.warning(f"Failed to extract scan ID from {facts_file_path}: {e}")
238
+ return None
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  import sys
3
3
  import traceback
4
+ import shutil
4
5
 
5
6
  from dotenv import load_dotenv
6
7
  from git import InvalidGitRepositoryError, NoSuchPathError
@@ -75,6 +76,50 @@ def main_code():
75
76
  log.debug("loaded client")
76
77
  core = Core(socket_config, sdk)
77
78
  log.debug("loaded core")
79
+
80
+ # Check for required dependencies if reachability analysis is enabled
81
+ if config.reach:
82
+ log.info("Reachability analysis enabled, checking for required dependencies...")
83
+ required_deps = ["npm", "uv", "npx"]
84
+ missing_deps = []
85
+ found_deps = []
86
+
87
+ for dep in required_deps:
88
+ if shutil.which(dep):
89
+ found_deps.append(dep)
90
+ log.debug(f"Found required dependency: {dep}")
91
+ else:
92
+ missing_deps.append(dep)
93
+
94
+ if missing_deps:
95
+ log.error(f"Reachability analysis requires the following dependencies: {', '.join(required_deps)}")
96
+ log.error(f"Missing dependencies: {', '.join(missing_deps)}")
97
+ log.error("Please install the missing dependencies and try again.")
98
+ sys.exit(3)
99
+
100
+ log.info(f"All required dependencies found: {', '.join(found_deps)}")
101
+
102
+ # Check if organization has an enterprise plan
103
+ log.info("Checking organization plan for reachability analysis eligibility...")
104
+ org_response = sdk.org.get(use_types=True)
105
+ organizations = org_response.get("organizations", {})
106
+
107
+ if organizations:
108
+ org_id = next(iter(organizations))
109
+ org_plan = organizations[org_id].get('plan', '')
110
+
111
+ # Check if plan matches enterprise* pattern (enterprise, enterprise_trial, etc.)
112
+ if not org_plan.startswith('enterprise'):
113
+ log.error(f"Reachability analysis is only available for enterprise plans.")
114
+ log.error(f"Your organization plan is: {org_plan}")
115
+ log.error("Please upgrade to an enterprise plan to use reachability analysis.")
116
+ sys.exit(3)
117
+
118
+ log.info(f"Organization plan verified: {org_plan}")
119
+ else:
120
+ log.error("Unable to retrieve organization information for plan verification.")
121
+ sys.exit(3)
122
+
78
123
  # Parse files argument
79
124
  try:
80
125
  if isinstance(config.files, list):
@@ -112,6 +157,9 @@ def main_code():
112
157
  # Determine if files were explicitly specified
113
158
  files_explicitly_specified = config.files != "[]" and len(specified_files) > 0
114
159
 
160
+ # Variable to track if we need to override files with facts file
161
+ facts_file_to_submit = None
162
+
115
163
  # Git setup
116
164
  is_repo = False
117
165
  git_repo: Git
@@ -172,6 +220,85 @@ def main_code():
172
220
  # If no repo name was set but workspace_name is provided, we'll use it later
173
221
  log.debug(f"Workspace name provided: {config.workspace_name}")
174
222
 
223
+ # Run reachability analysis if enabled
224
+ if config.reach:
225
+ from socketsecurity.core.tools.reachability import ReachabilityAnalyzer
226
+
227
+ log.info("Starting reachability analysis...")
228
+
229
+ # Find manifest files in scan paths (excluding .socket.facts.json to avoid circular dependency)
230
+ log.info("Finding manifest files for reachability analysis...")
231
+ manifest_files = []
232
+ for scan_path in scan_paths:
233
+ scan_manifests = core.find_files(scan_path)
234
+ # Filter out .socket.facts.json files from manifest upload
235
+ scan_manifests = [f for f in scan_manifests if not f.endswith('.socket.facts.json')]
236
+ manifest_files.extend(scan_manifests)
237
+
238
+ if not manifest_files:
239
+ log.warning("No manifest files found for reachability analysis")
240
+ else:
241
+ log.info(f"Found {len(manifest_files)} manifest files for reachability upload")
242
+
243
+ # Upload manifests and get tar hash
244
+ log.info("Uploading manifest files...")
245
+ try:
246
+ # Get org_slug early (we'll need it)
247
+ org_slug = core.config.org_slug
248
+
249
+ # Upload manifest files
250
+ tar_hash = sdk.uploadmanifests.upload_manifest_files(
251
+ org_slug=org_slug,
252
+ file_paths=manifest_files,
253
+ workspace=config.repo or "default-workspace",
254
+ base_path=None,
255
+ base_paths=base_paths,
256
+ use_lazy_loading=False
257
+ )
258
+ log.info(f"Manifest upload successful, tar hash: {tar_hash}")
259
+
260
+ # Initialize and run reachability analyzer
261
+ analyzer = ReachabilityAnalyzer(sdk, config.api_token)
262
+
263
+ # Determine output path
264
+ output_path = config.reach_output_file or ".socket.facts.json"
265
+
266
+ # Run the analysis
267
+ result = analyzer.run_reachability_analysis(
268
+ org_slug=org_slug,
269
+ target_directory=config.target_path,
270
+ tar_hash=tar_hash,
271
+ output_path=output_path,
272
+ timeout=config.reach_analysis_timeout,
273
+ memory_limit=config.reach_analysis_memory_limit,
274
+ ecosystems=config.reach_ecosystems,
275
+ exclude_paths=config.reach_exclude_paths,
276
+ min_severity=config.reach_min_severity,
277
+ skip_cache=config.reach_skip_cache or False,
278
+ disable_analytics=config.reach_disable_analytics or False,
279
+ repo_name=config.repo,
280
+ branch_name=config.branch,
281
+ version=config.reach_version
282
+ )
283
+
284
+ log.info(f"Reachability analysis completed successfully")
285
+ log.info(f"Results written to: {result['report_path']}")
286
+ if result.get('scan_id'):
287
+ log.info(f"Reachability scan ID: {result['scan_id']}")
288
+
289
+ # If only-facts-file mode, mark the facts file for submission
290
+ if config.only_facts_file:
291
+ import os
292
+ facts_file_to_submit = os.path.abspath(output_path)
293
+ log.info(f"Only-facts-file mode: will submit only {facts_file_to_submit}")
294
+
295
+ except Exception as e:
296
+ log.error(f"Reachability analysis failed: {str(e)}")
297
+ if not config.disable_blocking:
298
+ sys.exit(3)
299
+
300
+ log.info("Continuing with normal scan flow...")
301
+
175
302
  scm = None
176
303
  if config.scm == "github":
177
304
  from socketsecurity.core.scm.github import Github, GithubConfig
@@ -188,6 +315,12 @@ def main_code():
188
315
  if scm is not None and not config.default_branch:
189
316
  config.default_branch = scm.config.is_default_branch
190
317
 
318
+ # Override files if only-facts-file mode is active
319
+ if facts_file_to_submit:
320
+ specified_files = [facts_file_to_submit]
321
+ files_explicitly_specified = True
322
+ log.debug(f"Overriding files to only submit facts file: {facts_file_to_submit}")
323
+
191
324
  # Determine files to check based on the new logic
192
325
  files_to_check = []
193
326
  force_api_mode = False
@@ -282,7 +415,8 @@ def main_code():
282
415
  pull_request=pr_number,
283
416
  committers=config.committers,
284
417
  make_default_branch=is_default_branch,
285
- set_as_pending_head=is_default_branch
418
+ set_as_pending_head=is_default_branch,
419
+ tmp=False
286
420
  )
287
421
 
288
422
  params.include_license_details = not config.exclude_license_details
@@ -35,6 +35,31 @@ wheels = [
35
35
  { url = "https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", size = 30181, upload-time = "2024-05-28T17:01:53.112Z" },
36
36
  ]
37
37
 
38
+ [[package]]
39
+ name = "beautifulsoup4"
40
+ version = "4.14.2"
41
+ source = { registry = "https://pypi.org/simple" }
42
+ dependencies = [
43
+ { name = "soupsieve" },
44
+ { name = "typing-extensions" },
45
+ ]
46
+ sdist = { url = "https://files.pythonhosted.org/packages/77/e9/df2358efd7659577435e2177bfa69cba6c33216681af51a707193dec162a/beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e", size = 625822, upload-time = "2025-09-29T10:05:42.613Z" }
47
+ wheels = [
48
+ { url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392, upload-time = "2025-09-29T10:05:43.771Z" },
49
+ ]
50
+
51
+ [[package]]
52
+ name = "bs4"
53
+ version = "0.0.2"
54
+ source = { registry = "https://pypi.org/simple" }
55
+ dependencies = [
56
+ { name = "beautifulsoup4" },
57
+ ]
58
+ sdist = { url = "https://files.pythonhosted.org/packages/c9/aa/4acaf814ff901145da37332e05bb510452ebed97bc9602695059dd46ef39/bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925", size = 698, upload-time = "2024-01-17T18:15:47.371Z" }
59
+ wheels = [
60
+ { url = "https://files.pythonhosted.org/packages/51/bb/bf7aab772a159614954d84aa832c129624ba6c32faa559dfb200a534e50b/bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc", size = 1189, upload-time = "2024-01-17T18:15:48.613Z" },
61
+ ]
62
+
38
63
  [[package]]
39
64
  name = "certifi"
40
65
  version = "2025.8.3"
@@ -1027,22 +1052,23 @@ wheels = [
1027
1052
 
1028
1053
  [[package]]
1029
1054
  name = "socketdev"
1030
- version = "3.0.5"
1055
+ version = "3.0.16"
1031
1056
  source = { registry = "https://pypi.org/simple" }
1032
1057
  dependencies = [
1033
1058
  { name = "requests" },
1034
1059
  { name = "typing-extensions" },
1035
1060
  ]
1036
- sdist = { url = "https://files.pythonhosted.org/packages/19/b7/fe90d55105df76e9ff3af025f64b2d2b515c30ac0866a9973a093f25c5ed/socketdev-3.0.5.tar.gz", hash = "sha256:58cbe8613c3c892cdbae4941cb53f065051f8e991500d9d61618b214acf4ffc2", size = 129576, upload-time = "2025-09-09T07:15:48.232Z" }
1061
+ sdist = { url = "https://files.pythonhosted.org/packages/02/0d/6da0e0c34b97eef3a926d55470fa4bda2fcbbc42cc9e26ac51a34c6f117d/socketdev-3.0.16.tar.gz", hash = "sha256:5145300945e4e8d2d7f71db9c55cb44cc1449874f9d6416cc1d6ec129c64d638", size = 132505, upload-time = "2025-11-07T03:24:16.231Z" }
1037
1062
  wheels = [
1038
- { url = "https://files.pythonhosted.org/packages/de/05/c3fc7d0418c2598302ad4b0baf111fa492b31a8fa14acfa394af6f55b373/socketdev-3.0.5-py3-none-any.whl", hash = "sha256:e050f50d2c6b4447107edd3368b56b053e1df62056d424cc1616e898303638ef", size = 55083, upload-time = "2025-09-09T07:15:46.52Z" },
1063
+ { url = "https://files.pythonhosted.org/packages/a3/91/8486b2a62ba71d62a8f4f2f9ad22c61fcaabb461c5f269bbe0734eae76f9/socketdev-3.0.16-py3-none-any.whl", hash = "sha256:f5e413f5f2f8c0c938d5654da7f0a157c0be02a25e14d94af62c252e9fb3b502", size = 58567, upload-time = "2025-11-07T03:24:14.965Z" },
1039
1064
  ]
1040
1065
 
1041
1066
  [[package]]
1042
1067
  name = "socketsecurity"
1043
- version = "2.2.7"
1068
+ version = "2.2.18"
1044
1069
  source = { editable = "." }
1045
1070
  dependencies = [
1071
+ { name = "bs4" },
1046
1072
  { name = "gitpython" },
1047
1073
  { name = "mdutils" },
1048
1074
  { name = "packaging" },
@@ -1070,6 +1096,7 @@ test = [
1070
1096
 
1071
1097
  [package.metadata]
1072
1098
  requires-dist = [
1099
+ { name = "bs4", specifier = ">=0.0.2" },
1073
1100
  { name = "gitpython" },
1074
1101
  { name = "hatch", marker = "extra == 'dev'" },
1075
1102
  { name = "mdutils" },
@@ -1084,12 +1111,21 @@ requires-dist = [
1084
1111
  { name = "python-dotenv" },
1085
1112
  { name = "requests" },
1086
1113
  { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.3.0" },
1087
- { name = "socketdev", specifier = ">=3.0.5,<4.0.0" },
1114
+ { name = "socketdev", specifier = ">=3.0.16,<4.0.0" },
1088
1115
  { name = "twine", marker = "extra == 'dev'" },
1089
1116
  { name = "uv", marker = "extra == 'dev'", specifier = ">=0.1.0" },
1090
1117
  ]
1091
1118
  provides-extras = ["test", "dev"]
1092
1119
 
1120
+ [[package]]
1121
+ name = "soupsieve"
1122
+ version = "2.8"
1123
+ source = { registry = "https://pypi.org/simple" }
1124
+ sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" }
1125
+ wheels = [
1126
+ { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" },
1127
+ ]
1128
+
1093
1129
  [[package]]
1094
1130
  name = "tomli"
1095
1131
  version = "2.2.1"
File without changes