Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d83e880
feat(setup): add safe resumable connector drafts
mohanagy Jul 27, 2026
4c3a86d
test(console): use private draft parents on Windows
mohanagy Jul 27, 2026
5ed2f9d
fix(setup): harden resumable draft lifecycle
mohanagy Jul 27, 2026
a485858
fix(setup): avoid redundant Windows draft ACL probes
mohanagy Jul 27, 2026
c1ff104
fix(windows): reuse verified setup draft ACL state
mohanagy Jul 27, 2026
f1d0d12
fix(windows): avoid duplicate first-run ACL probe
mohanagy Jul 27, 2026
4c41130
fix(windows): avoid redundant private ACL reads
mohanagy Jul 27, 2026
2ae03a6
fix(console): reuse known draft revision for cleanup
mohanagy Jul 27, 2026
4402bdf
fix(windows): batch first-run ACL checks
mohanagy Jul 27, 2026
e556e33
docs(cli): clarify resumed checkpoint state
mohanagy Jul 27, 2026
b48a9de
fix(windows): bound ACL helper command size
mohanagy Jul 27, 2026
b6f3d2c
fix(windows): verify ACL ancestor chain
mohanagy Jul 27, 2026
c1d453a
fix(windows): scope ACL ancestor checks
mohanagy Jul 27, 2026
272eb72
test(windows): diagnose temporary ACL ancestor boundary
mohanagy Jul 27, 2026
5affcb3
test(windows): anchor Console first-run fixtures
mohanagy Jul 27, 2026
03f0cd1
test(windows): diagnose profile ancestor boundary
mohanagy Jul 27, 2026
ac4b845
test(windows): classify ancestor mutation rights
mohanagy Jul 27, 2026
eca6450
fix(windows): exclude terminal volume root from ACL chain
mohanagy Jul 27, 2026
a7a433e
fix(windows): recheck first-run directory before write
mohanagy Jul 27, 2026
4863ffe
fix(windows): bind first-run config write
mohanagy Jul 27, 2026
ac721e1
fix(windows): use checked helper for first-run config writes
mohanagy Jul 27, 2026
d2cd069
fix(windows): make private config writes recoverable
mohanagy Jul 27, 2026
0e07743
build(windows): refresh checked helper artifact
mohanagy Jul 27, 2026
077d77e
test(windows): isolate first-run draft cleanup boundary
mohanagy Jul 27, 2026
f5527a0
test(windows): split draft lifecycle coverage
mohanagy Jul 27, 2026
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
It also adds guarded profile rename through the CLI and local Console. The operation atomically updates configuration-owned default, routing, routing-plugin, and profile-lock references with recovery backup and fail-closed audit records, while leaving credentials, provider caches, profile state, identity records, and active clients untouched. Native OAuth-bound profiles remain explicitly refused until configuration rename and OS-vault credential migration can be one atomic lifecycle.
- [#204](https://github.com/mohanagy/miftah/issues/204) Added outcome-first source choices to the interactive CLI and Console: known connector or pinned package, remote HTTPS endpoint, local executable, remote MCP with browser sign-in, or one existing client entry. Each choice reuses the existing hardened setup path; generic remote setup remains network-free and authentication-free, local setup remains no-shell and does not launch the executable, and client settings remain manual and untouched.
- [#204](https://github.com/mohanagy/miftah/issues/204) Added review-before-write setup planning: a complete noninteractive `miftah setup --plan` emits only validated non-secret structure without touching the filesystem or an upstream, and the first-run Console requires a fresh structural review before its separate create action. The review omits paths, endpoints, launch arguments, credential references, OAuth details, and secret values; changing the form invalidates it.
- [#204](https://github.com/mohanagy/miftah/issues/204) Added a private resumable connector-setup checkpoint shared by `miftah setup` and the first-run Console. It stores only a bounded configuration name, catalog preset, and setup stage with atomic compare-and-swap protection; URLs, paths, commands, arguments, environment references, client entries, browser state, OAuth state, credentials, and secrets must be entered again. A saved checkpoint can be resumed or explicitly discarded, survives cancellation or a pre-publication failure, and is cleared only after configuration publication succeeds.

### Changed

Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,20 @@ On a bare interactive `miftah setup`, Miftah starts with `What do you already ha

Add `--plan` to a complete noninteractive `miftah setup` command to validate and print its non-secret structure before Miftah writes anything. The plan includes the output location you supplied plus configuration, profile, and upstream transport shape; it excludes the endpoint, executable, working directory, arguments, credential references, and secret values. The Console uses the same review-before-write boundary: **Review configuration** validates a candidate without writing it, and **Create reviewed configuration** is the separate explicit write. Changing the form clears the review.

If a bare connector setup is interrupted, continue it with:

```bash
miftah setup --resume
```

Or remove that checkpoint without creating a configuration:

```bash
miftah setup --discard-draft
```

Miftah saves only the connector source, configuration name, catalog preset, and setup stage in a private local checkpoint. You re-enter every URL, executable, argument, path, environment reference, client entry, and browser or OAuth detail. The checkpoint stays after cancellation or a failed pre-publication write, and is removed only after a configuration is created or you explicitly discard it. This safe resume is intentionally limited to the bare `connector` path; import and browser-sign-in data are never persisted. In the first-run Console, **Save connector choice**, **Continue saved connector choice**, and **Discard saved choice** use that same checkpoint and never use browser storage.

If a selected client entry cannot be safely imported, Miftah writes no configuration from it. Miftah does not retain rejected arguments, headers, environment values, or credentials. The guided CLI keeps the non-secret configuration name and output location, then takes you to manual transport setup: re-enter a reviewed local executable with literal arguments, or a canonical HTTPS endpoint. In the Console, choose **Set up local executable manually** or **Set up remote HTTPS MCP manually**; it clears the pasted client JSON before opening the existing form. Configure authentication separately through the upstream's documented flow and Miftah secret references.

When setup finishes, Miftah tells you whether it ran a reviewed check, skipped one, or had no declared safe check at all. Browser sign-in setup can finish with browser authorization still outstanding. It saves a discovered connection plan, not a credential; use the later connect action to start the provider's browser flow. A completed configuration is not the same as a working client. You still review and merge any client JSON yourself, then restart or reconnect the client.
Expand Down
4 changes: 3 additions & 1 deletion docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Documented command names, options, JSON success forms, and exit categories are c
| `miftah validate --config <file>` | `--config` | `--config <file>` | Validates the JSON configuration without starting an upstream. Writes a JSON object with `ok`, `name`, and `profiles`. |
| `miftah doctor --config <file>` | `--config` | `--config <file>`, `--json` | Validates configuration and checks upstream readiness. Default output is a human-readable report; `--json` writes only the JSON report. A healthy or degraded report exits `0`; a failed report exits `1`. |
| `miftah schema` | none | none | Writes the Miftah JSON Schema as pretty-printed JSON. |
| `miftah setup [name]` | none, or `--add-profile --config <file>` | The safe preset options accepted by `init`, except `--interactive`, plus `--native-oauth` and `--verify`; `--import-file <file>` with `--import-entry <name>`; `--add-profile --config <file> --profile <name> --oauth-client-secrets-file <file>` for a reviewed provider-owned account; or `--add-profile --config <file> --profile <name> --credential-env <name>` for a simple local environment-backed account | Opens the guided first-configuration flow. With a bare TTY invocation, it asks what you already have: `connector`, `remote`, `local`, `browser sign-in`, or `import`. The connector path selects a known connector or pinned package; remote creates a strict generic HTTPS reference without calling it; local collects a literal executable and argument array; browser sign-in runs endpoint-first native OAuth discovery; and import requires one explicit absolute client JSON file, lists only entry names, and requires one selected entry. It asks for no secret value, validates the complete candidate, creates the output exclusively with owner-restricted permissions, and can print client JSON for manual review. For `local-stdio`, it collects literal argv elements, shows a no-secret review summary, and requires acknowledgement before saving; it never starts that generic local executable. For Google Search Console it can collect one or more named accounts and an explicit default. `--add-profile` either adds one later reviewed provider account or, only for a single simple local credential binding, a new environment-backed account. `--verify` opts into one provider-declared read-only check for every created reviewed adapter profile, or just the newly added account; it is unavailable for generic environment-backed addition and client-entry import because Miftah does not infer a safe probe. The explicit and guided import paths accept one selected local stdio or credential-free HTTPS remote entry and never edit a client configuration file. |
| `miftah setup [name]` | none, or `--add-profile --config <file>` | The safe preset options accepted by `init`, except `--interactive`, plus `--native-oauth` and `--verify`; `--import-file <file>` with `--import-entry <name>`; `--add-profile --config <file> --profile <name> --oauth-client-secrets-file <file>` for a reviewed provider-owned account; `--add-profile --config <file> --profile <name> --credential-env <name>` for a simple local environment-backed account; or exactly one of `--resume` and `--discard-draft` for a private connector-choice checkpoint | Opens the guided first-configuration flow. With a bare TTY invocation, it asks what you already have: `connector`, `remote`, `local`, `browser sign-in`, or `import`. The connector path selects a known connector or pinned package; remote creates a strict generic HTTPS reference without calling it; local collects a literal executable and argument array; browser sign-in runs endpoint-first native OAuth discovery; and import requires one explicit absolute client JSON file, lists only entry names, and requires one selected entry. It asks for no secret value, validates the complete candidate, creates the output exclusively with owner-restricted permissions, and can print client JSON for manual review. For `local-stdio`, it collects literal argv elements, shows a no-secret review summary, and requires acknowledgement before saving; it never starts that generic local executable. For Google Search Console it can collect one or more named accounts and an explicit default. `--add-profile` either adds one later reviewed provider account or, only for a single simple local credential binding, a new environment-backed account. `--verify` opts into one provider-declared read-only check for every created reviewed adapter profile, or just the newly added account; it is unavailable for generic environment-backed addition and client-entry import because Miftah does not infer a safe probe. The explicit and guided import paths accept one selected local stdio or credential-free HTTPS remote entry and never edit a client configuration file. |
| `miftah init [name]` | none | `--name <name>`, `--preset <name>`, `--output <file>`, `--interactive`, `--client <claude-desktop\|claude-code\|cursor\|vscode\|all>`, `--credential-env <name>`, `--npm-package <package>`, `--docker-image <image>`, `--url <url>`, `--header-name <name>`, `--header-prefix <prefix>`, `--oauth-client-secrets-file <file>`, `--local-command <executable>`, repeated `--arg <value>`, `--cwd <directory>`, `--accept-local-command` | Writes a strict catalog configuration with exclusive creation and can print client JSON snippets. The positional `name` and `--name` are alternatives; the default name is `miftah-wrapper`. |
| `miftah migrate-config --config <file>` | `--config` | `--config <file>`, `--write` | Plans a supported configuration-format migration and writes a safe JSON report. It is dry-run by default. `--write` validates the candidate, makes an exact exclusive `<file>.bak`, then uses a same-directory non-overwriting publication for a changed regular non-symlink source; it never resolves secrets or starts an upstream. |
| `miftah profile list --config <file>` | `--config` | `--config <file>` | Lists the durable default and fixed non-secret metadata for every configured account: name, optional description/tags/policy, and profile-level upstream override names. It only loads and validates configuration; it never resolves secret references, reads credentials, headers, OAuth vault entries, or provider token caches, starts an upstream, or changes configuration. |
Expand Down Expand Up @@ -78,6 +78,8 @@ miftah validate --config "$HOME/Miftah configs/work wrapper.json"

`miftah setup ... --plan` validates a complete noninteractive first-configuration candidate and prints a structural, non-secret JSON plan without creating a directory, writing a file, or contacting an upstream. It includes the supplied output location, but deliberately excludes the endpoint, executable, working directory, arguments, environment names, credential references, headers, OAuth details, and secret values. `--plan` is unavailable for interactive, import, profile-addition, and native-OAuth setup because those flows have their own review or discovery boundaries.

For a bare interactive `connector` journey, Miftah writes a private checkpoint after the configuration name and catalog preset are selected, before it asks for connection details. `miftah setup --resume` requires a TTY and cannot be combined with any setup-input option. It restores the connector source, configuration name, catalog preset, and setup stage, then asks again for every URL, executable, argument, path, environment reference, credential-file choice, client selection, and OAuth/browser detail. It never creates a configuration until the normal final publication succeeds. `miftah setup --discard-draft` deletes that checkpoint without creating a configuration and does not require a TTY. Both operations reject a malformed, stale, concurrent, unsafe, or unavailable private checkpoint rather than replacing or guessing from it.

`miftah setup --add-profile --config <file>` has two typed returning-user paths. For a reviewed provider-owned adapter, noninteractive use requires `--profile` and `--oauth-client-secrets-file`; `--description`, `--make-default`, and `--verify` are optional. Miftah proves that the entire selected configuration matches one adapter and that every existing account has a literal absolute credential-file reference plus a distinct canonical provider-state directory. It then atomically adds one new profile and isolated state directory through that adapter contract. Miftah never reads the credential file or the upstream token cache. Unsupported, modified, mixed, or shared-state configurations fail closed with `PROVIDER_ACCOUNT_ADDITION_UNSUPPORTED`; an invalid credential-file path fails with `PROVIDER_ACCOUNT_INPUT_INVALID`. `--verify` checks the new account only.

For a static local account, noninteractive use requires `--profile` and `--credential-env`; the latter is an environment-variable name, never a secret value. This path accepts only a configuration with exactly one unnamed `stdio` upstream, one direct `${ENV_NAME}` credential binding in every profile, one shared destination/policy, no provider adapter, no native OAuth, and no named upstreams or other profile overrides. It atomically adds one profile that points at a different environment variable, enforces profile-switch confirmation and explicit destructive selection, and never starts the upstream or reads the credential. Remote HTTP credentials use headers rather than profile environments, so remote configurations fail closed with `ENVIRONMENT_PROFILE_ADDITION_UNSUPPORTED`. Generic `--verify` is deliberately rejected because Miftah has no declared safe probe. The durable default changes only when `--make-default` is present, so existing MCP clients still need a restart or new connection.
Expand Down
5 changes: 5 additions & 0 deletions docs/console-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,14 @@ Every request must use the exact listener `Host`. Browser mutations, including b

`POST /api/v1/profiles/environment-account` accepts only a profile slug, optional description, an environment-variable name, and optional `makeDefault: true`. It is available only for the currently selected trusted configuration when every existing profile has one matching direct credential environment binding for a single local `stdio` upstream. It rejects remote HTTP, provider-adapter, OAuth, named-upstream, raw credential, duplicate source-variable, and arbitrary profile-override cases. The operation records intent and completion in the fail-closed Console journal, writes via the guarded configuration transaction, never starts the upstream, and returns no credential environment reference or configuration bytes.

The unconfigured first-run dashboard exposes **Save connector choice**, **Continue saved connector choice**, and **Discard saved choice** through the private setup-draft endpoint. `GET /api/v1/setup-draft` returns the current safe draft or `null`. `PUT /api/v1/setup-draft` accepts only the safe connector intent (`source: "connector"`, bounded configuration name, catalog preset, and stage) plus an optional `expectedRevision` compare-and-swap value, then returns the saved draft with its revision and timestamp. `DELETE /api/v1/setup-draft` requires `{ revision: number }` and returns `204 No Content`. `PUT` and `DELETE` require the existing exact-Origin CSRF proof. The checkpoint is private, owner-restricted, versioned, and shared with `miftah setup --resume`; it is not stored in browser memory beyond the active page, localStorage, sessionStorage, configuration files, audit records, OAuth metadata, or client settings. It never accepts or returns a URL, local command, argument, working directory, credential reference or value, header, client-entry document, client-secret path, token/cache location, browser state, OAuth code, or OAuth token. It is available only while no configuration has been selected or created, and it is cleared after successful first-run publication or an explicit discard.

| Method and path | Purpose |
| --- | --- |
| `POST /api/v1/sessions` | Exchange the one-use bootstrap code for one browser session. |
| `GET /api/v1/setup-draft` | Return the current first-run safe connector checkpoint, or `null`. Requires a Console session and returns no connection details. |
| `PUT /api/v1/setup-draft` | Save one strict non-secret connector name/preset/stage checkpoint. Requires CSRF and optional exact expected revision. |
| `DELETE /api/v1/setup-draft` | Discard one exact-revision first-run checkpoint. Requires CSRF and never creates or changes a configuration. |
| `POST /api/v1/onboarding/preset` | Exclusively create the first validated configuration from a reviewed preset and safe connector metadata. Requires CSRF; GSC accepts only structured account names/descriptions/client-secrets paths and rejects raw credential values. `local-stdio` additionally requires an explicit acknowledgement and stores only a literal executable/argument array; onboarding does not launch it. |
| `POST /api/v1/onboarding/client-entry` | Exclusively create the first validated configuration from one explicitly selected local stdio or credential-free HTTPS remote client entry. Requires CSRF; the pasted document is in-memory only. Local imports use the shared static launch grammar; remote imports require a `url` plus explicit `type: "http"` or `"streamable-http"`, HTTPS without userinfo/query/fragment or opaque credential-shaped path segments, and never trigger OAuth discovery or an upstream call. On Windows, a local import accepts only a direct absolute `.exe` or `.com` executable, not bare runners or `.cmd`/`.bat` shims. `env`, headers, shell settings, unsupported remote transports, environment wrappers, inline code, opaque values or assignments, unpinned package references, and credential-shaped arguments are rejected. |
| `POST /api/v1/onboarding/native-oauth` | Exclusively create the first validated v3 native-OAuth profile, upstream, and connection. Requires CSRF and refuses an existing file. |
Expand Down
4 changes: 4 additions & 0 deletions src/cli/exit-codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ export const ERROR_EXIT_CODES = {
ENVIRONMENT_PROFILE_ADDITION_UNSUPPORTED: CLI_EXIT_CODES.config,
ENVIRONMENT_PROFILE_INPUT_INVALID: CLI_EXIT_CODES.config,
CLIENT_ENTRY_STATIC_LAUNCH_UNSUPPORTED: CLI_EXIT_CODES.config,
SETUP_DRAFT_INPUT_INVALID: CLI_EXIT_CODES.config,
SETUP_DRAFT_INVALID: CLI_EXIT_CODES.config,
SETUP_DRAFT_CONFLICT: CLI_EXIT_CODES.config,
SETUP_DRAFT_UNAVAILABLE: CLI_EXIT_CODES.operation,
CONSOLE_LAUNCHER_UNAVAILABLE: CLI_EXIT_CODES.operation,
CONSOLE_CONFIGURATION_NOT_FOUND: CLI_EXIT_CODES.config,
CONSOLE_CONFIGURATION_SELECTION_REQUIRED: CLI_EXIT_CODES.config,
Expand Down
Loading
Loading