@@ -17,6 +17,7 @@ import {
1717 extractSpecializedInternalDetails ,
1818 type SanitizedResponse ,
1919} from '../utils/responseFormatter.js' ;
20+ import { CircuitOpenError , scanCircuitBreaker } from '../utils/circuitBreaker.js' ;
2021
2122/**
2223 * Phase 8b: Client-side size limits for file writes.
@@ -204,21 +205,23 @@ export async function scanFileWrite(input: FileWriteInput, customerId: string =
204205 let highestSeverity : string | undefined ;
205206 let totalConfidence = 1.0 ;
206207
207- // Step 1: Scan the file path
208- const pathResponse = await fetch ( `${ config . backendUrl } /api/scan/specialized` , {
209- method : 'POST' ,
210- headers : getAuthHeaders ( ) , // Includes Authorization header if API key is set
211- body : JSON . stringify ( {
212- content : input . path ,
213- content_type : 'file_path' ,
214- context : {
215- session_id : getSessionId ( ) ,
216- agent_id : getAgentId ( ) ,
217- source_application : 'shrike-mcp' ,
218- } ,
219- } ) ,
220- signal : controller . signal ,
221- } ) ;
208+ // Step 1: Scan the file path (through circuit breaker)
209+ const pathResponse = await scanCircuitBreaker . execute ( ( ) =>
210+ fetch ( `${ config . backendUrl } /api/scan/specialized` , {
211+ method : 'POST' ,
212+ headers : getAuthHeaders ( ) ,
213+ body : JSON . stringify ( {
214+ content : input . path ,
215+ content_type : 'file_path' ,
216+ context : {
217+ session_id : getSessionId ( ) ,
218+ agent_id : getAgentId ( ) ,
219+ source_application : 'shrike-mcp' ,
220+ } ,
221+ } ) ,
222+ signal : controller . signal ,
223+ } )
224+ ) ;
222225
223226 if ( ! pathResponse . ok ) {
224227 clearTimeout ( timeoutId ) ;
@@ -246,22 +249,24 @@ export async function scanFileWrite(input: FileWriteInput, customerId: string =
246249 } ) ;
247250 }
248251
249- // Step 2: Scan the file content (path + content together for context)
250- const contentResponse = await fetch ( `${ config . backendUrl } /api/scan/specialized` , {
251- method : 'POST' ,
252- headers : getAuthHeaders ( ) , // Includes Authorization header if API key is set
253- body : JSON . stringify ( {
254- content : input . path ,
255- content_type : 'file_content' ,
256- context : {
257- content : input . content , // Backend expects "content" key, not "file_content"
258- session_id : getSessionId ( ) ,
259- agent_id : getAgentId ( ) ,
260- source_application : 'shrike-mcp' ,
261- } ,
262- } ) ,
263- signal : controller . signal ,
264- } ) ;
252+ // Step 2: Scan the file content (through circuit breaker)
253+ const contentResponse = await scanCircuitBreaker . execute ( ( ) =>
254+ fetch ( `${ config . backendUrl } /api/scan/specialized` , {
255+ method : 'POST' ,
256+ headers : getAuthHeaders ( ) ,
257+ body : JSON . stringify ( {
258+ content : input . path ,
259+ content_type : 'file_content' ,
260+ context : {
261+ content : input . content ,
262+ session_id : getSessionId ( ) ,
263+ agent_id : getAgentId ( ) ,
264+ source_application : 'shrike-mcp' ,
265+ } ,
266+ } ) ,
267+ signal : controller . signal ,
268+ } )
269+ ) ;
265270
266271 clearTimeout ( timeoutId ) ;
267272
@@ -333,7 +338,10 @@ export async function scanFileWrite(input: FileWriteInput, customerId: string =
333338 clearTimeout ( timeoutId ) ;
334339
335340 let internalResult : FileWriteResult ;
336- if ( error instanceof Error && error . name === 'AbortError' ) {
341+ if ( error instanceof CircuitOpenError ) {
342+ console . error ( `[file] ${ requestId } circuit breaker OPEN — blocking (fail-closed)` ) ;
343+ internalResult = createFailClosedResponse ( Date . now ( ) - startTime , 'Security service unavailable (circuit breaker open)' , pathLength , contentLength , fileExtension ) ;
344+ } else if ( error instanceof Error && error . name === 'AbortError' ) {
337345 console . warn ( `File scan timed out after ${ config . scanTimeoutMs } ms, BLOCKING (fail-closed)` ) ;
338346 internalResult = createFailClosedResponse ( Date . now ( ) - startTime , 'Analysis timeout' , pathLength , contentLength , fileExtension ) ;
339347 } else {
0 commit comments