Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ All notable changes to this project will be documented in this file. The format

### Added

- [#81](https://github.com/mohanagy/miftah/issues/81) Added configuration format v3 and a strict, non-secret OAuth connection core: opaque profile/upstream/resource/issuer bindings, canonical HTTPS Streamable HTTP validation, static-Authorization collision refusal, OS-vault credential isolation, crash-released binding-scoped transaction coordination, redacted lifecycle primitives, and v1/v2-to-v3 migration with no credential synthesis. Native OAuth remains intentionally disabled until the separate discovery, browser, callback, exchange, and header-injection work is complete; v3 OAuth declarations fail closed with `OAUTH_AUTHORIZATION_NOT_ENABLED`.
- [#81](https://github.com/mohanagy/miftah/issues/81) Added configuration format v3 and a strict, non-secret OAuth connection core: opaque profile/upstream/resource/issuer bindings, canonical HTTPS Streamable HTTP validation, static-Authorization collision refusal, OS-vault credential isolation, crash-released binding-scoped transaction coordination, redacted lifecycle primitives, and v1/v2-to-v3 migration with no credential synthesis.
- [#82](https://github.com/mohanagy/miftah/issues/82) Enabled standards-compatible OAuth for exact v3 HTTPS Streamable HTTP connections: protected-resource plus authorization-server/OpenID discovery, explicit pre-registered, Client ID Metadata, or Dynamic Client Registration, system-browser PKCE authorization through a single-use literal-loopback callback, RFC 9207 issuer validation, profile-bound OS-vault credentials, refresh and reconnect, typed redacted failures, and unchanged non-OAuth transport behavior. Provider-specific/local OAuth, revocation, and operator lifecycle commands remain outside this release surface.

## [0.3.3] - 2026-07-22

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ When you need the other product, select its profile deliberately. When a tool ca
- **Other compatible MCP servers:** configure a generic STDIO, Streamable HTTP, or legacy SSE upstream, then add profiles around it.
- **Multiple upstreams in one wrapper:** use an account bundle when related upstreams belong behind one controlled connection.

Miftah does not run provider OAuth browser, callback, refresh, or revocation flows. If an upstream owns its OAuth flow, complete that flow with the upstream or provider and then configure Miftah around the resulting supported connection. Read [OAuth support](docs/oauth-support.md) before assuming a provider OAuth model is compatible.
Miftah can authorize a standards-compatible remote HTTPS Streamable HTTP MCP server through discovery, PKCE, a bounded loopback browser callback, OS-vault storage, and refresh. Provider-specific or local STDIO OAuth remains owned by that upstream, and revocation/lifecycle commands are not available yet. Read [OAuth support](docs/oauth-support.md) for the exact compatibility and configuration boundary.

## Trust and control boundaries

Expand Down
5 changes: 3 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Architecture

The [threat model](threat-model.md) maps these components and their boundaries to public security controls and residual risks. The [OAuth and Console security design delta](oauth-console-threat-model.md) describes the remaining control-plane and credential-lifecycle gates. Version 3 adds a non-secret connection/vault core, but it does not add an enabled browser, callback, discovery, exchange, or header-injection runtime component.
The [threat model](threat-model.md) maps these components and their boundaries to public security controls and residual risks. The [OAuth and Console security design delta](oauth-console-threat-model.md) describes the control-plane and credential-lifecycle gates. Version 3 adds a standards-compatible remote OAuth runtime for exact HTTPS Streamable HTTP connections; provider adapters, operator lifecycle commands, revocation, and the local Console remain separate milestones.

Miftah is an MCP-aware proxy, not a byte-level reverse proxy:

Expand All @@ -16,7 +16,7 @@ MCP client
-> upstream STDIO server
```

The STDIO public server is built with the official `@modelcontextprotocol/sdk` `Server` and `StdioServerTransport`. Each profile/upstream pair gets an SDK `Client` and its configured transport on first use: local processes use `StdioClientTransport`; remote upstreams use Streamable HTTP by default (the historical `"http"` alias is accepted only by version 1) or the deprecated legacy SSE client. Resolved profile headers override upstream headers case-insensitively before either remote transport is constructed. Remote SDK transports receive those static headers only; they do not receive an OAuth client provider. A version-3 OAuth declaration is rejected before runtime construction with `OAUTH_AUTHORIZATION_NOT_ENABLED`, rather than being ignored or merged with static headers. Streamable HTTP session shutdown sends DELETE while its session ID is still available, then closes the local client transport; legacy SSE has no corresponding server-session teardown request. Request cancellation and requested progress notifications retain their MCP request context across both STDIO and Streamable HTTP upstreams. Startup HTTP failures and operation HTTP/MCP protocol failures are normalized before crossing the proxy boundary, retaining only stable category/code metadata rather than server response text. See [OAuth support](oauth-support.md) for the intentionally incomplete native OAuth boundary.
The STDIO public server is built with the official `@modelcontextprotocol/sdk` `Server` and `StdioServerTransport`. Each profile/upstream pair gets an SDK `Client` and its configured transport on first use: local processes use `StdioClientTransport`; remote upstreams use Streamable HTTP by default (the historical `"http"` alias is accepted only by version 1) or the deprecated legacy SSE client. Resolved profile headers override upstream headers case-insensitively before either remote transport is constructed. An exact version-3 OAuth binding gives only that Streamable HTTP session a profile-bound SDK OAuth provider; static Authorization headers on the same target are rejected. The provider validates protected-resource and authorization-server discovery, drives PKCE/browser/callback exchange, and loads or refreshes credentials through the exact OS-vault binding before reconnecting with bearer authorization. Streamable HTTP session shutdown sends DELETE while its session ID is still available, then closes the local client transport and any pending OAuth handoff; legacy SSE has no corresponding server-session teardown request. Request cancellation and requested progress notifications retain their MCP request context across both STDIO and Streamable HTTP upstreams. Startup HTTP failures and operation HTTP/MCP/OAuth failures are normalized before crossing the proxy boundary, retaining only stable category/code metadata rather than server response text. See [OAuth support](oauth-support.md) for the supported native OAuth boundary.

The optional HTTP host owns the listener and no shared Miftah session state. It validates the exact `/mcp` target, Host, Origin, bearer authentication, a bounded JSON initialize request, and capacity before creating a `StreamableHTTPServerTransport`. That transport and a new `MiftahServer`, `ProfileManager`, and upstream manager belong to one HTTP session only. The host forces the session runtime to in-memory `session` profile state, even if normal STDIO configuration uses workspace/global persistence. Its session registry owns only opaque MCP session IDs, idle timers, and cleanup tasks; DELETE, expiry, connection shutdown, and wrapper shutdown detach the record before asynchronous runtime cleanup so no client can reuse a closing runtime, while retaining that capacity reservation until cleanup succeeds. A rejected cleanup is recorded, retains capacity, and makes graceful shutdown fail rather than silently admitting unlimited replacement runtimes. A reconnect is routed to the existing transport by its session ID, while the SDK permits only one active standalone SSE stream for that session.

Expand Down Expand Up @@ -53,6 +53,7 @@ Configuration and runtime concerns are intentionally separate:
- `policy/` classifies proxied-operation risk and returns allow/deny/confirm decisions.
- `upstream/` owns child processes, MCP initialization, caching, health, and cleanup.
- `audit/` owns request scopes, redacted local JSONL metadata, restrictive file permissions, cross-process journal rotation/retention, optional local integrity evidence, snapshots, and audit health.
- `oauth/` owns exact connection bindings, OS-vault credentials, standards discovery validation, registration, refresh, and the bounded literal-loopback browser handoff.
- `mcp/server/` adapts those services to MCP operations and management tools.

The design keeps transport and multi-upstream seams in the config and interfaces. Remote endpoint validation requires HTTPS outside explicit loopback development URLs, and Node certificate validation remains enabled for remote TLS.
2 changes: 1 addition & 1 deletion docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ Use `transport: "streamable-http"` for new remote MCP servers. The historical `t

Remote URLs must use `https`. Miftah accepts `http` only for loopback development URLs on `localhost`, `127.0.0.0/8`, or `::1`; other cleartext URLs and non-HTTP URL schemes fail config validation at the exact `upstream.url` or `upstreams.<name>.url` path. Profile headers override upstream headers case-insensitively, so `Authorization` and `authorization` are one credential slot rather than two combined values.

Remote authentication is active only through explicit `headers`/secret references that the upstream documents. Version 3 also accepts a strict non-secret `oauth.connections` declaration for one exact HTTPS Streamable HTTP profile/upstream target, but it rejects token, refresh-token, client-secret, callback, and Authorization-header fields. An OAuth declaration is intentionally fail-closed at runtime with `OAUTH_AUTHORIZATION_NOT_ENABLED` until discovery, browser authorization, exchange, and safe remote-header injection are implemented. Unknown OAuth keys are rejected instead of being silently accepted. See [OAuth support](oauth-support.md) for the exact provider, local-stdio, and remote-HTTP boundary.
Remote authentication can use explicit `headers`/secret references documented by the upstream. Version 3 can instead bind one strict non-secret `oauth.connections` record to an exact HTTPS Streamable HTTP profile/upstream target. The connection selects `pre-registered:<client-id>`, `client-id-metadata:<https-url>`, or `dynamic` registration; discovery must advertise the selected capability plus authorization code, PKCE `S256`, and the RFC 9207 issuer response parameter. Token, refresh-token, client-secret, callback, and same-target Authorization-header fields remain forbidden in configuration. Unknown OAuth keys and unsupported registration modes are rejected instead of being silently accepted. See [OAuth support](oauth-support.md) for a complete example, OS-vault behavior, and the provider/local-STDIO boundary.

Miftah uses Node's normal TLS validation and does not disable certificate verification. A self-signed remote certificate therefore fails closed unless the operator establishes a trusted local CA through the normal Node trust configuration. Do not use cleartext remote HTTP to transport profile credentials.

Expand Down
4 changes: 2 additions & 2 deletions docs/library-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ The configuration contract exposes `ActiveProfileStateScope`, `AuditConfig`, `Au

`MiftahConfigVersion` is the union of format versions accepted by this installed release. `CURRENT_CONFIG_VERSION` is the version generated by presets; it does not cause `loadConfig` to rewrite a legacy file. Use the explicit [configuration migration command](cli.md#migrate-config) when an on-disk upgrade is intended.

`MiftahConfig` is a version-discriminated union. A `version: "1"` value retains the documented compatibility aliases; `version: "2"` accepts the canonical static-credential surface; and `version: "3"` adds opaque `OAuthConfig` bindings. Version 3 contains no token, refresh-token, client-secret, or callback fields, and an OAuth-configured runtime fails closed with `OAUTH_AUTHORIZATION_NOT_ENABLED` until the separate authorization engine exists. This intentional pre-1.0 type-surface change means application-side composition must use an intersection such as `type AppConfig = MiftahConfig & { readonly appMetadata: AppMetadata }`, rather than `interface AppConfig extends MiftahConfig`; strip application-only metadata before strict Miftah config validation.
`MiftahConfig` is a version-discriminated union. A `version: "1"` value retains the documented compatibility aliases; `version: "2"` accepts the canonical static-credential surface; and `version: "3"` adds opaque `OAuthConfig` bindings. Version 3 contains no token, refresh-token, client-secret, or callback fields; an exact OAuth binding enables the internal standards-compatible remote Streamable HTTP authorization runtime. This intentional pre-1.0 type-surface change means application-side composition must use an intersection such as `type AppConfig = MiftahConfig & { readonly appMetadata: AppMetadata }`, rather than `interface AppConfig extends MiftahConfig`; strip application-only metadata before strict Miftah config validation.

`AuditRotationConfig` requires `retainFiles` (maximum `2000`) plus at least one positive trigger (`maxBytes` or `maxAgeMs`). `AuditIntegrityConfig` currently exposes the explicit local `"sha256-chain"` option only. Both configure the runtime journal; journal writers, readers, and CLI export/verification implementations remain internal.

Expand All @@ -41,7 +41,7 @@ The configuration contract exposes `ActiveProfileStateScope`, `AuditConfig`, `Au

`UpstreamConfig.trustToolAnnotations` is opt-in and defaults to false. `ToolingConfig.unknownToolRisk` uses the exported `UnknownToolRisk` union (`"write" | "destructive"`) and defaults to `"destructive"`; callers can use exact `toolRiskOverrides` for known read tools.

`OAuthConfig`, `OAuthConnectionConfig`, and the opaque `OAuthConnectionRef` expose only version-3 non-secret connection declarations. There is no public OAuth adapter, browser client, callback, token value, credential-store, or token-lifecycle export. `UpstreamConfig.headers` remains the active remote credential surface; an OAuth declaration does not imply a supported OAuth workflow until its runtime authorization engine exists. See [OAuth support](oauth-support.md).
`OAuthConfig`, `OAuthConnectionConfig`, and the opaque `OAuthConnectionRef` expose only version-3 non-secret connection declarations. There is no public OAuth adapter, browser client, callback, token value, credential-store, or token-lifecycle export. `UpstreamConfig.headers` remains available for manual remote credentials, while an exact OAuth declaration activates the internal standards-compatible flow only for its bound HTTPS Streamable HTTP target. See [OAuth support](oauth-support.md).

`ProfileRoutingConfig` describes opt-in identifiers for Miftah's fixed in-tree provider matchers. It is declarative configuration only: it does not load third-party code, resolve secrets, or grant a matcher access to process or network APIs.

Expand Down
Loading