mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 18:55:22 +00:00
feat: vendor permission system source
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import type { Rule, RuleOrigin, Ruleset } from "./rule";
|
||||
import type { PermissionState } from "./types";
|
||||
|
||||
/**
|
||||
* Synthesize a single universal catch-all rule from the universal default.
|
||||
*
|
||||
* Produces one rule:
|
||||
* `{ surface: "*", pattern: "*", action: universalDefault, layer: "default" }`
|
||||
*
|
||||
* Per-surface catch-alls (`bash["*"]`, `mcp["*"]`, etc.) are expressed as
|
||||
* regular config rules from `normalizeFlatConfig()` and sit at higher indices
|
||||
* in the composed array, so they override this default via last-match-wins.
|
||||
*/
|
||||
export function synthesizeDefaults(
|
||||
universalDefault: PermissionState,
|
||||
origin: RuleOrigin = "builtin",
|
||||
): Ruleset {
|
||||
return [
|
||||
{
|
||||
surface: "*",
|
||||
pattern: "*",
|
||||
action: universalDefault,
|
||||
layer: "default",
|
||||
origin,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* MCP metadata operation targets that are auto-allowed when any explicit MCP
|
||||
* allow rule exists in the config layer.
|
||||
*/
|
||||
const MCP_BASELINE_TARGETS: readonly string[] = [
|
||||
"mcp_status",
|
||||
"mcp_list",
|
||||
"mcp_search",
|
||||
"mcp_describe",
|
||||
"mcp_connect",
|
||||
];
|
||||
|
||||
/**
|
||||
* Conditionally synthesize MCP baseline auto-allow rules.
|
||||
*
|
||||
* Emits allow rules for the 5 MCP metadata targets only when `configRules`
|
||||
* contains at least one `surface: "mcp", action: "allow"` rule. This replicates
|
||||
* the `hasAnyMcpAllowRule` heuristic as actual rules.
|
||||
*
|
||||
* When `permission["mcp"]` is `"allow"` (or `mcp["*"]` is `"allow"`), the
|
||||
* synthesized config catch-all already covers all MCP targets — no separate
|
||||
* baseline rules are needed (and this function is not called in that case).
|
||||
*
|
||||
* Baseline rules are placed BEFORE config rules in the composed array so
|
||||
* that explicit config deny rules can still override them.
|
||||
*
|
||||
* All rules carry `layer: "baseline"`.
|
||||
*/
|
||||
export function synthesizeBaseline(configRules: Ruleset): Ruleset {
|
||||
const hasAnyMcpAllow = configRules.some(
|
||||
(r) => r.surface === "mcp" && r.action === "allow",
|
||||
);
|
||||
if (!hasAnyMcpAllow) {
|
||||
return [];
|
||||
}
|
||||
return MCP_BASELINE_TARGETS.map(
|
||||
(target): Rule => ({
|
||||
surface: "mcp",
|
||||
pattern: target,
|
||||
action: "allow",
|
||||
layer: "baseline",
|
||||
origin: "baseline",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenate all rule layers into a single flat ruleset.
|
||||
*
|
||||
* Priority order (lowest → highest, i.e. earlier index → later index):
|
||||
* defaults → baseline → config
|
||||
*
|
||||
* Session rules are NOT included here — they are appended at call-time inside
|
||||
* `checkPermission()` so that the cached composed ruleset remains session-agnostic.
|
||||
*
|
||||
* `evaluate()` scans from the end, so later layers override earlier ones.
|
||||
*/
|
||||
export function composeRuleset(
|
||||
defaults: Ruleset,
|
||||
baseline: Ruleset,
|
||||
config: Ruleset,
|
||||
): Ruleset {
|
||||
return [...defaults, ...baseline, ...config];
|
||||
}
|
||||
Reference in New Issue
Block a user