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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file. The format
- [#18](https://github.com/mohanagy/miftah/issues/18) The CLI reference now documents generated help, every command and option, version compatibility output, exit statuses, JSON streams, and audit snapshot/follow safety boundaries.
- [#19](https://github.com/mohanagy/miftah/issues/19) The versioned strict preset catalog, first-run onboarding wizard, generated absolute client snippets, compatibility matrix, and exact generated examples are documented and contract-tested. Tests validate generated configuration without constructing or starting external providers.
- [#20](https://github.com/mohanagy/miftah/issues/20) Metadata-only routing context now combines bounded workspace signals with deterministic profile selection, capability-gated MCP roots, strict project markers, and sanitized route-preview/audit evidence.
- [#21](https://github.com/mohanagy/miftah/issues/21) Delivered opt-in upstream identity fingerprint verification: strict expected/probe configuration, safe in-memory status and bounded caching, explicit MCP verification, required write/destructive gating, redacted audit evidence, and doctor readiness reporting.

### Changed

Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ miftah --config ~/.config/miftah/github.json

Profiles are named credential environments. Keep secret values outside JSON and use the exact generated references in the checked-in [GitHub](examples/github.miftah.json), [Sentry](examples/sentry.miftah.json), or [generic reference](examples/generic.miftah.json) example. The strict catalog pins GitHub to `ghcr.io/github/github-mcp-server:v1.5.0` with its documented read-only tool configuration; it does not claim a digest. The [compatibility matrix](docs/presets-and-clients.md) describes safe promotion and deployment recording for that tag.

Claude can call `miftah_list_profiles`, `miftah_current_profile`, `miftah_use_profile`, `miftah_profile_info`, `miftah_health`, `miftah_validate_config`, `miftah_list_upstream_tools`, `miftah_restart_profile`, and `miftah_route_preview`. Upstream tools are exposed unchanged unless they collide with a reserved management name. After a profile change, restart, upstream failure, or recovery that changes a public capability surface, MCP clients receive list-change notifications and should re-list the affected tools, resources, or prompts before relying on cached capabilities.
Claude can call `miftah_list_profiles`, `miftah_current_profile`, `miftah_use_profile`, `miftah_profile_info`, `miftah_health`, `miftah_validate_config`, `miftah_list_upstream_tools`, `miftah_restart_profile`, `miftah_verify_identity`, and `miftah_route_preview`. Upstream tools are exposed unchanged unless they collide with a reserved management name. After a profile change, restart, upstream failure, or recovery that changes a public capability surface, MCP clients receive list-change notifications and should re-list the affected tools, resources, or prompts before relying on cached capabilities.

For account bundles, define `upstreams` instead of `upstream`. Tools, resource names, and prompt names are exposed as `<upstream>__<name>` (for example `github__search_issues` and `github__account_prompt`), and each profile can provide per-upstream environment or header overrides. A multi-upstream resource URI is a Miftah-owned route such as `miftah://resource/github?uri=account%3A%2F%2Fcurrent`; its encoded value is redacted before exposure, while Miftah retains the exact original URI only in its per-profile route map. Before exposing multi-upstream resource or prompt URI metadata, Miftah strips URI userinfo and fragments and redacts every query value. Reads and prompt gets are routed only through the exact route map, never by guessing from a client identifier.

Expand Down Expand Up @@ -105,6 +105,12 @@ When several profiles match, Miftah refuses to guess. Use explicit profile switc

Miftah can also match bounded workspace metadata without treating a project file as configuration. It resolves a valid environment hint, then the nearest valid project-marker hint, then matching rules over tool arguments and collected context, then the configured fallback. Rules that select different profiles return `ROUTING_AMBIGUOUS`, and a context hint never authorizes a destructive operation that requires an explicit rule. `miftah_route_preview` and eligible audit records expose only sanitized routing evidence, not raw project environment values or project file contents. See [routing context](docs/config.md#routing-context) for the marker schema, root behavior, and evidence boundary.

## Identity verification

Identity verification is an optional account-fingerprint check, not provider authentication or authorization. Configure it under `profiles.<profile>.identity`, or under `profiles.<profile>.upstreams.<upstream>.identity` to replace that profile-level configuration for one named upstream. It can require a fresh matching fingerprint before explicitly configured write or destructive operations execute. Read discovery, resources, and prompts are not identity-gated.

`miftah_current_profile`, `miftah_health`, and `miftah_route_preview` show configured or cached safe status without starting an upstream or probing it. Use `miftah_verify_identity` to explicitly refresh configured targets; it returns safe structured status even if verification does not complete. See [identity verification configuration](docs/config.md#identity-verification) and the [security boundary](docs/security.md#identity-verification-boundary).

## Secret handling

Supported local references include environment variables (`${NAME}` and `secretref:env://NAME`), configured dotenv files (`secretref:dotenv://NAME`), and explicitly opt-in plaintext (`secretref:plain://...`). OS keychains and external secret CLIs are reserved extension points. Secrets are redacted from diagnostics, errors, stderr forwarding, audit entries, and tool responses.
Expand Down Expand Up @@ -152,7 +158,7 @@ Structured success output is written to stdout with stderr empty. Stable nonzero

## Current boundaries

The current experimental code implements local STDIO and remote HTTP/SSE upstream clients, profile switching, hybrid routing rules, policies, namespaced tools/resources/prompts for account bundles, resilient healthy-upstream discovery, configurable local process lifecycle controls, in-memory process/session caching, redacted JSONL audit logging, and a packageable CLI. Local process controls cover startup and shutdown deadlines, optional idle cleanup, opt-in crash recovery with a bounded retry budget, and no-eviction profile-session capacity limits. Persisted state, UI, routing plugins, profile matchers, `process.startMode`, `process.cache`, and configurable tool namespaces are rejected with `UNSUPPORTED_CONFIG_OPTION` rather than silently ignored.
The current experimental code implements local STDIO and remote HTTP/SSE upstream clients, profile switching, hybrid routing rules, policies, optional upstream identity verification, namespaced tools/resources/prompts for account bundles, resilient healthy-upstream discovery, configurable local process lifecycle controls, in-memory process/session caching, redacted JSONL audit logging, and a packageable CLI. Local process controls cover startup and shutdown deadlines, optional idle cleanup, opt-in crash recovery with a bounded retry budget, and no-eviction profile-session capacity limits. Persisted state, UI, routing plugins, profile matchers, `process.startMode`, `process.cache`, and configurable tool namespaces are rejected with `UNSUPPORTED_CONFIG_OPTION` rather than silently ignored.

## License

Expand Down
4 changes: 4 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ The metadata-only routing context collector is the sole source of workspace cont

Every supported MCP request enters one outer audit scope. The scope records one terminal operation event on success or safe failure and also covers discovery/list failures, unknown names, and management tools that do not enter the proxy pipeline. `OperationPipeline` enriches proxied tool calls, resource reads, and prompt retrieval with captured source/target profile, upstream, routing, policy, risk, and sanitized `routingEvidence`; it does not emit its own record. It captures the source profile state before awaiting work, resolves routing against that fixed active-profile fallback, evaluates the selected profile policy, resolves the exact target upstream route, executes, and redacts the result or error. Route preview uses the same captured fallback and collector contract. Tools retain their original upstream names for routing and policy compatibility; resource reads and prompt retrieval use the stable policy names `resources/read` and `prompts/get`. Denied, confirmation-required, blocked, and ambiguous operations never resolve or execute an upstream read/get route. Upstream managers publish typed lifecycle transitions, which the server records as separate audit events without letting audit I/O interrupt cleanup or recovery.

`IdentityManager` is an opt-in, process-only fingerprint verifier keyed by the exact profile/upstream target and live upstream session generation. A named upstream identity replaces the profile identity for that target. It discovers the configured read-risk, no-required-input probe and calls it with `{}`; it never treats a probe as credential, authentication, authorization, or scope validation. Verified results are cached only in memory for the bounded `maxAgeMs`; concurrent verification for the same live target coalesces. A stale verified result is reported as `expired`, and a required protected request refreshes it once. Restart, crash, idle replacement, or wrapper replacement invalidates the session generation and requires re-verification.

The manager keeps only the configured expected fingerprint, the corresponding allowed actual fields, a verification timestamp, and a stable error code. It never retains raw responses, tool arguments, error bodies, arbitrary JSON, credentials, or persistent identity state. After routing, policy, and target resolution, the operation pipeline verifies only explicitly required write or destructive risks before execution; read discovery, resource, and prompt operations are not identity-gated. The cached/configured state is exposed without probing by `miftah_current_profile`, `miftah_health`, and `miftah_route_preview`. `miftah_verify_identity` explicitly verifies either one requested target or the configured targets in deterministic order, returns safe structured results even for nonverified status, and audits only safe evidence plus the failure outcome.

For a multi-entry `upstreams` map, `ResourcePromptRegistry` discovers resources and prompts from every configured upstream and publishes namespaced public values only after collision checks succeed. It names resources and prompts `<upstream>__<name>`, and exposes resources as `miftah://resource/<encoded-upstream>?uri=<encoded-redacted-upstream-uri>`. The registry retains the original upstream URI privately with the exact profile and upstream route. Prompt resource links and read-result sub-resources are registered as exact Miftah routes to their originating upstream. Before resource/prompt URI metadata crosses the boundary, structural redaction strips userinfo/fragments and redacts query values, including URI metadata returned by reads and prompt content. After the operation pipeline authorizes its selected profile, a read or prompt get resolves that exact route and forwards only to its originating upstream; unknown identifiers are rejected rather than forwarded. Aggregate pagination stores the individual upstream cursors behind opaque, bounded in-memory LRU state scoped to the profile and capability kind.

The server advertises `resources.listChanged` and `prompts.listChanged` with `tools.listChanged`, and emits the matching notification when aggregate availability changes through a failure or recovery. Clients must re-list because route maps and aggregate cursors are profile-local and invalidated on profile changes, restarts, and unexpected upstream loss. A standard `upstream` and a named `upstreams` map with exactly one entry proxy resources and prompts through that sole upstream while preserving credential-free raw URIs, names, and cursors; URI and icon fields with userinfo, query values, or fragments are structurally redacted. A zero-entry map omits the MCP `resources` and `prompts` capabilities and does not register their handlers; direct resource or prompt requests then receive the standard `-32601` method-not-found response. Permissive multi-upstream discovery returns only healthy contributions and removes failed upstream routes; all-failed capability discovery returns `UPSTREAM_DISCOVERY_FAILED`. Strict mode rejects a partial aggregate. `miftah_health` exposes redacted process and per-capability discovery state for each started upstream. Reserved management tool names are protected; the default collision strategy prefixes a conflicting upstream name with `upstream_`.
Expand Down
8 changes: 8 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ miftah doctor --json --config github.json

The JSON report intentionally omits resolved secret values, raw configuration paths, configured upstream command arguments, and its synthetic redaction canary.

When identity verification is unconfigured, doctor records `DOCTOR_IDENTITY` as `skipped`. A configured verified identity is `pass`; mismatch, unsupported, or failed required identity verification is `error`; and nonverified optional identity verification is `warning`. Identity doctor output never includes raw probe output or fingerprint values.

### MCP identity management

`miftah_verify_identity` is an MCP management tool, not a shell subcommand. It accepts optional `profile` and `upstream` strings. `profile` defaults to the active profile. Supplying a named `upstream` verifies only that target; `upstream: "default"` is an alias only for a single unnamed upstream. With `upstream` omitted, Miftah verifies every configured target in deterministic upstream order. The response always contains safe structured identity results, including nonverified states, and its audit event contains only safe evidence and a failure outcome when verification did not succeed.

`miftah_current_profile`, `miftah_health`, and `miftah_route_preview` expose configured or cached identity status but do not start an upstream or run a probe.

## Global version options

These forms all print the package SemVer to stdout and nothing to stderr:
Expand Down
52 changes: 52 additions & 0 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,58 @@ MCP roots are optional client metadata. After initialization, Miftah calls `root

`miftah_route_preview` resolves against one collector snapshot using the same context inputs as a proxied operation. Its response contains the selected profile, reason, policy decision, and sanitized `evidence`. Proxied operation audit records carry the same snapshot as additive `routingEvidence`, including an ambiguity that prevents forwarding. Evidence contains only allowlisted metadata and redacted URI components; it never contains arbitrary project file content or the raw `MIFTAH_PROJECT` value.

## Identity verification

Identity verification is opt-in. Add `identity` at `profiles.<profile>.identity` to configure a profile fingerprint. For an `upstreams` bundle, `profiles.<profile>.upstreams.<upstream>.identity` replaces the profile identity for that exact named target; it does not merge fields from the profile identity.

An identity configuration is strict and contains:

- `expected`: a nonempty fingerprint with only optional `provider`, `login`, `organization`, and `host` string fields. Each identity fingerprint string, `probe.tool`, and any `probe.provider` is trimmed and must be nonempty, with a maximum 256 JavaScript characters;
- `probe`: `{ "tool": "<discovered-tool>", "resultFormat": "text" | "json" }`, with optional `provider` only for `"text"`;
- positive integer `maxAgeMs`, with a maximum 86,400,000 ms (24 hours); and
- optional nonempty, unique `requiredForRisk`, containing only `"write"` and/or `"destructive"`. `"read"` is not accepted.

The expected fingerprint has exactly these fields:

| Field | Meaning |
| --- | --- |
| `provider` | Provider identifier. |
| `login` | Account login. |
| `organization` | Organization identifier. |
| `host` | Provider host identifier. |

For example, this profile identity requires a GitHub login fingerprint before configured write or destructive risks:

```json
{
"profiles": {
"work": {
"identity": {
"expected": {
"provider": "github",
"login": "octo-work"
},
"probe": {
"tool": "whoami",
"resultFormat": "text",
"provider": "github"
},
"maxAgeMs": 300000,
"requiredForRisk": ["write", "destructive"]
}
}
}
}
```

The probe must be a discovered read-risk tool with no required input fields; Miftah calls it with `{}`. A missing probe, a non-read probe, or a probe with required input is unsupported. A probe is an account-fingerprint observation only: it does not validate credentials, authentication, provider authorization, or token scopes.

Before parsing or normalization, a probe response must contain exactly one MCP text content item. Its text has a maximum 4,096 JavaScript characters; a response with another content shape or a longer text fails verification. For `"json"` probes, the text must parse to a JSON object, and Miftah retains only allowed string fields (`provider`, `login`, `organization`, and `host`) after their normal validation.

For a `"text"` response, Miftah uses the response as `login` and adds the configured static `provider` when supplied. Text probes require `expected.login`, cannot verify `organization` or `host`, and their static provider must equal `expected.provider` when an expected provider is configured. For a `"json"` response, Miftah retains only allowed string `provider`, `login`, `organization`, and `host` fields; provider must come from that response, so a static probe provider is prohibited. Matching uses exact equality for every configured expected field. Miftah retains only actual fields that were configured in `expected`.

Identity gating is applied only after routing, policy, and target resolution and before the protected operation executes. It applies only when `requiredForRisk` explicitly names the selected write or destructive risk. Read discovery, resource reads, and prompt retrieval are not gated. Mismatch, unsupported, or failed required checks block the protected operation; an identity configuration without `requiredForRisk` never gates an operation.

## Operation routing and policy

Miftah applies one safety pipeline to every proxied upstream tool call, resource read, and prompt retrieval. It captures the active profile once at request start, resolves the routing rule using that immutable fallback, evaluates the selected profile's policy before resolving an aggregate route or forwarding, redacts the result or error, and records the terminal operation metadata in audit output.
Expand Down
4 changes: 3 additions & 1 deletion docs/library-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ await runtime.connect(new StdioServerTransport());

## Type exports

The configuration contract exposes `AuditConfig`, `MiftahConfig`, `PolicyConfig`, `ProcessConfig`, `ProfileConfig`, `ProfileUpstreamOverride`, `RiskLevel`, `RoutingConfig`, `RoutingRule`, `SecurityConfig`, `ToolDiscoveryMode`, `ToolingConfig`, `TransportType`, `UpstreamConfig`, and `ValidatedRoutingConfig`.
The configuration contract exposes `AuditConfig`, `IdentityConfig`, `IdentityFingerprint`, `IdentityProbeConfig`, `MiftahConfig`, `PolicyConfig`, `ProcessConfig`, `ProfileConfig`, `ProfileUpstreamOverride`, `RiskLevel`, `RoutingConfig`, `RoutingRule`, `SecurityConfig`, `ToolDiscoveryMode`, `ToolingConfig`, `TransportType`, `UpstreamConfig`, and `ValidatedRoutingConfig`.

For identity configurations, format-dependent structural constraints and unique `requiredForRisk` tuples are static. For text probes, `validateConfig` runtime-validates equality between `expected.provider` and a static `probe.provider`; JSON probes do not permit a static provider.

Programmatic diagnostics expose `ConfigDiagnostic`, `MiftahErrorCode`, and `MiftahErrorDetails`. The wrapper factory exposes `MiftahRuntime`.

Expand Down
Loading
Loading