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
4 changes: 2 additions & 2 deletions docs/guides/posthog-github-continuous-ai.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ You only need to configure the PostHog MCP credential - it automatically handles
organization
- It automatically uses your default project (no project ID
needed)
- If you have multiple projects, use `mcp__posthog__switch-project` to
- If you have multiple projects, use `switch-project` to
change
- The MCP connects via `https://mcp.posthog.com/sse` using your account context.

Expand Down Expand Up @@ -300,7 +300,7 @@ The main workflow above focuses on analyzing session recordings to identify UX i

```bash
# Get all feature flags and analyze them
cn "Use PostHog MCP to fetch all feature flags with mcp__posthog__feature-flag-get-all. Then analyze each flag to identify: 1) Flags that are 100% rolled out and could be removed, 2) Flags that haven't been updated in 90+ days, 3) Flags with complex targeting that might need simplification, 4) Experimental flags that should be cleaned up."
cn "Use PostHog MCP to fetch all feature flags with feature-flag-get-all. Then analyze each flag to identify: 1) Flags that are 100% rolled out and could be removed, 2) Flags that haven't been updated in 90+ days, 3) Flags with complex targeting that might need simplification, 4) Experimental flags that should be cleaned up."

# Create cleanup issues for identified flags
cn "For each problematic feature flag identified, create a GitHub issue using gh CLI:
Expand Down
3 changes: 1 addition & 2 deletions extensions/cli/src/permissions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ The system comes with sensible default policies:
- Read-only tools (`readFile`, `listFiles`, `searchCode`, `fetch`) are **allowed** by default
- Write operations (`writeFile`) require **confirmation** (ask)
- Terminal commands (`runTerminalCommand`) require **confirmation** (ask)
- MCP tools with IDE prefix are **allowed** by default
- Other MCP tools require **confirmation** (ask)
- MCP tools and Bash require **confirmation** (ask) in TUI mode, but are **allowed** automatically in headless mode
- Any unmatched tools default to **ask**

## How It Works
Expand Down
12 changes: 10 additions & 2 deletions extensions/cli/src/permissions/defaultPolicies.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, it, expect } from "vitest";
import { describe, expect, it } from "vitest";

import { DEFAULT_TOOL_POLICIES } from "./defaultPolicies.js";
import { getDefaultToolPolicies } from "./defaultPolicies.js";
const DEFAULT_TOOL_POLICIES = getDefaultToolPolicies();

describe("defaultPolicies", () => {
it("should have correct permissions for read-only tools", () => {
Expand All @@ -21,6 +22,13 @@ describe("defaultPolicies", () => {
}
});

it("should not have prefix wildcard policies in defaults", () => {
const prefixWildcardPolicy = DEFAULT_TOOL_POLICIES.find(
(p) => p.tool.endsWith("*") && p.tool !== "*",
);
expect(prefixWildcardPolicy).toBeUndefined();
});

it("should have correct permissions for write tools", () => {
const writeTools = ["Write", "Edit", "MultiEdit", "Bash"];

Expand Down
72 changes: 53 additions & 19 deletions extensions/cli/src/permissions/defaultPolicies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,64 @@ import { ToolPermissionPolicy } from "./types.js";
* Default permission policies for all built-in tools.
* These policies are applied in order - first match wins.
*/
export const DEFAULT_TOOL_POLICIES: ToolPermissionPolicy[] = [
// Read-only tools are generally safe to allow
{ tool: "Read", permission: "allow" },
{ tool: "List", permission: "allow" },
{ tool: "Search", permission: "allow" },
{ tool: "Fetch", permission: "allow" },
export function getDefaultToolPolicies(
isHeadless = false,
): ToolPermissionPolicy[] {
const policies: ToolPermissionPolicy[] = [
// Write tools
{ tool: "Edit", permission: "ask" },
{ tool: "MultiEdit", permission: "ask" },
{ tool: "Write", permission: "ask" },

// Write operations should require confirmation
{ tool: "Write", permission: "ask" },
{ tool: "Edit", permission: "ask" },
{ tool: "MultiEdit", permission: "ask" },
{ tool: "Checklist", permission: "allow" },
{ tool: "Diff", permission: "allow" },
{ tool: "Exit", permission: "allow" }, // Exit tool is generally safe (headless mode only)
{ tool: "Fetch", permission: "allow" }, // Technically not read only but edge casey to post w query params
{ tool: "List", permission: "allow" },
{ tool: "Read", permission: "allow" },
{ tool: "Search", permission: "allow" },
{ tool: "Status", permission: "allow" },
{ tool: "ReportFailure", permission: "allow" },
{ tool: "UploadArtifact", permission: "allow" },
];

// Write to a checklist
{ tool: "Checklist", permission: "allow" },
// MCP and Bash are ask in TUI mode, auto in headless
if (isHeadless) {
policies.push({ tool: "Bash", permission: "allow" });
policies.push({ tool: "*", permission: "allow" });
} else {
policies.push({ tool: "Bash", permission: "ask" });
policies.push({ tool: "*", permission: "ask" });
}

// Terminal commands should require confirmation by default
{ tool: "Bash", permission: "ask" },
return policies;
}

// Exit tool is generally safe (headless mode only)
{ tool: "Exit", permission: "allow" },
// Plan mode: Complete override - exclude all write operations, allow only reads and bash
export const PLAN_MODE_POLICIES: ToolPermissionPolicy[] = [
{ tool: "Edit", permission: "exclude" },
{ tool: "MultiEdit", permission: "exclude" },
{ tool: "Write", permission: "exclude" },

// View diff is read-only
// TODO address bash read only concerns, maybe make permissions more granular
{ tool: "Bash", permission: "allow" },

{ tool: "Checklist", permission: "allow" },
{ tool: "Diff", permission: "allow" },
{ tool: "Exit", permission: "allow" },
{ tool: "Fetch", permission: "allow" },
{ tool: "List", permission: "allow" },
{ tool: "Read", permission: "allow" },
{ tool: "ReportFailure", permission: "allow" },
{ tool: "Search", permission: "allow" },
{ tool: "Status", permission: "allow" },
{ tool: "UploadArtifact", permission: "allow" },

// Allow MCP tools
{ tool: "*", permission: "allow" },
];

// Default fallback - ask for any unmatched tools
{ tool: "*", permission: "ask" },
// Auto mode: Complete override - allow everything without asking
export const AUTO_MODE_POLICIES: ToolPermissionPolicy[] = [
{ tool: "*", permission: "allow" },
];
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { DEFAULT_TOOL_POLICIES } from "./defaultPolicies.js";
import { getDefaultToolPolicies } from "./defaultPolicies.js";
import { checkToolPermission } from "./permissionChecker.js";
import { resolvePermissionPrecedence } from "./precedenceResolver.js";

const DEFAULT_TOOL_POLICIES = getDefaultToolPolicies();

describe("Headless Permissions Integration", () => {
describe("precedence resolution", () => {
it("should use default policies", () => {
Expand Down
1 change: 0 additions & 1 deletion extensions/cli/src/permissions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ export type {
ToolPermissions,
} from "./types.js";

export { DEFAULT_TOOL_POLICIES } from "./defaultPolicies.js";
export {
checkToolPermission,
matchesArguments,
Expand Down
20 changes: 11 additions & 9 deletions extensions/cli/src/permissions/permissionChecker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,13 @@ describe("Permission Checker", () => {
});

it("should match prefix wildcards", () => {
expect(matchesToolPattern("mcp__ide__getDiagnostics", "mcp__*")).toBe(
expect(
matchesToolPattern("external_ide_getDiagnostics", "external_*"),
).toBe(true);
expect(matchesToolPattern("external_filesystem_read", "external_*")).toBe(
true,
);
expect(matchesToolPattern("mcp__filesystem__read", "mcp__*")).toBe(true);
expect(matchesToolPattern("builtin__readFile", "mcp__*")).toBe(false);
expect(matchesToolPattern("builtin_readFile", "external_*")).toBe(false);
});

it("should match suffix wildcards", () => {
Expand Down Expand Up @@ -89,7 +91,7 @@ describe("Permission Checker", () => {
it("should handle wildcard patterns with special regex characters", () => {
expect(matchesToolPattern("test[abc].txt", "test[abc].*")).toBe(true);
expect(matchesToolPattern("test[abc]_file", "test[abc].*")).toBe(false);
expect(matchesToolPattern("mcp__tool[1]", "mcp__*")).toBe(true);
expect(matchesToolPattern("external_tool[1]", "external_*")).toBe(true);
expect(matchesToolPattern("file.test.txt", "*.test.*")).toBe(true);
expect(matchesToolPattern("(tool)_name", "(tool)*")).toBe(true);
expect(matchesToolPattern("tool+plus_extra", "tool+plus*")).toBe(true);
Expand Down Expand Up @@ -417,17 +419,17 @@ describe("Permission Checker", () => {
it("should match wildcard patterns", () => {
const permissions: ToolPermissions = {
policies: [
{ tool: "mcp__*", permission: "ask" },
{ tool: "external_*", permission: "ask" },
{ tool: "*", permission: "allow" },
],
};

const mcpResult = checkToolPermission(
{ name: "mcp__ide__getDiagnostics", arguments: {} },
const externalResult = checkToolPermission(
{ name: "external_ide_getDiagnostics", arguments: {} },
permissions,
);
expect(mcpResult.permission).toBe("ask");
expect(mcpResult.matchedPolicy?.tool).toBe("mcp__*");
expect(externalResult.permission).toBe("ask");
expect(externalResult.matchedPolicy?.tool).toBe("external_*");

const builtinResult = checkToolPermission(
{ name: "readFile", arguments: { path: "/test.txt" } },
Expand Down
2 changes: 1 addition & 1 deletion extensions/cli/src/permissions/permissionChecker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function matchesToolPattern(
return false;
}

// Handle regular wildcard patterns like "mcp__*"
// Handle regular wildcard patterns like "external_*"
if (pattern.includes("*") || pattern.includes("?")) {
// Escape all regex metacharacters except * and ?
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
yamlConfigToPolicies,
parseToolPattern,
yamlConfigToPolicies,
} from "./permissionsYamlLoader.js";

describe("permissionsYamlLoader", () => {
Expand Down Expand Up @@ -46,15 +46,15 @@ describe("permissionsYamlLoader", () => {

it("should handle wildcard patterns", () => {
const config = {
allow: ["mcp__*"],
allow: ["external_*"],
exclude: ["*"],
};

const policies = yamlConfigToPolicies(config);

expect(policies).toEqual([
{ tool: "*", permission: "exclude" },
{ tool: "mcp__*", permission: "allow" },
{ tool: "external_*", permission: "allow" },
]);
});

Expand Down
56 changes: 5 additions & 51 deletions extensions/cli/src/permissions/precedenceResolver.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { DEFAULT_TOOL_POLICIES } from "./defaultPolicies.js";
import { getDefaultToolPolicies } from "./defaultPolicies.js";
import { resolvePermissionPrecedence } from "./precedenceResolver.js";
import { ToolPermissionPolicy } from "./types.js";

const DEFAULT_TOOL_POLICIES = getDefaultToolPolicies();

describe("precedenceResolver", () => {
describe("resolvePermissionPrecedence", () => {
Expand Down Expand Up @@ -31,7 +32,7 @@ describe("precedenceResolver", () => {
it("should handle wildcard patterns", () => {
const policies = resolvePermissionPrecedence({
commandLineFlags: {
allow: ["mcp__*"],
allow: ["external_*"],
exclude: ["*"],
},
useDefaults: false,
Expand All @@ -40,7 +41,7 @@ describe("precedenceResolver", () => {

expect(policies).toEqual([
{ tool: "*", permission: "exclude" },
{ tool: "mcp__*", permission: "allow" },
{ tool: "external_*", permission: "allow" },
]);
});

Expand Down Expand Up @@ -92,37 +93,11 @@ describe("precedenceResolver", () => {
]);
});

it("should apply config permissions with proper precedence", () => {
const configPolicies: ToolPermissionPolicy[] = [
{ tool: "Write", permission: "allow" },
{ tool: "Read", permission: "ask" },
];

const policies = resolvePermissionPrecedence({
commandLineFlags: {
exclude: ["Read"], // Should override config
},
configPermissions: configPolicies,
personalSettings: false,
useDefaults: false,
});

// CLI flag should override config
expect(policies[0]).toEqual({ tool: "Read", permission: "exclude" });
// Config policy should be present
expect(policies[1]).toEqual({ tool: "Write", permission: "allow" });
});

it("should handle all layers with proper precedence", () => {
const configPolicies: ToolPermissionPolicy[] = [
{ tool: "Search", permission: "ask" },
];

const policies = resolvePermissionPrecedence({
commandLineFlags: {
allow: ["Write"],
},
configPermissions: configPolicies,
personalSettings: false,
useDefaults: true,
});
Expand All @@ -131,10 +106,6 @@ describe("precedenceResolver", () => {
const writePolicy = policies.find((p) => p.tool === "Write");
expect(writePolicy?.permission).toBe("allow");

// Find the Search policy - should be from config
const searchPolicy = policies.find((p) => p.tool === "Search");
expect(searchPolicy?.permission).toBe("ask");

// Should still have default policies
const readPolicy = policies.find((p) => p.tool === "Read");
expect(readPolicy).toBeDefined();
Expand Down Expand Up @@ -164,23 +135,6 @@ describe("precedenceResolver", () => {
// Default policies should follow
expect(policies.slice(1)).toEqual(DEFAULT_TOOL_POLICIES);
});

it("should allow config policies to override defaults", () => {
const configPolicies: ToolPermissionPolicy[] = [
{ tool: "Bash", permission: "allow" },
];

const policies = resolvePermissionPrecedence({
configPermissions: configPolicies,
useDefaults: true,
personalSettings: false,
});

// Config policy should override default
expect(policies[0]).toEqual({ tool: "Bash", permission: "allow" });
// Default policies should follow
expect(policies.slice(1)).toEqual(DEFAULT_TOOL_POLICIES);
});
});
});
});
Loading
Loading