Keep the OpenAI API key out of the browser bundle - #66
Merged
Jack Williams (jack-williams) merged 6 commits intoSep 3, 2026
Merged
Conversation
Copilot started reviewing on behalf of
Jack Williams (jack-williams)
September 3, 2026 12:11
View session
There was a problem hiding this comment.
🟡 Changes recommended
The unbounded request body and unresolved proxy configuration and test coverage issues must be addressed.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Moves OpenAI credentials out of browser bundles by introducing a Vite-hosted reverse proxy and updating both reference apps.
Changes:
- Adds OpenAI/Azure credential injection, forwarding, and streaming.
- Migrates chat and image apps to server-only credentials.
- Updates image generation to
gpt-image-1.
File summaries
| File | Description | Review |
|---|---|---|
yarn.lock |
Registers proxy dependencies. | No issues. |
README.md |
Documents secure configuration and migration. | Nit (2 votes), line 104: Correct the migration note: the legacy key prevents startup rather than warning. Nit (1 vote), line 153: Include OPENAI_IMAGE_MODEL among injected client values. |
packages/promptions-openai-proxy/tsconfig.json |
Configures proxy type-checking. | No issues. |
packages/promptions-openai-proxy/src/index.js |
Implements proxy configuration and forwarding. | Critical (1 vote), line 225: Bound or stream request bodies to prevent unbounded memory consumption. Moderate (1 vote), line 114: Prevent build-time client settings from diverging from preview-time proxy settings. |
packages/promptions-openai-proxy/src/index.d.ts |
Exposes plugin API types. | No issues. |
packages/promptions-openai-proxy/README.md |
Documents proxy usage and limitations. | No issues. |
packages/promptions-openai-proxy/project.json |
Defines the Nx project. | Moderate (1 vote), line 7: Commit the claimed security-sensitive tests and expose a test target. |
packages/promptions-openai-proxy/package.json |
Defines the proxy package. | No issues. |
apps/promptions-image/vite.config.ts |
Enables the proxy plugin. | No issues. |
apps/promptions-image/src/vite-env.d.ts |
Declares injected proxy settings. | No issues. |
apps/promptions-image/src/types.ts |
Updates image parameter types. | No issues. |
apps/promptions-image/src/services/ImageService.ts |
Routes API requests through the proxy. | No issues. |
apps/promptions-image/src/App.tsx |
Selects gpt-image-1. |
No issues. |
apps/promptions-image/package.json |
Adds the proxy dependency. | No issues. |
apps/promptions-image/.env.example |
Documents server-side configuration. | No issues. |
apps/promptions-chat/vite.config.ts |
Enables the proxy plugin. | No issues. |
apps/promptions-chat/src/vite-env.d.ts |
Declares injected proxy settings. | No issues. |
apps/promptions-chat/src/services/ChatService.ts |
Routes chat requests through the proxy. | No issues. |
apps/promptions-chat/README.md |
Updates setup and security guidance. | No issues. |
apps/promptions-chat/package.json |
Adds the proxy dependency. | No issues. |
apps/promptions-chat/.env.example |
Documents secure environment configuration. | No issues. |
Review details
Suppressed comments (1)
README.md:153
OPENAI_IMAGE_MODELis also injected into client code by the plugin, so the parenthetical list is incomplete and contradicts the configuration table below.
Both apps read these variables from their respective `.env` files. They are read by the dev/preview server only and are never sent to the browser (except `OPENAI_API_VERSION` and `OPENAI_MODEL`, which are not secret).
- Files reviewed: 17/21 changed files
- Comments generated: 4
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
VITE_OPENAI_API_KEY was inlined into the shipped JavaScript by Vite and used client-side with dangerouslyAllowBrowser, so any visitor could extract the credential from the served assets. Add @promptions/promptions-openai-proxy, a Vite plugin that proxies requests through the dev and preview servers. The key is now read from OPENAI_API_KEY without the VITE_ prefix, which makes inlining impossible, and is attached to requests server-side. The browser calls a same-origin /api/openai path with a placeholder credential. The proxy forwards the path and query verbatim, so both OpenAI and Azure OpenAI URL shapes work unchanged, and streams responses back so SSE token streaming is unaffected. OPENAI_API_STYLE overrides the inferred auth scheme for OpenAI-compatible backends that need a custom base URL. dangerouslyAllowBrowser remains set because the SDK requires it to run in a browser at all, but there is no longer a real credential to expose. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The current OpenAI images API rejects `response_format` and no longer serves `dall-e-3` or `dall-e-2` on new keys, so image generation failed with `unknown_parameter` / `model does not exist`. gpt-image-1 returns base64 payloads by default, so the parameter is redundant. Drop the DALL-E parameter type and correct the gpt-image-1 size and quality unions to the values the API accepts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ents Follow-up to code review of the proxy change: - Fail startup when VITE_OPENAI_API_KEY is set. Vite serves the whole import.meta.env object to the browser in dev, so a key left over from the pre-proxy setup is still handed to every page visitor even though no code references it. Warn about any other credential-shaped VITE_ variable (KEY/SECRET/TOKEN/PASSWORD). - Add OPENAI_IMAGE_MODEL. On Azure the SDK turns the request's model into a deployment name, so hardcoding gpt-image-1 404s unless the deployment happens to share the model id. - Send x-should-retry: false with the unconfigured-proxy 500 so the SDK does not turn a missing key into three requests and several seconds of backoff. - Document the migration in both .env.example files and the READMEs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Cap proxied request bodies at 10 MB (413, no SDK retry) so a browser cannot stream an unbounded payload through the dev/preview server. - Emit the resolved non-secret client config into the build and warn on �ite preview when dist/ was built with different proxy settings. - Add a node:test suite covering config resolution, build/preview drift and forwarding (credential injection, auth styles, streaming, 413, missing key), wired into a est target and the CI workflow. - Clarify the README migration note and the injected client values. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Neither package has any test files and jest is not a dependency anywhere in the workspace, so both targets failed unconditionally. They only surfaced now that CI runs \yarn test\. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Aligns with the 7.3.5 the apps use so the lockfile does not gain a second vite resolution and its esbuild platform packages. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Jack Williams (jack-williams)
force-pushed
the
fix/openai-key-server-proxy
branch
from
September 3, 2026 13:12
fdbdba7 to
3d48e6c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
VITE_OPENAI_API_KEYwas read in the browser and passed to the OpenAI/Azure SDK withdangerouslyAllowBrowser: true. Vite inlines everyVITE_-prefixed variable into the shipped JavaScript, so the key was extractable from the static assets by any page visitor — an anonymous credential disclosure with cost-abuse impact.Approach
A credential-injecting reverse proxy, hosted inside the Vite dev/preview server as a plugin (
packages/promptions-openai-proxy).OPENAI_API_KEYwithout theVITE_prefix, which makes inlining structurally impossible rather than merely unused./api/openai, no hardcoded host) using a literal placeholder credential; the server swaps in the real key./v1/...) and Azure (/openai/deployments/<name>/...?api-version=...) URL shapes work unchanged.OPENAI_BASE_URLand can be overridden withOPENAI_API_STYLE:Authorization: Bearerfor OpenAI, the rawapi-keyheader for Azure.accept,content-typeandopenai-betaare forwarded upstream; cookies and the client's placeholder credential are dropped. Responses stream unbuffered so SSE token streaming still works, and a browser disconnect aborts the upstream request.The dev server fails to start if
VITE_OPENAI_API_KEYis still set, and warns about any other credential-shapedVITE_variable. This matters because Vite serves the entireimport.meta.envobject to the browser in dev, so a key left over from the previous setup stays exposed even after the code stops reading it — deleting the code alone does not fix an existing checkout.Also included
gpt-image-1replacesdall-e-3/dall-e-2, which no longer exist on current API keys; the images API also now rejectsresponse_format. Size/quality unions intypes.tswere corrected to match.OPENAI_IMAGE_MODEL— on Azure the SDK turns the request'smodelinto a deployment name, so a hardcodedgpt-image-1404s unless the deployment happens to share the model id.x-should-retry: false, so a missing key produces one request instead of three plus backoff.Scope
This is a reference implementation. The proxy is active for
vite devandvite previewonly, and it is an unauthenticated pass-through — anyone who can reach the dev server can spend the key. A static deployment ofdist/has no server and needs an equivalent proxy (for example a serverless function holding the key) in front of it. Both caveats are documented in the package README.Verification
definecontents, retry header)import.meta.env, browser storage and the live DOMimport.meta.envpayload inspected directly: contains only path, mode, api-version and modelprettier:check,typecheck(5 projects) andbuild(4 projects) pass; built bundles contain only the placeholder and the proxy pathUpgrading
Existing
.envfiles must be migrated: renameVITE_OPENAI_API_KEYtoOPENAI_API_KEYand rotate the key, since it was previously served to browsers. The dev server refuses to start until this is done.