@@ -121,26 +121,47 @@ impl App {
121121 }
122122
123123 // Cloud providers: always block when secrets detected
124- if self . config . provider != crate :: config:: Provider :: Ollama {
125- return Err ( Error :: SecretsDetected {
126- patterns : secrets. iter ( ) . map ( |s| s. pattern_name . clone ( ) ) . collect ( ) ,
127- } ) ;
128- }
129-
130- // Ollama: only allow with --allow-secrets and verified-local host
131124 if !self . cli . allow_secrets {
132125 return Err ( Error :: SecretsDetected {
133126 patterns : secrets. iter ( ) . map ( |s| s. pattern_name . clone ( ) ) . collect ( ) ,
134127 } ) ;
135128 }
136129
137- if !Self :: is_local_host ( & self . config . ollama_host ) {
130+ // --allow-secrets passed: always require interactive confirmation
131+ if std:: io:: stdin ( ) . is_terminal ( ) {
132+ progress. finish ( ) ;
133+ eprintln ! ( "\n warning: Potential secrets detected in staged changes." ) ;
134+ for s in & secrets {
135+ eprintln ! (
136+ " {} in {} (line ~{})" ,
137+ s. pattern_name,
138+ s. file,
139+ s. line. unwrap_or( 0 )
140+ ) ;
141+ }
142+ eprintln ! (
143+ "Provider: {} ({})" ,
144+ self . config. provider,
145+ if self . config. provider == crate :: config:: Provider :: Ollama {
146+ & self . config. ollama_host
147+ } else {
148+ "cloud API"
149+ }
150+ ) ;
151+ eprint ! ( "Send diff to LLM anyway? [y/N] " ) ;
152+ let mut input = String :: new ( ) ;
153+ std:: io:: stdin ( ) . read_line ( & mut input) . ok ( ) ;
154+ if !input. trim ( ) . eq_ignore_ascii_case ( "y" ) {
155+ return Err ( Error :: SecretsDetected {
156+ patterns : secrets. iter ( ) . map ( |s| s. pattern_name . clone ( ) ) . collect ( ) ,
157+ } ) ;
158+ }
159+ } else {
160+ // Non-interactive: always block even with --allow-secrets
138161 return Err ( Error :: SecretsDetected {
139162 patterns : secrets. iter ( ) . map ( |s| s. pattern_name . clone ( ) ) . collect ( ) ,
140163 } ) ;
141164 }
142-
143- progress. info ( "Proceeding with local Ollama (data stays local)" ) ;
144165 }
145166
146167 if self . cancel_token . is_cancelled ( ) {
@@ -1383,23 +1404,4 @@ fi
13831404
13841405 Ok ( ( ) )
13851406 }
1386-
1387- // ─── Security Helpers ───
1388-
1389- /// Check if a URL host resolves to a loopback address (localhost, 127.0.0.1, ::1).
1390- fn is_local_host ( url : & str ) -> bool {
1391- // Parse out the host from the URL
1392- let host = url
1393- . strip_prefix ( "http://" )
1394- . or_else ( || url. strip_prefix ( "https://" ) )
1395- . unwrap_or ( url)
1396- . split ( '/' )
1397- . next ( )
1398- . unwrap_or ( "" )
1399- . split ( ':' )
1400- . next ( )
1401- . unwrap_or ( "" ) ;
1402-
1403- matches ! ( host, "localhost" | "127.0.0.1" | "::1" | "[::1]" )
1404- }
14051407}
0 commit comments