feat: allowedModels / disallowedModels policy enforcement in api-proxy#5095
Conversation
Implements issue #4389: allowed and disallowed model glob pattern lists enforced at two points: 1. Alias resolution: policy-violating candidates filtered in _resolveAliasPatterns 2. Inference guard: model-policy-guard.js checked in enforceGuards pipeline Config flows from awf.yml → TypeScript config → env vars AWF_ALLOWED_MODELS / AWF_DISALLOWED_MODELS (JSON arrays) → api-proxy container. Disallowed takes priority over allowed when a model matches both lists.
✅ Coverage Check PassedOverall Coverage
📁 Per-file Coverage Changes (1 files)
Coverage comparison generated by |
There was a problem hiding this comment.
Pull request overview
This PR adds model access control to the API proxy by introducing allowlist/denylist configuration (allowedModels / disallowedModels) expressed as glob patterns, then enforcing the policy during model alias resolution and request guarding.
Changes:
- Introduces a new
model-policy-guardand wires it into the shared guard-check pipeline. - Threads model-policy config from
awf.yml→ CLI options/types/schema → docker-compose env vars → api-proxy resolution/guards. - Extends model alias resolution to filter candidates against policy and adds tests for both proxy env forwarding and resolver behavior.
Show a summary per file
| File | Description |
|---|---|
| src/types/api-proxy-model-options.ts | Documents new allowedModels / disallowedModels options (including behavior and env vars). |
| src/services/api-proxy-service-misc-forwarding.test.ts | Adds tests asserting allowed/disallowed model lists are forwarded (or omitted when empty). |
| src/services/api-proxy-service-config.ts | Forwards allowed/disallowed lists into AWF_ALLOWED_MODELS / AWF_DISALLOWED_MODELS env vars. |
| src/config-file.ts | Parses apiProxy.allowedModels / apiProxy.disallowedModels from awf.yml into CLI options. |
| src/awf-config-schema.json | Adds schema definitions for allowedModels / disallowedModels. |
| docs/awf-config.schema.json | Mirrors schema updates for documentation tooling. |
| containers/api-proxy/websocket-proxy.js | Threads model-policy guard dependencies into the WebSocket guard dependency bundle. |
| containers/api-proxy/proxy-request.js | Wires model-policy guard into the HTTP enforceGuards path. |
| containers/api-proxy/model-resolver.test.js | Adds coverage for policy filtering behavior during alias resolution. |
| containers/api-proxy/model-resolver.js | Threads modelPolicyConfig through alias resolution and filters candidate models accordingly. |
| containers/api-proxy/model-config.js | Constructs MODEL_POLICY_CONFIG from env-derived policy lists and passes it into body rewrite resolution. |
| containers/api-proxy/model-body-rewriter.js | Passes modelPolicyConfig through to resolveModel(...). |
| containers/api-proxy/guards/model-policy-guard.test.js | Adds unit tests for parsing, permit/deny evaluation, block-state, and error-body building. |
| containers/api-proxy/guards/model-policy-guard.js | Implements model policy parsing, evaluation, block state, and error-body builder. |
| containers/api-proxy/guards/common-guard-checks.js | Adds model_policy_violation as a shared guard descriptor. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 15/15 changed files
- Comments generated: 7
| isBlocked: block => !!block, | ||
| statusCode: 400, | ||
| eventName: 'model_policy_violation', | ||
| buildError: buildModelPolicyError, |
| if (!Array.isArray(parsed)) return null; | ||
| const strings = parsed.filter(p => typeof p === 'string' && p.trim()); | ||
| return strings.length > 0 ? strings : null; |
| /** | ||
| * Builds the structured 400 error response body for a model-policy rejection. | ||
| * | ||
| * @param {{ model: string, reason: string }} state | ||
| * @returns {{ error: object }} | ||
| */ |
|
|
||
| const [aliasKey, aliasRaw] = aliasEntry; | ||
| const aliasDefinition = resolveAliasDefinition(aliasRaw); | ||
| return _resolveAliasPatterns(aliasKey, aliasDefinition, requestedModel, aliases, availableModels, currentProvider, newChain, fallbackConfig, log); | ||
| return _resolveAliasPatterns(aliasKey, aliasDefinition, requestedModel, aliases, availableModels, currentProvider, newChain, fallbackConfig, log, modelPolicyConfig); |
…ort, function or class' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
|
✅ Copilot review passed. @copilot Please add the |
|
@copilot address review feedback and add the ready-for-aw label to this PR to trigger agentic CI smoke tests. Use: gh pr edit 5095 --add-label ready-for-aw |
- common-guard-checks.js: move model_policy_violation check before unknown_model_ai_credits so policy takes precedence; use 403 status - model-policy-guard.js: trim patterns in parseModelPatterns; update buildModelPolicyError JSDoc to reflect 403 status code - model-resolver.js: apply modelPolicyConfig in _resolveDirectMatch path (direct match and family-version fallback) - src/types/api-proxy-model-options.ts: update docstrings to say 403
Done in commit 2b1025a. Here's a summary of what was addressed:
Also added the |
|
📡 Smoke OTel Tracing completed. All tracing scenarios validated. ✅ |
|
🔑 Smoke Copilot PAT PAT auth validated. All systems operational. ✅ |
|
✅ Smoke Copilot BYOK AOAI (Entra) completed. Copilot AOAI BYOK (Entra) mode operational. 🔓 Commented on PR and added smoke-copilot-byok-aoai-entra label |
|
✅ Smoke Gemini completed. All facets verified. 💎 |
|
Chroot tests passed! Smoke Chroot - All security and functionality tests succeeded. |
|
✨ The prophecy is fulfilled... Smoke Codex has completed its mystical journey. The stars align. 🌟 |
|
📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤 |
|
Running in direct BYOK mode (AWF_AUTH_TYPE=github-oidc + AWF_AUTH_AZURE_* + COPILOT_PROVIDER_BASE_URL) via api-proxy → Azure OpenAI (Foundry, o4-mini-aw) authenticated via Microsoft Entra Overall status: FAIL
|
Smoke Test: PAT Auth — FAIL
Auth mode: PAT (COPILOT_GITHUB_TOKEN) Pre-step outputs (
|
🔍 Smoke Test: API Proxy OpenTelemetry Tracing
All scenarios pass. OTEL tracing integration is functioning correctly.
|
Smoke Test: Copilot BYOK (Direct) ModeStatus: PASS Running in direct BYOK mode (COPILOT_PROVIDER_API_KEY) via api-proxy → api.githubcopilot.com
|
|
Smoke Test Results
Running in direct BYOK mode (COPILOT_PROVIDER_API_KEY + COPILOT_PROVIDER_BASE_URL) via api-proxy → Azure OpenAI (Foundry, o4-mini-aw) Overall: PASS
|
Chroot Version Comparison Results
Overall: ❌ Tests did not pass — Python and Node.js versions differ between host and chroot environments.
|
🔬 Smoke Test ResultsPR: feat: allowedModels / disallowedModels policy enforcement in api-proxy
Overall: FAIL — pre-step outputs were not substituted into the workflow prompt.
|
|
Merged PRs:
Results:
|
🏗️ Build Test Suite Results
Overall: 8/8 ecosystems passed — ✅ PASS
|
Smoke Test Results — FAIL ❌
|
Smoke Test Results
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.
|
- Fix disallowedModels description in awf-config.schema.json: HTTP 400 → HTTP 403 (commit 8de2222 changed the rejection status code to 403) - Add apiProxy.allowedModels and apiProxy.disallowedModels to the CLI mapping list in awf-config-spec.md (maps to AWF_ALLOWED_MODELS / AWF_DISALLOWED_MODELS) Triggered by: 8de2222 feat: allowedModels / disallowedModels policy enforcement in api-proxy (#5095) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs: sync schemas and specs with allowedModels/disallowedModels changes - Fix disallowedModels description in awf-config.schema.json: HTTP 400 → HTTP 403 (commit 8de2222 changed the rejection status code to 403) - Add apiProxy.allowedModels and apiProxy.disallowedModels to the CLI mapping list in awf-config-spec.md (maps to AWF_ALLOWED_MODELS / AWF_DISALLOWED_MODELS) Triggered by: 8de2222 feat: allowedModels / disallowedModels policy enforcement in api-proxy (#5095) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: sync src schema disallowedModels status wording --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Adds enterprise-grade model access control to the api-proxy: an allowlist (
allowedModels) and denylist (disallowedModels) accepting arrays of glob patterns. Disallowed always takes precedence over allowed.Enforcement happens at two points:
null(no silent fallback to an unauthorized model)model-policy-guardruns inenforceGuardson every request, rejecting blocked models with 403 before the request is forwardedConfig
Flows through TypeScript config →
AWF_ALLOWED_MODELS/AWF_DISALLOWED_MODELSenv vars (JSON arrays) into the api-proxy container.Changes
containers/api-proxy/guards/model-policy-guard.js(new) — core policy logic; exportsparseModelPatterns,isModelPermittedByPolicy,getModelPolicyBlockState,buildModelPolicyErrormodel-resolver.js—resolveModel/_resolveAliasPatternsacceptmodelPolicyConfigand filter candidates inline (avoids circular dep — no import of guard module)model-body-rewriter.js/model-config.js— threadMODEL_POLICY_CONFIGfrom env through to resolverproxy-request.js/websocket-proxy.js/common-guard-checks.js— wire guard into both HTTP and WebSocket enforce-guards pathssrc/types/api-proxy-model-options.ts— addallowedModels?: string[],disallowedModels?: string[]src/config-file.ts— parse fields fromawf.ymland map through CLI optionssrc/services/api-proxy-service-config.ts— forward as env vars (only when non-empty)src/awf-config-schema.json/docs/awf-config.schema.json— schema definitions for both fields