cycode 3.8.10.dev1__py3-none-any.whl → 3.9.0__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/app.py +2 -1
- cycode/cli/apps/ai_guardrails/__init__.py +19 -0
- cycode/cli/apps/ai_guardrails/command_utils.py +66 -0
- cycode/cli/apps/ai_guardrails/consts.py +78 -0
- cycode/cli/apps/ai_guardrails/hooks_manager.py +200 -0
- cycode/cli/apps/ai_guardrails/install_command.py +78 -0
- cycode/cli/apps/ai_guardrails/scan/__init__.py +1 -0
- cycode/cli/apps/ai_guardrails/scan/consts.py +48 -0
- cycode/cli/apps/ai_guardrails/scan/handlers.py +341 -0
- cycode/cli/apps/ai_guardrails/scan/payload.py +72 -0
- cycode/cli/apps/ai_guardrails/scan/policy.py +85 -0
- cycode/cli/apps/ai_guardrails/scan/response_builders.py +86 -0
- cycode/cli/apps/ai_guardrails/scan/scan_command.py +134 -0
- cycode/cli/apps/ai_guardrails/scan/types.py +54 -0
- cycode/cli/apps/ai_guardrails/scan/utils.py +72 -0
- cycode/cli/apps/ai_guardrails/status_command.py +92 -0
- cycode/cli/apps/ai_guardrails/uninstall_command.py +73 -0
- cycode/cli/apps/scan/code_scanner.py +1 -1
- cycode/cli/cli_types.py +13 -0
- cycode/cli/utils/get_api_client.py +15 -2
- cycode/cli/utils/scan_utils.py +24 -0
- cycode/cyclient/ai_security_manager_client.py +86 -0
- cycode/cyclient/ai_security_manager_service_config.py +27 -0
- cycode/cyclient/client_creator.py +20 -0
- {cycode-3.8.10.dev1.dist-info → cycode-3.9.0.dist-info}/METADATA +1 -1
- {cycode-3.8.10.dev1.dist-info → cycode-3.9.0.dist-info}/RECORD +30 -12
- {cycode-3.8.10.dev1.dist-info → cycode-3.9.0.dist-info}/WHEEL +0 -0
- {cycode-3.8.10.dev1.dist-info → cycode-3.9.0.dist-info}/entry_points.txt +0 -0
- {cycode-3.8.10.dev1.dist-info → cycode-3.9.0.dist-info}/licenses/LICENCE +0 -0
cycode/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '3.
|
|
1
|
+
__version__ = '3.9.0' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
|
cycode/cli/app.py
CHANGED
|
@@ -9,7 +9,7 @@ from typer._completion_shared import Shells
|
|
|
9
9
|
from typer.completion import install_callback, show_callback
|
|
10
10
|
|
|
11
11
|
from cycode import __version__
|
|
12
|
-
from cycode.cli.apps import ai_remediation, auth, configure, ignore, report, report_import, scan, status
|
|
12
|
+
from cycode.cli.apps import ai_guardrails, ai_remediation, auth, configure, ignore, report, report_import, scan, status
|
|
13
13
|
|
|
14
14
|
if sys.version_info >= (3, 10):
|
|
15
15
|
from cycode.cli.apps import mcp
|
|
@@ -45,6 +45,7 @@ app = typer.Typer(
|
|
|
45
45
|
add_completion=False, # we add it manually to control the rich help panel
|
|
46
46
|
)
|
|
47
47
|
|
|
48
|
+
app.add_typer(ai_guardrails.app)
|
|
48
49
|
app.add_typer(ai_remediation.app)
|
|
49
50
|
app.add_typer(auth.app)
|
|
50
51
|
app.add_typer(configure.app)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from cycode.cli.apps.ai_guardrails.install_command import install_command
|
|
4
|
+
from cycode.cli.apps.ai_guardrails.scan.scan_command import scan_command
|
|
5
|
+
from cycode.cli.apps.ai_guardrails.status_command import status_command
|
|
6
|
+
from cycode.cli.apps.ai_guardrails.uninstall_command import uninstall_command
|
|
7
|
+
|
|
8
|
+
app = typer.Typer(name='ai-guardrails', no_args_is_help=True, hidden=True)
|
|
9
|
+
|
|
10
|
+
app.command(hidden=True, name='install', short_help='Install AI guardrails hooks for supported IDEs.')(install_command)
|
|
11
|
+
app.command(hidden=True, name='uninstall', short_help='Remove AI guardrails hooks from supported IDEs.')(
|
|
12
|
+
uninstall_command
|
|
13
|
+
)
|
|
14
|
+
app.command(hidden=True, name='status', short_help='Show AI guardrails hook installation status.')(status_command)
|
|
15
|
+
app.command(
|
|
16
|
+
hidden=True,
|
|
17
|
+
name='scan',
|
|
18
|
+
short_help='Scan content from AI IDE hooks for secrets (reads JSON from stdin).',
|
|
19
|
+
)(scan_command)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Common utilities for AI guardrails commands."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from cycode.cli.apps.ai_guardrails.consts import AIIDEType
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def validate_and_parse_ide(ide: str) -> AIIDEType:
|
|
16
|
+
"""Validate IDE parameter and convert to AIIDEType enum.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
ide: IDE name string (e.g., 'cursor')
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
AIIDEType enum value
|
|
23
|
+
|
|
24
|
+
Raises:
|
|
25
|
+
typer.Exit: If IDE is invalid
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
return AIIDEType(ide.lower())
|
|
29
|
+
except ValueError:
|
|
30
|
+
valid_ides = ', '.join([ide_type.value for ide_type in AIIDEType])
|
|
31
|
+
console.print(
|
|
32
|
+
f'[red]Error:[/] Invalid IDE "{ide}". Supported IDEs: {valid_ides}',
|
|
33
|
+
style='bold red',
|
|
34
|
+
)
|
|
35
|
+
raise typer.Exit(1) from None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def validate_scope(scope: str, allowed_scopes: tuple[str, ...] = ('user', 'repo')) -> None:
|
|
39
|
+
"""Validate scope parameter.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
scope: Scope string to validate
|
|
43
|
+
allowed_scopes: Tuple of allowed scope values
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
typer.Exit: If scope is invalid
|
|
47
|
+
"""
|
|
48
|
+
if scope not in allowed_scopes:
|
|
49
|
+
scopes_list = ', '.join(f'"{s}"' for s in allowed_scopes)
|
|
50
|
+
console.print(f'[red]Error:[/] Invalid scope. Use {scopes_list}.', style='bold red')
|
|
51
|
+
raise typer.Exit(1)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def resolve_repo_path(scope: str, repo_path: Optional[Path]) -> Optional[Path]:
|
|
55
|
+
"""Resolve repository path, defaulting to current directory for repo scope.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
scope: The command scope ('user' or 'repo')
|
|
59
|
+
repo_path: Provided repo path or None
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Resolved Path for repo scope, None for user scope
|
|
63
|
+
"""
|
|
64
|
+
if scope == 'repo' and repo_path is None:
|
|
65
|
+
return Path(os.getcwd())
|
|
66
|
+
return repo_path
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Constants for AI guardrails hooks management.
|
|
2
|
+
|
|
3
|
+
Currently supports:
|
|
4
|
+
- Cursor
|
|
5
|
+
|
|
6
|
+
To add a new IDE (e.g., Claude Code):
|
|
7
|
+
1. Add new value to AIIDEType enum
|
|
8
|
+
2. Create _get_<ide>_hooks_dir() function with platform-specific paths
|
|
9
|
+
3. Add entry to IDE_CONFIGS dict with IDE-specific hook event names
|
|
10
|
+
4. Unhide --ide option in commands (install, uninstall, status)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import platform
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import NamedTuple
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AIIDEType(str, Enum):
|
|
20
|
+
"""Supported AI IDE types."""
|
|
21
|
+
|
|
22
|
+
CURSOR = 'cursor'
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class IDEConfig(NamedTuple):
|
|
26
|
+
"""Configuration for an AI IDE."""
|
|
27
|
+
|
|
28
|
+
name: str
|
|
29
|
+
hooks_dir: Path
|
|
30
|
+
repo_hooks_subdir: str # Subdirectory in repo for hooks (e.g., '.cursor')
|
|
31
|
+
hooks_file_name: str
|
|
32
|
+
hook_events: list[str] # List of supported hook event names for this IDE
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _get_cursor_hooks_dir() -> Path:
|
|
36
|
+
"""Get Cursor hooks directory based on platform."""
|
|
37
|
+
if platform.system() == 'Darwin':
|
|
38
|
+
return Path.home() / '.cursor'
|
|
39
|
+
if platform.system() == 'Windows':
|
|
40
|
+
return Path.home() / 'AppData' / 'Roaming' / 'Cursor'
|
|
41
|
+
# Linux
|
|
42
|
+
return Path.home() / '.config' / 'Cursor'
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# IDE-specific configurations
|
|
46
|
+
IDE_CONFIGS: dict[AIIDEType, IDEConfig] = {
|
|
47
|
+
AIIDEType.CURSOR: IDEConfig(
|
|
48
|
+
name='Cursor',
|
|
49
|
+
hooks_dir=_get_cursor_hooks_dir(),
|
|
50
|
+
repo_hooks_subdir='.cursor',
|
|
51
|
+
hooks_file_name='hooks.json',
|
|
52
|
+
hook_events=['beforeSubmitPrompt', 'beforeReadFile', 'beforeMCPExecution'],
|
|
53
|
+
),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Default IDE
|
|
57
|
+
DEFAULT_IDE = AIIDEType.CURSOR
|
|
58
|
+
|
|
59
|
+
# Command used in hooks
|
|
60
|
+
CYCODE_SCAN_PROMPT_COMMAND = 'cycode ai-guardrails scan'
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_hooks_config(ide: AIIDEType) -> dict:
|
|
64
|
+
"""Get the hooks configuration for a specific IDE.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
ide: The AI IDE type
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Dict with hooks configuration for the specified IDE
|
|
71
|
+
"""
|
|
72
|
+
config = IDE_CONFIGS[ide]
|
|
73
|
+
hooks = {event: [{'command': CYCODE_SCAN_PROMPT_COMMAND}] for event in config.hook_events}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
'version': 1,
|
|
77
|
+
'hooks': hooks,
|
|
78
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hooks manager for AI guardrails.
|
|
3
|
+
|
|
4
|
+
Handles installation, removal, and status checking of AI IDE hooks.
|
|
5
|
+
Supports multiple IDEs: Cursor, Claude Code (future).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
from cycode.cli.apps.ai_guardrails.consts import (
|
|
13
|
+
CYCODE_SCAN_PROMPT_COMMAND,
|
|
14
|
+
DEFAULT_IDE,
|
|
15
|
+
IDE_CONFIGS,
|
|
16
|
+
AIIDEType,
|
|
17
|
+
get_hooks_config,
|
|
18
|
+
)
|
|
19
|
+
from cycode.logger import get_logger
|
|
20
|
+
|
|
21
|
+
logger = get_logger('AI Guardrails Hooks')
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_hooks_path(scope: str, repo_path: Optional[Path] = None, ide: AIIDEType = DEFAULT_IDE) -> Path:
|
|
25
|
+
"""Get the hooks.json path for the given scope and IDE.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
scope: 'user' for user-level hooks, 'repo' for repository-level hooks
|
|
29
|
+
repo_path: Repository path (required if scope is 'repo')
|
|
30
|
+
ide: The AI IDE type (default: Cursor)
|
|
31
|
+
"""
|
|
32
|
+
config = IDE_CONFIGS[ide]
|
|
33
|
+
if scope == 'repo' and repo_path:
|
|
34
|
+
return repo_path / config.repo_hooks_subdir / config.hooks_file_name
|
|
35
|
+
return config.hooks_dir / config.hooks_file_name
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def load_hooks_file(hooks_path: Path) -> Optional[dict]:
|
|
39
|
+
"""Load existing hooks.json file."""
|
|
40
|
+
if not hooks_path.exists():
|
|
41
|
+
return None
|
|
42
|
+
try:
|
|
43
|
+
content = hooks_path.read_text(encoding='utf-8')
|
|
44
|
+
return json.loads(content)
|
|
45
|
+
except Exception as e:
|
|
46
|
+
logger.debug('Failed to load hooks file', exc_info=e)
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def save_hooks_file(hooks_path: Path, hooks_config: dict) -> bool:
|
|
51
|
+
"""Save hooks.json file."""
|
|
52
|
+
try:
|
|
53
|
+
hooks_path.parent.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
hooks_path.write_text(json.dumps(hooks_config, indent=2), encoding='utf-8')
|
|
55
|
+
return True
|
|
56
|
+
except Exception as e:
|
|
57
|
+
logger.error('Failed to save hooks file', exc_info=e)
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def is_cycode_hook_entry(entry: dict) -> bool:
|
|
62
|
+
"""Check if a hook entry is from cycode-cli."""
|
|
63
|
+
command = entry.get('command', '')
|
|
64
|
+
return CYCODE_SCAN_PROMPT_COMMAND in command
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def install_hooks(
|
|
68
|
+
scope: str = 'user', repo_path: Optional[Path] = None, ide: AIIDEType = DEFAULT_IDE
|
|
69
|
+
) -> tuple[bool, str]:
|
|
70
|
+
"""
|
|
71
|
+
Install Cycode AI guardrails hooks.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
scope: 'user' for user-level hooks, 'repo' for repository-level hooks
|
|
75
|
+
repo_path: Repository path (required if scope is 'repo')
|
|
76
|
+
ide: The AI IDE type (default: Cursor)
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Tuple of (success, message)
|
|
80
|
+
"""
|
|
81
|
+
hooks_path = get_hooks_path(scope, repo_path, ide)
|
|
82
|
+
|
|
83
|
+
# Load existing hooks or create new
|
|
84
|
+
existing = load_hooks_file(hooks_path) or {'version': 1, 'hooks': {}}
|
|
85
|
+
existing.setdefault('version', 1)
|
|
86
|
+
existing.setdefault('hooks', {})
|
|
87
|
+
|
|
88
|
+
# Get IDE-specific hooks configuration
|
|
89
|
+
hooks_config = get_hooks_config(ide)
|
|
90
|
+
|
|
91
|
+
# Add/update Cycode hooks
|
|
92
|
+
for event, entries in hooks_config['hooks'].items():
|
|
93
|
+
existing['hooks'].setdefault(event, [])
|
|
94
|
+
|
|
95
|
+
# Remove any existing Cycode entries for this event
|
|
96
|
+
existing['hooks'][event] = [e for e in existing['hooks'][event] if not is_cycode_hook_entry(e)]
|
|
97
|
+
|
|
98
|
+
# Add new Cycode entries
|
|
99
|
+
for entry in entries:
|
|
100
|
+
existing['hooks'][event].append(entry)
|
|
101
|
+
|
|
102
|
+
# Save
|
|
103
|
+
if save_hooks_file(hooks_path, existing):
|
|
104
|
+
return True, f'AI guardrails hooks installed: {hooks_path}'
|
|
105
|
+
return False, f'Failed to install hooks to {hooks_path}'
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def uninstall_hooks(
|
|
109
|
+
scope: str = 'user', repo_path: Optional[Path] = None, ide: AIIDEType = DEFAULT_IDE
|
|
110
|
+
) -> tuple[bool, str]:
|
|
111
|
+
"""
|
|
112
|
+
Remove Cycode AI guardrails hooks.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
scope: 'user' for user-level hooks, 'repo' for repository-level hooks
|
|
116
|
+
repo_path: Repository path (required if scope is 'repo')
|
|
117
|
+
ide: The AI IDE type (default: Cursor)
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Tuple of (success, message)
|
|
121
|
+
"""
|
|
122
|
+
hooks_path = get_hooks_path(scope, repo_path, ide)
|
|
123
|
+
|
|
124
|
+
existing = load_hooks_file(hooks_path)
|
|
125
|
+
if existing is None:
|
|
126
|
+
return True, f'No hooks file found at {hooks_path}'
|
|
127
|
+
|
|
128
|
+
# Remove Cycode entries from all events
|
|
129
|
+
modified = False
|
|
130
|
+
for event in list(existing.get('hooks', {}).keys()):
|
|
131
|
+
original_count = len(existing['hooks'][event])
|
|
132
|
+
existing['hooks'][event] = [e for e in existing['hooks'][event] if not is_cycode_hook_entry(e)]
|
|
133
|
+
if len(existing['hooks'][event]) != original_count:
|
|
134
|
+
modified = True
|
|
135
|
+
# Remove empty event lists
|
|
136
|
+
if not existing['hooks'][event]:
|
|
137
|
+
del existing['hooks'][event]
|
|
138
|
+
|
|
139
|
+
if not modified:
|
|
140
|
+
return True, 'No Cycode hooks found to remove'
|
|
141
|
+
|
|
142
|
+
# Save or delete if empty
|
|
143
|
+
if not existing.get('hooks'):
|
|
144
|
+
try:
|
|
145
|
+
hooks_path.unlink()
|
|
146
|
+
return True, f'Removed hooks file: {hooks_path}'
|
|
147
|
+
except Exception as e:
|
|
148
|
+
logger.debug('Failed to delete hooks file', exc_info=e)
|
|
149
|
+
return False, f'Failed to remove hooks file: {hooks_path}'
|
|
150
|
+
|
|
151
|
+
if save_hooks_file(hooks_path, existing):
|
|
152
|
+
return True, f'Cycode hooks removed from: {hooks_path}'
|
|
153
|
+
return False, f'Failed to update hooks file: {hooks_path}'
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def get_hooks_status(scope: str = 'user', repo_path: Optional[Path] = None, ide: AIIDEType = DEFAULT_IDE) -> dict:
|
|
157
|
+
"""
|
|
158
|
+
Get the status of AI guardrails hooks.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
scope: 'user' for user-level hooks, 'repo' for repository-level hooks
|
|
162
|
+
repo_path: Repository path (required if scope is 'repo')
|
|
163
|
+
ide: The AI IDE type (default: Cursor)
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Dict with status information
|
|
167
|
+
"""
|
|
168
|
+
hooks_path = get_hooks_path(scope, repo_path, ide)
|
|
169
|
+
|
|
170
|
+
status = {
|
|
171
|
+
'scope': scope,
|
|
172
|
+
'ide': ide.value,
|
|
173
|
+
'ide_name': IDE_CONFIGS[ide].name,
|
|
174
|
+
'hooks_path': str(hooks_path),
|
|
175
|
+
'file_exists': hooks_path.exists(),
|
|
176
|
+
'cycode_installed': False,
|
|
177
|
+
'hooks': {},
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
existing = load_hooks_file(hooks_path)
|
|
181
|
+
if existing is None:
|
|
182
|
+
return status
|
|
183
|
+
|
|
184
|
+
# Check each hook event for this IDE
|
|
185
|
+
ide_config = IDE_CONFIGS[ide]
|
|
186
|
+
has_cycode_hooks = False
|
|
187
|
+
for event in ide_config.hook_events:
|
|
188
|
+
entries = existing.get('hooks', {}).get(event, [])
|
|
189
|
+
cycode_entries = [e for e in entries if is_cycode_hook_entry(e)]
|
|
190
|
+
if cycode_entries:
|
|
191
|
+
has_cycode_hooks = True
|
|
192
|
+
status['hooks'][event] = {
|
|
193
|
+
'total_entries': len(entries),
|
|
194
|
+
'cycode_entries': len(cycode_entries),
|
|
195
|
+
'enabled': len(cycode_entries) > 0,
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
status['cycode_installed'] = has_cycode_hooks
|
|
199
|
+
|
|
200
|
+
return status
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Install command for AI guardrails hooks."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated, Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
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
|
|
15
|
+
from cycode.cli.apps.ai_guardrails.hooks_manager import install_hooks
|
|
16
|
+
from cycode.cli.utils.sentry import add_breadcrumb
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def install_command(
|
|
20
|
+
ctx: typer.Context,
|
|
21
|
+
scope: Annotated[
|
|
22
|
+
str,
|
|
23
|
+
typer.Option(
|
|
24
|
+
'--scope',
|
|
25
|
+
'-s',
|
|
26
|
+
help='Installation scope: "user" for all projects, "repo" for current repository only.',
|
|
27
|
+
),
|
|
28
|
+
] = 'user',
|
|
29
|
+
ide: Annotated[
|
|
30
|
+
str,
|
|
31
|
+
typer.Option(
|
|
32
|
+
'--ide',
|
|
33
|
+
help='IDE to install hooks for (e.g., "cursor"). Defaults to cursor.',
|
|
34
|
+
),
|
|
35
|
+
] = 'cursor',
|
|
36
|
+
repo_path: Annotated[
|
|
37
|
+
Optional[Path],
|
|
38
|
+
typer.Option(
|
|
39
|
+
'--repo-path',
|
|
40
|
+
help='Repository path for repo-scoped installation (defaults to current directory).',
|
|
41
|
+
exists=True,
|
|
42
|
+
file_okay=False,
|
|
43
|
+
dir_okay=True,
|
|
44
|
+
resolve_path=True,
|
|
45
|
+
),
|
|
46
|
+
] = None,
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Install AI guardrails hooks for supported IDEs.
|
|
49
|
+
|
|
50
|
+
This command configures the specified IDE to use Cycode for scanning prompts, file reads,
|
|
51
|
+
and MCP tool calls for secrets before they are sent to AI models.
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
cycode ai-guardrails install # Install for all projects (user scope)
|
|
55
|
+
cycode ai-guardrails install --scope repo # Install for current repo only
|
|
56
|
+
cycode ai-guardrails install --ide cursor # Install for Cursor IDE
|
|
57
|
+
cycode ai-guardrails install --scope repo --repo-path /path/to/repo
|
|
58
|
+
"""
|
|
59
|
+
add_breadcrumb('ai-guardrails-install')
|
|
60
|
+
|
|
61
|
+
# Validate inputs
|
|
62
|
+
validate_scope(scope)
|
|
63
|
+
repo_path = resolve_repo_path(scope, repo_path)
|
|
64
|
+
ide_type = validate_and_parse_ide(ide)
|
|
65
|
+
ide_name = IDE_CONFIGS[ide_type].name
|
|
66
|
+
success, message = install_hooks(scope, repo_path, ide=ide_type)
|
|
67
|
+
|
|
68
|
+
if success:
|
|
69
|
+
console.print(f'[green]✓[/] {message}')
|
|
70
|
+
console.print()
|
|
71
|
+
console.print('[bold]Next steps:[/]')
|
|
72
|
+
console.print(f'1. Restart {ide_name} to activate the hooks')
|
|
73
|
+
console.print('2. (Optional) Customize policy in ~/.cycode/ai-guardrails.yaml')
|
|
74
|
+
console.print()
|
|
75
|
+
console.print('[dim]The hooks will scan prompts, file reads, and MCP tool calls for secrets.[/]')
|
|
76
|
+
else:
|
|
77
|
+
console.print(f'[red]✗[/] {message}', style='bold red')
|
|
78
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Prompt scan command for AI guardrails (hooks)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Constants and default configuration for AI guardrails.
|
|
3
|
+
|
|
4
|
+
These defaults can be overridden by:
|
|
5
|
+
1. User-level config: ~/.cycode/ai-guardrails.yaml
|
|
6
|
+
2. Repo-level config: <workspace>/.cycode/ai-guardrails.yaml
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# Policy file name
|
|
10
|
+
POLICY_FILE_NAME = 'ai-guardrails.yaml'
|
|
11
|
+
|
|
12
|
+
# Default policy configuration
|
|
13
|
+
DEFAULT_POLICY = {
|
|
14
|
+
'version': 1,
|
|
15
|
+
'mode': 'block', # block | warn
|
|
16
|
+
'fail_open': True, # allow if scan fails/timeouts
|
|
17
|
+
'secrets': {
|
|
18
|
+
'scan_type': 'secret',
|
|
19
|
+
'timeout_ms': 30000,
|
|
20
|
+
'max_bytes': 200000,
|
|
21
|
+
},
|
|
22
|
+
'prompt': {
|
|
23
|
+
'enabled': True,
|
|
24
|
+
'action': 'block',
|
|
25
|
+
},
|
|
26
|
+
'file_read': {
|
|
27
|
+
'enabled': True,
|
|
28
|
+
'action': 'block',
|
|
29
|
+
'deny_globs': [
|
|
30
|
+
'.env',
|
|
31
|
+
'.env.*',
|
|
32
|
+
'*.pem',
|
|
33
|
+
'*.p12',
|
|
34
|
+
'*.key',
|
|
35
|
+
'.aws/**',
|
|
36
|
+
'.ssh/**',
|
|
37
|
+
'*kubeconfig*',
|
|
38
|
+
'.npmrc',
|
|
39
|
+
'.netrc',
|
|
40
|
+
],
|
|
41
|
+
'scan_content': True,
|
|
42
|
+
},
|
|
43
|
+
'mcp': {
|
|
44
|
+
'enabled': True,
|
|
45
|
+
'action': 'block',
|
|
46
|
+
'scan_arguments': True,
|
|
47
|
+
},
|
|
48
|
+
}
|