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: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ jobs:
- name: Test core contracts
run: npm run test:core

- name: Test OAuth and Console compatibility
run: npm run test:oauth-console

- name: Test CLI compatibility
run: npm run test:cli

Expand Down
50 changes: 50 additions & 0 deletions docs/oauth-console-validation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# OAuth and Console validation

This page records what Miftah's release gate proves for native remote OAuth and the optional local Console, what it deliberately does not prove, and which external evidence is still missing. It is an evidence record, not a claim that every OAuth provider is supported.

## Automated compatibility gate

Every pull request and push to `development` or `main` runs `npm run test:oauth-console` on Ubuntu, macOS, and Windows with Node.js 20, 22, and 24. The same matrix also runs Miftah's core and packaged-CLI contracts. A release candidate cannot rely only on the Linux full-test job.

The dedicated suite uses deterministic local fixtures and does not contact live OAuth providers. It exercises:

- protected-resource and authorization-server discovery, exact resource and issuer validation, PKCE, callback handoff, exchange, refresh, and bearer injection;
- connection/profile/upstream isolation, credential envelopes, expiry, identity mismatch, redaction, and audit integrity;
- configuration migration, connection planning and writes, CLI-only operation, Console lifecycle commands, and provider-adapter contracts;
- literal-loopback binding, single-use state, bounded cancellation and timeout cleanup, exact Console listener and Origin checks, CSRF, and browser launch boundaries.

The secure-store tests validate the vault adapter boundary, credential-envelope isolation, module construction, and unavailable-vault diagnostics. The suite does not write test credentials into the operator's real OS vault. It also does not prove that a live provider granted the requested scopes or that the selected account is the intended account; those require provider and user evidence.

## Recovery scenarios

### Refresh and reauthorization

An expiring credential is refreshed only after discovery is revalidated. Refresh failure never falls through to another profile or connection. `miftah auth reauth` keeps the existing credential until its replacement succeeds, and failed interactive authorization returns a typed diagnostic without printing provider output.

### Disconnect and cleanup

`miftah auth disconnect` deletes only the exact local vault binding and marks the connection disconnected. It does not claim provider-side revocation. Callback listeners and Console-owned resources close during success, failure, or shutdown.

### Cancellation and callback timeout

The loopback callback accepts one exact state, issuer, host, and path. Cancellation and timeout close the listener and discard transient authorization material without changing another connection or exposing callback parameters.

### Configuration backup and recovery

Connection writes are plan-first, reject symlink or concurrent-replacement races, and create a unique recovery backup before atomic installation. Stop active clients before restoring a backup, validate the restored file, and then restart clients.

For provider-adapter-backed or upstream-owned OAuth, follow the provider's own reauthentication and cache-recovery instructions. Miftah does not import, repair, or replay opaque provider token caches.

## Release status and limitations

OAuth and Console are being validated for a pre-v1 feature release. Passing this automated gate is required release evidence, but it is not v1.0 readiness and does not close the demand or external-validation gates by itself. The current support classes and manual fallbacks remain defined in the [OAuth support matrix](oauth-support.md#support-matrix).

## External design-partner evidence

Snapshot date: 2026-07-22.

- Interested external users: 5
- Recorded completed external workflows: 0
- Recorded returning external users: 0

The external design-partner gate remains open. Closing it requires five external users to complete real multi-account workflows and at least three to return after setup. Record only deidentified workflow, setup friction, return behavior, and rejected assumptions; never record credentials, OAuth codes, tokens, provider payloads, or personal account identifiers.
2 changes: 1 addition & 1 deletion docs/oauth-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Miftah performs protected-resource and authorization-server discovery, browser a

Miftah does not support OAuth for every MCP server or provider. It does not guess private endpoints, scrape provider caches, automate local STDIO providers' custom login flows, accept passwords or browser cookies as OAuth state, or treat a valid token as proof that the correct account was selected. Operator lifecycle commands manage only Miftah's exact local binding and vault credential; `auth disconnect` does not claim provider-side token revocation.

The [OAuth and local Console design delta](oauth-console-threat-model.md) records the enforced OAuth security controls, residual risks, and the separately launched local Console control-plane boundary. Run `miftah dashboard` for the optional browser-local UI, or `miftah console --config <file>` for the API-only compatibility surface.
The [OAuth and local Console design delta](oauth-console-threat-model.md) records the enforced OAuth security controls, residual risks, and the separately launched local Console control-plane boundary. The [OAuth and Console validation](oauth-console-validation.md) page records the cross-platform automated gate, recovery coverage, limitations, and external-user evidence separately. Run `miftah dashboard` for the optional browser-local UI, or `miftah console --config <file>` for the API-only compatibility surface.

## Support matrix

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"dev": "tsup --watch",
"test": "vitest run",
"test:core": "vitest run tests/config.test.ts tests/config-loader.test.ts tests/config-diagnostics.test.ts tests/config-migration.test.ts tests/config-public-contract.test.ts tests/config-runtime-parity.test.ts tests/config-schema-contract.test.ts tests/executable-resolver.test.ts tests/http-server.test.ts tests/presets.test.ts tests/profile-manager.test.ts tests/provider-routing-matchers.test.ts tests/provider-routing-matchers-docs-contract.test.ts tests/routing-context.test.ts tests/routing-policy.test.ts tests/secret-provider-availability.test.ts tests/secret-providers.test.ts tests/secrets.test.ts tests/tooling-config.test.ts tests/windows-config-acl.test.ts tests/windows-config-migration-acl-failure.test.ts tests/windows-config-migration-acl.test.ts tests/windows-secret-process-resolution.test.ts",
"test:oauth-console": "vitest run tests/oauth tests/remote-oauth tests/console tests/identity tests/provider-adapter-contract.test.ts tests/preset-catalog.test.ts tests/init-command.test.ts tests/cli-exit-codes.test.ts tests/cli-parse.test.ts tests/config-migration.test.ts tests/audit-integrity.test.ts",
"test:package": "vitest run tests/package-contract.test.ts",
"test:coverage": "vitest run --coverage",
"smoke:cli": "node dist/cli/main.js schema",
Expand Down
12 changes: 6 additions & 6 deletions tests/console-application-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,12 @@ describe("Console application service", () => {
expect(JSON.stringify(config)).not.toMatch(/token|secret|password/iu);

const snippets = await service.clientSnippets("claude-desktop");
expect(snippets).toEqual([
expect.objectContaining({
client: "claude-desktop",
json: expect.stringContaining(configPath)
})
]);
expect(snippets).toHaveLength(1);
expect(snippets[0]).toMatchObject({ client: "claude-desktop" });
const snippetConfig = JSON.parse(snippets[0]?.json ?? "") as {
mcpServers: Record<string, { args: string[] }>;
};
expect(snippetConfig.mcpServers["posthog-work"]?.args).toContain(configPath);
expect(JSON.stringify(snippets)).not.toContain("auth.example.test");

await expect(service.onboardNativeOAuth({
Expand Down
9 changes: 7 additions & 2 deletions tests/console-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,14 @@ describe("local Console control server", () => {
headers: { origin: server.url.origin, cookie: session.cookie }
});
expect(snippets.status).toBe(200);
const snippetBody = await snippets.json();
const snippetBody = await snippets.json() as {
data: Array<{ client: string; json: string }>;
};
expect(snippetBody).toMatchObject({ data: [{ client: "claude-desktop" }] });
expect(JSON.stringify(snippetBody)).toContain(configPath);
const snippetConfig = JSON.parse(snippetBody.data[0]?.json ?? "") as {
mcpServers: Record<string, { args: string[] }>;
};
expect(snippetConfig.mcpServers["posthog-work"]?.args).toContain(configPath);
expect(JSON.stringify(snippetBody)).not.toContain("auth.example.test");
} finally {
await server.close();
Expand Down
68 changes: 68 additions & 0 deletions tests/oauth-console-validation-contract.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { readFile } from "node:fs/promises";
import { describe, expect, it } from "vitest";

async function repositoryFile(path: string): Promise<string> {
return readFile(new URL(`../${path}`, import.meta.url), "utf8");
}

function workflowJob(workflow: string, jobName: string): string | undefined {
const lines = workflow.split(/\r?\n/u);
const start = lines.findIndex((line) => line === ` ${jobName}:`);
if (start === -1) return undefined;
const nextJob = lines.findIndex((line, index) => index > start && /^ {2}[a-z0-9_-]+:$/u.test(line));
return lines.slice(start, nextJob === -1 ? undefined : nextJob).join("\n");
}

describe("OAuth and Console validation contract", () => {
it("keeps the deterministic OAuth and Console suite in every supported OS and Node job", async () => {
const [packageJson, workflow] = await Promise.all([
repositoryFile("package.json"),
repositoryFile(".github/workflows/ci.yml")
]);
const packageManifest = JSON.parse(packageJson) as {
scripts?: Record<string, string>;
};
const compatibilityJob = workflowJob(workflow, "compatibility");

expect(packageManifest.scripts?.["test:oauth-console"]).toBe(
"vitest run tests/oauth tests/remote-oauth tests/console tests/identity " +
"tests/provider-adapter-contract.test.ts tests/preset-catalog.test.ts " +
"tests/init-command.test.ts tests/cli-exit-codes.test.ts tests/cli-parse.test.ts " +
"tests/config-migration.test.ts tests/audit-integrity.test.ts"
);
expect(compatibilityJob).toBeDefined();
expect(compatibilityJob).toContain("Test OAuth and Console compatibility");
expect(compatibilityJob).toContain("run: npm run test:oauth-console");
expect(compatibilityJob).toContain("os: [ubuntu-latest, macos-latest, windows-latest]");
expect(compatibilityJob).toContain('node: ["20", "22", "24"]');
});

it("publishes truthful automated, recovery, and external-user release evidence", async () => {
const [oauthSupport, validation] = await Promise.all([
repositoryFile("docs/oauth-support.md"),
repositoryFile("docs/oauth-console-validation.md")
]);

expect(oauthSupport).toContain("[OAuth and Console validation](oauth-console-validation.md)");

for (const requiredEvidence of [
"# OAuth and Console validation",
"Ubuntu, macOS, and Windows",
"Node.js 20, 22, and 24",
"deterministic local fixtures",
"does not contact live OAuth providers",
"does not write test credentials into the operator's real OS vault",
"Refresh and reauthorization",
"Disconnect and cleanup",
"Cancellation and callback timeout",
"Configuration backup and recovery",
"pre-v1 feature release",
"Interested external users: 5",
"Recorded completed external workflows: 0",
"Recorded returning external users: 0",
"The external design-partner gate remains open"
]) {
expect(validation).toContain(requiredEvidence);
}
});
});