cycode 3.15.3.dev8__py3-none-any.whl → 3.15.4.dev2__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.
- cycode/__init__.py +1 -1
- cycode/cli/apps/ai_guardrails/command_utils.py +2 -45
- cycode/cli/apps/ai_guardrails/consts.py +3 -135
- cycode/cli/apps/ai_guardrails/hooks_manager.py +123 -152
- cycode/cli/apps/ai_guardrails/ides/__init__.py +45 -0
- cycode/cli/apps/ai_guardrails/ides/_plugin_utils.py +73 -0
- cycode/cli/apps/ai_guardrails/ides/base.py +176 -0
- cycode/cli/apps/ai_guardrails/ides/claude_code.py +369 -0
- cycode/cli/apps/ai_guardrails/ides/codex.py +310 -0
- cycode/cli/apps/ai_guardrails/ides/cursor.py +119 -0
- cycode/cli/apps/ai_guardrails/install_command.py +14 -23
- cycode/cli/apps/ai_guardrails/scan/handlers.py +102 -101
- cycode/cli/apps/ai_guardrails/scan/payload.py +14 -255
- cycode/cli/apps/ai_guardrails/scan/scan_command.py +60 -48
- cycode/cli/apps/ai_guardrails/scan/types.py +8 -30
- cycode/cli/apps/ai_guardrails/session_start_command.py +14 -78
- cycode/cli/apps/ai_guardrails/status_command.py +13 -16
- cycode/cli/apps/ai_guardrails/uninstall_command.py +12 -22
- cycode/cli/utils/jwt_utils.py +8 -0
- {cycode-3.15.3.dev8.dist-info → cycode-3.15.4.dev2.dist-info}/METADATA +3 -1
- {cycode-3.15.3.dev8.dist-info → cycode-3.15.4.dev2.dist-info}/RECORD +24 -21
- cycode/cli/apps/ai_guardrails/scan/claude_config.py +0 -159
- cycode/cli/apps/ai_guardrails/scan/cursor_config.py +0 -36
- cycode/cli/apps/ai_guardrails/scan/response_builders.py +0 -135
- {cycode-3.15.3.dev8.dist-info → cycode-3.15.4.dev2.dist-info}/WHEEL +0 -0
- {cycode-3.15.3.dev8.dist-info → cycode-3.15.4.dev2.dist-info}/entry_points.txt +0 -0
- {cycode-3.15.3.dev8.dist-info → cycode-3.15.4.dev2.dist-info}/licenses/LICENCE +0 -0
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
1
|
+
"""Hook handlers for AI IDE events.
|
|
2
|
+
|
|
3
|
+
Each handler receives a unified payload and policy, applies the scan + policy
|
|
4
|
+
logic, and returns a canonical ``HookDecision``. ``scan_command`` translates
|
|
5
|
+
that decision into the IDE-specific JSON response via ``IDE.build_hook_response``.
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
|
|
7
|
+
Handlers are agent-agnostic by design — adding a new IDE doesn't require
|
|
8
|
+
touching any handler in this module.
|
|
6
9
|
"""
|
|
7
10
|
|
|
8
11
|
import json
|
|
9
12
|
import os
|
|
13
|
+
from dataclasses import dataclass
|
|
10
14
|
from multiprocessing.pool import ThreadPool
|
|
11
15
|
from multiprocessing.pool import TimeoutError as PoolTimeoutError
|
|
12
16
|
from typing import Callable, Optional
|
|
@@ -14,9 +18,9 @@ from typing import Callable, Optional
|
|
|
14
18
|
import typer
|
|
15
19
|
|
|
16
20
|
from cycode.cli.apps.ai_guardrails.consts import PolicyMode
|
|
21
|
+
from cycode.cli.apps.ai_guardrails.ides.base import HookDecision
|
|
17
22
|
from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload
|
|
18
23
|
from cycode.cli.apps.ai_guardrails.scan.policy import get_policy_value
|
|
19
|
-
from cycode.cli.apps.ai_guardrails.scan.response_builders import get_response_builder
|
|
20
24
|
from cycode.cli.apps.ai_guardrails.scan.types import AiHookEventType, AIHookOutcome, BlockReason
|
|
21
25
|
from cycode.cli.apps.ai_guardrails.scan.utils import is_denied_path, truncate_utf8
|
|
22
26
|
from cycode.cli.apps.scan.code_scanner import _get_scan_documents_thread_func
|
|
@@ -30,21 +34,17 @@ from cycode.logger import get_logger
|
|
|
30
34
|
logger = get_logger('AI Guardrails')
|
|
31
35
|
|
|
32
36
|
|
|
33
|
-
|
|
34
|
-
"""
|
|
35
|
-
Handle beforeSubmitPrompt hook.
|
|
37
|
+
HandlerFn = Callable[[typer.Context, AIHookPayload, dict], HookDecision]
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"""
|
|
39
|
+
|
|
40
|
+
def handle_before_submit_prompt(ctx: typer.Context, payload: AIHookPayload, policy: dict) -> HookDecision:
|
|
41
|
+
"""Scan prompt text for secrets before it's sent to the AI model."""
|
|
40
42
|
ai_client = ctx.obj['ai_security_client']
|
|
41
|
-
ide = payload.ide_provider
|
|
42
|
-
response_builder = get_response_builder(ide)
|
|
43
43
|
|
|
44
44
|
prompt_config = get_policy_value(policy, 'prompt', default={})
|
|
45
45
|
if not get_policy_value(prompt_config, 'enabled', default=True):
|
|
46
46
|
ai_client.create_event(payload, AiHookEventType.PROMPT, AIHookOutcome.ALLOWED)
|
|
47
|
-
return
|
|
47
|
+
return HookDecision.allow(AiHookEventType.PROMPT)
|
|
48
48
|
|
|
49
49
|
mode = get_policy_value(policy, 'mode', default=PolicyMode.BLOCK)
|
|
50
50
|
prompt = payload.prompt or ''
|
|
@@ -66,9 +66,9 @@ def handle_before_submit_prompt(ctx: typer.Context, payload: AIHookPayload, poli
|
|
|
66
66
|
if action == PolicyMode.BLOCK and mode == PolicyMode.BLOCK:
|
|
67
67
|
outcome = AIHookOutcome.BLOCKED
|
|
68
68
|
user_message = f'{violation_summary}. Remove secrets before sending.'
|
|
69
|
-
return
|
|
69
|
+
return HookDecision.deny(AiHookEventType.PROMPT, user_message)
|
|
70
70
|
outcome = AIHookOutcome.WARNED
|
|
71
|
-
return
|
|
71
|
+
return HookDecision.allow(AiHookEventType.PROMPT)
|
|
72
72
|
except Exception as e:
|
|
73
73
|
outcome = (
|
|
74
74
|
AIHookOutcome.ALLOWED if get_policy_value(policy, 'fail_open', default=True) else AIHookOutcome.BLOCKED
|
|
@@ -87,21 +87,14 @@ def handle_before_submit_prompt(ctx: typer.Context, payload: AIHookPayload, poli
|
|
|
87
87
|
)
|
|
88
88
|
|
|
89
89
|
|
|
90
|
-
def handle_before_read_file(ctx: typer.Context, payload: AIHookPayload, policy: dict) ->
|
|
91
|
-
"""
|
|
92
|
-
Handle beforeReadFile hook.
|
|
93
|
-
|
|
94
|
-
Blocks sensitive files (via deny_globs) and scans file content for secrets.
|
|
95
|
-
Returns {"permission": "deny"} to block, {"permission": "allow"} to allow.
|
|
96
|
-
"""
|
|
90
|
+
def handle_before_read_file(ctx: typer.Context, payload: AIHookPayload, policy: dict) -> HookDecision:
|
|
91
|
+
"""Block sensitive paths and scan file content for secrets."""
|
|
97
92
|
ai_client = ctx.obj['ai_security_client']
|
|
98
|
-
ide = payload.ide_provider
|
|
99
|
-
response_builder = get_response_builder(ide)
|
|
100
93
|
|
|
101
94
|
file_read_config = get_policy_value(policy, 'file_read', default={})
|
|
102
95
|
if not get_policy_value(file_read_config, 'enabled', default=True):
|
|
103
96
|
ai_client.create_event(payload, AiHookEventType.FILE_READ, AIHookOutcome.ALLOWED)
|
|
104
|
-
return
|
|
97
|
+
return HookDecision.allow(AiHookEventType.FILE_READ)
|
|
105
98
|
|
|
106
99
|
mode = get_policy_value(policy, 'mode', default=PolicyMode.BLOCK)
|
|
107
100
|
file_path = payload.file_path or ''
|
|
@@ -113,20 +106,19 @@ def handle_before_read_file(ctx: typer.Context, payload: AIHookPayload, policy:
|
|
|
113
106
|
error_message = None
|
|
114
107
|
|
|
115
108
|
try:
|
|
116
|
-
# Check path-based denylist first
|
|
117
109
|
is_sensitive_path = is_denied_path(file_path, policy)
|
|
118
110
|
if is_sensitive_path:
|
|
119
111
|
block_reason = BlockReason.SENSITIVE_PATH
|
|
120
112
|
if mode == PolicyMode.BLOCK and action == PolicyMode.BLOCK:
|
|
121
113
|
outcome = AIHookOutcome.BLOCKED
|
|
122
114
|
user_message = f'Cycode blocked sending {file_path} to the AI (sensitive path policy).'
|
|
123
|
-
return
|
|
115
|
+
return HookDecision.deny(
|
|
116
|
+
AiHookEventType.FILE_READ,
|
|
124
117
|
user_message,
|
|
125
118
|
'This file path is classified as sensitive; do not read/send it to the model.',
|
|
126
119
|
)
|
|
127
|
-
# Warn mode
|
|
120
|
+
# Warn mode: if content scan is enabled, emit a separate event for the
|
|
128
121
|
# sensitive path so the finally block can independently track the scan result.
|
|
129
|
-
# If content scan is disabled, a single event (from finally) is enough.
|
|
130
122
|
outcome = AIHookOutcome.WARNED
|
|
131
123
|
if get_policy_value(file_read_config, 'scan_content', default=True):
|
|
132
124
|
ai_client.create_event(
|
|
@@ -136,11 +128,9 @@ def handle_before_read_file(ctx: typer.Context, payload: AIHookPayload, policy:
|
|
|
136
128
|
block_reason=BlockReason.SENSITIVE_PATH,
|
|
137
129
|
file_path=payload.file_path,
|
|
138
130
|
)
|
|
139
|
-
# Reset for the content scan result tracked by the finally block
|
|
140
131
|
block_reason = None
|
|
141
132
|
outcome = AIHookOutcome.ALLOWED
|
|
142
133
|
|
|
143
|
-
# Scan file content if enabled
|
|
144
134
|
if get_policy_value(file_read_config, 'scan_content', default=True):
|
|
145
135
|
violation_summary, scan_id = _scan_path_for_secrets(ctx, file_path, policy)
|
|
146
136
|
if violation_summary:
|
|
@@ -148,27 +138,28 @@ def handle_before_read_file(ctx: typer.Context, payload: AIHookPayload, policy:
|
|
|
148
138
|
if mode == PolicyMode.BLOCK and action == PolicyMode.BLOCK:
|
|
149
139
|
outcome = AIHookOutcome.BLOCKED
|
|
150
140
|
user_message = f'Cycode blocked reading {file_path}. {violation_summary}'
|
|
151
|
-
return
|
|
141
|
+
return HookDecision.deny(
|
|
142
|
+
AiHookEventType.FILE_READ,
|
|
152
143
|
user_message,
|
|
153
144
|
'Secrets detected; do not send this file to the model.',
|
|
154
145
|
)
|
|
155
|
-
# Warn mode - ask user for permission
|
|
156
146
|
outcome = AIHookOutcome.WARNED
|
|
157
147
|
user_message = f'Cycode detected secrets in {file_path}. {violation_summary}'
|
|
158
|
-
return
|
|
148
|
+
return HookDecision.ask(
|
|
149
|
+
AiHookEventType.FILE_READ,
|
|
159
150
|
user_message,
|
|
160
151
|
'Possible secrets detected; proceed with caution.',
|
|
161
152
|
)
|
|
162
153
|
|
|
163
|
-
# If path was sensitive but content scan found no secrets (or scan disabled), still warn
|
|
164
154
|
if is_sensitive_path:
|
|
165
155
|
user_message = f'Cycode flagged {file_path} as sensitive. Allow reading?'
|
|
166
|
-
return
|
|
156
|
+
return HookDecision.ask(
|
|
157
|
+
AiHookEventType.FILE_READ,
|
|
167
158
|
user_message,
|
|
168
159
|
'This file path is classified as sensitive; proceed with caution.',
|
|
169
160
|
)
|
|
170
161
|
|
|
171
|
-
return
|
|
162
|
+
return HookDecision.allow(AiHookEventType.FILE_READ)
|
|
172
163
|
except Exception as e:
|
|
173
164
|
outcome = (
|
|
174
165
|
AIHookOutcome.ALLOWED if get_policy_value(policy, 'fail_open', default=True) else AIHookOutcome.BLOCKED
|
|
@@ -188,31 +179,44 @@ def handle_before_read_file(ctx: typer.Context, payload: AIHookPayload, policy:
|
|
|
188
179
|
)
|
|
189
180
|
|
|
190
181
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
182
|
+
@dataclass(frozen=True)
|
|
183
|
+
class _ArgScanFeature:
|
|
184
|
+
"""Configuration for a "scan some text and decide" event.
|
|
194
185
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
{"permission": "allow"} to allow.
|
|
186
|
+
MCP execution and command exec share identical scan-and-decide logic;
|
|
187
|
+
only the policy key, event type, and user-facing messages differ.
|
|
198
188
|
"""
|
|
189
|
+
|
|
190
|
+
policy_key: str # 'mcp' or 'command_exec'
|
|
191
|
+
scan_key: str # 'scan_arguments' or 'scan_command'
|
|
192
|
+
event_type: AiHookEventType
|
|
193
|
+
block_reason: BlockReason
|
|
194
|
+
deny_message: Callable[[str], str]
|
|
195
|
+
deny_agent_message: str
|
|
196
|
+
ask_message: Callable[[str], str]
|
|
197
|
+
ask_agent_message: str
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _handle_arg_scan(
|
|
201
|
+
ctx: typer.Context,
|
|
202
|
+
payload: AIHookPayload,
|
|
203
|
+
policy: dict,
|
|
204
|
+
feature: _ArgScanFeature,
|
|
205
|
+
scan_text: str,
|
|
206
|
+
) -> HookDecision:
|
|
207
|
+
"""Shared scan + decision flow for MCP_EXECUTION and COMMAND_EXEC events."""
|
|
199
208
|
ai_client = ctx.obj['ai_security_client']
|
|
200
|
-
ide = payload.ide_provider
|
|
201
|
-
response_builder = get_response_builder(ide)
|
|
202
209
|
|
|
203
|
-
|
|
204
|
-
if not get_policy_value(
|
|
205
|
-
ai_client.create_event(payload,
|
|
206
|
-
return
|
|
210
|
+
feature_config = get_policy_value(policy, feature.policy_key, default={})
|
|
211
|
+
if not get_policy_value(feature_config, 'enabled', default=True):
|
|
212
|
+
ai_client.create_event(payload, feature.event_type, AIHookOutcome.ALLOWED)
|
|
213
|
+
return HookDecision.allow(feature.event_type)
|
|
207
214
|
|
|
208
215
|
mode = get_policy_value(policy, 'mode', default=PolicyMode.BLOCK)
|
|
209
|
-
tool = payload.mcp_tool_name or 'unknown'
|
|
210
|
-
args = payload.mcp_arguments or {}
|
|
211
|
-
args_text = args if isinstance(args, str) else json.dumps(args)
|
|
212
216
|
max_bytes = get_policy_value(policy, 'secrets', 'max_bytes', default=200000)
|
|
213
217
|
timeout_ms = get_policy_value(policy, 'secrets', 'timeout_ms', default=30000)
|
|
214
|
-
clipped = truncate_utf8(
|
|
215
|
-
action = get_policy_value(
|
|
218
|
+
clipped = truncate_utf8(scan_text, max_bytes)
|
|
219
|
+
action = get_policy_value(feature_config, 'action', default=PolicyMode.BLOCK)
|
|
216
220
|
|
|
217
221
|
scan_id = None
|
|
218
222
|
block_reason = None
|
|
@@ -220,24 +224,25 @@ def handle_before_mcp_execution(ctx: typer.Context, payload: AIHookPayload, poli
|
|
|
220
224
|
error_message = None
|
|
221
225
|
|
|
222
226
|
try:
|
|
223
|
-
if get_policy_value(
|
|
227
|
+
if get_policy_value(feature_config, feature.scan_key, default=True):
|
|
224
228
|
violation_summary, scan_id = _scan_text_for_secrets(ctx, clipped, timeout_ms)
|
|
225
229
|
if violation_summary:
|
|
226
|
-
block_reason =
|
|
230
|
+
block_reason = feature.block_reason
|
|
227
231
|
if mode == PolicyMode.BLOCK and action == PolicyMode.BLOCK:
|
|
228
232
|
outcome = AIHookOutcome.BLOCKED
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
+
return HookDecision.deny(
|
|
234
|
+
feature.event_type,
|
|
235
|
+
feature.deny_message(violation_summary),
|
|
236
|
+
feature.deny_agent_message,
|
|
233
237
|
)
|
|
234
238
|
outcome = AIHookOutcome.WARNED
|
|
235
|
-
return
|
|
236
|
-
|
|
237
|
-
|
|
239
|
+
return HookDecision.ask(
|
|
240
|
+
feature.event_type,
|
|
241
|
+
feature.ask_message(violation_summary),
|
|
242
|
+
feature.ask_agent_message,
|
|
238
243
|
)
|
|
239
244
|
|
|
240
|
-
return
|
|
245
|
+
return HookDecision.allow(feature.event_type)
|
|
241
246
|
except Exception as e:
|
|
242
247
|
outcome = (
|
|
243
248
|
AIHookOutcome.ALLOWED if get_policy_value(policy, 'fail_open', default=True) else AIHookOutcome.BLOCKED
|
|
@@ -248,7 +253,7 @@ def handle_before_mcp_execution(ctx: typer.Context, payload: AIHookPayload, poli
|
|
|
248
253
|
finally:
|
|
249
254
|
ai_client.create_event(
|
|
250
255
|
payload,
|
|
251
|
-
|
|
256
|
+
feature.event_type,
|
|
252
257
|
outcome,
|
|
253
258
|
scan_id=scan_id,
|
|
254
259
|
block_reason=block_reason,
|
|
@@ -256,16 +261,32 @@ def handle_before_mcp_execution(ctx: typer.Context, payload: AIHookPayload, poli
|
|
|
256
261
|
)
|
|
257
262
|
|
|
258
263
|
|
|
259
|
-
def
|
|
260
|
-
"""
|
|
264
|
+
def handle_before_mcp_execution(ctx: typer.Context, payload: AIHookPayload, policy: dict) -> HookDecision:
|
|
265
|
+
"""Scan MCP tool arguments for secrets before execution."""
|
|
266
|
+
tool = payload.mcp_tool_name or 'unknown'
|
|
267
|
+
args = payload.mcp_arguments or {}
|
|
268
|
+
args_text = args if isinstance(args, str) else json.dumps(args)
|
|
269
|
+
return _handle_arg_scan(
|
|
270
|
+
ctx,
|
|
271
|
+
payload,
|
|
272
|
+
policy,
|
|
273
|
+
_ArgScanFeature(
|
|
274
|
+
policy_key='mcp',
|
|
275
|
+
scan_key='scan_arguments',
|
|
276
|
+
event_type=AiHookEventType.MCP_EXECUTION,
|
|
277
|
+
block_reason=BlockReason.SECRETS_IN_MCP_ARGS,
|
|
278
|
+
deny_message=lambda v: f'Cycode blocked MCP tool call "{tool}". {v}',
|
|
279
|
+
deny_agent_message='Do not pass secrets to tools. Use secret references (name/id) instead.',
|
|
280
|
+
ask_message=lambda v: f'{v} in MCP tool call "{tool}". Allow execution?',
|
|
281
|
+
ask_agent_message='Possible secrets detected in tool arguments; proceed with caution.',
|
|
282
|
+
),
|
|
283
|
+
scan_text=args_text,
|
|
284
|
+
)
|
|
261
285
|
|
|
262
|
-
Args:
|
|
263
|
-
event_type: Canonical event type string (from AiHookEventType enum)
|
|
264
286
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
handlers = {
|
|
287
|
+
def get_handler_for_event(event_type: str) -> Optional[HandlerFn]:
|
|
288
|
+
"""Look up the handler for a canonical event type."""
|
|
289
|
+
handlers: dict[str, HandlerFn] = {
|
|
269
290
|
AiHookEventType.PROMPT.value: handle_before_submit_prompt,
|
|
270
291
|
AiHookEventType.FILE_READ.value: handle_before_read_file,
|
|
271
292
|
AiHookEventType.MCP_EXECUTION.value: handle_before_mcp_execution,
|
|
@@ -275,32 +296,24 @@ def get_handler_for_event(event_type: str) -> Optional[Callable[[typer.Context,
|
|
|
275
296
|
|
|
276
297
|
def _setup_scan_context(ctx: typer.Context) -> typer.Context:
|
|
277
298
|
"""Set up minimal context for scan_documents without progress bars or printing."""
|
|
278
|
-
|
|
279
|
-
# Set up minimal required context
|
|
280
299
|
ctx.obj['progress_bar'] = DummyProgressBar([ScanProgressBarSection])
|
|
281
|
-
ctx.obj['sync'] = True
|
|
282
|
-
ctx.obj['scan_type'] = ScanTypeOption.SECRET
|
|
283
|
-
ctx.obj['severity_threshold'] = SeverityOption.INFO
|
|
284
|
-
|
|
285
|
-
# Set command name for scan logic
|
|
300
|
+
ctx.obj['sync'] = True
|
|
301
|
+
ctx.obj['scan_type'] = ScanTypeOption.SECRET
|
|
302
|
+
ctx.obj['severity_threshold'] = SeverityOption.INFO
|
|
286
303
|
ctx.info_name = 'ai_guardrails'
|
|
287
|
-
|
|
288
304
|
return ctx
|
|
289
305
|
|
|
290
306
|
|
|
291
307
|
def _perform_scan(
|
|
292
308
|
ctx: typer.Context, documents: list[Document], scan_parameters: dict, timeout_seconds: float
|
|
293
309
|
) -> tuple[Optional[str], Optional[str]]:
|
|
294
|
-
"""
|
|
295
|
-
Perform a scan on documents and extract results.
|
|
310
|
+
"""Run a scan on documents, returning (violation_summary, scan_id).
|
|
296
311
|
|
|
297
|
-
|
|
298
|
-
Raises exception if scan fails or times out (triggers fail_open policy).
|
|
312
|
+
Raises on scan failure / timeout so the fail-open policy can take over.
|
|
299
313
|
"""
|
|
300
314
|
if not documents:
|
|
301
315
|
return None, None
|
|
302
316
|
|
|
303
|
-
# Get the thread function for scanning
|
|
304
317
|
scan_batch_thread_func = _get_scan_documents_thread_func(
|
|
305
318
|
ctx, is_git_diff=False, is_commit_range=False, scan_parameters=scan_parameters
|
|
306
319
|
)
|
|
@@ -324,7 +337,6 @@ def _perform_scan(
|
|
|
324
337
|
|
|
325
338
|
scan_id = local_scan_result.scan_id
|
|
326
339
|
|
|
327
|
-
# Check if there are any detections
|
|
328
340
|
if local_scan_result.detections_count > 0:
|
|
329
341
|
violation_summary = build_violation_summary([local_scan_result])
|
|
330
342
|
return violation_summary, scan_id
|
|
@@ -333,12 +345,7 @@ def _perform_scan(
|
|
|
333
345
|
|
|
334
346
|
|
|
335
347
|
def _scan_text_for_secrets(ctx: typer.Context, text: str, timeout_ms: int) -> tuple[Optional[str], Optional[str]]:
|
|
336
|
-
"""
|
|
337
|
-
Scan text content for secrets using Cycode CLI.
|
|
338
|
-
|
|
339
|
-
Returns tuple of (violation_summary, scan_id) if secrets found, (None, scan_id) if clean.
|
|
340
|
-
Raises exception on error or timeout.
|
|
341
|
-
"""
|
|
348
|
+
"""Scan text content for secrets using Cycode CLI."""
|
|
342
349
|
if not text:
|
|
343
350
|
return None, None
|
|
344
351
|
|
|
@@ -349,12 +356,7 @@ def _scan_text_for_secrets(ctx: typer.Context, text: str, timeout_ms: int) -> tu
|
|
|
349
356
|
|
|
350
357
|
|
|
351
358
|
def _scan_path_for_secrets(ctx: typer.Context, file_path: str, policy: dict) -> tuple[Optional[str], Optional[str]]:
|
|
352
|
-
"""
|
|
353
|
-
Scan a file path for secrets.
|
|
354
|
-
|
|
355
|
-
Returns tuple of (violation_summary, scan_id) if secrets found, (None, scan_id) if clean.
|
|
356
|
-
Raises exception on error or timeout.
|
|
357
|
-
"""
|
|
359
|
+
"""Scan a file path for secrets."""
|
|
358
360
|
if not file_path or not os.path.isfile(file_path):
|
|
359
361
|
return None, None
|
|
360
362
|
|
|
@@ -363,7 +365,6 @@ def _scan_path_for_secrets(ctx: typer.Context, file_path: str, policy: dict) ->
|
|
|
363
365
|
with open(file_path, encoding='utf-8', errors='replace') as f:
|
|
364
366
|
content = f.read(max_bytes)
|
|
365
367
|
|
|
366
|
-
# Get timeout from policy
|
|
367
368
|
timeout_ms = get_policy_value(policy, 'secrets', 'timeout_ms', default=30000)
|
|
368
369
|
timeout_seconds = timeout_ms / 1000.0
|
|
369
370
|
|