OpenAI-Proxy-PHP: The tiny PHP gatekeeper that keeps OpenAI keys off the client

A single-file middleware layer signs requests, freezes the model and prompt server-side, and turns image uploads into temporary URLs. It is less a proxy than a programmable security fence for AI apps.

7 min read · adamlyttleapps/OpenAI-Proxy-PHP

A wide editorial scene of a mobile app handing a glowing key toward a server checkpoint, with the key stopped behind a locked gate while a sealed request packet passes through. The image explains that the proxy’s real job is keeping secrets on the server side.
The proxy is not just a relay. It is a checkpoint that keeps the OpenAI key off the client.
Key Takeaways

The dangerous part of AI apps is the key, not the model

If an app ships with an OpenAI API key inside it, the key is already public the moment someone inspects the binary or watches traffic closely enough. The model can be swapped later. The leaked key can be abused immediately.

OpenAI-Proxy-PHP attacks that problem with a boring toolchain and a sharp boundary. The client talks to PHP. PHP decides whether the request is authentic, what prompt it gets, and which model it can reach.

A single file becomes a trust boundary

The whole repo lives in one file, openai_proxy.php. No framework, no build step, no package graph. That is not just minimalism. It is a way to make every policy decision easy to audit.

Once you read it that way, the code stops looking like a pass-through and starts looking like policy. The proxy is where trust is granted, narrowed, or denied. Everything else is just transport.

A close-up editorial scene of a wax-sealed envelope being checked against a matching ledger entry on a server desk. The image explains the shared-secret pattern, where the server recomputes the expected hash and rejects any mismatch.
The shared secret turns request validation into a physical-looking checkpoint.

How the shared-secret check actually blocks abuse

The client sends the messages plus a hash. The server recomputes the expected hash with a shared secret, then compares them with hash_equals. If the values do not match, the request dies before it can spend tokens.

That tiny move matters. hash_equals closes the timing leak that a naive string compare can open, so the proxy is not just checking identity. It is checking it in constant time.

$expected_hash = hash_hmac('sha256', $_POST['messages'], $shared_secret_key);
if (!hash_equals($expected_hash, $_POST['hash'])) {
    http_response_code(403);
    exit('Invalid hash');
}

The proxy is doing governance, not just transport.

The server owns the prompt, the model, and the temperature

This is where the repo becomes opinionated. The client can supply messages, but the server injects the system prompt, hardcodes the model, and controls sampling settings. The proxy is not neutral middleware. It is policy enforcement.

That matters when you are shipping a frontend or mobile app. Once prompt and model move server-side, you can change behavior without shipping a new client, and users cannot quietly steer the model around your guardrails.

A medium editorial scene of a server clerk placing a rigid template sheet over a messy stack of client notes on a desk. The image explains that the server inserts the system prompt and controls the model settings, while client messages remain below.
The client contributes input, but the server owns the template on top.

The image trick is the most interesting part

The repo’s strangest move is also its most pragmatic. Base64 image data is validated, written to /tmp, then exposed as a temporary URL that OpenAI can fetch as if it were a normal hosted asset.

That lets a lightweight PHP script support multimodal requests without building a media pipeline. It also creates new responsibilities: cleanup, public URL exposure, filesystem permissions, and the possibility that temporary files outlive their welcome.

A wide editorial scene showing a ribbon of encoded image data unspooling from an upload form, passing through a temporary file cabinet, and emerging as a short-lived URL tag for an API request. The image explains how base64 becomes a hosted asset for multimodal input.
The script turns an upload into a temporary resource the API can fetch.

Why this is elegant, and where it hurts

The elegance is obvious once you compare it with a fuller backend. There is very little to deploy, very little to update, and very little surface area to audit. For a narrow AI gateway, that is a feature.

The trade-offs are just as clear. Requests are synchronous, temp files need lifecycle management, the server needs write access, and the public file path is part of the security story whether you want it to be or not.

A split editorial scene. On one side, a phone app shows a visible embedded key and a cracked screen. On the other, a tidy PHP checkpoint stands between the app and the API, while a small pile of temporary files accumulates nearby. The image explains the security win and the operational cost.
The proxy removes key exposure, but it replaces it with file and process management.
CriterionDirect client keyOpenAI-Proxy-PHPFull backend or gateway
Secret exposureThe key ships with the app.The key stays on the server.The key stays on the server.
Prompt controlThe client can alter behavior.The server fixes the prompt and model.The server can fix prompt, policy, and routing.
Image handlingAwkward or unsafe.Base64 becomes a temporary URL.Usually handled by storage, queues, or a media service.
ComplexityLow code, high risk.Low code, moderate ops.Higher code, higher ops.
Operational burdenAlmost none until the key leaks.Cleanup and file permissions matter.More infrastructure, more moving parts.
Best fitNever for secrets.Small frontends and mobile apps that need a narrow gate.Teams that need broader policy, logging, and scale.

What this repo is really for

This is for builders who want the smallest possible server shim between a client and OpenAI. Not a platform. Not a framework. A controlled doorway.

If your problem is, "I need the app to call AI without shipping the key," this repo is enough to start. If your problem is, "I need full governance, observability, retries, and queues," it is the wrong tool on purpose.