[PR #6764] [proxy] enforce model allowlist for URL-routed providers (Bedrock/Vertex) #29873

Closed
opened 2026-08-05 08:09:14 -04:00 by saavagebueno · 0 comments
Owner

Original Pull Request: https://github.com/netbirdio/netbird/pull/6764

State: closed
Merged: Yes


Describe your changes

The Agent Network policy Guardrail "Model Allowlist" was not enforced for providers whose model travels in the URL/path rather than the JSON body — most visibly AWS Bedrock (reported in netbirdio/netbird#6751), and the same class applies to Google Vertex.

Root cause: the llm_guardrail allowlist check failed open. evaluateAllowlist returned allow whenever the request model was absent from the metadata bag (middleware.go, if !modelPresent { return nil }). The model is stamped upstream by llm_request_parser; for body-routed providers (OpenAI/Anthropic) it comes from the JSON body, but for path-routed providers the model is recovered only when the request matches a recognized path shape (Bedrock /model/{id}/{invoke|converse|...}, Vertex /v1/projects/.../publishers/.../models/...). Any shape the parser did not recognize reached the guardrail with no model and was allowed regardless of the allowlist.

Fix (provider-agnostic): fail closed. When an allowlist is configured and the model cannot be determined (absent or empty), the request is denied 403 with a distinct llm_policy.model_unknown reason. This closes the bypass for Bedrock, Vertex, and any future URL-routed provider in one place. When no allowlist is configured, behavior is unchanged.

The model allowlist is enforced solely in the proxy llm_guardrail; management's CheckLLMPolicyLimits handles only token/budget caps, so no management change is required.

https://github.com/netbirdio/netbird/discussions/6751

Stack

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)
  • This change does not modify the public API, gRPC protocols, functionality behavior, CLI / service flags, or introduce a new feature — OR I have discussed it with the NetBird team beforehand (link the issue / Slack thread in the description). See CONTRIBUTING.md.

By submitting this pull request, you confirm that you have read and agree to the terms of the Contributor License Agreement.

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

Bug fix that restores the documented allowlist behavior; no user-facing surface changes.

Docs PR URL (required if "docs added" is checked)

Paste the PR link from https://github.com/netbirdio/docs here:

https://github.com/netbirdio/docs/pull/__

Tests

  • llm_guardrail: absent/empty model under a configured allowlist now denies (model_unknown); empty allowlist still allows a missing model (fail-closed only applies when a list is set); existing allow/deny/case-insensitive cases retained.
  • llm_request_parser: new parser→guardrail integration test drives real Bedrock (/model/{id}/invoke) and Vertex (/v1/projects/.../models/...) URL shapes and asserts allowed→200, disallowed→403 (model_blocked), and an unrecognized Bedrock action→403 (model_unknown, the #6751 regression guard).

Note: a full through-tunnel e2e for the allowlist is intentionally deferred — the agent-network e2e (WaitProxyPeer) is currently red on main/0.74.x for an unrelated lazy-connection reason; it will be added once that harness gate is fixed.

**Original Pull Request:** https://github.com/netbirdio/netbird/pull/6764 **State:** closed **Merged:** Yes --- ## Describe your changes The Agent Network policy Guardrail "Model Allowlist" was not enforced for providers whose model travels in the URL/path rather than the JSON body — most visibly AWS Bedrock (reported in netbirdio/netbird#6751), and the same class applies to Google Vertex. Root cause: the `llm_guardrail` allowlist check **failed open**. `evaluateAllowlist` returned allow whenever the request model was absent from the metadata bag (`middleware.go`, `if !modelPresent { return nil }`). The model is stamped upstream by `llm_request_parser`; for body-routed providers (OpenAI/Anthropic) it comes from the JSON body, but for path-routed providers the model is recovered only when the request matches a recognized path shape (Bedrock `/model/{id}/{invoke|converse|...}`, Vertex `/v1/projects/.../publishers/.../models/...`). Any shape the parser did not recognize reached the guardrail with no model and was allowed regardless of the allowlist. Fix (provider-agnostic): **fail closed**. When an allowlist is configured and the model cannot be determined (absent or empty), the request is denied `403` with a distinct `llm_policy.model_unknown` reason. This closes the bypass for Bedrock, Vertex, and any future URL-routed provider in one place. When no allowlist is configured, behavior is unchanged. The model allowlist is enforced solely in the proxy `llm_guardrail`; management's `CheckLLMPolicyLimits` handles only token/budget caps, so no management change is required. ## Issue ticket number and link <https://github.com/netbirdio/netbird/discussions/6751> ## Stack - \#6726 <!-- branch-stack --> - \#6764 :point\_left: ### Checklist - [x] Is it a bug fix - [ ] Is a typo/documentation fix - [ ] Is a feature enhancement - [ ] It is a refactor - [x] Created tests that fail without the change (if possible) - [x] This change does **not** modify the public API, gRPC protocols, functionality behavior, CLI / service flags, or introduce a new feature — **OR** I have discussed it with the NetBird team beforehand (link the issue / Slack thread in the description). See [CONTRIBUTING.md](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTING.md#discuss-changes-with-the-netbird-team-first). > By submitting this pull request, you confirm that you have read and agree to the terms of the [Contributor License Agreement](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT.md). ## Documentation Select exactly one: - [ ] I added/updated documentation for this change - [x] Documentation is **not needed** for this change (explain why) Bug fix that restores the documented allowlist behavior; no user-facing surface changes. ### Docs PR URL (required if "docs added" is checked) Paste the PR link from <https://github.com/netbirdio/docs> here: <https://github.com/netbirdio/docs/pull/>\_\_ ## Tests - `llm_guardrail`: absent/empty model under a configured allowlist now denies (`model_unknown`); empty allowlist still allows a missing model (fail-closed only applies when a list is set); existing allow/deny/case-insensitive cases retained. - `llm_request_parser`: new parser→guardrail integration test drives real **Bedrock** (`/model/{id}/invoke`) and **Vertex** (`/v1/projects/.../models/...`) URL shapes and asserts allowed→200, disallowed→403 (`model_blocked`), and an unrecognized Bedrock action→403 (`model_unknown`, the #6751 regression guard). Note: a full through-tunnel e2e for the allowlist is intentionally deferred — the agent-network e2e (`WaitProxyPeer`) is currently red on `main`/`0.74.x` for an unrelated lazy-connection reason; it will be added once that harness gate is fixed.
saavagebueno added the pull-request label 2026-08-05 08:09:14 -04:00
Sign in to join this conversation.
No Label pull-request
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DYNR/netbird#29873