fix(ingress): refuse hostile bytes before the disabled-model shortcut (#16)
Quality gates / quality (pull_request) Canceled after 0s
Quality gates / quality (pull_request) Canceled after 0s
The release builder runs its browser gates with TIMMY_VISION_ENABLED=false, and the 503 "AI analysis is disabled" branch ran before the request budget, body limits, and byte-level ingress guard. Under builder conditions every hostile upload answered 503, so the refusal boundary was never exercised and the ingress-capture gate this same PR added failed: AssertionError: the server must refuse non-image bytes, saw 503 Refusals now run in cost order regardless of model availability: budget, body, bytes, then the disabled switch. Verified with vision disabled: hostile bytes -> 400 not_an_image, a valid synthetic photo -> 503 with the existing copy. npm test 277 pass; ingress-capture and the other browser gates green under the builder's env.
This commit is contained in:
1 parent
bc64055196
commit
8ce5bc174b
1 file changed
+5
-2
+5
-2
@@ -76,8 +76,9 @@ http.createServer(async(req,res)=>{
|
||||
return sendJson(res,200,{...visionConfig.publicStatus(),providerReady:provider.ready,modelSeen:provider.modelSeen,privacy});
|
||||
}
|
||||
if(appPath==='/api/analyze'&&req.method==='POST'){
|
||||
if(!visionConfig.enabled)return sendJson(res,503,{error:'AI analysis is disabled. Continue manually.'});
|
||||
// Cheap refusals first, in cost order: the request budget, then the body, then the image itself.
|
||||
// Refusals first, in cost order: the request budget, then the body, then the image bytes. None of
|
||||
// these depend on the model, so hostile bytes are refused as hostile even when analysis is off —
|
||||
// otherwise the disabled switch hides the ingress boundary behind a 503 and the refusal is untested.
|
||||
const budget=analyzeLimiter.take(clientBucket(req));
|
||||
if(!budget.allowed){
|
||||
res.setHeader('retry-after',String(Math.max(1,Math.ceil(budget.retryAfterMs/1000))));
|
||||
@@ -94,6 +95,8 @@ http.createServer(async(req,res)=>{
|
||||
}
|
||||
try{ingressGuard(payload)}
|
||||
catch(error){return sendIngressError(res,error)}
|
||||
// Bytes are known to be a real photo at this point, so the disabled switch is the honest answer.
|
||||
if(!visionConfig.enabled)return sendJson(res,503,{error:'AI analysis is disabled. Continue manually.'});
|
||||
try{return sendJson(res,200,await analyzePhoto({payload,config:visionConfig}))}
|
||||
catch(error){const failure=visionFailureResponse(error);return sendJson(res,failure.status,failure.body)}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user