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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ All notable changes to this project will be documented in this file. The format
- Least-privilege CI and OIDC trusted-publishing workflows.
- Dependency update, contribution, vulnerability-reporting, and repository templates.

### Changed

- Configuration now rejects unsupported lifecycle controls, state/UI settings, routing plugins and non-hybrid modes, profile metadata and matchers, per-profile upstream `transport`/`command`/`url` overrides, and configurable tool namespaces with `UNSUPPORTED_CONFIG_OPTION` instead of silently ignoring them. Remove those settings from existing configs; only `process.startupTimeoutMs` currently controls process lifecycle behavior.
- Secret and audit redaction remain force-on protections. Existing configs may retain `security.redactSecrets: true` and `audit.redact: true`, but setting either to `false` is now rejected.

## Release policy

Miftah is experimental and pre-1.0, so incompatible changes may occur between minor versions and must be called out here. For each release, maintainers move **Unreleased** entries into a dated version section, update `package.json` and `package-lock.json` together using npm tooling, and publish a GitHub release tagged `v<package-version>`. The release workflow publishes only after the tag, ancestry, tests, build, CLI smoke test, and package contents are verified.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ Use `miftah doctor` to inspect config and upstream readiness without printing pr

## Current boundaries

The current experimental code implements local STDIO and remote HTTP/SSE upstream clients, profile switching, routing rules, policies, tools/resources/prompts proxying, process/session caching, redacted audit logging, and a packageable CLI. The configuration model reserves interfaces for multi-upstream account bundles, persisted state, and optional UI.
The current experimental code implements local STDIO and remote HTTP/SSE upstream clients, profile switching, hybrid routing rules, policies, tools/resources/prompts proxying, in-memory process/session caching, redacted JSONL audit logging, and a packageable CLI. Unsupported lifecycle tuning, persisted state, UI, routing plugins, profile matchers, and configurable tool namespaces are rejected with `UNSUPPORTED_CONFIG_OPTION` rather than silently ignored.

## License

Expand Down
8 changes: 7 additions & 1 deletion docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Required fields are `version: "1"`, `name`, `defaultProfile`, `profiles`, and ei

With `upstreams`, each profile may override `env`, `headers`, `args`, or `cwd` under a named upstream. Miftah namespaces discovered tools as `<upstream>__<tool>` so one wrapper can safely expose several providers.

Profile `env` values can reference `${ENV_NAME}` or `secretref:env://ENV_NAME`. Put dotenv paths in `secrets.envFiles`; paths are resolved relative to the config file. Profile metadata, tags, policy names, routing matchers, and upstream-specific overrides are non-secret.
Profile `env` values can reference `${ENV_NAME}` or `secretref:env://ENV_NAME`. Put dotenv paths in `secrets.envFiles`; paths are resolved relative to the config file. Profile descriptions, tags, policy names, and upstream-specific `args` and `cwd` overrides are non-secret. Per-upstream `env` and `headers` values may contain credentials and use the same secret-resolution and redaction safeguards as profile-level values.

The supported routing fallback values are:

Expand All @@ -22,3 +22,9 @@ The supported routing fallback values are:
Policies classify tools as `read`, `write`, or `destructive` using configurable overrides and conservative name heuristics. `denyRisk` takes precedence over `allowRisk`; `requireConfirmation` returns a structured error instead of forwarding the call.

Audit logging defaults to local JSONL when a path is configured. Arguments are excluded unless `includeArguments` is true, and all configured secret values are redacted before writing.

## Runtime-supported controls

Miftah rejects settings without a runtime implementation with `UNSUPPORTED_CONFIG_OPTION` and the exact config path. The only supported routing mode is `"hybrid"`; use `routing.rules` and `routing.fallback` to control its behavior. Routing plugins, profile metadata and matchers, persistent state, UI settings, configurable management-tool namespaces, and lifecycle tuning other than `process.startupTimeoutMs` are not available yet.

Secret and audit redaction are force-on protections. `security.redactSecrets` and `audit.redact` may be declared as `true`, but setting either to `false` is rejected. Audit output is always JSONL; `audit.format` therefore accepts only `"jsonl"`.
7 changes: 1 addition & 6 deletions examples/github.miftah.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,6 @@
"requireExplicitProfileForDestructive": true
},
"process": {
"startMode": "lazy",
"cache": true,
"restartOnCrash": true,
"startupTimeoutMs": 30000
},
"audit": {
Expand All @@ -87,8 +84,6 @@
"redact": true
},
"tooling": {
"managementToolPrefix": "miftah_",
"collisionStrategy": "prefix-upstream",
"toolDiscoveryMode": "defaultProfile"
"collisionStrategy": "prefix-upstream"
}
}
1 change: 0 additions & 1 deletion examples/multi-upstream.miftah.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@
"redact": true
},
"tooling": {
"upstreamToolNamespace": "upstreamName",
"collisionStrategy": "prefix-upstream"
}
}
8 changes: 2 additions & 6 deletions src/cli/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,10 @@ async function createRuntime(configPath: string) {
: undefined;
const manager = resolvedConfig.upstreams
? new MultiUpstreamProcessManager(resolvedConfig, {
startupTimeoutMs: config.process?.startupTimeoutMs,
restartOnCrash: config.process?.restartOnCrash,
maxRestarts: config.process?.maxRestarts
startupTimeoutMs: config.process?.startupTimeoutMs
})
: new UpstreamProcessManager(upstream!, profiles, {
startupTimeoutMs: config.process?.startupTimeoutMs,
restartOnCrash: config.process?.restartOnCrash,
maxRestarts: config.process?.maxRestarts
startupTimeoutMs: config.process?.startupTimeoutMs
});
const profileManager = new ProfileManager(resolvedConfig, resolvedConfig.security);
return { config: resolvedConfig, manager, profileManager };
Expand Down
91 changes: 81 additions & 10 deletions src/config/generate-json-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@ export function generateConfigSchema(): Record<string, unknown> {
profiles: { type: "object", additionalProperties: { $ref: "#/$defs/profile" } },
routing: { $ref: "#/$defs/routing" },
policies: { type: "object", additionalProperties: { $ref: "#/$defs/policy" } },
security: { type: "object" },
process: { type: "object" },
audit: { type: "object" },
tooling: { type: "object" },
secrets: { type: "object" },
state: { type: "object" }
security: { $ref: "#/$defs/security" },
process: { $ref: "#/$defs/process" },
audit: { $ref: "#/$defs/audit" },
tooling: { $ref: "#/$defs/tooling" },
secrets: { $ref: "#/$defs/secrets" }
},
$defs: {
upstream: {
Expand All @@ -43,18 +42,90 @@ export function generateConfigSchema(): Record<string, unknown> {
env: { type: "object", additionalProperties: { type: "string" } },
args: { type: "array", items: { type: "string" } },
cwd: { type: "string" },
policy: { type: "string" }
headers: { type: "object", additionalProperties: { type: "string" } },
policy: { type: "string" },
upstreams: { type: "object", additionalProperties: { $ref: "#/$defs/profileUpstreamOverride" } }
}
},
profileUpstreamOverride: {
type: "object",
properties: {
args: { type: "array", items: { type: "string" } },
env: { type: "object", additionalProperties: { type: "string" } },
cwd: { type: "string" },
headers: { type: "object", additionalProperties: { type: "string" } }
}
},
routing: {
type: "object",
properties: {
mode: { enum: ["active", "rules", "hybrid"] },
mode: { const: "hybrid" },
fallback: { enum: ["default", "activeProfile", "ask", "block"] },
rules: { type: "array", items: { type: "object" } }
rules: { type: "array", items: { $ref: "#/$defs/routingRule" } }
}
},
routingRule: {
type: "object",
required: ["when", "profile"],
properties: {
name: { type: "string" },
when: { type: "object", additionalProperties: true },
profile: { type: "string", minLength: 1 }
}
},
policy: { type: "object" }
policy: {
type: "object",
properties: {
allow: { type: "array", items: { enum: ["read", "write", "destructive"] } },
allowRisk: { type: "array", items: { enum: ["read", "write", "destructive"] } },
deny: { type: "array", items: { type: "string" } },
denyRisk: { type: "array", items: { enum: ["read", "write", "destructive"] } },
requireConfirmation: { type: "array", items: { type: "string" } }
}
},
security: {
type: "object",
properties: {
allowPlaintextSecrets: { type: "boolean" },
redactSecrets: { const: true, description: "Secret redaction is always enabled." },
allowProfileSwitchingFromMcp: { type: "boolean" },
requireExplicitProfileForDestructive: { type: "boolean" },
lockToProfile: { type: ["string", "null"] }
}
},
process: {
type: "object",
properties: {
startupTimeoutMs: { type: "integer", exclusiveMinimum: 0 }
}
},
audit: {
type: "object",
properties: {
enabled: { type: "boolean" },
path: { type: "string" },
format: { const: "jsonl" },
includeArguments: { type: "boolean" },
redact: { const: true, description: "Audit redaction is always enabled." }
}
},
tooling: {
type: "object",
properties: {
collisionStrategy: { enum: ["prefix-upstream", "fail"] },
toolRiskOverrides: {
type: "object",
additionalProperties: { enum: ["read", "write", "destructive"] }
}
}
},
secrets: {
type: "object",
properties: {
envFiles: { type: "array", items: { type: "string" } },
allowPlaintextSecrets: { type: "boolean" }
}
}
}
};
}
4 changes: 2 additions & 2 deletions src/config/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ function buildSharedDefaults(): SharedDefaults {
allowProfileSwitchingFromMcp: true,
requireExplicitProfileForDestructive: true
},
process: { startMode: "lazy", cache: true, restartOnCrash: true, startupTimeoutMs: 30_000 },
process: { startupTimeoutMs: 30_000 },
audit: {
enabled: true,
path: "~/.local/state/miftah/audit.jsonl",
format: "jsonl",
includeArguments: false,
redact: true
},
tooling: { managementToolPrefix: "miftah_", collisionStrategy: "prefix-upstream" }
tooling: { collisionStrategy: "prefix-upstream" }
};
}

Expand Down
Loading