Skip to content

FEAT: Add a "no key" auth mode for OpenAI-compatible targets - #3809

Open
varunj-msft wants to merge 3 commits into
microsoft:mainfrom
varunj-msft:varunj-msft/13178-No-Key-Auth-Mode
Open

varunj-msft wants to merge 3 commits into
microsoft:mainfrom
varunj-msft:varunj-msft/13178-No-Key-Auth-Mode

Conversation

@varunj-msft

Copy link
Copy Markdown
Contributor

Description

This is the "No key" option from the work on saving Registry entries across restarts. It doesn't depend on the rest of that work.

Problem. An OpenAI-compatible server that needs no key, such as Ollama, vLLM or LM Studio, can't be added without one. Leaving the key empty uses the server's OPENAI_CHAT_KEY and OPENAI_ADDITIONAL_REQUEST_HEADERS and sends them to that endpoint, or fails with "No API key available" when the variable isn't set. The workaround is typing a dummy key.

Change. A new auth_mode="none", offered by the OpenAI targets:

  • PyRIT reads no key (argument or variable), mints no Entra ID token and skips OPENAI_ADDITIONAL_REQUEST_HEADERS. The OpenAI SDK requires a key, so the target passes it not-needed, the placeholder the startup initializer already uses for keyless endpoints; servers that need no key ignore it. An explicit api_key with "none" raises in Python, and the API drops it, as it does for identity.
  • A copy of a no-key target (for example a temperature change in chat) stays no-key, even if the copy sets api_key or headers to null. A copy that passes a key, even an empty one, is rejected unless it also sets auth_mode to api_key.
  • /api/targets/types lists none for the OpenAI targets, and the create dialog shows a "No key" option for target types that list it (and for the OpenAI targets when the type list can't load), without the key field.
  • Azure ML, Prompt Shield and Blob Storage also take AuthMode, so they now reject a mode they don't list (a small PromptTarget._validate_auth_mode) instead of treating "none" as a key.
  • The backend request and type models and TargetIdentifier use the shared AuthMode instead of their own two-value copies.

Why a placeholder rather than dropping the header: the SDK rejects a request without a credential unless that request omits the header itself, and some calls can't (video polling takes no per-request headers), so dropping it would mean changing every call site for a header these servers ignore.

What stays the same. The default (api_key), identity, the deprecated Entra ID fallback, the empty-key behavior of api_key mode, and existing targets and their identifier hashes. The OpenAI SDK still reads its own OPENAI_ORG_ID, OPENAI_PROJECT_ID and OPENAI_CUSTOM_HEADERS in every mode.

#3600 and #3777 change lines next to this PR's in pyrit/backend/models/targets.py and tests/unit/registry/test_target_registry.py, so whichever merges second needs a small merge.

Tests and Documentation

  • New: the resolver reads no key, mints no token and rejects an explicit key; the target ignores the key and headers variables and keeps explicit headers; the API creates a no-key target that ignores the server's credentials and drops a posted key, and rejects none for types that don't list it; copies stay keyless, including with api_key or headers set to null or headers set to an empty string; Azure ML, Prompt Shield and Blob reject none; /types lists it for the OpenAI targets only; the dialog offers and submits "No key", doesn't offer it for Azure ML, falls back to API key when switching to such a type, and offers it for the OpenAI targets when the type list can't load.
  • pytest -n 4 tests/unit/auth tests/unit/embedding tests/unit/models/identifiers tests/unit/models/test_import_boundary.py tests/unit/registry/test_target_registry.py tests/unit/backend/test_target_service.py tests/unit/backend/test_temporary_components.py tests/unit/backend/test_api_routes.py::TestTargetRoutes tests/unit/prompt_target/target/test_openai_target_auth.py tests/unit/prompt_target/target/test_openai_chat_target.py tests/unit/prompt_target/target/test_realtime_target.py tests/unit/prompt_target/target/test_prompt_target.py tests/unit/prompt_target/target/test_azure_ml_chat_target.py tests/unit/prompt_target/target/test_prompt_shield_target.py tests/unit/prompt_target/target/test_prompt_target_azure_blob_storage.py: 1193 passed.
  • npx jest src/components/Config/CreateTargetDialog.test.tsx: 75 passed. npx eslint --max-warnings 0 on the changed frontend files: clean.
  • uv run --frozen --extra all --link-mode=copy ty check pyrit: all checks passed. pre-commit: passed.
  • Live, against local backends built from this branch and from main, with the server's OPENAI_CHAT_KEY and OPENAI_ADDITIONAL_REQUEST_HEADERS set to test values, creating an OpenAIChatTarget for a local OpenAI-compatible test server and sending a chat message through the attacks API: on main, auth_mode: "none" is a 422, and an empty-key target sends the server's key and extra header to that server. On this branch, auth_mode: "none" is a 201, the message gets the server's reply, and the server receives Authorization: Bearer not-needed and no server header. AzureMLChatTarget with none is a 400.
  • Docs: doc/code/setup/1_configuration.py and its notebook get a paragraph and example for auth_mode="none" (Markdown only, both files edited); the backend README's trust-boundary list mentions the opt-out.

OpenAI-compatible servers such as Ollama and vLLM need no key, but a target
could only be created with one: an empty key fell back to the server's key
variable or failed with "No API key available", so users typed a dummy key.

Add auth_mode "none" for the OpenAI targets. It reads no key, mints no Entra
token and skips OPENAI_ADDITIONAL_REQUEST_HEADERS, and passes the SDK the
same non-secret "not-needed" placeholder the startup initializer already
uses. Copies of a no-key target stay no-key. The create API and the target
dialog offer it only for target types that list it; Azure ML, Prompt Shield
and Blob Storage reject it.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant