1212import sys
1313from typing import Annotated
1414
15+ import click
1516import typer
1617
1718from cycode .cli .apps .scan .prompt .handlers import get_handler_for_event
2021from cycode .cli .apps .scan .prompt .response_builders import get_response_builder
2122from cycode .cli .apps .scan .prompt .types import AiHookEventType
2223from cycode .cli .apps .scan .prompt .utils import output_json , safe_json_parse
24+ from cycode .cli .exceptions .custom_exceptions import HttpUnauthorizedError
2325from cycode .cli .utils .get_api_client import get_ai_security_manager_client , get_scan_cycode_client
2426from cycode .cli .utils .sentry import add_breadcrumb
2527from cycode .logger import get_logger
2628
2729logger = get_logger ('AI Guardrails' )
2830
2931
32+ def _get_auth_error_message (error : Exception ) -> str :
33+ """Get user-friendly message for authentication errors."""
34+ if isinstance (error , click .ClickException ):
35+ # Missing credentials
36+ return f'{ error .message } Please run `cycode configure` to set up your credentials.'
37+
38+ if isinstance (error , HttpUnauthorizedError ):
39+ # Invalid/expired credentials
40+ return (
41+ 'Unable to authenticate to Cycode. Your credentials are invalid or have expired. '
42+ 'Please run `cycode configure` to update your credentials.'
43+ )
44+
45+ # Fallback
46+ return 'Authentication failed. Please run `cycode configure` to set up your credentials.'
47+
48+
49+ def _initialize_clients (ctx : typer .Context ) -> None :
50+ """Initialize API clients.
51+
52+ May raise click.ClickException if credentials are missing,
53+ or HttpUnauthorizedError if credentials are invalid.
54+ """
55+ scan_client = get_scan_cycode_client (ctx )
56+ ctx .obj ['client' ] = scan_client
57+
58+ ai_security_client = get_ai_security_manager_client (ctx )
59+ ctx .obj ['ai_security_client' ] = ai_security_client
60+
61+
3062def prompt_command (
3163 ctx : typer .Context ,
3264 ide : Annotated [
@@ -52,7 +84,6 @@ def prompt_command(
5284 """
5385 add_breadcrumb ('prompt' )
5486
55- # Read JSON payload from stdin
5687 stdin_data = sys .stdin .read ().strip ()
5788 payload = safe_json_parse (stdin_data )
5889
@@ -64,50 +95,39 @@ def prompt_command(
6495 output_json (response_builder .allow_prompt ())
6596 return
6697
67- # Create unified payload object
6898 unified_payload = AIHookPayload .from_payload (payload , tool = tool )
69-
70- # Extract event type from unified payload
7199 event_name = unified_payload .event_name
72100 logger .debug ('Processing AI guardrails hook' , extra = {'event_name' : event_name , 'tool' : tool })
73101
74- scan_client = get_scan_cycode_client (ctx )
75- ctx .obj ['client' ] = scan_client
76-
77- ai_security_client = get_ai_security_manager_client (ctx )
78- ctx .obj ['ai_security_client' ] = ai_security_client
79-
80- # Load policy (merges defaults <- user config <- repo config)
81- # Extract first workspace root from payload if available
82102 workspace_roots = payload .get ('workspace_roots' , ['.' ])
83103 policy = load_policy (workspace_roots [0 ])
84104
85- # Get the appropriate handler for this event
86- handler = get_handler_for_event ( event_name )
105+ try :
106+ _initialize_clients ( ctx )
87107
88- if handler is None :
89- logger . debug ( 'Unknown hook event, allowing by default' , extra = { 'event_name' : event_name })
90- # Unknown event type - allow by default
91- output_json (response_builder .allow_prompt ())
92- return
108+ handler = get_handler_for_event ( event_name )
109+ if handler is None :
110+ logger . debug ( 'Unknown hook event, allowing by default' , extra = { 'event_name' : event_name })
111+ output_json (response_builder .allow_prompt ())
112+ return
93113
94- # Execute the handler and output the response
95- try :
96114 response = handler (ctx , unified_payload , policy )
97115 logger .debug ('Hook handler completed' , extra = {'event_name' : event_name , 'response' : response })
98116 output_json (response )
117+
118+ except (click .ClickException , HttpUnauthorizedError ) as e :
119+ error_message = _get_auth_error_message (e )
120+ if event_name == AiHookEventType .PROMPT :
121+ output_json (response_builder .deny_prompt (error_message ))
122+ return
123+ output_json (response_builder .deny_permission (error_message , 'Authentication required' ))
124+
99125 except Exception as e :
100126 logger .error ('Hook handler failed' , exc_info = e )
101- # Fail open by default
102127 if policy .get ('fail_open' , True ):
103128 output_json (response_builder .allow_prompt ())
104- else :
105- # Fail closed
106- if event_name == AiHookEventType .PROMPT :
107- output_json (
108- response_builder .deny_prompt ('Cycode guardrails error - blocking due to fail-closed policy' )
109- )
110- else :
111- output_json (
112- response_builder .deny_permission ('Cycode guardrails error' , 'Blocking due to fail-closed policy' )
113- )
129+ return
130+ if event_name == AiHookEventType .PROMPT :
131+ output_json (response_builder .deny_prompt ('Cycode guardrails error - blocking due to fail-closed policy' ))
132+ return
133+ output_json (response_builder .deny_permission ('Cycode guardrails error' , 'Blocking due to fail-closed policy' ))
0 commit comments