socketsecurity 2.2.60__tar.gz → 2.2.63__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 (92) hide show
  1. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/PKG-INFO +80 -10
  2. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/README.md +78 -8
  3. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/pyproject.toml +2 -2
  4. socketsecurity-2.2.63/session.md +127 -0
  5. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/__init__.py +1 -1
  6. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/core/__init__.py +4 -1
  7. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/plugins/slack.py +227 -6
  8. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/socketcli.py +1 -0
  9. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/uv.lock +5 -5
  10. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/.github/CODEOWNERS +0 -0
  11. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/.github/PULL_REQUEST_TEMPLATE/bug-fix.md +0 -0
  12. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/.github/PULL_REQUEST_TEMPLATE/feature.md +0 -0
  13. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/.github/PULL_REQUEST_TEMPLATE/improvement.md +0 -0
  14. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  15. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/.github/workflows/docker-stable.yml +0 -0
  16. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/.github/workflows/pr-preview.yml +0 -0
  17. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/.github/workflows/release.yml +0 -0
  18. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/.github/workflows/version-check.yml +0 -0
  19. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/.gitignore +0 -0
  20. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/.hooks/sync_version.py +0 -0
  21. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/.pre-commit-config.yaml +0 -0
  22. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/.python-version +0 -0
  23. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/CHANGELOG.md +0 -0
  24. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/Dockerfile +0 -0
  25. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/LICENSE +0 -0
  26. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/Makefile +0 -0
  27. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/docs/README.md +0 -0
  28. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/pytest.ini +0 -0
  29. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/scripts/build_container.sh +0 -0
  30. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/scripts/build_container_flexible.sh +0 -0
  31. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/scripts/deploy-test-docker.sh +0 -0
  32. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/scripts/deploy-test-pypi.sh +0 -0
  33. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/scripts/docker-entrypoint.sh +0 -0
  34. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/scripts/run.sh +0 -0
  35. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/config.py +0 -0
  36. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/core/classes.py +0 -0
  37. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/core/cli_client.py +0 -0
  38. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/core/exceptions.py +0 -0
  39. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/core/git_interface.py +0 -0
  40. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/core/helper/__init__.py +0 -0
  41. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/core/helper/socket_facts_loader.py +0 -0
  42. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/core/lazy_file_loader.py +0 -0
  43. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/core/logging.py +0 -0
  44. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/core/messages.py +0 -0
  45. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/core/resource_utils.py +0 -0
  46. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/core/scm/__init__.py +0 -0
  47. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/core/scm/base.py +0 -0
  48. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/core/scm/client.py +0 -0
  49. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/core/scm/github.py +0 -0
  50. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/core/scm/gitlab.py +0 -0
  51. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/core/scm_comments.py +0 -0
  52. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/core/socket_config.py +0 -0
  53. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/core/tools/reachability.py +0 -0
  54. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/core/utils.py +0 -0
  55. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/output.py +0 -0
  56. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/plugins/__init__.py +0 -0
  57. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/plugins/base.py +0 -0
  58. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/plugins/formatters/__init__.py +0 -0
  59. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/plugins/formatters/slack.py +0 -0
  60. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/plugins/jira.py +0 -0
  61. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/plugins/manager.py +0 -0
  62. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/plugins/teams.py +0 -0
  63. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/socketsecurity/plugins/webhook.py +0 -0
  64. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/__init__.py +0 -0
  65. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/core/conftest.py +0 -0
  66. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/core/create_diff_input.json +0 -0
  67. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/core/test_diff_generation.py +0 -0
  68. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/core/test_package_and_alerts.py +0 -0
  69. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/core/test_sdk_methods.py +0 -0
  70. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/core/test_supporting_methods.py +0 -0
  71. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/data/fullscans/create_response.json +0 -0
  72. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/data/fullscans/diff/stream_diff.json +0 -0
  73. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/data/fullscans/diff/stream_diff_full.json +0 -0
  74. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/data/fullscans/head_scan/metadata.json +0 -0
  75. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/data/fullscans/head_scan/stream_scan.json +0 -0
  76. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/data/fullscans/head_scan/stream_scan_full.json +0 -0
  77. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/data/fullscans/new_scan/metadata.json +0 -0
  78. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/data/fullscans/new_scan/stream_scan.json +0 -0
  79. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/data/repos/repo_info_error.json +0 -0
  80. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/data/repos/repo_info_no_head.json +0 -0
  81. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/data/repos/repo_info_success.json +0 -0
  82. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/data/settings/security-policy.json +0 -0
  83. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/unit/__init__.py +0 -0
  84. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/unit/test_cli_config.py +0 -0
  85. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/unit/test_client.py +0 -0
  86. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/unit/test_config.py +0 -0
  87. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/unit/test_gitlab_auth.py +0 -0
  88. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/unit/test_gitlab_auth_fallback.py +0 -0
  89. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/tests/unit/test_output.py +0 -0
  90. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/workflows/bitbucket-pipelines.yml +0 -0
  91. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/workflows/github-actions.yml +0 -0
  92. {socketsecurity-2.2.60 → socketsecurity-2.2.63}/workflows/gitlab-ci.yml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: socketsecurity
3
- Version: 2.2.60
3
+ Version: 2.2.63
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>
@@ -41,7 +41,7 @@ Requires-Dist: packaging
41
41
  Requires-Dist: prettytable
42
42
  Requires-Dist: python-dotenv
43
43
  Requires-Dist: requests
44
- Requires-Dist: socketdev<4.0.0,>=3.0.25
44
+ Requires-Dist: socketdev<4.0.0,>=3.0.28
45
45
  Provides-Extra: dev
46
46
  Requires-Dist: hatch; extra == 'dev'
47
47
  Requires-Dist: pre-commit; extra == 'dev'
@@ -279,43 +279,113 @@ Example `SOCKET_JIRA_CONFIG_JSON` value
279
279
 
280
280
  | Environment Variable | Required | Default | Description |
281
281
  |:-------------------------|:---------|:--------|:-----------------------------------|
282
- | SOCKET_SLACK_CONFIG_JSON | False | None | Slack webhook configuration (enables plugin when set). Alternatively, use --slack-webhook CLI flag. |
282
+ | SOCKET_SLACK_CONFIG_JSON | False | None | Slack configuration (enables plugin when set). Supports webhook or bot mode. Alternatively, use --slack-webhook CLI flag for simple webhook mode. |
283
+ | SOCKET_SLACK_BOT_TOKEN | False | None | Slack Bot User OAuth Token (starts with `xoxb-`). Required when using bot mode. |
283
284
 
284
- Example `SOCKET_SLACK_CONFIG_JSON` value (simple webhook):
285
+ **Slack supports two modes:**
286
+
287
+ 1. **Webhook Mode** (default): Posts to incoming webhooks
288
+ 2. **Bot Mode**: Posts via Slack API with bot token authentication
289
+
290
+ ###### Webhook Mode Examples
291
+
292
+ Simple webhook:
285
293
 
286
294
  ````json
287
- {"url": "https://REPLACE_ME_WEBHOOK"}
295
+ {"url": "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"}
288
296
  ````
289
297
 
290
- Example with advanced filtering (reachability-only alerts):
298
+ Multiple webhooks with advanced filtering:
291
299
 
292
300
  ````json
293
301
  {
302
+ "mode": "webhook",
294
303
  "url": [
295
304
  {
296
305
  "name": "prod_alerts",
297
306
  "url": "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
307
+ },
308
+ {
309
+ "name": "critical_only",
310
+ "url": "https://hooks.slack.com/services/YOUR/OTHER/WEBHOOK/URL"
298
311
  }
299
312
  ],
300
313
  "url_configs": {
301
314
  "prod_alerts": {
302
315
  "reachability_alerts_only": true,
303
- "always_send_reachability": true
316
+ "severities": ["high", "critical"]
317
+ },
318
+ "critical_only": {
319
+ "severities": ["critical"]
304
320
  }
305
321
  }
306
322
  }
307
323
  ````
308
324
 
309
- **Advanced Configuration Options:**
325
+ ###### Bot Mode Examples
326
+
327
+ **Setting up a Slack Bot:**
328
+ 1. Go to https://api.slack.com/apps and create a new app
329
+ 2. Under "OAuth & Permissions", add the `chat:write` bot scope
330
+ 3. Install the app to your workspace and copy the "Bot User OAuth Token"
331
+ 4. Invite the bot to your channels: `/invite @YourBotName`
332
+
333
+ Basic bot configuration:
334
+
335
+ ````json
336
+ {
337
+ "mode": "bot",
338
+ "bot_configs": [
339
+ {
340
+ "name": "security_alerts",
341
+ "channels": ["security-alerts", "dev-team"]
342
+ }
343
+ ]
344
+ }
345
+ ````
346
+
347
+ Bot with filtering (reachability-only alerts):
348
+
349
+ ````json
350
+ {
351
+ "mode": "bot",
352
+ "bot_configs": [
353
+ {
354
+ "name": "critical_reachable",
355
+ "channels": ["security-critical"],
356
+ "severities": ["critical", "high"],
357
+ "reachability_alerts_only": true
358
+ },
359
+ {
360
+ "name": "all_alerts",
361
+ "channels": ["security-all"],
362
+ "repos": ["myorg/backend", "myorg/frontend"]
363
+ }
364
+ ]
365
+ }
366
+ ````
367
+
368
+ Set the bot token:
369
+ ```bash
370
+ export SOCKET_SLACK_BOT_TOKEN="xoxb-your-bot-token-here"
371
+ ```
310
372
 
311
- The `url_configs` object allows per-webhook filtering:
373
+ **Configuration Options:**
312
374
 
375
+ Webhook mode (`url_configs`):
313
376
  - `reachability_alerts_only` (boolean, default: false): When `--reach` is enabled, only send blocking alerts (error=true) from diff scans
314
- - `always_send_reachability` (boolean, default: true): Send reachability alerts even on non-diff scans when `--reach` is enabled. Set to false to only send reachability alerts when there are diff alerts.
315
377
  - `repos` (array): Only send alerts for specific repositories (e.g., `["owner/repo1", "owner/repo2"]`)
316
378
  - `alert_types` (array): Only send specific alert types (e.g., `["malware", "typosquat"]`)
317
379
  - `severities` (array): Only send alerts with specific severities (e.g., `["high", "critical"]`)
318
380
 
381
+ Bot mode (`bot_configs` array items):
382
+ - `name` (string, required): Friendly name for this configuration
383
+ - `channels` (array, required): Channel names (without #) where alerts will be posted
384
+ - `severities` (array, optional): Only send alerts with specific severities (e.g., `["high", "critical"]`)
385
+ - `repos` (array, optional): Only send alerts for specific repositories
386
+ - `alert_types` (array, optional): Only send specific alert types
387
+ - `reachability_alerts_only` (boolean, default: false): Only send reachable vulnerabilities when using `--reach`
388
+
319
389
  ## Automatic Git Detection
320
390
 
321
391
  The CLI now automatically detects repository information from your git environment, significantly simplifying usage in CI/CD pipelines:
@@ -221,43 +221,113 @@ Example `SOCKET_JIRA_CONFIG_JSON` value
221
221
 
222
222
  | Environment Variable | Required | Default | Description |
223
223
  |:-------------------------|:---------|:--------|:-----------------------------------|
224
- | SOCKET_SLACK_CONFIG_JSON | False | None | Slack webhook configuration (enables plugin when set). Alternatively, use --slack-webhook CLI flag. |
224
+ | SOCKET_SLACK_CONFIG_JSON | False | None | Slack configuration (enables plugin when set). Supports webhook or bot mode. Alternatively, use --slack-webhook CLI flag for simple webhook mode. |
225
+ | SOCKET_SLACK_BOT_TOKEN | False | None | Slack Bot User OAuth Token (starts with `xoxb-`). Required when using bot mode. |
225
226
 
226
- Example `SOCKET_SLACK_CONFIG_JSON` value (simple webhook):
227
+ **Slack supports two modes:**
228
+
229
+ 1. **Webhook Mode** (default): Posts to incoming webhooks
230
+ 2. **Bot Mode**: Posts via Slack API with bot token authentication
231
+
232
+ ###### Webhook Mode Examples
233
+
234
+ Simple webhook:
227
235
 
228
236
  ````json
229
- {"url": "https://REPLACE_ME_WEBHOOK"}
237
+ {"url": "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"}
230
238
  ````
231
239
 
232
- Example with advanced filtering (reachability-only alerts):
240
+ Multiple webhooks with advanced filtering:
233
241
 
234
242
  ````json
235
243
  {
244
+ "mode": "webhook",
236
245
  "url": [
237
246
  {
238
247
  "name": "prod_alerts",
239
248
  "url": "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
249
+ },
250
+ {
251
+ "name": "critical_only",
252
+ "url": "https://hooks.slack.com/services/YOUR/OTHER/WEBHOOK/URL"
240
253
  }
241
254
  ],
242
255
  "url_configs": {
243
256
  "prod_alerts": {
244
257
  "reachability_alerts_only": true,
245
- "always_send_reachability": true
258
+ "severities": ["high", "critical"]
259
+ },
260
+ "critical_only": {
261
+ "severities": ["critical"]
246
262
  }
247
263
  }
248
264
  }
249
265
  ````
250
266
 
251
- **Advanced Configuration Options:**
267
+ ###### Bot Mode Examples
268
+
269
+ **Setting up a Slack Bot:**
270
+ 1. Go to https://api.slack.com/apps and create a new app
271
+ 2. Under "OAuth & Permissions", add the `chat:write` bot scope
272
+ 3. Install the app to your workspace and copy the "Bot User OAuth Token"
273
+ 4. Invite the bot to your channels: `/invite @YourBotName`
274
+
275
+ Basic bot configuration:
276
+
277
+ ````json
278
+ {
279
+ "mode": "bot",
280
+ "bot_configs": [
281
+ {
282
+ "name": "security_alerts",
283
+ "channels": ["security-alerts", "dev-team"]
284
+ }
285
+ ]
286
+ }
287
+ ````
288
+
289
+ Bot with filtering (reachability-only alerts):
290
+
291
+ ````json
292
+ {
293
+ "mode": "bot",
294
+ "bot_configs": [
295
+ {
296
+ "name": "critical_reachable",
297
+ "channels": ["security-critical"],
298
+ "severities": ["critical", "high"],
299
+ "reachability_alerts_only": true
300
+ },
301
+ {
302
+ "name": "all_alerts",
303
+ "channels": ["security-all"],
304
+ "repos": ["myorg/backend", "myorg/frontend"]
305
+ }
306
+ ]
307
+ }
308
+ ````
309
+
310
+ Set the bot token:
311
+ ```bash
312
+ export SOCKET_SLACK_BOT_TOKEN="xoxb-your-bot-token-here"
313
+ ```
252
314
 
253
- The `url_configs` object allows per-webhook filtering:
315
+ **Configuration Options:**
254
316
 
317
+ Webhook mode (`url_configs`):
255
318
  - `reachability_alerts_only` (boolean, default: false): When `--reach` is enabled, only send blocking alerts (error=true) from diff scans
256
- - `always_send_reachability` (boolean, default: true): Send reachability alerts even on non-diff scans when `--reach` is enabled. Set to false to only send reachability alerts when there are diff alerts.
257
319
  - `repos` (array): Only send alerts for specific repositories (e.g., `["owner/repo1", "owner/repo2"]`)
258
320
  - `alert_types` (array): Only send specific alert types (e.g., `["malware", "typosquat"]`)
259
321
  - `severities` (array): Only send alerts with specific severities (e.g., `["high", "critical"]`)
260
322
 
323
+ Bot mode (`bot_configs` array items):
324
+ - `name` (string, required): Friendly name for this configuration
325
+ - `channels` (array, required): Channel names (without #) where alerts will be posted
326
+ - `severities` (array, optional): Only send alerts with specific severities (e.g., `["high", "critical"]`)
327
+ - `repos` (array, optional): Only send alerts for specific repositories
328
+ - `alert_types` (array, optional): Only send specific alert types
329
+ - `reachability_alerts_only` (boolean, default: false): Only send reachable vulnerabilities when using `--reach`
330
+
261
331
  ## Automatic Git Detection
262
332
 
263
333
  The CLI now automatically detects repository information from your git environment, significantly simplifying usage in CI/CD pipelines:
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "socketsecurity"
9
- version = "2.2.60"
9
+ version = "2.2.63"
10
10
  requires-python = ">= 3.10"
11
11
  license = {"file" = "LICENSE"}
12
12
  dependencies = [
@@ -16,7 +16,7 @@ dependencies = [
16
16
  'GitPython',
17
17
  'packaging',
18
18
  'python-dotenv',
19
- "socketdev>=3.0.25,<4.0.0",
19
+ "socketdev>=3.0.28,<4.0.0",
20
20
  "bs4>=0.0.2",
21
21
  "markdown>=3.10",
22
22
  ]
@@ -0,0 +1,127 @@
1
+ # Session Directions: Add Slack Bot Mode Support
2
+
3
+ ## Context
4
+ The Socket Python CLI currently supports Slack notifications via incoming webhooks. We need to add an alternative "bot" mode that uses a Slack App with Bot Token for more flexible channel routing.
5
+
6
+ ## Current Implementation
7
+ - File: `socketsecurity/plugins/slack.py`
8
+ - File: `socketsecurity/config.py`
9
+ - Env var: `SOCKET_SLACK_CONFIG_JSON`
10
+ - Current config uses `url` and `url_configs` for webhook routing
11
+
12
+ ## Requirements
13
+
14
+ ### 1. Add Mode Selection
15
+ - Add top-level `mode` field to Slack config
16
+ - Valid values: "webhook" (default), "bot"
17
+ - Mode determines which authentication and routing method to use
18
+
19
+ ### 2. Webhook Mode (existing, default)
20
+ ```json
21
+ {
22
+ "enabled": true,
23
+ "mode": "webhook",
24
+ "url": ["https://hooks.slack.com/..."],
25
+ "url_configs": {
26
+ "webhook_0": {"repos": ["repo1"], "severities": ["critical"]}
27
+ }
28
+ }
29
+ ```
30
+ Keep all existing webhook functionality unchanged.
31
+
32
+ ### 3. Bot Mode (new)
33
+ ```json
34
+ {
35
+ "enabled": true,
36
+ "mode": "bot",
37
+ "bot_configs": [
38
+ {
39
+ "name": "critical_alerts",
40
+ "channels": ["security-alerts", "critical-incidents"],
41
+ "repos": ["prod-app"],
42
+ "severities": ["critical"],
43
+ "reachability_alerts_only": true
44
+ },
45
+ {
46
+ "name": "all_alerts",
47
+ "channels": ["dev-alerts"],
48
+ "severities": ["high", "medium"]
49
+ }
50
+ ]
51
+ }
52
+ ```
53
+
54
+ - New env var: `SOCKET_SLACK_BOT_TOKEN` (Bot User OAuth Token starting with `xoxb-`)
55
+ - Use `bot_configs` array instead of `url` + `url_configs`
56
+ - Each bot_config has:
57
+ - `name`: identifier for logging
58
+ - `channels`: array of Slack channel names or IDs to post to
59
+ - All existing filter options: `repos`, `severities`, `alert_types`, `reachability_alerts_only`, `always_send_reachability`
60
+
61
+ ### 4. Channel Routing
62
+ - Slack API accepts both channel names (without #) and channel IDs (C1234567890)
63
+ - Recommend supporting both: try name first, fallback to ID if needed
64
+ - API endpoint: `https://slack.com/api/chat.postMessage`
65
+ - Request format:
66
+ ```python
67
+ {
68
+ "channel": "channel-name", # or "C1234567890"
69
+ "blocks": blocks
70
+ }
71
+ ```
72
+ - Headers: `{"Authorization": f"Bearer {bot_token}", "Content-Type": "application/json"}`
73
+
74
+ ### 5. Implementation Tasks
75
+
76
+ #### config.py
77
+ - No changes needed (config is loaded from JSON env var)
78
+
79
+ #### slack.py
80
+ 1. Update `send()` method:
81
+ - Check `self.config.get("mode", "webhook")`
82
+ - If "webhook": call existing `_send_webhook_alerts()` (refactor current logic)
83
+ - If "bot": call new `_send_bot_alerts()`
84
+
85
+ 2. Create `_send_bot_alerts()` method:
86
+ - Get bot token from env: `os.getenv("SOCKET_SLACK_BOT_TOKEN")`
87
+ - Validate token exists and starts with "xoxb-"
88
+ - Get `bot_configs` from config
89
+ - For each bot_config, filter alerts same way as webhooks
90
+ - For each channel in bot_config's channels array, post message via chat.postMessage API
91
+
92
+ 3. Create `_post_to_slack_api()` helper method:
93
+ - Takes bot_token, channel, blocks
94
+ - Posts to https://slack.com/api/chat.postMessage
95
+ - Returns response
96
+ - Log errors with channel name/ID for debugging
97
+
98
+ 4. Error handling:
99
+ - Log if bot token missing when mode is "bot"
100
+ - Handle API errors (invalid channel, missing permissions, rate limits)
101
+ - Parse Slack API response JSON (it returns 200 with error in body)
102
+
103
+ 5. Reuse existing:
104
+ - All filtering logic (`_filter_alerts`)
105
+ - All block building (`create_slack_blocks_from_diff`, `_create_reachability_slack_blocks_from_structured`)
106
+ - All reachability data loading
107
+
108
+ ### 6. Testing Considerations
109
+ - Test both modes don't interfere with each other
110
+ - Test channel name resolution
111
+ - Test channel ID usage
112
+ - Test multiple channels per bot_config
113
+ - Test error handling when bot token invalid or missing
114
+ - Verify block count limits still respected (50 blocks)
115
+
116
+ ### 7. Documentation Updates (README.md)
117
+ Add bot mode configuration examples and SOCKET_SLACK_BOT_TOKEN env var documentation.
118
+
119
+ ## Key Files to Modify
120
+ 1. `socketsecurity/plugins/slack.py` - main implementation
121
+ 2. `README.md` - add bot mode documentation
122
+
123
+ ## Notes
124
+ - Slack chat.postMessage returns HTTP 200 even on errors. Check response JSON for `"ok": false`
125
+ - Rate limit: 1 message per second per channel (more generous than webhooks)
126
+ - Channel names are case-insensitive, don't need # prefix
127
+ - Public and private channels both work if bot is invited
@@ -1,3 +1,3 @@
1
1
  __author__ = 'socket.dev'
2
- __version__ = '2.2.60'
2
+ __version__ = '2.2.63'
3
3
  USER_AGENT = f'SocketPythonCLI/{__version__}'
@@ -553,7 +553,10 @@ class Core:
553
553
 
554
554
  # Finalize tier1 scan if reachability analysis was enabled
555
555
  if self.cli_config and self.cli_config.reach:
556
- facts_file_path = self.cli_config.reach_output_file or ".socket.facts.json"
556
+ facts_file_path = os.path.join(
557
+ self.cli_config.target_path or ".",
558
+ self.cli_config.reach_output_file
559
+ )
557
560
  log.debug(f"Reachability analysis enabled, finalizing tier1 scan for full scan {full_scan.id}")
558
561
  try:
559
562
  success = self.finalize_tier1_scan(full_scan.id, facts_file_path)
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import os
2
3
  import requests
3
4
  from socketsecurity.config import CliConfig
4
5
  from .base import Plugin
@@ -20,10 +21,19 @@ class SlackPlugin(Plugin):
20
21
  return "slack"
21
22
 
22
23
  def send(self, diff, config: CliConfig):
23
- if not self.config.get("enabled", False):
24
- if config.enable_debug:
25
- logger.debug("Slack plugin is disabled - skipping webhook notification")
24
+ # Check mode and route to appropriate handler
25
+ mode = self.config.get("mode", "webhook")
26
+
27
+ if mode == "webhook":
28
+ self._send_webhook_alerts(diff, config)
29
+ elif mode == "bot":
30
+ self._send_bot_alerts(diff, config)
31
+ else:
32
+ logger.error(f"Invalid Slack mode '{mode}'. Valid modes are 'webhook' or 'bot'.")
26
33
  return
34
+
35
+ def _send_webhook_alerts(self, diff, config: CliConfig):
36
+ """Send alerts using webhook mode."""
27
37
  if not self.config.get("url"):
28
38
  logger.warning("Slack webhook URL not configured.")
29
39
  if config.enable_debug:
@@ -37,7 +47,7 @@ class SlackPlugin(Plugin):
37
47
  logger.warning("No valid Slack webhook URLs configured.")
38
48
  return
39
49
 
40
- logger.debug("Slack Plugin Enabled")
50
+ logger.debug("Slack Plugin Enabled (webhook mode)")
41
51
  logger.debug("Alert levels: %s", self.config.get("levels"))
42
52
 
43
53
  # Get url_configs parameter (filtering configuration)
@@ -117,6 +127,214 @@ class SlackPlugin(Plugin):
117
127
  elif config.enable_debug:
118
128
  logger.debug(f"Slack webhook response for {name}: {response.status_code}")
119
129
 
130
+ def _send_bot_alerts(self, diff, config: CliConfig):
131
+ """Send alerts using bot mode with Slack API."""
132
+ # Get bot token from environment
133
+ bot_token = os.getenv("SOCKET_SLACK_BOT_TOKEN")
134
+
135
+ if not bot_token:
136
+ logger.error("SOCKET_SLACK_BOT_TOKEN environment variable not set for bot mode.")
137
+ return
138
+
139
+ if not bot_token.startswith("xoxb-"):
140
+ logger.error("SOCKET_SLACK_BOT_TOKEN must start with 'xoxb-' (Bot User OAuth Token).")
141
+ return
142
+
143
+ # Get bot_configs from configuration
144
+ bot_configs = self.config.get("bot_configs", [])
145
+
146
+ if not bot_configs:
147
+ logger.warning("No bot_configs configured for bot mode.")
148
+ return
149
+
150
+ logger.debug("Slack Plugin Enabled (bot mode)")
151
+ logger.debug("Alert levels: %s", self.config.get("levels"))
152
+ logger.debug(f"Number of bot_configs: {len(bot_configs)}")
153
+ logger.debug(f"config.reach: {config.reach}")
154
+ logger.debug(f"len(diff.new_alerts): {len(diff.new_alerts) if diff.new_alerts else 0}")
155
+
156
+ # Get repo name from config
157
+ repo_name = config.repo or ""
158
+
159
+ # Handle reachability data if --reach is enabled
160
+ if config.reach:
161
+ self._send_bot_reachability_alerts(bot_configs, bot_token, repo_name, config, diff)
162
+
163
+ # Handle diff alerts (if any)
164
+ if not diff.new_alerts:
165
+ logger.debug("No new diff alerts to notify via Slack.")
166
+ else:
167
+ # Send to each configured bot_config with filtering
168
+ for bot_config in bot_configs:
169
+ name = bot_config.get("name", "unnamed")
170
+ channels = bot_config.get("channels", [])
171
+
172
+ if not channels:
173
+ logger.warning(f"No channels configured for bot_config '{name}'. Skipping.")
174
+ continue
175
+
176
+ # Filter alerts based on bot config
177
+ # When --reach is used, reachability_alerts_only applies to diff alerts
178
+ filtered_alerts = self._filter_alerts(
179
+ diff.new_alerts,
180
+ bot_config,
181
+ repo_name,
182
+ config,
183
+ is_reachability_data=False,
184
+ apply_reachability_only_filter=config.reach
185
+ )
186
+
187
+ if not filtered_alerts:
188
+ logger.debug(f"No diff alerts match filter criteria for bot_config '{name}'. Skipping.")
189
+ continue
190
+
191
+ # Create a temporary diff object with filtered alerts for message creation
192
+ filtered_diff = Diff(
193
+ new_alerts=filtered_alerts,
194
+ diff_url=getattr(diff, "diff_url", ""),
195
+ new_packages=getattr(diff, "new_packages", []),
196
+ removed_packages=getattr(diff, "removed_packages", []),
197
+ packages=getattr(diff, "packages", {})
198
+ )
199
+
200
+ message = self.create_slack_blocks_from_diff(filtered_diff, config)
201
+
202
+ if config.enable_debug:
203
+ logger.debug(f"Bot config '{name}': Total diff alerts: {len(diff.new_alerts)}, Filtered alerts: {len(filtered_alerts)}")
204
+ logger.debug(f"Message blocks count: {len(message)}")
205
+
206
+ # Send to each channel in the bot_config
207
+ for channel in channels:
208
+ logger.debug(f"Sending diff alerts message to channel '{channel}' (bot_config: {name})")
209
+ self._post_to_slack_api(bot_token, channel, message, config, name)
210
+
211
+ def _send_bot_reachability_alerts(self, bot_configs: list, bot_token: str, repo_name: str, config: CliConfig, diff=None):
212
+ """Send reachability alerts using bot mode with Slack API."""
213
+ # Construct path to socket facts file
214
+ facts_file_path = os.path.join(config.target_path or ".", f"{config.reach_output_file}")
215
+ logger.debug(f"Loading reachability data from {facts_file_path}")
216
+
217
+ # Load socket facts file
218
+ facts_data = load_socket_facts(facts_file_path)
219
+
220
+ if not facts_data:
221
+ logger.debug("No .socket.facts.json file found or failed to load")
222
+ return
223
+
224
+ # Get components with vulnerabilities
225
+ components_with_vulns = get_components_with_vulnerabilities(facts_data)
226
+
227
+ if not components_with_vulns:
228
+ logger.debug("No components with vulnerabilities found in .socket.facts.json")
229
+ return
230
+
231
+ # Convert to alerts format
232
+ components_with_alerts = convert_to_alerts(components_with_vulns)
233
+
234
+ if not components_with_alerts:
235
+ logger.debug("No alerts generated from .socket.facts.json")
236
+ return
237
+
238
+ logger.debug(f"Found {len(components_with_alerts)} components with reachability alerts")
239
+
240
+ # Send to each configured bot_config with filtering
241
+ for bot_config in bot_configs:
242
+ name = bot_config.get("name", "unnamed")
243
+ channels = bot_config.get("channels", [])
244
+
245
+ if not channels:
246
+ logger.warning(f"No channels configured for bot_config '{name}'. Skipping.")
247
+ continue
248
+
249
+ # Filter components based on severities only (for reachability data)
250
+ filtered_components = []
251
+ for component in components_with_alerts:
252
+ component_alerts = component.get('alerts', [])
253
+ # Filter alerts using only severities
254
+ filtered_component_alerts = self._filter_alerts(
255
+ component_alerts,
256
+ bot_config,
257
+ repo_name,
258
+ config,
259
+ is_reachability_data=True
260
+ )
261
+
262
+ if filtered_component_alerts:
263
+ # Create a copy of component with only filtered alerts
264
+ filtered_component = component.copy()
265
+ filtered_component['alerts'] = filtered_component_alerts
266
+ filtered_components.append(filtered_component)
267
+
268
+ if not filtered_components:
269
+ logger.debug(f"No reachability alerts match filter criteria for bot_config '{name}'. Skipping.")
270
+ continue
271
+
272
+ # Format for Slack using the formatter (max 45 blocks for findings + 5 for header/footer)
273
+ slack_notifications = format_socket_facts_for_slack(
274
+ filtered_components,
275
+ max_blocks=45,
276
+ include_traces=True
277
+ )
278
+
279
+ # Convert to Slack blocks format and send
280
+ for notification in slack_notifications:
281
+ blocks = self._create_reachability_slack_blocks_from_structured(
282
+ notification,
283
+ config,
284
+ diff
285
+ )
286
+
287
+ if config.enable_debug:
288
+ logger.debug(f"Bot config '{name}': Reachability components: {len(filtered_components)}")
289
+ logger.debug(f"Message blocks count: {len(blocks)}")
290
+
291
+ # Send to each channel in the bot_config
292
+ for channel in channels:
293
+ logger.debug(f"Sending reachability alerts message to channel '{channel}' (bot_config: {name})")
294
+ self._post_to_slack_api(bot_token, channel, blocks, config, name)
295
+
296
+ def _post_to_slack_api(self, bot_token: str, channel: str, blocks: list, config: CliConfig, config_name: str = None):
297
+ """Post message to Slack using chat.postMessage API.
298
+
299
+ Args:
300
+ bot_token: Slack bot token (starts with xoxb-)
301
+ channel: Channel name (without #) or channel ID (C1234567890)
302
+ blocks: List of Slack blocks to send
303
+ config: CliConfig object for debug logging
304
+ config_name: Name of the bot_config for logging
305
+
306
+ Returns:
307
+ Response dict from Slack API
308
+ """
309
+ url = "https://slack.com/api/chat.postMessage"
310
+ headers = {
311
+ "Authorization": f"Bearer {bot_token}",
312
+ "Content-Type": "application/json"
313
+ }
314
+ payload = {
315
+ "channel": channel,
316
+ "blocks": blocks
317
+ }
318
+
319
+ try:
320
+ response = requests.post(url, headers=headers, json=payload)
321
+ response_data = response.json()
322
+
323
+ # Slack returns 200 even on errors, check response JSON
324
+ if not response_data.get("ok", False):
325
+ error_msg = response_data.get("error", "unknown error")
326
+ logger.error(f"Slack API error for channel '{channel}': {error_msg}")
327
+ if config.enable_debug:
328
+ logger.debug(f"Full response: {response_data}")
329
+ elif config.enable_debug:
330
+ logger.debug(f"Successfully posted to channel '{channel}' (config: {config_name})")
331
+
332
+ return response_data
333
+
334
+ except Exception as e:
335
+ logger.error(f"Exception posting to Slack channel '{channel}': {str(e)}")
336
+ return {"ok": False, "error": str(e)}
337
+
120
338
  def _filter_alerts(
121
339
  self,
122
340
  alerts: list,
@@ -210,10 +428,13 @@ class SlackPlugin(Plugin):
210
428
  config: CliConfig object
211
429
  diff: Diff object containing diff_url for report link
212
430
  """
213
- logger.debug("Loading reachability data from .socket.facts.json")
431
+ # Construct path to socket facts file
432
+ import os as os_module
433
+ facts_file_path = os_module.path.join(config.target_path or ".", config.reach_output_file)
434
+ logger.debug(f"Loading reachability data from {facts_file_path}")
214
435
 
215
436
  # Load socket facts file
216
- facts_data = load_socket_facts(".socket.facts.json")
437
+ facts_data = load_socket_facts(facts_file_path)
217
438
 
218
439
  if not facts_data:
219
440
  logger.debug("No .socket.facts.json file found or failed to load")
@@ -591,6 +591,7 @@ def main_code():
591
591
  )
592
592
  log.info(f"Full scan created with ID: {diff.id}")
593
593
  log.info(f"Full scan report URL: {diff.report_url}")
594
+ output_handler.handle_output(diff)
594
595
  else:
595
596
  log.info("API Mode")
596
597
  diff = core.create_new_diff(
@@ -1250,20 +1250,20 @@ wheels = [
1250
1250
 
1251
1251
  [[package]]
1252
1252
  name = "socketdev"
1253
- version = "3.0.25"
1253
+ version = "3.0.28"
1254
1254
  source = { registry = "https://pypi.org/simple" }
1255
1255
  dependencies = [
1256
1256
  { name = "requests" },
1257
1257
  { name = "typing-extensions" },
1258
1258
  ]
1259
- sdist = { url = "https://files.pythonhosted.org/packages/88/49/207860555b4a78d621d5f45db2255754010862292aa09c212893dc3161ab/socketdev-3.0.25.tar.gz", hash = "sha256:422d06ae6ba50ed1fb07b6ede3b20bfc9fbbeae144d03aeed3fdbd8190966a9a", size = 168706, upload-time = "2026-01-01T21:58:40.829Z" }
1259
+ sdist = { url = "https://files.pythonhosted.org/packages/f8/db/3446da069f8f2e0235970fa3e1cbcc2cf9a2da9ab75bd965f00e7ab3c7ab/socketdev-3.0.28.tar.gz", hash = "sha256:60f8635502fa291a9bf4fb0f2a13c8dfeb1d3dc2dc395ec5265a1e2f4147cf52", size = 170539, upload-time = "2026-01-05T22:24:53.637Z" }
1260
1260
  wheels = [
1261
- { url = "https://files.pythonhosted.org/packages/cf/1f/49ca3bfa137921899f050b030af072b5679dba3f72f0162cacd5910a8a79/socketdev-3.0.25-py3-none-any.whl", hash = "sha256:a23f76f1536a6c7473c54608e57e33735d01cd287d8f2ae75a5378f2ddedf7e5", size = 65711, upload-time = "2026-01-01T21:58:39Z" },
1261
+ { url = "https://files.pythonhosted.org/packages/bc/72/4bbd4a70e5fa317f4e33a134f5871793eeaba45e05b949685f3502e451b4/socketdev-3.0.28-py3-none-any.whl", hash = "sha256:61a9e09ab24180e7b033ad1e141110b90f1f714336b04f21c20035b901311229", size = 66681, upload-time = "2026-01-05T22:24:51.736Z" },
1262
1262
  ]
1263
1263
 
1264
1264
  [[package]]
1265
1265
  name = "socketsecurity"
1266
- version = "2.2.59"
1266
+ version = "2.2.62"
1267
1267
  source = { editable = "." }
1268
1268
  dependencies = [
1269
1269
  { name = "bs4" },
@@ -1316,7 +1316,7 @@ requires-dist = [
1316
1316
  { name = "python-dotenv" },
1317
1317
  { name = "requests" },
1318
1318
  { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.3.0" },
1319
- { name = "socketdev", specifier = ">=3.0.25,<4.0.0" },
1319
+ { name = "socketdev", specifier = ">=3.0.28,<4.0.0" },
1320
1320
  { name = "twine", marker = "extra == 'dev'" },
1321
1321
  { name = "uv", marker = "extra == 'dev'", specifier = ">=0.1.0" },
1322
1322
  ]
File without changes