feat: max-permission-denied guard to halt runs on repeated auth failures#4206
feat: max-permission-denied guard to halt runs on repeated auth failures#4206Copilot wants to merge 3 commits into
Conversation
✅ Coverage Check PassedOverall Coverage
📁 Per-file Coverage Changes (3 files)
Coverage comparison generated by |
There was a problem hiding this comment.
Pull request overview
Adds a new “max permission denied” circuit-breaker guard to the API proxy to prevent runs from repeatedly burning tokens/time when upstream credentials are missing or misconfigured (repeated 401/403 responses).
Changes:
- Introduces
max-permission-denied-guardthat tracks upstream 401/403 counts viaAWF_MAX_PERMISSION_DENIED, with block/reflect/reset helpers. - Integrates guard enforcement into HTTP requests (
proxy-request.js) and WebSocket upgrades (websocket-proxy.js), returning a structured JSON error when blocked. - Exposes guard state under
/reflectand adds unit/integration tests covering the new behavior.
Show a summary per file
| File | Description |
|---|---|
| containers/api-proxy/guards/max-permission-denied-guard.js | Implements the new permission-denied counter + block/reflect helpers. |
| containers/api-proxy/guards/max-permission-denied-guard.test.js | Unit tests for config parsing, counting, reflect state, and error payload. |
| containers/api-proxy/upstream-response.js | Increments the permission-denied counter on upstream 401/403 logging path. |
| containers/api-proxy/proxy-request.js | Blocks requests before forwarding when the permission-denied limit is exceeded; exports reflect/reset helpers. |
| containers/api-proxy/websocket-proxy.js | Applies the same guard pre-check for WebSocket upgrade requests. |
| containers/api-proxy/management.js | Adds permission_denied state into the /reflect response payload. |
| containers/api-proxy/server.js | Wires permission-denied reflect state into management handler deps. |
| containers/api-proxy/server.token-guards.test.js | Integration-style tests verifying HTTP blocking on repeated 401/403 and no-op when unset. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 8/8 changed files
- Comments generated: 1
| function getPermDeniedConfig() { | ||
| const rawMax = process.env.AWF_MAX_PERMISSION_DENIED; | ||
| if (permDeniedConfigCache.rawMax === rawMax) { |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
@copilot address review feedback and make sure that any new configuration params are thoroughly described in teh specs and schema. non-security sensitive config params should be passed through stdin config and security sensitive through environment variables |
Done in commit
Schema and docs:
Tests updated across |
Smoke Test: Claude Engine ✅
Result: PASS
|
🔥 Smoke Test: Copilot BYOK (Offline) Mode
Running in BYOK offline mode ( Author: Overall: PARTIAL — BYOK inference ✅, pre-step data unavailable (template vars not resolved in workflow)
|
|
Merged PRs reviewed:\n- refactor(api-proxy): extract duplicate collectLogOutput() into shared test helper\n- [Test Coverage] container-lifecycle.ts branch coverage\n- GitHub access: ✅\n- Playwright title check: ✅\n- Temp file write/read: ✅\n- Discussion comment: ✅\n- Build: ✅\n- Overall status: PASS Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "registry.npmjs.org"See Network Configuration for more information.
|
🔬 Smoke Test Results
PR: feat: max-permission-denied guard to halt runs on repeated auth failures
|
Smoke Test: API Proxy OpenTelemetry Tracing
All 5 scenarios passed. OTEL tracing integration is working correctly.
|
Chroot Smoke Test Results
Overall: ❌ Not all tests passed — Python and Node.js versions differ between host and chroot.
|
🏗️ Build Test Suite Results
Overall: 8/8 ecosystems passed — ✅ PASS
|
Smoke Test Results
Overall: FAIL
|
Smoke Test: Gemini Engine Validation
Overall status: FAIL Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "localhost"See Network Configuration for more information.
|
Permission misconfigurations (bad/missing API keys) waste tokens by letting agents spin through repeated 401/403 errors. This adds a configurable circuit-breaker that stops the run once a threshold of upstream permission denials is hit.
Changes
New guard:
guards/max-permission-denied-guard.jsAWF_MAX_PERMISSION_DENIEDmax-runs-guard: config cache, state object, block/reflect/reset APIUpstream integration
upstream-response.js— callsapplyPermissionDenied()wheneverlogUpstreamAuthErrorfires (i.e., on any 401 or 403 from upstream)proxy-request.js— checks guard state before forwarding; blocks withHTTP 403+ structured JSON when limit is exceeded; exportsgetPermissionDeniedReflectState/resetPermissionDeniedGuardForTestswebsocket-proxy.js— same check for WebSocket upgrade requestsmanagement.js+server.js— exposespermission_deniedstate under/reflectHost-side config wiring
src/types/rate-limit-options.ts— addedmaxPermissionDenied?: numbertoRateLimitOptionssrc/config-file.ts— added field toAwfFileConfig.apiProxy; mapped inmapAwfFileConfigToCliOptionssrc/commands/validators/log-and-limits.ts— reads and validates the field (must be a positive integer)src/commands/validators/config-assembly.ts+src/commands/build-config.ts— passes it through toWrapperConfigsrc/services/api-proxy-service.ts— setsAWF_MAX_PERMISSION_DENIEDenv var on the api-proxy container when configured via config fileSchema and documentation
docs/awf-config.schema.json+src/awf-config-schema.json—maxPermissionDeniedfield with full descriptiondocs/api-proxy-sidecar.md— added "Max-permission-denied limit" section covering configuration, enforcement, introspection, and error detectionError response
{ "error": { "type": "permission_denied_limit_exceeded", "message": "Permission denied limit exceeded (3 / 3). The run has been stopped due to repeated permission errors — check that all API keys and tokens are correctly configured.", "denied_count": 3, "max_permission_denied": 3 } }Config
{ "apiProxy": { "maxPermissionDenied": 3 } }Configured via the AWF config file (stdin config). When unset, the guard is disabled (no-op).