cycode 3.15.3.dev7__py3-none-any.whl → 3.15.4.dev1__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 +51 -138
- cycode/cli/apps/ai_guardrails/ides/__init__.py +44 -0
- cycode/cli/apps/ai_guardrails/ides/base.py +156 -0
- cycode/cli/apps/ai_guardrails/ides/claude_code.py +389 -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 +46 -89
- 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-3.15.3.dev7.dist-info → cycode-3.15.4.dev1.dist-info}/METADATA +36 -1
- {cycode-3.15.3.dev7.dist-info → cycode-3.15.4.dev1.dist-info}/RECORD +21 -20
- 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.dev7.dist-info → cycode-3.15.4.dev1.dist-info}/WHEEL +0 -0
- {cycode-3.15.3.dev7.dist-info → cycode-3.15.4.dev1.dist-info}/entry_points.txt +0 -0
- {cycode-3.15.3.dev7.dist-info → cycode-3.15.4.dev1.dist-info}/licenses/LICENCE +0 -0
|
@@ -1,26 +1,22 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Scan command for AI guardrails.
|
|
1
|
+
"""Scan command for AI guardrails IDE hooks.
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
for secrets before they are sent to AI models.
|
|
3
|
+
Reads a JSON payload from stdin, routes it through the IDE-specific parser and
|
|
4
|
+
the shared event handlers, then writes an IDE-specific JSON response to stdout.
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
beforeReadFile, beforeMCPExecution).
|
|
6
|
+
The handlers in ``handlers.py`` are agent-agnostic (they return
|
|
7
|
+
``HookDecision``); ``IDE.build_hook_response`` is the per-IDE translation step.
|
|
11
8
|
"""
|
|
12
9
|
|
|
13
10
|
import sys
|
|
14
|
-
from typing import Annotated
|
|
11
|
+
from typing import Annotated, Optional, Union
|
|
15
12
|
|
|
16
13
|
import click
|
|
17
14
|
import typer
|
|
18
15
|
|
|
19
|
-
from cycode.cli.apps.ai_guardrails.
|
|
16
|
+
from cycode.cli.apps.ai_guardrails.ides import DEFAULT_IDE_NAME, get_ide
|
|
17
|
+
from cycode.cli.apps.ai_guardrails.ides.base import HookDecision
|
|
20
18
|
from cycode.cli.apps.ai_guardrails.scan.handlers import get_handler_for_event
|
|
21
|
-
from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload
|
|
22
19
|
from cycode.cli.apps.ai_guardrails.scan.policy import load_policy
|
|
23
|
-
from cycode.cli.apps.ai_guardrails.scan.response_builders import get_response_builder
|
|
24
20
|
from cycode.cli.apps.ai_guardrails.scan.types import AiHookEventType
|
|
25
21
|
from cycode.cli.apps.ai_guardrails.scan.utils import output_json, safe_json_parse
|
|
26
22
|
from cycode.cli.exceptions.custom_exceptions import HttpUnauthorizedError
|
|
@@ -31,7 +27,7 @@ logger = get_logger('AI Guardrails')
|
|
|
31
27
|
|
|
32
28
|
|
|
33
29
|
def _get_auth_error_message(error: Exception) -> str:
|
|
34
|
-
"""
|
|
30
|
+
"""User-friendly message for authentication errors."""
|
|
35
31
|
if isinstance(error, click.ClickException):
|
|
36
32
|
# Missing credentials
|
|
37
33
|
return f'{error.message} Please run `cycode auth` to set up your credentials.'
|
|
@@ -47,6 +43,23 @@ def _get_auth_error_message(error: Exception) -> str:
|
|
|
47
43
|
return 'Authentication failed. Please run `cycode auth` to set up your credentials.'
|
|
48
44
|
|
|
49
45
|
|
|
46
|
+
def _deny_for_event(
|
|
47
|
+
event_name: Optional[Union[str, AiHookEventType]],
|
|
48
|
+
user_message: str,
|
|
49
|
+
agent_message: Optional[str] = None,
|
|
50
|
+
) -> HookDecision:
|
|
51
|
+
"""Build a deny decision matched to ``event_name``'s response shape.
|
|
52
|
+
|
|
53
|
+
PROMPT events use the prompt-block shape (no agent_message). For anything
|
|
54
|
+
else — including unknown event names — fall back to FILE_READ since
|
|
55
|
+
FILE_READ and MCP_EXECUTION share the same response shape on both IDEs.
|
|
56
|
+
"""
|
|
57
|
+
if event_name == AiHookEventType.PROMPT:
|
|
58
|
+
return HookDecision.deny(AiHookEventType.PROMPT, user_message)
|
|
59
|
+
target = event_name if isinstance(event_name, AiHookEventType) else AiHookEventType.FILE_READ
|
|
60
|
+
return HookDecision.deny(target, user_message, agent_message)
|
|
61
|
+
|
|
62
|
+
|
|
50
63
|
def _initialize_clients(ctx: typer.Context) -> None:
|
|
51
64
|
"""Initialize API clients.
|
|
52
65
|
|
|
@@ -69,44 +82,36 @@ def scan_command(
|
|
|
69
82
|
help='IDE that sent the payload (e.g., "cursor"). Defaults to cursor.',
|
|
70
83
|
hidden=True,
|
|
71
84
|
),
|
|
72
|
-
] =
|
|
85
|
+
] = DEFAULT_IDE_NAME,
|
|
73
86
|
) -> None:
|
|
74
87
|
"""Scan content from AI IDE hooks for secrets.
|
|
75
88
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
The hook event type is determined from the event field in the payload (field name
|
|
80
|
-
varies by IDE). Each IDE may support different hook events for scanning prompts,
|
|
81
|
-
file access, and tool executions.
|
|
82
|
-
|
|
83
|
-
Example usage (from IDE hooks configuration):
|
|
84
|
-
{ "command": "cycode ai-guardrails scan" }
|
|
89
|
+
Reads a JSON payload from stdin and outputs a JSON response to stdout
|
|
90
|
+
indicating whether to allow or block the action.
|
|
85
91
|
"""
|
|
92
|
+
ide_integration = get_ide(ide)
|
|
93
|
+
|
|
86
94
|
stdin_data = sys.stdin.read().strip()
|
|
87
95
|
payload = safe_json_parse(stdin_data)
|
|
88
96
|
|
|
89
|
-
tool = ide.lower()
|
|
90
|
-
response_builder = get_response_builder(tool)
|
|
91
|
-
|
|
92
97
|
if not payload:
|
|
93
98
|
logger.debug('Empty or invalid JSON payload received')
|
|
94
|
-
output_json(
|
|
99
|
+
output_json(ide_integration.build_hook_response(HookDecision.allow(AiHookEventType.PROMPT)))
|
|
95
100
|
return
|
|
96
101
|
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
if not
|
|
102
|
+
# Prevent cross-IDE processing (e.g. Cursor reading Claude Code hooks
|
|
103
|
+
# from ~/.claude/settings.json).
|
|
104
|
+
if not ide_integration.matches_payload(payload):
|
|
100
105
|
logger.debug(
|
|
101
106
|
'Payload event does not match expected IDE, skipping',
|
|
102
|
-
extra={'hook_event_name': payload.get('hook_event_name'), 'expected_ide':
|
|
107
|
+
extra={'hook_event_name': payload.get('hook_event_name'), 'expected_ide': ide_integration.name},
|
|
103
108
|
)
|
|
104
|
-
output_json(
|
|
109
|
+
output_json(ide_integration.build_hook_response(HookDecision.allow(AiHookEventType.PROMPT)))
|
|
105
110
|
return
|
|
106
111
|
|
|
107
|
-
unified_payload =
|
|
112
|
+
unified_payload = ide_integration.parse_hook_payload(payload)
|
|
108
113
|
event_name = unified_payload.event_name
|
|
109
|
-
logger.debug('Processing AI guardrails hook', extra={'event_name': event_name, '
|
|
114
|
+
logger.debug('Processing AI guardrails hook', extra={'event_name': event_name, 'ide': ide_integration.name})
|
|
110
115
|
|
|
111
116
|
workspace_roots = payload.get('workspace_roots', ['.'])
|
|
112
117
|
policy = load_policy(workspace_roots[0])
|
|
@@ -117,26 +122,33 @@ def scan_command(
|
|
|
117
122
|
handler = get_handler_for_event(event_name)
|
|
118
123
|
if handler is None:
|
|
119
124
|
logger.debug('Unknown hook event, allowing by default', extra={'event_name': event_name})
|
|
120
|
-
output_json(
|
|
125
|
+
output_json(ide_integration.build_hook_response(HookDecision.allow(AiHookEventType.PROMPT)))
|
|
121
126
|
return
|
|
122
127
|
|
|
123
|
-
|
|
124
|
-
logger.debug('Hook handler completed', extra={'event_name': event_name, '
|
|
125
|
-
output_json(
|
|
128
|
+
decision = handler(ctx, unified_payload, policy)
|
|
129
|
+
logger.debug('Hook handler completed', extra={'event_name': event_name, 'action': decision.action.value})
|
|
130
|
+
output_json(ide_integration.build_hook_response(decision))
|
|
126
131
|
|
|
127
132
|
except (click.ClickException, HttpUnauthorizedError) as e:
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
output_json(
|
|
134
|
+
ide_integration.build_hook_response(
|
|
135
|
+
_deny_for_event(event_name, _get_auth_error_message(e), 'Authentication required')
|
|
136
|
+
)
|
|
137
|
+
)
|
|
133
138
|
|
|
134
139
|
except Exception as e:
|
|
135
140
|
logger.error('Hook handler failed', exc_info=e)
|
|
136
141
|
if policy.get('fail_open', True):
|
|
137
|
-
output_json(
|
|
138
|
-
return
|
|
139
|
-
if event_name == AiHookEventType.PROMPT:
|
|
140
|
-
output_json(response_builder.deny_prompt('Cycode guardrails error - blocking due to fail-closed policy'))
|
|
142
|
+
output_json(ide_integration.build_hook_response(HookDecision.allow(AiHookEventType.PROMPT)))
|
|
141
143
|
return
|
|
142
|
-
output_json(
|
|
144
|
+
output_json(
|
|
145
|
+
ide_integration.build_hook_response(
|
|
146
|
+
_deny_for_event(
|
|
147
|
+
event_name,
|
|
148
|
+
'Cycode guardrails error - blocking due to fail-closed policy'
|
|
149
|
+
if event_name == AiHookEventType.PROMPT
|
|
150
|
+
else 'Cycode guardrails error',
|
|
151
|
+
'Blocking due to fail-closed policy',
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
)
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Canonical event types and outcome enums for AI guardrails.
|
|
2
|
+
|
|
3
|
+
Per-IDE event-name mappings live on the IDE class (in
|
|
4
|
+
`cycode/cli/apps/ai_guardrails/ides/`); only the IDE-agnostic enums are kept
|
|
5
|
+
here.
|
|
6
|
+
"""
|
|
2
7
|
|
|
3
8
|
import sys
|
|
4
9
|
|
|
@@ -13,36 +18,13 @@ else:
|
|
|
13
18
|
|
|
14
19
|
|
|
15
20
|
class AiHookEventType(StrEnum):
|
|
16
|
-
"""Canonical event types
|
|
17
|
-
|
|
18
|
-
These are IDE-agnostic event types. Each IDE's specific event names
|
|
19
|
-
are mapped to these canonical types using the mapping dictionaries below.
|
|
20
|
-
"""
|
|
21
|
+
"""Canonical, IDE-agnostic hook event types."""
|
|
21
22
|
|
|
22
23
|
PROMPT = 'Prompt'
|
|
23
24
|
FILE_READ = 'FileRead'
|
|
24
25
|
MCP_EXECUTION = 'McpExecution'
|
|
25
26
|
|
|
26
27
|
|
|
27
|
-
# IDE-specific event name mappings to canonical types
|
|
28
|
-
CURSOR_EVENT_MAPPING = {
|
|
29
|
-
'beforeSubmitPrompt': AiHookEventType.PROMPT,
|
|
30
|
-
'beforeReadFile': AiHookEventType.FILE_READ,
|
|
31
|
-
'beforeMCPExecution': AiHookEventType.MCP_EXECUTION,
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
# Claude Code event mapping - note that PreToolUse requires tool_name inspection
|
|
35
|
-
# to determine the actual event type (file read vs MCP execution)
|
|
36
|
-
CLAUDE_CODE_EVENT_MAPPING = {
|
|
37
|
-
'UserPromptSubmit': AiHookEventType.PROMPT,
|
|
38
|
-
'PreToolUse': None, # Requires tool_name inspection to determine actual type
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
# Set of known event names per IDE (for IDE detection)
|
|
42
|
-
CURSOR_EVENT_NAMES = set(CURSOR_EVENT_MAPPING.keys())
|
|
43
|
-
CLAUDE_CODE_EVENT_NAMES = set(CLAUDE_CODE_EVENT_MAPPING.keys())
|
|
44
|
-
|
|
45
|
-
|
|
46
28
|
class AIHookOutcome(StrEnum):
|
|
47
29
|
"""Outcome of an AI hook event evaluation."""
|
|
48
30
|
|
|
@@ -52,11 +34,7 @@ class AIHookOutcome(StrEnum):
|
|
|
52
34
|
|
|
53
35
|
|
|
54
36
|
class BlockReason(StrEnum):
|
|
55
|
-
"""
|
|
56
|
-
|
|
57
|
-
These are categorical reasons sent to the backend for tracking/analytics,
|
|
58
|
-
separate from the detailed user-facing messages.
|
|
59
|
-
"""
|
|
37
|
+
"""Categorical reason for blocking (sent to backend for tracking)."""
|
|
60
38
|
|
|
61
39
|
SECRETS_IN_PROMPT = 'secrets_in_prompt'
|
|
62
40
|
SECRETS_IN_FILE = 'secrets_in_file'
|
|
@@ -1,18 +1,12 @@
|
|
|
1
|
+
"""Handle AI guardrails session start: auth, conversation creation, session context."""
|
|
2
|
+
|
|
1
3
|
import sys
|
|
2
4
|
from typing import TYPE_CHECKING, Annotated, Optional
|
|
3
5
|
|
|
4
6
|
import typer
|
|
5
7
|
|
|
6
|
-
from cycode.cli.apps.ai_guardrails.
|
|
7
|
-
from cycode.cli.apps.ai_guardrails.
|
|
8
|
-
get_mcp_servers,
|
|
9
|
-
get_user_email,
|
|
10
|
-
load_claude_config,
|
|
11
|
-
load_claude_settings,
|
|
12
|
-
resolve_plugins,
|
|
13
|
-
)
|
|
14
|
-
from cycode.cli.apps.ai_guardrails.scan.cursor_config import load_cursor_config
|
|
15
|
-
from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload, extract_from_claude_transcript
|
|
8
|
+
from cycode.cli.apps.ai_guardrails.ides import DEFAULT_IDE_NAME, get_ide
|
|
9
|
+
from cycode.cli.apps.ai_guardrails.ides.base import IDE
|
|
16
10
|
from cycode.cli.apps.ai_guardrails.scan.utils import safe_json_parse
|
|
17
11
|
from cycode.cli.apps.auth.auth_common import get_authorization_info
|
|
18
12
|
from cycode.cli.apps.auth.auth_manager import AuthManager
|
|
@@ -26,69 +20,10 @@ if TYPE_CHECKING:
|
|
|
26
20
|
logger = get_logger('AI Guardrails')
|
|
27
21
|
|
|
28
22
|
|
|
29
|
-
def
|
|
30
|
-
"""Build an AIHookPayload from a session-start stdin payload."""
|
|
31
|
-
if ide == AIIDEType.CLAUDE_CODE:
|
|
32
|
-
claude_config = load_claude_config()
|
|
33
|
-
ide_user_email = get_user_email(claude_config) if claude_config else None
|
|
34
|
-
ide_version, _, _ = extract_from_claude_transcript(payload.get('transcript_path'))
|
|
35
|
-
|
|
36
|
-
return AIHookPayload(
|
|
37
|
-
conversation_id=payload.get('session_id'),
|
|
38
|
-
ide_user_email=ide_user_email,
|
|
39
|
-
model=payload.get('model'),
|
|
40
|
-
ide_provider=AIIDEType.CLAUDE_CODE.value,
|
|
41
|
-
ide_version=ide_version,
|
|
42
|
-
source=payload.get('source'),
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
# Cursor
|
|
46
|
-
return AIHookPayload(
|
|
47
|
-
conversation_id=payload.get('conversation_id'),
|
|
48
|
-
ide_user_email=payload.get('user_email'),
|
|
49
|
-
model=payload.get('model'),
|
|
50
|
-
ide_provider=AIIDEType.CURSOR.value,
|
|
51
|
-
ide_version=payload.get('cursor_version'),
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def _get_claude_code_session_context() -> tuple[dict, dict]:
|
|
56
|
-
"""Return (mcp_servers, enabled_plugins) for Claude Code.
|
|
57
|
-
|
|
58
|
-
Merges MCP servers from ~/.claude.json (user-configured) with those contributed
|
|
59
|
-
by enabled plugins. Plugin metadata (name, version, description) is included in
|
|
60
|
-
the enabled_plugins dict when resolvable.
|
|
61
|
-
"""
|
|
62
|
-
config = load_claude_config()
|
|
63
|
-
mcp_servers = dict(get_mcp_servers(config) or {}) if config else {}
|
|
64
|
-
|
|
65
|
-
settings = load_claude_settings()
|
|
66
|
-
if settings:
|
|
67
|
-
plugin_mcp, enriched_plugins = resolve_plugins(settings)
|
|
68
|
-
mcp_servers.update(plugin_mcp)
|
|
69
|
-
else:
|
|
70
|
-
enriched_plugins = {}
|
|
71
|
-
|
|
72
|
-
return mcp_servers, enriched_plugins
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def _get_cursor_session_context() -> tuple[dict, dict]:
|
|
76
|
-
"""Return (mcp_servers, enabled_plugins) for Cursor. Cursor has no plugin system."""
|
|
77
|
-
config = load_cursor_config()
|
|
78
|
-
mcp_servers = dict(get_mcp_servers(config) or {}) if config else {}
|
|
79
|
-
return mcp_servers, {}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def _report_session_context(ai_client: 'AISecurityManagerClient', ide: str, user_email: Optional[str]) -> None:
|
|
23
|
+
def _report_session_context(ai_client: 'AISecurityManagerClient', ide: IDE, user_email: Optional[str]) -> None:
|
|
83
24
|
"""Report IDE session context to the AI security manager. Never raises."""
|
|
84
25
|
try:
|
|
85
|
-
|
|
86
|
-
mcp_servers, enabled_plugins = _get_claude_code_session_context()
|
|
87
|
-
elif ide == AIIDEType.CURSOR:
|
|
88
|
-
mcp_servers, enabled_plugins = _get_cursor_session_context()
|
|
89
|
-
else:
|
|
90
|
-
return
|
|
91
|
-
|
|
26
|
+
mcp_servers, enabled_plugins = ide.get_session_context()
|
|
92
27
|
if not mcp_servers and not enabled_plugins:
|
|
93
28
|
return
|
|
94
29
|
ai_client.report_session_context(
|
|
@@ -109,16 +44,17 @@ def session_start_command(
|
|
|
109
44
|
help='IDE that triggered the session start.',
|
|
110
45
|
hidden=True,
|
|
111
46
|
),
|
|
112
|
-
] =
|
|
47
|
+
] = DEFAULT_IDE_NAME,
|
|
113
48
|
) -> None:
|
|
114
49
|
"""Handle session start: ensure auth, create conversation, report session context."""
|
|
50
|
+
ide_integration = get_ide(ide)
|
|
51
|
+
|
|
115
52
|
# Step 1: Ensure authentication
|
|
116
53
|
auth_info = get_authorization_info(ctx)
|
|
117
54
|
if auth_info is None:
|
|
118
55
|
logger.debug('Not authenticated, starting authentication')
|
|
119
56
|
try:
|
|
120
|
-
|
|
121
|
-
auth_manager.authenticate()
|
|
57
|
+
AuthManager().authenticate()
|
|
122
58
|
except Exception as err:
|
|
123
59
|
handle_auth_exception(ctx, err)
|
|
124
60
|
return
|
|
@@ -136,8 +72,8 @@ def session_start_command(
|
|
|
136
72
|
logger.debug('Empty or invalid stdin payload, skipping session initialization')
|
|
137
73
|
return
|
|
138
74
|
|
|
139
|
-
# Step 3: Build session payload
|
|
140
|
-
session_payload =
|
|
75
|
+
# Step 3: Build session payload + initialize API client
|
|
76
|
+
session_payload = ide_integration.build_session_payload(payload)
|
|
141
77
|
|
|
142
78
|
try:
|
|
143
79
|
ai_client = get_ai_security_manager_client(ctx)
|
|
@@ -151,5 +87,5 @@ def session_start_command(
|
|
|
151
87
|
except Exception as e:
|
|
152
88
|
logger.debug('Failed to create conversation during session start', exc_info=e)
|
|
153
89
|
|
|
154
|
-
# Step 5: Report session context (MCP servers)
|
|
155
|
-
_report_session_context(ai_client,
|
|
90
|
+
# Step 5: Report session context (MCP servers, enabled plugins)
|
|
91
|
+
_report_session_context(ai_client, ide_integration, session_payload.ide_user_email)
|
|
@@ -7,9 +7,9 @@ from typing import Annotated, Optional
|
|
|
7
7
|
import typer
|
|
8
8
|
from rich.table import Table
|
|
9
9
|
|
|
10
|
-
from cycode.cli.apps.ai_guardrails.command_utils import console,
|
|
11
|
-
from cycode.cli.apps.ai_guardrails.consts import IDE_CONFIGS, AIIDEType
|
|
10
|
+
from cycode.cli.apps.ai_guardrails.command_utils import console, validate_scope
|
|
12
11
|
from cycode.cli.apps.ai_guardrails.hooks_manager import get_hooks_status
|
|
12
|
+
from cycode.cli.apps.ai_guardrails.ides import DEFAULT_IDE_NAME, IDES, resolve_ides
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def status_command(
|
|
@@ -26,9 +26,9 @@ def status_command(
|
|
|
26
26
|
str,
|
|
27
27
|
typer.Option(
|
|
28
28
|
'--ide',
|
|
29
|
-
help='IDE to check status for (
|
|
29
|
+
help=f'IDE to check status for ({", ".join(IDES)}, or "all").',
|
|
30
30
|
),
|
|
31
|
-
] =
|
|
31
|
+
] = DEFAULT_IDE_NAME,
|
|
32
32
|
repo_path: Annotated[
|
|
33
33
|
Optional[Path],
|
|
34
34
|
typer.Option(
|
|
@@ -43,32 +43,30 @@ def status_command(
|
|
|
43
43
|
) -> None:
|
|
44
44
|
"""Show AI guardrails hook installation status.
|
|
45
45
|
|
|
46
|
-
Displays the current status of Cycode AI guardrails hooks for the specified IDE.
|
|
47
|
-
|
|
48
46
|
Examples:
|
|
49
47
|
cycode ai-guardrails status # Show both user and repo status
|
|
50
48
|
cycode ai-guardrails status --scope user # Show only user-level status
|
|
51
49
|
cycode ai-guardrails status --scope repo # Show only repo-level status
|
|
52
|
-
cycode ai-guardrails status --ide
|
|
53
|
-
cycode ai-guardrails status --ide all # Check
|
|
50
|
+
cycode ai-guardrails status --ide claude-code
|
|
51
|
+
cycode ai-guardrails status --ide all # Check every supported IDE
|
|
54
52
|
"""
|
|
55
|
-
# Validate inputs (status allows 'all' scope)
|
|
56
53
|
validate_scope(scope, allowed_scopes=('user', 'repo', 'all'))
|
|
57
54
|
if repo_path is None:
|
|
58
55
|
repo_path = Path(os.getcwd())
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
ides_to_check: list[AIIDEType] = list(AIIDEType) if ide_type is None else [ide_type]
|
|
56
|
+
ides_to_check = resolve_ides(ide)
|
|
62
57
|
|
|
63
58
|
scopes_to_check = ['user', 'repo'] if scope == 'all' else [scope]
|
|
64
59
|
|
|
65
60
|
for current_ide in ides_to_check:
|
|
66
|
-
ide_name = IDE_CONFIGS[current_ide].name
|
|
67
61
|
console.print()
|
|
68
|
-
console.print(f'[bold cyan]═══ {
|
|
62
|
+
console.print(f'[bold cyan]═══ {current_ide.display_name} ═══[/]')
|
|
69
63
|
|
|
70
64
|
for check_scope in scopes_to_check:
|
|
71
|
-
status = get_hooks_status(
|
|
65
|
+
status = get_hooks_status(
|
|
66
|
+
current_ide,
|
|
67
|
+
check_scope,
|
|
68
|
+
repo_path if check_scope == 'repo' else None,
|
|
69
|
+
)
|
|
72
70
|
|
|
73
71
|
console.print()
|
|
74
72
|
console.print(f'[bold]{check_scope.upper()} SCOPE[/]')
|
|
@@ -83,7 +81,6 @@ def status_command(
|
|
|
83
81
|
else:
|
|
84
82
|
console.print('[yellow]○ Cycode AI guardrails: NOT INSTALLED[/]')
|
|
85
83
|
|
|
86
|
-
# Show hook details
|
|
87
84
|
table = Table(show_header=True, header_style='bold')
|
|
88
85
|
table.add_column('Hook Event')
|
|
89
86
|
table.add_column('Cycode Enabled')
|
|
@@ -5,14 +5,9 @@ from typing import Annotated, Optional
|
|
|
5
5
|
|
|
6
6
|
import typer
|
|
7
7
|
|
|
8
|
-
from cycode.cli.apps.ai_guardrails.command_utils import
|
|
9
|
-
console,
|
|
10
|
-
resolve_repo_path,
|
|
11
|
-
validate_and_parse_ide,
|
|
12
|
-
validate_scope,
|
|
13
|
-
)
|
|
14
|
-
from cycode.cli.apps.ai_guardrails.consts import IDE_CONFIGS, AIIDEType
|
|
8
|
+
from cycode.cli.apps.ai_guardrails.command_utils import console, resolve_repo_path, validate_scope
|
|
15
9
|
from cycode.cli.apps.ai_guardrails.hooks_manager import uninstall_hooks
|
|
10
|
+
from cycode.cli.apps.ai_guardrails.ides import DEFAULT_IDE_NAME, IDES, resolve_ides
|
|
16
11
|
|
|
17
12
|
|
|
18
13
|
def uninstall_command(
|
|
@@ -29,9 +24,9 @@ def uninstall_command(
|
|
|
29
24
|
str,
|
|
30
25
|
typer.Option(
|
|
31
26
|
'--ide',
|
|
32
|
-
help='IDE to uninstall hooks from (
|
|
27
|
+
help=f'IDE to uninstall hooks from ({", ".join(IDES)}, or "all").',
|
|
33
28
|
),
|
|
34
|
-
] =
|
|
29
|
+
] = DEFAULT_IDE_NAME,
|
|
35
30
|
repo_path: Annotated[
|
|
36
31
|
Optional[Path],
|
|
37
32
|
typer.Option(
|
|
@@ -46,32 +41,27 @@ def uninstall_command(
|
|
|
46
41
|
) -> None:
|
|
47
42
|
"""Remove AI guardrails hooks from supported IDEs.
|
|
48
43
|
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
Removes Cycode hooks from the IDE's hooks configuration. Other hooks
|
|
45
|
+
(if any) are preserved.
|
|
51
46
|
|
|
52
47
|
Examples:
|
|
53
48
|
cycode ai-guardrails uninstall # Remove user-level hooks
|
|
54
49
|
cycode ai-guardrails uninstall --scope repo # Remove repo-level hooks
|
|
55
|
-
cycode ai-guardrails uninstall --ide
|
|
56
|
-
cycode ai-guardrails uninstall --ide all # Uninstall from
|
|
50
|
+
cycode ai-guardrails uninstall --ide claude-code # Uninstall from a specific IDE
|
|
51
|
+
cycode ai-guardrails uninstall --ide all # Uninstall from every supported IDE
|
|
57
52
|
"""
|
|
58
|
-
# Validate inputs
|
|
59
53
|
validate_scope(scope)
|
|
60
54
|
repo_path = resolve_repo_path(scope, repo_path)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
ides_to_uninstall: list[AIIDEType] = list(AIIDEType) if ide_type is None else [ide_type]
|
|
55
|
+
ides_to_uninstall = resolve_ides(ide)
|
|
64
56
|
|
|
65
57
|
results: list[tuple[str, bool, str]] = []
|
|
66
58
|
for current_ide in ides_to_uninstall:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
results.append((ide_name, success, message))
|
|
59
|
+
success, message = uninstall_hooks(current_ide, scope, repo_path)
|
|
60
|
+
results.append((current_ide.display_name, success, message))
|
|
70
61
|
|
|
71
|
-
# Report results for each IDE
|
|
72
62
|
any_success = False
|
|
73
63
|
all_success = True
|
|
74
|
-
for
|
|
64
|
+
for _name, success, message in results:
|
|
75
65
|
if success:
|
|
76
66
|
console.print(f'[green]✓[/] {message}')
|
|
77
67
|
any_success = True
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cycode
|
|
3
|
-
Version: 3.15.
|
|
3
|
+
Version: 3.15.4.dev1
|
|
4
4
|
Summary: Boost security in your dev lifecycle via SAST, SCA, Secrets & IaC scanning.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENCE
|
|
@@ -62,6 +62,7 @@ This guide walks you through both installation and usage.
|
|
|
62
62
|
2. [Available Options](#available-options)
|
|
63
63
|
3. [MCP Tools](#mcp-tools)
|
|
64
64
|
4. [Usage Examples](#usage-examples)
|
|
65
|
+
5. [Advanced Configuration](#advanced-configuration)
|
|
65
66
|
5. [Platform Command](#platform-command-beta)
|
|
66
67
|
1. [Discovering Commands](#discovering-commands)
|
|
67
68
|
2. [Examples](#platform-examples)
|
|
@@ -600,6 +601,38 @@ cycode mcp -t streamable-http -H 127.0.0.2 -p 9000 &
|
|
|
600
601
|
}
|
|
601
602
|
```
|
|
602
603
|
|
|
604
|
+
### Advanced Configuration
|
|
605
|
+
##### Custom Certificates and Timeouts (Proxy Environments)
|
|
606
|
+
|
|
607
|
+
If your organization uses a corporate proxy or a custom CA bundle for HTTPS inspection, you need to tell Cycode CLI (and the underlying Python TLS stack) where to find the trusted certificate bundle. You can also increase the MCP tool call timeout if scans are being cut short.
|
|
608
|
+
|
|
609
|
+
| Environment Variable | Description |
|
|
610
|
+
|----------------------|-------------|
|
|
611
|
+
| `REQUESTS_CA_BUNDLE` | Path to a custom CA bundle file (`.pem` or `.crt`). Used by the `requests` library for all HTTPS calls made by Cycode CLI. |
|
|
612
|
+
| `SSL_CERT_FILE` | Path to a custom CA bundle file. Used by Python's low-level `ssl` module. Set this alongside `REQUESTS_CA_BUNDLE` for full coverage. |
|
|
613
|
+
| `MCP_TOOL_TIMEOUT` | Timeout (in seconds) that MCP clients such as Claude and GitHub Copilot wait for a tool call to complete. Increase this if long-running scans are being cut off before they finish. |
|
|
614
|
+
|
|
615
|
+
> [!TIP]
|
|
616
|
+
> Set both `REQUESTS_CA_BUNDLE` and `SSL_CERT_FILE` to the same CA bundle path. `REQUESTS_CA_BUNDLE` covers the HTTP layer; `SSL_CERT_FILE` covers the lower-level TLS layer. Using only one may still cause certificate errors in some environments.
|
|
617
|
+
|
|
618
|
+
Example `mcp.json` configuration with custom certificates and a longer timeout:
|
|
619
|
+
|
|
620
|
+
```json
|
|
621
|
+
{
|
|
622
|
+
"mcpServers": {
|
|
623
|
+
"cycode": {
|
|
624
|
+
"command": "cycode",
|
|
625
|
+
"args": ["mcp"],
|
|
626
|
+
"env": {
|
|
627
|
+
"REQUESTS_CA_BUNDLE": "/path/to/your/corporate-ca-bundle.pem",
|
|
628
|
+
"SSL_CERT_FILE": "/path/to/your/corporate-ca-bundle.pem",
|
|
629
|
+
"MCP_TOOL_TIMEOUT": "1800"
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
```
|
|
635
|
+
|
|
603
636
|
> [!NOTE]
|
|
604
637
|
> The MCP server requires proper Cycode CLI authentication to function. Make sure you have authenticated using `cycode auth` or configured your credentials before starting the MCP server.
|
|
605
638
|
|
|
@@ -649,6 +682,8 @@ This information can be helpful when:
|
|
|
649
682
|
- Identifying authentication problems
|
|
650
683
|
- Debugging transport-specific issues
|
|
651
684
|
|
|
685
|
+
### MCP Configuration
|
|
686
|
+
|
|
652
687
|
|
|
653
688
|
# Platform Command \[BETA\]
|
|
654
689
|
|
|
@@ -1,28 +1,29 @@
|
|
|
1
|
-
cycode/__init__.py,sha256=
|
|
1
|
+
cycode/__init__.py,sha256=UPgVpILY2bWvXK6Bul8uIehLpBu3njNwqn83H0Jz-wo,115
|
|
2
2
|
cycode/__main__.py,sha256=Z3bD5yrA7yPvAChcADQrqCaZd0ChGI1gdiwALwbWJ6U,104
|
|
3
3
|
cycode/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
cycode/cli/app.py,sha256=7ReEcVkRX9IaQ2I7jAj7Sl9smbtvxiuK8-9bitMEQik,7491
|
|
5
5
|
cycode/cli/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
cycode/cli/apps/activation_manager.py,sha256=Hz9PDJFB-ZmYi4HSG8iYC-fR8j5v25VuUU-l95Otsdk,1678
|
|
7
7
|
cycode/cli/apps/ai_guardrails/__init__.py,sha256=NsqB1Ca83BIjJMcDSt6suec6Ed0iNnacC0gBqkuuTtI,1367
|
|
8
|
-
cycode/cli/apps/ai_guardrails/command_utils.py,sha256=
|
|
9
|
-
cycode/cli/apps/ai_guardrails/consts.py,sha256=
|
|
10
|
-
cycode/cli/apps/ai_guardrails/hooks_manager.py,sha256=
|
|
11
|
-
cycode/cli/apps/ai_guardrails/
|
|
8
|
+
cycode/cli/apps/ai_guardrails/command_utils.py,sha256=NVwd0-2RGRKIqhsQ-4LNDR1D0gVm_o7n-z5LxG2bqAo,800
|
|
9
|
+
cycode/cli/apps/ai_guardrails/consts.py,sha256=Js2QtSNYG9Kt0eo3vepRd5TFciCeJHEC9NN18zqKvlE,620
|
|
10
|
+
cycode/cli/apps/ai_guardrails/hooks_manager.py,sha256=dXrVMD_4CcN5aZNZetgtajJab0g4lVGW5ktoS7ALxzc,6864
|
|
11
|
+
cycode/cli/apps/ai_guardrails/ides/__init__.py,sha256=1HgTyTmsX-JPGiYk1at7jbzlay6ASkKfA5RXo7K1_a8,1621
|
|
12
|
+
cycode/cli/apps/ai_guardrails/ides/base.py,sha256=_KzOlD_tTU0HcZ-NaXc6NZ0Tn0ngLxqovQtY_Pcj5H4,5752
|
|
13
|
+
cycode/cli/apps/ai_guardrails/ides/claude_code.py,sha256=YqgxV_kQ39LUlAA9fQbgQmWC7ALC1xMMaEVdsObZtEQ,14197
|
|
14
|
+
cycode/cli/apps/ai_guardrails/ides/cursor.py,sha256=MnzhEgYYukTx7XVdNu-u5yO_PzOrZNteTIIEf0mymYc,5237
|
|
15
|
+
cycode/cli/apps/ai_guardrails/install_command.py,sha256=vGZSIvHHVMS9_zhV_6lEhxqtmr5H6uykSq4AS5nxQYw,4278
|
|
12
16
|
cycode/cli/apps/ai_guardrails/scan/__init__.py,sha256=qJc82XiQGiAuc1sYY8Ij_A-qXpxgLPuayQq8xWlouMA,48
|
|
13
|
-
cycode/cli/apps/ai_guardrails/scan/claude_config.py,sha256=2hVuPHfT-9_kgf5yCNgN522IcporEZvJEyYTLaaae2c,5195
|
|
14
17
|
cycode/cli/apps/ai_guardrails/scan/consts.py,sha256=drAslw6vW3kxmbCs2qPCUbUPR7PJouT2lsXtu5sD-lQ,1094
|
|
15
|
-
cycode/cli/apps/ai_guardrails/scan/
|
|
16
|
-
cycode/cli/apps/ai_guardrails/scan/
|
|
17
|
-
cycode/cli/apps/ai_guardrails/scan/payload.py,sha256=WGvniNa0PuqgGoeNdpXGAYK6aJqOiaB38xNUYaL2CWk,10458
|
|
18
|
+
cycode/cli/apps/ai_guardrails/scan/handlers.py,sha256=7hnawiQhH8rNKQ0DrSyzcy41C9YYeW7LYOJA1QUpkOM,14218
|
|
19
|
+
cycode/cli/apps/ai_guardrails/scan/payload.py,sha256=pvT3UUqNMvdK3EVzzPjy4JMlOrF-WgxZ3fHN2AtN5eA,1126
|
|
18
20
|
cycode/cli/apps/ai_guardrails/scan/policy.py,sha256=39s8hnxgjny1l6XAO59wsRcAlpW-LG00GUnO0PfqvuY,2566
|
|
19
|
-
cycode/cli/apps/ai_guardrails/scan/
|
|
20
|
-
cycode/cli/apps/ai_guardrails/scan/
|
|
21
|
-
cycode/cli/apps/ai_guardrails/scan/types.py,sha256=H25MKJhAXmp7Mz1YeCIRmAY1Zg5GSpgBq8G1TEI9PFk,1868
|
|
21
|
+
cycode/cli/apps/ai_guardrails/scan/scan_command.py,sha256=-Gl7cHELF1wlwLGno6ZVukFtK6NI6Z3-dTpIOsz8ors,6079
|
|
22
|
+
cycode/cli/apps/ai_guardrails/scan/types.py,sha256=lDttkYFBfOkdMEEaRbq1IT2QTK0R-7Ht3T8vZdyB3b4,1038
|
|
22
23
|
cycode/cli/apps/ai_guardrails/scan/utils.py,sha256=KVfX-NrcM-QW4quLtoNqfmz4GF0FlDs-TkqUOu1hAWM,2057
|
|
23
|
-
cycode/cli/apps/ai_guardrails/session_start_command.py,sha256=
|
|
24
|
-
cycode/cli/apps/ai_guardrails/status_command.py,sha256=
|
|
25
|
-
cycode/cli/apps/ai_guardrails/uninstall_command.py,sha256=
|
|
24
|
+
cycode/cli/apps/ai_guardrails/session_start_command.py,sha256=z-yClXkV-SAxh5E8zKGIyo_qbhkpujoTgnx-lUDGS0I,3279
|
|
25
|
+
cycode/cli/apps/ai_guardrails/status_command.py,sha256=Uqss68TEPCYPXpLix6Bh-4J3g-khxWsAqlIGYH5x4bQ,3203
|
|
26
|
+
cycode/cli/apps/ai_guardrails/uninstall_command.py,sha256=dOmePfZmlHAPy2zEJM1yMtSuDqvzDwtqgmLKYK-T9PI,2698
|
|
26
27
|
cycode/cli/apps/ai_remediation/__init__.py,sha256=8vYthY9RQeJqEni3AIF5sryz8n-XJQ6VNqG4aEFBAdY,553
|
|
27
28
|
cycode/cli/apps/ai_remediation/ai_remediation_command.py,sha256=u1EdebaKCEmzv9fXmnIN0xDSLcCmGyjueYKvYfLOj_8,1549
|
|
28
29
|
cycode/cli/apps/ai_remediation/apply_fix.py,sha256=9zgqiqF9HBQXi7Oz9ZIiANIAuKAMTji1PlNncCEOf5Q,817
|
|
@@ -204,8 +205,8 @@ cycode/cyclient/report_client.py,sha256=Scq30NeJPzgXv0hPLO1U05AdE9i_2iu6cIrSKpEJ
|
|
|
204
205
|
cycode/cyclient/scan_client.py,sha256=6TK5FQkfrvV7PHqRnUzEn1PBNd2oPYVamvIixcUfe3c,16755
|
|
205
206
|
cycode/cyclient/scan_config_base.py,sha256=mXsPZGYCtp85rv5GIige40yQZXuRcEKUW-VQJ0vgFzk,1201
|
|
206
207
|
cycode/logger.py,sha256=EfZGRK6VC5rE_LAjIcRrHFiQCueylCDXoG6bvGkrIME,2111
|
|
207
|
-
cycode-3.15.
|
|
208
|
-
cycode-3.15.
|
|
209
|
-
cycode-3.15.
|
|
210
|
-
cycode-3.15.
|
|
211
|
-
cycode-3.15.
|
|
208
|
+
cycode-3.15.4.dev1.dist-info/METADATA,sha256=zo-M7zLCUNGxECnRB06G7NpfNmynUAdahbN-tNa_0dI,89102
|
|
209
|
+
cycode-3.15.4.dev1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
210
|
+
cycode-3.15.4.dev1.dist-info/entry_points.txt,sha256=iDcVJM8ByLElVgvBgtYxDjw1kT7O8Mo0LcWZIT5L3Ig,45
|
|
211
|
+
cycode-3.15.4.dev1.dist-info/licenses/LICENCE,sha256=2Wx4N6mD_4xB7-E3hPkZ3MPhpJy__k_I8MaCSO-PDRo,1068
|
|
212
|
+
cycode-3.15.4.dev1.dist-info/RECORD,,
|