Skip to content

fix: OpenAI image plugin 403 on project-scoped keys - #60749

Open
perelin wants to merge 1 commit into
NousResearch:mainfrom
perelin:fix/openai-image-project-header-and-dashboard-auth
Open

fix: OpenAI image plugin 403 on project-scoped keys#60749
perelin wants to merge 1 commit into
NousResearch:mainfrom
perelin:fix/openai-image-project-header-and-dashboard-auth

Conversation

@perelin

@perelin perelin commented Jul 8, 2026

Copy link
Copy Markdown

Summary

Narrowed to the OpenAI image plugin issue following review feedback. The dashboard-auth hunk from the original submission has been dropped — current main (3e24b16f5) already ships the more complete implementation (proxy prefix, next validation/encoding, open-redirect defense).

Problem

Project-scoped OpenAI API keys (sk-proj-…) fail with 403 on image-generation calls. The pinned OpenAI 2.24.0 client derives project only from the constructor argument or OPENAI_PROJECT_ID (openai/_client.py:153-155), not from the API key itself. An earlier hypothesis — that default_headers={"OpenAI-Project": ""} makes the SDK inject the project from key association — does not hold; the SDK overlays custom headers after its own OpenAI-Project (:340-347), so this sends a literal empty header.

Next steps (per review)

  • Rebase onto current main; drop the dashboard-auth hunk entirely.
  • Run a 2×2 reproduction matrix: project-scoped key × {OPENAI_PROJECT_ID set / unset} × {default client / explicit empty header}.
  • Record the actual request headers and API result (without leaking key or project id).
  • Add focused header-construction regression coverage in tests/plugins/image_gen/test_openai_provider.py.
  • If the empty-header workaround holds, reframe the justification as empirical API-side behavior — not an SDK-injected project.
  • If the repro cannot be re-established reliably, close the PR rather than defend a one-liner on a shaky premise.

Edit note

PR title and body edited to reflect the narrowed scope; the dashboard-auth portion was dropped because it is covered by 3e24b16f5.

…500 on password-only providers

Two fixes:

1. OpenAI image generation plugin (`plugins/image_gen/openai`):
   The OpenAI SDK auto-injects the `OpenAI-Project` header from the API
   key's associated project. For the image generation endpoint, this
   triggers a project-level model access check that fails with 403 for
   project-scoped keys that have a restricted model allow-list — even
   when the model is explicitly in the project's allowed list.
   Fix: pass `default_headers={"OpenAI-Project": ""}` to bypass the
   project-level check and fall back to org-level access.

2. Dashboard auth routes (`hermes_cli/dashboard_auth/routes.py`):
   The `/auth/login?provider=basic` endpoint called `start_login()` on
   password-only providers, which raises `NotImplementedError` (500).
   Fix: check `supports_password` before calling `start_login()` and
   redirect to `/login` (which renders the password form) instead.

Closes NousResearch#60748
@alt-glitch alt-glitch added type/bug Something isn't working comp/dashboard Web dashboard / control panel UI (dashboard/, landing) comp/plugins Plugin system and bundled plugins area/auth Authentication, OAuth, credential pools provider/openai OpenAI / Codex Responses API tool/vision Vision analysis and image generation P2 Medium — degraded but workaround exists labels Jul 8, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for investigating both setup paths. The dashboard portion has since landed independently, while the image change needs revalidation against the pinned SDK.

Problems

  • hermes_cli/dashboard_auth/routes.py:195-202 on current main (commit 3e24b16f5) already redirects password providers, validates and encodes next, and preserves the proxy prefix. The PR's changed redirect uses a static /login and interpolates raw next, so that hunk should not be salvaged.
  • The pinned OpenAI 2.24.0 SDK obtains project only from the argument or OPENAI_PROJECT_ID (.venv/lib/python3.11/site-packages/openai/_client.py:153-155), not from API-key association. Its header assembly overlays custom headers after OpenAI-Project (:340-346), so the proposed value sends an explicit empty header. tests/plugins/image_gen/test_openai_provider.py:201-280 does not cover client/header construction, and the PR adds no regression test.

Suggested changes

  • Keep the current-main dashboard implementation and its E2E redirect coverage at tests/hermes_cli/test_dashboard_auth_password_login.py:211-217.
  • Reproduce the image case with OPENAI_PROJECT_ID and add a focused header-construction regression test before choosing a supported fix.

Automated hermes-sweeper review.


# Password-only providers don't have an OAuth redirect flow — redirect
# to the login page which renders the username/password form.
if getattr(p, "supports_password", False):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current main already has this redirect in 3e24b16f5, but it uses f"{_prefix(request)}/login" and validates/encodes next (routes.py:195-202). A static path here would break prefixed deployments, and raw next would discard the current open-redirect defense.

modality = "image" if is_edit else "text"

client = openai.OpenAI()
client = openai.OpenAI(default_headers={"OpenAI-Project": ""})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pinned OpenAI 2.24.0 client derives project only from OPENAI_PROJECT_ID, not from the API key (openai/_client.py:153-155), and custom default headers override its project header (:340-346). This sends a literal empty header rather than demonstrating omission; please establish the supported repro and add a constructor/header regression test before changing the client.

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 10, 2026
@perelin

perelin commented Jul 12, 2026

Copy link
Copy Markdown
Author

Thanks — agreed on both points. I'll rebase onto current main and drop the dashboard-auth hunk entirely; 3e24b16f5 already ships the more complete implementation (proxy prefix, next validation/encoding, open-redirect defense), so my older variant would be a regression. I'll narrow the PR title and body to the image issue and leave an edit note here so the scope change stays traceable.

For the image issue, my original explanation was too strong: OpenAI 2.24.0 does not derive project client-side from the API key (openai/_client.py:153-155), and default_headers={"OpenAI-Project": ""} sends a literal empty header rather than omitting it (:340-347). The observed behavior was that an explicitly empty OpenAI-Project header changed the API response for the same project-scoped sk-proj-… key, but that still needs a controlled reproduction.

Plan: narrow the PR to the image issue, run a 2×2 matrix (project-scoped key × {OPENAI_PROJECT_ID set / unset} × {default client / explicit empty header}), record the actual request headers and API result (without leaking key or project id), and add focused header-construction coverage. If the workaround holds, I'll reframe the justification as empirical API-side behavior — not an SDK-injected project. If the repro can't be re-established reliably, I'll close the PR rather than defend a one-liner on a shaky premise.

@perelin perelin changed the title fix: OpenAI image plugin 403 on project-scoped keys + dashboard auth 500 on password-only providers fix: OpenAI image plugin 403 on project-scoped keys Jul 12, 2026
@perelin

perelin commented Jul 12, 2026

Copy link
Copy Markdown
Author

Edited PR title and body to reflect the narrowed scope (dashboard-auth portion dropped — covered by 3e24b16f5).

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

Labels

area/auth Authentication, OAuth, credential pools comp/dashboard Web dashboard / control panel UI (dashboard/, landing) comp/plugins Plugin system and bundled plugins P2 Medium — degraded but workaround exists provider/openai OpenAI / Codex Responses API sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data tool/vision Vision analysis and image generation type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants