Executor version
1.6.10
How do you run Executor?
CLI
Operating system
Linux
Integration involved
MCP Server
What happened
Local Executor behind a public HTTPS reverse proxy uses loopback-only CIMD client metadata
Summary
When the local/supervised Executor is exposed through a public HTTPS reverse proxy and EXECUTOR_WEB_BASE_URL points to that public origin, OAuth providers supporting Client ID Metadata Documents (CIMD) can reject authorization with:
Invalid redirect URI. The redirect URI provided does not match any registered URI for this client.
I can reproduce this with the official Linear MCP server.
Environment
- Executor CLI running as the supervised Linux user service
- Executor itself bound locally
- Nginx reverse proxy providing public HTTPS
EXECUTOR_WEB_BASE_URL=https://executor-mcp.example.com
- MCP server:
https://mcp.linear.app/mcp
Actual behavior
The authorization request generated for Linear contains:
client_id=https://executor.sh/api/oauth/client-id-metadata/local.json
redirect_uri=https://executor-mcp.example.com/api/oauth/callback
Linear rejects the request with:
Invalid redirect URI. The redirect URI provided does not match any registered URI for this client.
This appears correct from the provider's point of view.
local.json is intentionally a native/loopback CIMD document. In packages/core/api/src/server/oauth-client-metadata.ts, localLoopbackRedirectUris() advertises only:
http://127.0.0.1/api/oauth/callback
http://localhost/api/oauth/callback
http://[::1]/api/oauth/callback
However, the local frontend is compiled with:
VITE_EXECUTOR_CIMD_CLIENT_ID_METADATA_BASE_URL=https://executor.sh
and oauthClientIdMetadataDocumentUrl() in packages/react/src/plugins/oauth-sign-in.tsx therefore selects:
https://executor.sh/api/oauth/client-id-metadata/local.json
even when the UI is actually being accessed through a non-loopback public origin.
At the same time, the OAuth callback is correctly derived from the public Executor origin:
https://executor-mcp.example.com/api/oauth/callback
This results in a CIMD client whose declared redirect_uris do not contain the redirect_uri sent in the authorization request.
Root cause
oauthClientIdMetadataDocumentUrl() currently prefers the configured hosted metadata base whenever it exists:
const hostedBaseUrl =
options?.hostedBaseUrl ?? configuredClientIdMetadataBaseUrl();
if (hostedBaseUrl) {
return new URL(
OAUTH_CLIENT_ID_METADATA_DOCUMENT_LOCAL_PATH,
hostedBaseUrl,
).toString();
}
The local Vite build defaults that base to:
Consequently, a local Executor accessed through a public reverse-proxy origin still identifies itself as the hosted loopback/native CIMD client.
Suggested fix
Use the hosted local.json document only when the current browser origin is actually loopback.
For non-loopback origins, fall through to the existing current-origin default.json behavior.
Conceptually:
const isLoopbackHostname = (hostname: string): boolean =>
hostname === "localhost" ||
hostname === "127.0.0.1" ||
hostname === "::1" ||
hostname === "[::1]";
const useHostedLocalDocument =
hostedBaseUrl &&
(typeof window === "undefined" ||
isLoopbackHostname(window.location.hostname));
if (useHostedLocalDocument) {
return new URL(
OAUTH_CLIENT_ID_METADATA_DOCUMENT_LOCAL_PATH,
hostedBaseUrl,
).toString();
}
This keeps the existing CLI/Desktop loopback behavior while allowing a reverse-proxied local Executor to expose a CIMD document whose redirect URI matches its public callback.
What you expected
Expected behavior
When local Executor is accessed directly on loopback, the current hosted native-client behavior should remain unchanged:
http://localhost:4788
->
https://executor.sh/api/oauth/client-id-metadata/local.json
When the same local Executor is accessed through a non-loopback HTTPS origin, the client metadata document should instead be served from that origin, for example:
https://executor-mcp.example.com/api/oauth/client-id-metadata/default.json
That document then advertises:
https://executor-mcp.example.com/api/oauth/callback
which matches the redirect URI used by the OAuth flow.
Steps to reproduce
Reproduction
-
Install Executor CLI as the supervised service.
-
Put it behind an HTTPS reverse proxy.
-
Configure:
EXECUTOR_WEB_BASE_URL=https://executor-mcp.example.com
-
Open Executor through that HTTPS URL.
-
Add the official Linear MCP server:
https://mcp.linear.app/mcp
-
Start OAuth authorization.
The resulting Linear /authorize request contains approximately:
client_id=https%3A%2F%2Fexecutor.sh%2Fapi%2Foauth%2Fclient-id-metadata%2Flocal.json
redirect_uri=https%3A%2F%2Fexecutor-mcp.example.com%2Fapi%2Foauth%2Fcallback
- Linear responds with:
Invalid redirect URI. The redirect URI provided does not match any registered URI for this client.
Diagnostics / logs
No response
Before you submit
Executor version
1.6.10
How do you run Executor?
CLI
Operating system
Linux
Integration involved
MCP Server
What happened
Local Executor behind a public HTTPS reverse proxy uses loopback-only CIMD client metadata
Summary
When the local/supervised Executor is exposed through a public HTTPS reverse proxy and
EXECUTOR_WEB_BASE_URLpoints to that public origin, OAuth providers supporting Client ID Metadata Documents (CIMD) can reject authorization with:I can reproduce this with the official Linear MCP server.
Environment
EXECUTOR_WEB_BASE_URL=https://executor-mcp.example.comhttps://mcp.linear.app/mcpActual behavior
The authorization request generated for Linear contains:
Linear rejects the request with:
This appears correct from the provider's point of view.
local.jsonis intentionally a native/loopback CIMD document. Inpackages/core/api/src/server/oauth-client-metadata.ts,localLoopbackRedirectUris()advertises only:However, the local frontend is compiled with:
and
oauthClientIdMetadataDocumentUrl()inpackages/react/src/plugins/oauth-sign-in.tsxtherefore selects:even when the UI is actually being accessed through a non-loopback public origin.
At the same time, the OAuth callback is correctly derived from the public Executor origin:
This results in a CIMD client whose declared
redirect_urisdo not contain theredirect_urisent in the authorization request.Root cause
oauthClientIdMetadataDocumentUrl()currently prefers the configured hosted metadata base whenever it exists:The local Vite build defaults that base to:
Consequently, a local Executor accessed through a public reverse-proxy origin still identifies itself as the hosted loopback/native CIMD client.
Suggested fix
Use the hosted
local.jsondocument only when the current browser origin is actually loopback.For non-loopback origins, fall through to the existing current-origin
default.jsonbehavior.Conceptually:
This keeps the existing CLI/Desktop loopback behavior while allowing a reverse-proxied local Executor to expose a CIMD document whose redirect URI matches its public callback.
What you expected
Expected behavior
When local Executor is accessed directly on loopback, the current hosted native-client behavior should remain unchanged:
When the same local Executor is accessed through a non-loopback HTTPS origin, the client metadata document should instead be served from that origin, for example:
That document then advertises:
which matches the redirect URI used by the OAuth flow.
Steps to reproduce
Reproduction
Install Executor CLI as the supervised service.
Put it behind an HTTPS reverse proxy.
Configure:
Open Executor through that HTTPS URL.
Add the official Linear MCP server:
Start OAuth authorization.
The resulting Linear
/authorizerequest contains approximately:Diagnostics / logs
No response
Before you submit