From 0f3c581bca015405489452b83d04138cb4a868cc Mon Sep 17 00:00:00 2001 From: Josh Lambert Date: Fri, 5 Dec 2025 00:58:34 -0500 Subject: [PATCH 01/18] Update documentation --- .changeset/pretty-pants-end.md | 6 ++ .../docs/features/auto-approving-actions.md | 28 +++++++- packages/types/src/global-settings.ts | 24 +++++++ src/core/ignore/RooIgnoreController.ts | 41 ++++++++--- src/core/task/Task.ts | 26 +++++-- src/shared/ExtensionMessage.ts | 1 + .../settings/AutoApproveSettings.tsx | 68 +++++++++++++++++++ webview-ui/src/i18n/locales/en/settings.json | 6 ++ 8 files changed, 187 insertions(+), 13 deletions(-) create mode 100644 .changeset/pretty-pants-end.md diff --git a/.changeset/pretty-pants-end.md b/.changeset/pretty-pants-end.md new file mode 100644 index 00000000000..3ef60d2630d --- /dev/null +++ b/.changeset/pretty-pants-end.md @@ -0,0 +1,6 @@ +--- +"kilo-code": minor +"@roo-code/types": patch +--- + +Adds the concept of a global list of ignored file types for privacy and security diff --git a/apps/kilocode-docs/docs/features/auto-approving-actions.md b/apps/kilocode-docs/docs/features/auto-approving-actions.md index e3d2db8a2db..33bcf79a6e2 100644 --- a/apps/kilocode-docs/docs/features/auto-approving-actions.md +++ b/apps/kilocode-docs/docs/features/auto-approving-actions.md @@ -80,7 +80,33 @@ _Complete settings panel view_ **Risk level:** Medium While this setting only allows reading files (not modifying them), it could potentially expose sensitive data. Still recommended as a starting point for most users, but be mindful of what files Kilo Code can access. -::: + +#### Globally Ignored Files + +When auto-approve read is enabled, you can configure a list of file patterns that are **always ignored**, even without a `.kilocodeignore` file. This provides an extra layer of protection for sensitive files. + +**Default patterns include:** + +- Environment files: `.env`, `.env.*` +- Keys and certificates: `*.pem`, `*.key`, `*.p12`, `*.pfx`, `*.jks` +- SSH keys: `id_rsa`, `id_dsa`, `id_ecdsa`, `id_ed25519`, `*.ppk` +- Encryption files: `*.gpg`, `*.asc`, `*.sig` + +**Key features:** + +- Works even without a `.kilocodeignore` file +- Takes priority over `.kilocodeignore` patterns +- Fully customizable - add or remove patterns as needed +- Includes sensible defaults for common sensitive files + +**How to configure:** + +1. Enable "Always approve read-only operations" +2. Scroll down to the "Globally ignored files" section +3. Add custom patterns using glob syntax (e.g., `*.key`, `.env.*`) +4. Click "Add Pattern" to save each pattern +5. Remove patterns by clicking the X on pattern chips + ::: ### Write Operations diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index f78682c3b33..c7c7d621ac9 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -45,6 +45,28 @@ export const MAX_CHECKPOINT_TIMEOUT_SECONDS = 60 */ export const DEFAULT_CHECKPOINT_TIMEOUT_SECONDS = 15 +/** + * Default globally ignored file patterns + * These are common sensitive files that should be ignored even without a .kilocodeignore file + */ +export const DEFAULT_GLOBALLY_IGNORED_FILES = [ + ".env", + ".env.*", + "*.pem", + "*.key", + "*.p12", + "*.pfx", + "*.jks", + "id_rsa", + "id_dsa", + "id_ecdsa", + "id_ed25519", + "*.ppk", + "*.gpg", + "*.asc", + "*.sig", +] + /** * GlobalSettings */ @@ -72,6 +94,7 @@ export const globalSettingsSchema = z.object({ yoloGatekeeperApiConfigId: z.string().optional(), // kilocode_change: AI gatekeeper for YOLO mode alwaysAllowReadOnly: z.boolean().optional(), alwaysAllowReadOnlyOutsideWorkspace: z.boolean().optional(), + globallyIgnoredFiles: z.array(z.string()).optional(), // kilocode_change: Global ignore patterns alwaysAllowWrite: z.boolean().optional(), alwaysAllowWriteOutsideWorkspace: z.boolean().optional(), alwaysAllowWriteProtected: z.boolean().optional(), @@ -334,6 +357,7 @@ export const EVALS_SETTINGS: RooCodeSettings = { autoApprovalEnabled: true, alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: false, + globallyIgnoredFiles: DEFAULT_GLOBALLY_IGNORED_FILES, // kilocode_change alwaysAllowWrite: true, alwaysAllowWriteOutsideWorkspace: false, alwaysAllowWriteProtected: false, diff --git a/src/core/ignore/RooIgnoreController.ts b/src/core/ignore/RooIgnoreController.ts index a416439f184..c99ded48aae 100644 --- a/src/core/ignore/RooIgnoreController.ts +++ b/src/core/ignore/RooIgnoreController.ts @@ -15,13 +15,21 @@ export const LOCK_TEXT_SYMBOL = "\u{1F512}" export class RooIgnoreController { private cwd: string private ignoreInstance: Ignore + private globalIgnoreInstance: Ignore private disposables: vscode.Disposable[] = [] rooIgnoreContent: string | undefined + private globallyIgnoredFiles: string[] - constructor(cwd: string) { + constructor(cwd: string, globallyIgnoredFiles: string[] = []) { this.cwd = cwd this.ignoreInstance = ignore() + this.globalIgnoreInstance = ignore() this.rooIgnoreContent = undefined + this.globallyIgnoredFiles = globallyIgnoredFiles + // Initialize global ignore patterns + if (globallyIgnoredFiles.length > 0) { + this.globalIgnoreInstance.add(globallyIgnoredFiles) + } // Set up file watcher for .kilocodeignore this.setupFileWatcher() } @@ -87,10 +95,6 @@ export class RooIgnoreController { * @returns true if file is accessible, false if ignored */ validateAccess(filePath: string): boolean { - // Always allow access if .kilocodeignore does not exist - if (!this.rooIgnoreContent) { - return true - } try { const absolutePath = path.resolve(this.cwd, filePath) @@ -104,17 +108,38 @@ export class RooIgnoreController { realPath = absolutePath } - // Convert real path to relative for .rooignore checking + // Convert real path to relative for checking const relativePath = path.relative(this.cwd, realPath).toPosix() - // Check if the real path is ignored - return !this.ignoreInstance.ignores(relativePath) + // First check global ignore patterns (always active) + if (this.globallyIgnoredFiles.length > 0 && this.globalIgnoreInstance.ignores(relativePath)) { + return false + } + + // Then check .kilocodeignore patterns if the file exists + if (this.rooIgnoreContent && this.ignoreInstance.ignores(relativePath)) { + return false + } + + return true } catch (error) { // Allow access to files outside cwd or on errors (backward compatibility) return true } } + /** + * Update the global ignore patterns + * @param patterns - Array of glob patterns to ignore globally + */ + updateGlobalIgnorePatterns(patterns: string[]): void { + this.globallyIgnoredFiles = patterns + this.globalIgnoreInstance = ignore() + if (patterns.length > 0) { + this.globalIgnoreInstance.add(patterns) + } + } + /** * Check if a terminal command should be allowed to execute based on file access patterns * @param command - Terminal command to validate diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 6ab543a4d40..b482431b83d 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -44,6 +44,7 @@ import { MIN_CHECKPOINT_TIMEOUT_SECONDS, TOOL_PROTOCOL, ToolProtocol, + DEFAULT_GLOBALLY_IGNORED_FILES, } from "@roo-code/types" import { TelemetryService } from "@roo-code/telemetry" import { CloudService, BridgeOrchestrator } from "@roo-code/cloud" @@ -392,13 +393,30 @@ export class Task extends EventEmitter implements TaskLike { this.instanceId = crypto.randomUUID().slice(0, 8) this.taskNumber = -1 - this.rooIgnoreController = new RooIgnoreController(this.cwd) + // Initialize RooIgnoreController with default global ignore patterns + // Will be updated with actual settings after initialization + this.rooIgnoreController = new RooIgnoreController(this.cwd, DEFAULT_GLOBALLY_IGNORED_FILES) this.rooProtectedController = new RooProtectedController(this.cwd) this.fileContextTracker = new FileContextTracker(provider, this.taskId) - this.rooIgnoreController.initialize().catch((error) => { - console.error("Failed to initialize RooIgnoreController:", error) - }) + // Initialize and update with actual settings + this.rooIgnoreController + .initialize() + .then(() => { + // Update with actual global ignore patterns from settings + provider + .getState() + .then((state) => { + const globallyIgnoredFiles = state?.globallyIgnoredFiles ?? DEFAULT_GLOBALLY_IGNORED_FILES + this.rooIgnoreController?.updateGlobalIgnorePatterns(globallyIgnoredFiles) + }) + .catch((error) => { + console.error("Failed to update global ignore patterns:", error) + }) + }) + .catch((error) => { + console.error("Failed to initialize RooIgnoreController:", error) + }) this.apiConfiguration = apiConfiguration this.api = buildApiHandler(apiConfiguration) diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 79a68504e58..54d80eb7d63 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -340,6 +340,7 @@ export type ExtensionState = Pick< | "yoloMode" // kilocode_change | "alwaysAllowReadOnly" | "alwaysAllowReadOnlyOutsideWorkspace" + | "globallyIgnoredFiles" | "alwaysAllowWrite" | "alwaysAllowWriteOutsideWorkspace" | "alwaysAllowWriteProtected" diff --git a/webview-ui/src/components/settings/AutoApproveSettings.tsx b/webview-ui/src/components/settings/AutoApproveSettings.tsx index b0565dbc7fa..ddecbfed87a 100644 --- a/webview-ui/src/components/settings/AutoApproveSettings.tsx +++ b/webview-ui/src/components/settings/AutoApproveSettings.tsx @@ -20,6 +20,7 @@ import { useAutoApprovalToggles } from "@/hooks/useAutoApprovalToggles" type AutoApproveSettingsProps = HTMLAttributes & { alwaysAllowReadOnly?: boolean alwaysAllowReadOnlyOutsideWorkspace?: boolean + globallyIgnoredFiles?: string[] alwaysAllowWrite?: boolean alwaysAllowWriteOutsideWorkspace?: boolean alwaysAllowWriteProtected?: boolean @@ -43,6 +44,7 @@ type AutoApproveSettingsProps = HTMLAttributes & { setCachedStateField: SetCachedStateField< | "alwaysAllowReadOnly" | "alwaysAllowReadOnlyOutsideWorkspace" + | "globallyIgnoredFiles" | "alwaysAllowWrite" | "alwaysAllowWriteOutsideWorkspace" | "alwaysAllowWriteProtected" @@ -70,6 +72,7 @@ type AutoApproveSettingsProps = HTMLAttributes & { export const AutoApproveSettings = ({ alwaysAllowReadOnly, alwaysAllowReadOnlyOutsideWorkspace, + globallyIgnoredFiles, alwaysAllowWrite, alwaysAllowWriteOutsideWorkspace, alwaysAllowWriteProtected, @@ -96,6 +99,7 @@ export const AutoApproveSettings = ({ const { t } = useAppTranslation() const [commandInput, setCommandInput] = useState("") const [deniedCommandInput, setDeniedCommandInput] = useState("") + const [globalIgnoreInput, setGlobalIgnoreInput] = useState("") const { autoApprovalEnabled, setAutoApprovalEnabled, listApiConfigMeta } = useExtensionState() // kilocode_change: Add listApiConfigMeta for gatekeeper const toggles = useAutoApprovalToggles() @@ -124,6 +128,17 @@ export const AutoApproveSettings = ({ } } + const handleAddGlobalIgnorePattern = () => { + const currentPatterns = globallyIgnoredFiles ?? [] + + if (globalIgnoreInput && !currentPatterns.includes(globalIgnoreInput)) { + const newPatterns = [...currentPatterns, globalIgnoreInput] + setCachedStateField("globallyIgnoredFiles", newPatterns) + setGlobalIgnoreInput("") + vscode.postMessage({ type: "updateSettings", updatedSettings: { globallyIgnoredFiles: newPatterns } }) + } + } + return (
@@ -242,6 +257,59 @@ export const AutoApproveSettings = ({ {t("settings:autoApprove.readOnly.outsideWorkspace.description")}
+
+ +
+ {t("settings:autoApprove.readOnly.globalIgnore.description")} +
+
+ +
+ setGlobalIgnoreInput(e.target.value)} + onKeyDown={(e: any) => { + if (e.key === "Enter") { + e.preventDefault() + handleAddGlobalIgnorePattern() + } + }} + placeholder={t("settings:autoApprove.readOnly.globalIgnore.patternPlaceholder")} + className="grow" + data-testid="global-ignore-input" + /> + +
+ +
+ {(globallyIgnoredFiles ?? []).map((pattern, index) => ( + + ))} +
)} diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 2f699a5c843..fd1603979ca 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -162,6 +162,12 @@ "outsideWorkspace": { "label": "Include files outside workspace", "description": "Allow Kilo Code to read files outside the current workspace without requiring approval." + }, + "globalIgnore": { + "label": "Globally ignored files", + "description": "File patterns that are always ignored, even without a .kilocodeignore file. Common sensitive files like .env, keys, and credentials are included by default.", + "addButton": "Add Pattern", + "patternPlaceholder": "Enter glob pattern (e.g., '*.key', '.env.*')" } }, "write": { From 6e903569c80e1d80fa046801c0ca4fbbefe0c313 Mon Sep 17 00:00:00 2001 From: Josh Lambert Date: Fri, 5 Dec 2025 01:23:09 -0500 Subject: [PATCH 02/18] Add tests and fix a default glob --- .../docs/features/auto-approving-actions.md | 21 +- packages/types/src/global-settings.ts | 29 ++- .../__tests__/RooIgnoreController.spec.ts | 191 ++++++++++++++++++ 3 files changed, 224 insertions(+), 17 deletions(-) diff --git a/apps/kilocode-docs/docs/features/auto-approving-actions.md b/apps/kilocode-docs/docs/features/auto-approving-actions.md index 33bcf79a6e2..652e212a219 100644 --- a/apps/kilocode-docs/docs/features/auto-approving-actions.md +++ b/apps/kilocode-docs/docs/features/auto-approving-actions.md @@ -87,7 +87,7 @@ When auto-approve read is enabled, you can configure a list of file patterns tha **Default patterns include:** -- Environment files: `.env`, `.env.*` +- Environment files: `.env*` (matches .env, .env.local, .env.production, etc.) - Keys and certificates: `*.pem`, `*.key`, `*.p12`, `*.pfx`, `*.jks` - SSH keys: `id_rsa`, `id_dsa`, `id_ecdsa`, `id_ed25519`, `*.ppk` - Encryption files: `*.gpg`, `*.asc`, `*.sig` @@ -103,7 +103,7 @@ When auto-approve read is enabled, you can configure a list of file patterns tha 1. Enable "Always approve read-only operations" 2. Scroll down to the "Globally ignored files" section -3. Add custom patterns using glob syntax (e.g., `*.key`, `.env.*`) +3. Add custom patterns using glob syntax (e.g., `*.key`, `.env*`) 4. Click "Add Pattern" to save each pattern 5. Remove patterns by clicking the X on pattern chips ::: @@ -128,6 +128,23 @@ This setting allows Kilo Code to modify your files without confirmation. The del - Lower values: Use only when speed is critical and you're in a controlled environment - Zero: No delay for diagnostics (not recommended for critical code) +#### Protected Files + +Kilo Code has **two levels of file protection**: + +1. **Globally Ignored Files** (configured in Read settings above): + + - Completely blocked from all operations (read AND write) + - Examples: `.env`, `*.key`, SSH keys, credentials + - These files cannot be accessed at all, regardless of any other settings + +2. **Write-Protected Configuration Files**: + - Can be read but require approval to modify by default + - Examples: `.kilocodeignore`, `.kilocode/`, `.vscode/` + - Can be auto-approved if you enable "Include protected files" checkbox below + +This layered protection ensures sensitive data files are completely inaccessible while configuration files remain readable. You can choose whether to auto-approve writes to configuration files based on your trust level. + #### Write Delay & Problems Pane Integration VSCode Problems pane showing diagnostic information diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index c7c7d621ac9..0bcf91fbbdb 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -50,21 +50,20 @@ export const DEFAULT_CHECKPOINT_TIMEOUT_SECONDS = 15 * These are common sensitive files that should be ignored even without a .kilocodeignore file */ export const DEFAULT_GLOBALLY_IGNORED_FILES = [ - ".env", - ".env.*", - "*.pem", - "*.key", - "*.p12", - "*.pfx", - "*.jks", - "id_rsa", - "id_dsa", - "id_ecdsa", - "id_ed25519", - "*.ppk", - "*.gpg", - "*.asc", - "*.sig", + ".env*", // Matches .env, .env.local, .env.production, etc. + "*.pem", // SSL certificates + "*.key", // Private keys + "*.p12", // PKCS#12 certificates + "*.pfx", // Windows certificates + "*.jks", // Java keystores + "id_rsa", // SSH private key (RSA) + "id_dsa", // SSH private key (DSA) + "id_ecdsa", // SSH private key (ECDSA) + "id_ed25519", // SSH private key (ED25519) + "*.ppk", // PuTTY private keys + "*.gpg", // GPG encrypted files + "*.asc", // ASCII-armored GPG files + "*.sig", // Signature files ] /** diff --git a/src/core/ignore/__tests__/RooIgnoreController.spec.ts b/src/core/ignore/__tests__/RooIgnoreController.spec.ts index 9e630d5f20b..d3d64ac31a8 100644 --- a/src/core/ignore/__tests__/RooIgnoreController.spec.ts +++ b/src/core/ignore/__tests__/RooIgnoreController.spec.ts @@ -526,4 +526,195 @@ describe("RooIgnoreController", () => { expect(controller.validateAccess("node_modules/package.json")).toBe(true) }) }) + + describe("global ignore patterns", () => { + /** + * Tests that global patterns work without .kilocodeignore + */ + it("should block globally ignored files even without .kilocodeignore", async () => { + // Setup global patterns but no .kilocodeignore + // Note: Use .env* (with wildcard) to match .env.production, .env.local, etc. + const globalPatterns = [".env*", "*.key", "id_rsa"] + const controllerWithGlobal = new RooIgnoreController(TEST_CWD, globalPatterns) + + mockFileExists.mockResolvedValue(false) // No .kilocodeignore + await controllerWithGlobal.initialize() + + // Verify no .kilocodeignore content + expect(controllerWithGlobal.rooIgnoreContent).toBeUndefined() + + // Global patterns should still block access + expect(controllerWithGlobal.validateAccess(".env")).toBe(false) + expect(controllerWithGlobal.validateAccess(".env.production")).toBe(false) + expect(controllerWithGlobal.validateAccess(".env.local")).toBe(false) + expect(controllerWithGlobal.validateAccess("config.key")).toBe(false) + expect(controllerWithGlobal.validateAccess("id_rsa")).toBe(false) + + // Non-matching files should be allowed + expect(controllerWithGlobal.validateAccess("src/app.ts")).toBe(true) + expect(controllerWithGlobal.validateAccess("README.md")).toBe(true) + expect(controllerWithGlobal.validateAccess("environment.ts")).toBe(true) // Should not match .env* + }) + + /** + * Tests that global patterns take priority over .kilocodeignore + */ + it("should give priority to global patterns over .kilocodeignore", async () => { + // Setup global patterns with wildcard + const globalPatterns = [".env*", "*.key"] + const controllerWithGlobal = new RooIgnoreController(TEST_CWD, globalPatterns) + + // Setup .kilocodeignore that tries to allow .env but blocks other files + mockFileExists.mockResolvedValue(true) + mockReadFile.mockResolvedValue("node_modules\n!.env*") // Negation pattern to try to allow .env + await controllerWithGlobal.initialize() + + // Global pattern should take priority - .env files should still be blocked + expect(controllerWithGlobal.validateAccess(".env")).toBe(false) + expect(controllerWithGlobal.validateAccess(".env.local")).toBe(false) + + // Global patterns should block regardless of .kilocodeignore + expect(controllerWithGlobal.validateAccess("config.key")).toBe(false) + + // .kilocodeignore patterns should still work + expect(controllerWithGlobal.validateAccess("node_modules/package.json")).toBe(false) + }) + + /** + * Tests updateGlobalIgnorePatterns method + */ + it("should update global patterns dynamically", async () => { + // Start with some patterns + const controllerWithGlobal = new RooIgnoreController(TEST_CWD, [".env*"]) + mockFileExists.mockResolvedValue(false) + await controllerWithGlobal.initialize() + + // Verify initial pattern works + expect(controllerWithGlobal.validateAccess(".env")).toBe(false) + expect(controllerWithGlobal.validateAccess(".env.local")).toBe(false) + expect(controllerWithGlobal.validateAccess("config.key")).toBe(true) + + // Update patterns + controllerWithGlobal.updateGlobalIgnorePatterns([".env*", "*.key", "*.pem"]) + + // Verify updated patterns work + expect(controllerWithGlobal.validateAccess(".env")).toBe(false) + expect(controllerWithGlobal.validateAccess(".env.production")).toBe(false) + expect(controllerWithGlobal.validateAccess("config.key")).toBe(false) + expect(controllerWithGlobal.validateAccess("cert.pem")).toBe(false) + expect(controllerWithGlobal.validateAccess("src/app.ts")).toBe(true) + }) + + /** + * Tests clearing global patterns + */ + it("should allow clearing all global patterns", async () => { + // Start with patterns + const controllerWithGlobal = new RooIgnoreController(TEST_CWD, [".env*", "*.key"]) + mockFileExists.mockResolvedValue(false) + await controllerWithGlobal.initialize() + + // Verify patterns work + expect(controllerWithGlobal.validateAccess(".env")).toBe(false) + expect(controllerWithGlobal.validateAccess(".env.local")).toBe(false) + expect(controllerWithGlobal.validateAccess("config.key")).toBe(false) + + // Clear patterns + controllerWithGlobal.updateGlobalIgnorePatterns([]) + + // Verify patterns no longer block + expect(controllerWithGlobal.validateAccess(".env")).toBe(true) + expect(controllerWithGlobal.validateAccess(".env.local")).toBe(true) + expect(controllerWithGlobal.validateAccess("config.key")).toBe(true) + }) + + /** + * Tests complex glob patterns in global ignore + */ + it("should support complex glob patterns", async () => { + const globalPatterns = [ + ".env*", // Wildcard suffix + "*.pem", // Wildcard prefix + "secrets/**", // Directory glob + "id_*", // Partial wildcard + ] + const controllerWithGlobal = new RooIgnoreController(TEST_CWD, globalPatterns) + mockFileExists.mockResolvedValue(false) + await controllerWithGlobal.initialize() + + // Test wildcard suffix + expect(controllerWithGlobal.validateAccess(".env")).toBe(false) + expect(controllerWithGlobal.validateAccess(".env.local")).toBe(false) + expect(controllerWithGlobal.validateAccess(".env.production")).toBe(false) + + // Test wildcard prefix + expect(controllerWithGlobal.validateAccess("cert.pem")).toBe(false) + expect(controllerWithGlobal.validateAccess("key.pem")).toBe(false) + + // Test directory glob + expect(controllerWithGlobal.validateAccess("secrets/api-key.txt")).toBe(false) + expect(controllerWithGlobal.validateAccess("secrets/nested/file.txt")).toBe(false) + + // Test partial wildcard + expect(controllerWithGlobal.validateAccess("id_rsa")).toBe(false) + expect(controllerWithGlobal.validateAccess("id_dsa")).toBe(false) + expect(controllerWithGlobal.validateAccess("id_ecdsa")).toBe(false) + + // Non-matching should be allowed + expect(controllerWithGlobal.validateAccess("environment.ts")).toBe(true) + expect(controllerWithGlobal.validateAccess("config.json")).toBe(true) + }) + + /** + * Tests that both global and .kilocodeignore patterns work together + */ + it("should enforce both global and .kilocodeignore patterns", async () => { + // Setup global patterns with wildcards + const globalPatterns = [".env*", "*.key"] + const controllerWithGlobal = new RooIgnoreController(TEST_CWD, globalPatterns) + + // Setup .kilocodeignore with different patterns + mockFileExists.mockResolvedValue(true) + mockReadFile.mockResolvedValue("node_modules\n*.log") + await controllerWithGlobal.initialize() + + // Global patterns should block + expect(controllerWithGlobal.validateAccess(".env")).toBe(false) + expect(controllerWithGlobal.validateAccess(".env.local")).toBe(false) + expect(controllerWithGlobal.validateAccess("config.key")).toBe(false) + + // .kilocodeignore patterns should also block + expect(controllerWithGlobal.validateAccess("node_modules/package.json")).toBe(false) + expect(controllerWithGlobal.validateAccess("app.log")).toBe(false) + + // Files not matching either should be allowed + expect(controllerWithGlobal.validateAccess("src/app.ts")).toBe(true) + expect(controllerWithGlobal.validateAccess("config.json")).toBe(true) + }) + + /** + * Tests default globally ignored files from DEFAULT_GLOBALLY_IGNORED_FILES + */ + it("should work with DEFAULT_GLOBALLY_IGNORED_FILES patterns", async () => { + // Import the defaults + const { DEFAULT_GLOBALLY_IGNORED_FILES } = await import("@roo-code/types") + + const controllerWithDefaults = new RooIgnoreController(TEST_CWD, DEFAULT_GLOBALLY_IGNORED_FILES) + mockFileExists.mockResolvedValue(false) + await controllerWithDefaults.initialize() + + // Test common sensitive files from defaults + expect(controllerWithDefaults.validateAccess(".env")).toBe(false) + expect(controllerWithDefaults.validateAccess(".env.local")).toBe(false) + expect(controllerWithDefaults.validateAccess("private.key")).toBe(false) + expect(controllerWithDefaults.validateAccess("cert.pem")).toBe(false) + expect(controllerWithDefaults.validateAccess("id_rsa")).toBe(false) + expect(controllerWithDefaults.validateAccess("id_ed25519")).toBe(false) + expect(controllerWithDefaults.validateAccess("key.gpg")).toBe(false) + + // Regular files should be allowed + expect(controllerWithDefaults.validateAccess("src/config.ts")).toBe(true) + expect(controllerWithDefaults.validateAccess("README.md")).toBe(true) + }) + }) }) From 2c704c1e704bea11a8a1e99a274bdd0b54ee35de Mon Sep 17 00:00:00 2001 From: Joshua Lambert <25085430+lambertjosh@users.noreply.github.com> Date: Fri, 5 Dec 2025 01:44:41 -0500 Subject: [PATCH 03/18] Update webview-ui/src/i18n/locales/en/settings.json --- webview-ui/src/i18n/locales/en/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index fd1603979ca..95e0c0d5376 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -165,7 +165,7 @@ }, "globalIgnore": { "label": "Globally ignored files", - "description": "File patterns that are always ignored, even without a .kilocodeignore file. Common sensitive files like .env, keys, and credentials are included by default.", + "description": "File patterns that are always ignored, even without a .kilocodeignore file.", "addButton": "Add Pattern", "patternPlaceholder": "Enter glob pattern (e.g., '*.key', '.env.*')" } From 3c8cb9b41abce8bbf55ba1768cc4161ffb04030d Mon Sep 17 00:00:00 2001 From: Joshua Lambert <25085430+lambertjosh@users.noreply.github.com> Date: Fri, 5 Dec 2025 03:13:25 -0500 Subject: [PATCH 04/18] Update apps/kilocode-docs/docs/features/auto-approving-actions.md --- apps/kilocode-docs/docs/features/auto-approving-actions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/kilocode-docs/docs/features/auto-approving-actions.md b/apps/kilocode-docs/docs/features/auto-approving-actions.md index 652e212a219..e7d2d63b63b 100644 --- a/apps/kilocode-docs/docs/features/auto-approving-actions.md +++ b/apps/kilocode-docs/docs/features/auto-approving-actions.md @@ -83,7 +83,7 @@ While this setting only allows reading files (not modifying them), it could pote #### Globally Ignored Files -When auto-approve read is enabled, you can configure a list of file patterns that are **always ignored**, even without a `.kilocodeignore` file. This provides an extra layer of protection for sensitive files. +When auto-approve read is enabled, you can configure a list of file patterns that are **always ignored**, even without a `.kilocodeignore` file. This provides an initial layer of protection for sensitive files. **Default patterns include:** From 5114ecf0b12b06e57d6213f50a98c48100578684 Mon Sep 17 00:00:00 2001 From: Joshua Lambert <25085430+lambertjosh@users.noreply.github.com> Date: Fri, 5 Dec 2025 11:37:52 -0500 Subject: [PATCH 05/18] Update packages/types/src/global-settings.ts Add kilocode_change start and end tags Co-authored-by: Remon Oldenbeuving --- packages/types/src/global-settings.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 0bcf91fbbdb..33f1c1d0945 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -45,6 +45,7 @@ export const MAX_CHECKPOINT_TIMEOUT_SECONDS = 60 */ export const DEFAULT_CHECKPOINT_TIMEOUT_SECONDS = 15 +// kilocode_change start /** * Default globally ignored file patterns * These are common sensitive files that should be ignored even without a .kilocodeignore file @@ -65,7 +66,7 @@ export const DEFAULT_GLOBALLY_IGNORED_FILES = [ "*.asc", // ASCII-armored GPG files "*.sig", // Signature files ] - +// kilocode_change end /** * GlobalSettings */ From 5a7cbb0aad3fbf1c94de757aa1fe6a5c08a218d5 Mon Sep 17 00:00:00 2001 From: Josh Lambert Date: Fri, 5 Dec 2025 13:52:34 -0500 Subject: [PATCH 06/18] Fix the settings propoagation and persistence --- src/core/webview/ClineProvider.ts | 4 ++++ webview-ui/src/components/settings/SettingsView.tsx | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 80cdbba24c5..01280349bfe 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -43,6 +43,7 @@ import { ORGANIZATION_ALLOW_ALL, DEFAULT_MODES, DEFAULT_CHECKPOINT_TIMEOUT_SECONDS, + DEFAULT_GLOBALLY_IGNORED_FILES, getModelId, } from "@roo-code/types" import { TelemetryService } from "@roo-code/telemetry" @@ -2056,6 +2057,7 @@ ${prompt} customInstructions, alwaysAllowReadOnly, alwaysAllowReadOnlyOutsideWorkspace, + globallyIgnoredFiles, // kilocode_change alwaysAllowWrite, alwaysAllowWriteOutsideWorkspace, alwaysAllowWriteProtected, @@ -2218,6 +2220,7 @@ ${prompt} customInstructions, alwaysAllowReadOnly: alwaysAllowReadOnly ?? true, alwaysAllowReadOnlyOutsideWorkspace: alwaysAllowReadOnlyOutsideWorkspace ?? true, + globallyIgnoredFiles: globallyIgnoredFiles ?? DEFAULT_GLOBALLY_IGNORED_FILES, // kilocode_change alwaysAllowWrite: alwaysAllowWrite ?? true, alwaysAllowWriteOutsideWorkspace: alwaysAllowWriteOutsideWorkspace ?? false, alwaysAllowWriteProtected: alwaysAllowWriteProtected ?? false, @@ -2498,6 +2501,7 @@ ${prompt} apiModelId: stateValues.apiModelId, alwaysAllowReadOnly: stateValues.alwaysAllowReadOnly ?? true, alwaysAllowReadOnlyOutsideWorkspace: stateValues.alwaysAllowReadOnlyOutsideWorkspace ?? true, + globallyIgnoredFiles: stateValues.globallyIgnoredFiles ?? DEFAULT_GLOBALLY_IGNORED_FILES, // kilocode_change alwaysAllowWrite: stateValues.alwaysAllowWrite ?? true, alwaysAllowWriteOutsideWorkspace: stateValues.alwaysAllowWriteOutsideWorkspace ?? false, alwaysAllowWriteProtected: stateValues.alwaysAllowWriteProtected ?? false, diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 4cf3fcc8a1c..419a4d2ef2d 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -9,6 +9,7 @@ import React, { useRef, useState, } from "react" +import { DEFAULT_GLOBALLY_IGNORED_FILES } from "@roo-code/types" import { CheckCheck, SquareMousePointer, @@ -159,6 +160,7 @@ const SettingsView = forwardRef(({ onDone, t const { alwaysAllowReadOnly, alwaysAllowReadOnlyOutsideWorkspace, + globallyIgnoredFiles, // kilocode_change allowedCommands, deniedCommands, allowedMaxRequests, @@ -476,6 +478,7 @@ const SettingsView = forwardRef(({ onDone, t language, alwaysAllowReadOnly: alwaysAllowReadOnly ?? undefined, alwaysAllowReadOnlyOutsideWorkspace: alwaysAllowReadOnlyOutsideWorkspace ?? undefined, + globallyIgnoredFiles: globallyIgnoredFiles ?? DEFAULT_GLOBALLY_IGNORED_FILES, // kilocode_change alwaysAllowWrite: alwaysAllowWrite ?? undefined, alwaysAllowWriteOutsideWorkspace: alwaysAllowWriteOutsideWorkspace ?? undefined, alwaysAllowWriteProtected: alwaysAllowWriteProtected ?? undefined, @@ -952,6 +955,7 @@ const SettingsView = forwardRef(({ onDone, t yoloGatekeeperApiConfigId={yoloGatekeeperApiConfigId} // kilocode_change: AI gatekeeper for YOLO mode alwaysAllowReadOnly={alwaysAllowReadOnly} alwaysAllowReadOnlyOutsideWorkspace={alwaysAllowReadOnlyOutsideWorkspace} + globallyIgnoredFiles={globallyIgnoredFiles ?? DEFAULT_GLOBALLY_IGNORED_FILES} // kilocode_change alwaysAllowWrite={alwaysAllowWrite} alwaysAllowWriteOutsideWorkspace={alwaysAllowWriteOutsideWorkspace} alwaysAllowWriteProtected={alwaysAllowWriteProtected} From 804a7e51d0d64a31daf54be0253c656886b7616b Mon Sep 17 00:00:00 2001 From: Joshua Lambert <25085430+lambertjosh@users.noreply.github.com> Date: Mon, 8 Dec 2025 01:02:05 -0500 Subject: [PATCH 07/18] Update src/shared/ExtensionMessage.ts Co-authored-by: Remon Oldenbeuving --- src/shared/ExtensionMessage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 54d80eb7d63..b4a48efdae5 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -340,7 +340,7 @@ export type ExtensionState = Pick< | "yoloMode" // kilocode_change | "alwaysAllowReadOnly" | "alwaysAllowReadOnlyOutsideWorkspace" - | "globallyIgnoredFiles" + | "globallyIgnoredFiles" // kilocode_change | "alwaysAllowWrite" | "alwaysAllowWriteOutsideWorkspace" | "alwaysAllowWriteProtected" From abf0c90595406f4dddc6af2f7a15d83c1c0634e1 Mon Sep 17 00:00:00 2001 From: Joshua Lambert <25085430+lambertjosh@users.noreply.github.com> Date: Mon, 8 Dec 2025 01:05:20 -0500 Subject: [PATCH 08/18] Update .changeset/pretty-pants-end.md Co-authored-by: Kevin van Dijk --- .changeset/pretty-pants-end.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/pretty-pants-end.md b/.changeset/pretty-pants-end.md index 3ef60d2630d..a760cecbff9 100644 --- a/.changeset/pretty-pants-end.md +++ b/.changeset/pretty-pants-end.md @@ -1,6 +1,5 @@ --- "kilo-code": minor -"@roo-code/types": patch --- Adds the concept of a global list of ignored file types for privacy and security From ac15ff022f2d1d6c75dea32d472914516c48a65c Mon Sep 17 00:00:00 2001 From: Josh Lambert Date: Mon, 8 Dec 2025 01:24:19 -0500 Subject: [PATCH 09/18] Add kilocode_change flags --- src/core/ignore/RooIgnoreController.ts | 17 +++++++++-------- .../__tests__/RooIgnoreController.spec.ts | 3 ++- src/core/webview/ClineProvider.ts | 2 +- .../src/components/settings/SettingsView.tsx | 2 +- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/core/ignore/RooIgnoreController.ts b/src/core/ignore/RooIgnoreController.ts index c99ded48aae..869511e4772 100644 --- a/src/core/ignore/RooIgnoreController.ts +++ b/src/core/ignore/RooIgnoreController.ts @@ -15,21 +15,22 @@ export const LOCK_TEXT_SYMBOL = "\u{1F512}" export class RooIgnoreController { private cwd: string private ignoreInstance: Ignore - private globalIgnoreInstance: Ignore + private globalIgnoreInstance: Ignore // kilocode_change + private globallyIgnoredFiles: string[] // kilocode_change private disposables: vscode.Disposable[] = [] rooIgnoreContent: string | undefined - private globallyIgnoredFiles: string[] constructor(cwd: string, globallyIgnoredFiles: string[] = []) { + // kilocode_change this.cwd = cwd this.ignoreInstance = ignore() - this.globalIgnoreInstance = ignore() + this.globalIgnoreInstance = ignore() // kilocode_change this.rooIgnoreContent = undefined - this.globallyIgnoredFiles = globallyIgnoredFiles + this.globallyIgnoredFiles = globallyIgnoredFiles // kilocode_change start // Initialize global ignore patterns if (globallyIgnoredFiles.length > 0) { this.globalIgnoreInstance.add(globallyIgnoredFiles) - } + } // kilocode_change end // Set up file watcher for .kilocodeignore this.setupFileWatcher() } @@ -108,10 +109,10 @@ export class RooIgnoreController { realPath = absolutePath } - // Convert real path to relative for checking + // Convert real path to relative for .rooignore checking const relativePath = path.relative(this.cwd, realPath).toPosix() - // First check global ignore patterns (always active) + // First check global ignore patterns (always active) // kilocode_change start if (this.globallyIgnoredFiles.length > 0 && this.globalIgnoreInstance.ignores(relativePath)) { return false } @@ -121,7 +122,7 @@ export class RooIgnoreController { return false } - return true + return true // kilocode_change end } catch (error) { // Allow access to files outside cwd or on errors (backward compatibility) return true diff --git a/src/core/ignore/__tests__/RooIgnoreController.spec.ts b/src/core/ignore/__tests__/RooIgnoreController.spec.ts index d3d64ac31a8..49cc3d96cb3 100644 --- a/src/core/ignore/__tests__/RooIgnoreController.spec.ts +++ b/src/core/ignore/__tests__/RooIgnoreController.spec.ts @@ -528,6 +528,7 @@ describe("RooIgnoreController", () => { }) describe("global ignore patterns", () => { + // kilocode_change start /** * Tests that global patterns work without .kilocodeignore */ @@ -717,4 +718,4 @@ describe("RooIgnoreController", () => { expect(controllerWithDefaults.validateAccess("README.md")).toBe(true) }) }) -}) +}) // kilocode_change end diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 01280349bfe..3dd693b344c 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -43,7 +43,7 @@ import { ORGANIZATION_ALLOW_ALL, DEFAULT_MODES, DEFAULT_CHECKPOINT_TIMEOUT_SECONDS, - DEFAULT_GLOBALLY_IGNORED_FILES, + DEFAULT_GLOBALLY_IGNORED_FILES, // kilocode_change getModelId, } from "@roo-code/types" import { TelemetryService } from "@roo-code/telemetry" diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 419a4d2ef2d..eecd7f66210 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -9,7 +9,7 @@ import React, { useRef, useState, } from "react" -import { DEFAULT_GLOBALLY_IGNORED_FILES } from "@roo-code/types" +import { DEFAULT_GLOBALLY_IGNORED_FILES } from "@roo-code/types" // kilocode_change import { CheckCheck, SquareMousePointer, From b9a84558fb1fa8ac94c5a47edf7dc3989528cd40 Mon Sep 17 00:00:00 2001 From: Josh Lambert Date: Mon, 8 Dec 2025 01:32:06 -0500 Subject: [PATCH 10/18] Fix missing kilochange flags --- packages/types/src/global-settings.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 33f1c1d0945..c4d5b048f83 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -45,8 +45,7 @@ export const MAX_CHECKPOINT_TIMEOUT_SECONDS = 60 */ export const DEFAULT_CHECKPOINT_TIMEOUT_SECONDS = 15 -// kilocode_change start -/** +/** kilocode_change start * Default globally ignored file patterns * These are common sensitive files that should be ignored even without a .kilocodeignore file */ @@ -65,8 +64,8 @@ export const DEFAULT_GLOBALLY_IGNORED_FILES = [ "*.gpg", // GPG encrypted files "*.asc", // ASCII-armored GPG files "*.sig", // Signature files -] -// kilocode_change end +] // kilocode_change end + /** * GlobalSettings */ From 60054fdb96ce372137a76c0ae1f9f0d23ee4cb73 Mon Sep 17 00:00:00 2001 From: Josh Lambert Date: Mon, 8 Dec 2025 01:37:18 -0500 Subject: [PATCH 11/18] Fix missing kilochange flags --- src/core/ignore/RooIgnoreController.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/ignore/RooIgnoreController.ts b/src/core/ignore/RooIgnoreController.ts index 869511e4772..97b60b0743e 100644 --- a/src/core/ignore/RooIgnoreController.ts +++ b/src/core/ignore/RooIgnoreController.ts @@ -129,7 +129,7 @@ export class RooIgnoreController { } } - /** + /** kilocode_change start * Update the global ignore patterns * @param patterns - Array of glob patterns to ignore globally */ @@ -139,7 +139,7 @@ export class RooIgnoreController { if (patterns.length > 0) { this.globalIgnoreInstance.add(patterns) } - } + } // kilocode_change end /** * Check if a terminal command should be allowed to execute based on file access patterns From b9c8f8a524b74ff62d9b0aab3e6f14eda804c7b9 Mon Sep 17 00:00:00 2001 From: Josh Lambert Date: Mon, 8 Dec 2025 01:41:45 -0500 Subject: [PATCH 12/18] Fix bad kilochange notification From c354aab02639169c119ccd11760b972994a2f0d1 Mon Sep 17 00:00:00 2001 From: Joshua Lambert <25085430+lambertjosh@users.noreply.github.com> Date: Mon, 8 Dec 2025 01:44:39 -0500 Subject: [PATCH 13/18] Update src/core/ignore/RooIgnoreController.ts --- src/core/ignore/RooIgnoreController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/ignore/RooIgnoreController.ts b/src/core/ignore/RooIgnoreController.ts index 97b60b0743e..ecadd8cd3a4 100644 --- a/src/core/ignore/RooIgnoreController.ts +++ b/src/core/ignore/RooIgnoreController.ts @@ -20,7 +20,7 @@ export class RooIgnoreController { private disposables: vscode.Disposable[] = [] rooIgnoreContent: string | undefined - constructor(cwd: string, globallyIgnoredFiles: string[] = []) { + constructor(cwd: string, globallyIgnoredFiles: string[] = []) { // kilocode_change // kilocode_change this.cwd = cwd this.ignoreInstance = ignore() From 3161b6bb51b71641da5fac46f8c5dca9fe2ba865 Mon Sep 17 00:00:00 2001 From: Joshua Lambert <25085430+lambertjosh@users.noreply.github.com> Date: Mon, 8 Dec 2025 01:45:01 -0500 Subject: [PATCH 14/18] Update src/core/ignore/RooIgnoreController.ts --- src/core/ignore/RooIgnoreController.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/ignore/RooIgnoreController.ts b/src/core/ignore/RooIgnoreController.ts index ecadd8cd3a4..1cc81b920f8 100644 --- a/src/core/ignore/RooIgnoreController.ts +++ b/src/core/ignore/RooIgnoreController.ts @@ -21,7 +21,6 @@ export class RooIgnoreController { rooIgnoreContent: string | undefined constructor(cwd: string, globallyIgnoredFiles: string[] = []) { // kilocode_change - // kilocode_change this.cwd = cwd this.ignoreInstance = ignore() this.globalIgnoreInstance = ignore() // kilocode_change From ec25fa06621bf4e35a188006f82a4b2697408338 Mon Sep 17 00:00:00 2001 From: Joshua Lambert <25085430+lambertjosh@users.noreply.github.com> Date: Mon, 8 Dec 2025 21:16:46 -0500 Subject: [PATCH 15/18] Update src/core/ignore/RooIgnoreController.ts --- src/core/ignore/RooIgnoreController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/ignore/RooIgnoreController.ts b/src/core/ignore/RooIgnoreController.ts index 1cc81b920f8..dc95bbbbbe0 100644 --- a/src/core/ignore/RooIgnoreController.ts +++ b/src/core/ignore/RooIgnoreController.ts @@ -111,7 +111,7 @@ export class RooIgnoreController { // Convert real path to relative for .rooignore checking const relativePath = path.relative(this.cwd, realPath).toPosix() - // First check global ignore patterns (always active) // kilocode_change start + // kilocode_change start: First check global ignore patterns (always active) if (this.globallyIgnoredFiles.length > 0 && this.globalIgnoreInstance.ignores(relativePath)) { return false } From bfe88e6c40f569bc108ea0fc4b1f747f8af6c760 Mon Sep 17 00:00:00 2001 From: Joshua Lambert <25085430+lambertjosh@users.noreply.github.com> Date: Mon, 8 Dec 2025 21:18:23 -0500 Subject: [PATCH 16/18] Update src/core/ignore/__tests__/RooIgnoreController.spec.ts --- src/core/ignore/__tests__/RooIgnoreController.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/ignore/__tests__/RooIgnoreController.spec.ts b/src/core/ignore/__tests__/RooIgnoreController.spec.ts index 49cc3d96cb3..45e076e0e16 100644 --- a/src/core/ignore/__tests__/RooIgnoreController.spec.ts +++ b/src/core/ignore/__tests__/RooIgnoreController.spec.ts @@ -528,7 +528,6 @@ describe("RooIgnoreController", () => { }) describe("global ignore patterns", () => { - // kilocode_change start /** * Tests that global patterns work without .kilocodeignore */ From 646652dff99e7a538815bca926c277c1adcf85f3 Mon Sep 17 00:00:00 2001 From: Joshua Lambert <25085430+lambertjosh@users.noreply.github.com> Date: Mon, 8 Dec 2025 21:18:32 -0500 Subject: [PATCH 17/18] Update src/core/ignore/__tests__/RooIgnoreController.spec.ts --- src/core/ignore/__tests__/RooIgnoreController.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/ignore/__tests__/RooIgnoreController.spec.ts b/src/core/ignore/__tests__/RooIgnoreController.spec.ts index 45e076e0e16..f4a0d5a96c2 100644 --- a/src/core/ignore/__tests__/RooIgnoreController.spec.ts +++ b/src/core/ignore/__tests__/RooIgnoreController.spec.ts @@ -527,7 +527,7 @@ describe("RooIgnoreController", () => { }) }) - describe("global ignore patterns", () => { + describe("global ignore patterns", () => { // kilocode_change start /** * Tests that global patterns work without .kilocodeignore */ From dcbf419d1d32145e6e81ccfe2e6077f0bdde0ca4 Mon Sep 17 00:00:00 2001 From: Josh Lambert Date: Thu, 18 Dec 2025 00:02:15 -0500 Subject: [PATCH 18/18] Refactor to a new component --- .../settings/AutoApproveSettings.tsx | 92 +--------------- .../settings/GlobalIgnoreSettings.tsx | 103 ++++++++++++++++++ .../__tests__/GlobalIgnoreSettings.spec.tsx | 90 +++++++++++++++ 3 files changed, 199 insertions(+), 86 deletions(-) create mode 100644 webview-ui/src/components/settings/GlobalIgnoreSettings.tsx create mode 100644 webview-ui/src/components/settings/__tests__/GlobalIgnoreSettings.spec.tsx diff --git a/webview-ui/src/components/settings/AutoApproveSettings.tsx b/webview-ui/src/components/settings/AutoApproveSettings.tsx index ddecbfed87a..afc41b48755 100644 --- a/webview-ui/src/components/settings/AutoApproveSettings.tsx +++ b/webview-ui/src/components/settings/AutoApproveSettings.tsx @@ -13,6 +13,7 @@ import { SectionHeader } from "./SectionHeader" import { Section } from "./Section" import { AutoApproveToggle } from "./AutoApproveToggle" import { MaxLimitInputs } from "./MaxLimitInputs" +import { GlobalIgnoreSettings } from "./GlobalIgnoreSettings" import { useExtensionState } from "@/context/ExtensionStateContext" import { useAutoApprovalState } from "@/hooks/useAutoApprovalState" import { useAutoApprovalToggles } from "@/hooks/useAutoApprovalToggles" @@ -99,7 +100,6 @@ export const AutoApproveSettings = ({ const { t } = useAppTranslation() const [commandInput, setCommandInput] = useState("") const [deniedCommandInput, setDeniedCommandInput] = useState("") - const [globalIgnoreInput, setGlobalIgnoreInput] = useState("") const { autoApprovalEnabled, setAutoApprovalEnabled, listApiConfigMeta } = useExtensionState() // kilocode_change: Add listApiConfigMeta for gatekeeper const toggles = useAutoApprovalToggles() @@ -128,17 +128,6 @@ export const AutoApproveSettings = ({ } } - const handleAddGlobalIgnorePattern = () => { - const currentPatterns = globallyIgnoredFiles ?? [] - - if (globalIgnoreInput && !currentPatterns.includes(globalIgnoreInput)) { - const newPatterns = [...currentPatterns, globalIgnoreInput] - setCachedStateField("globallyIgnoredFiles", newPatterns) - setGlobalIgnoreInput("") - vscode.postMessage({ type: "updateSettings", updatedSettings: { globallyIgnoredFiles: newPatterns } }) - } - } - return (
@@ -237,80 +226,11 @@ export const AutoApproveSettings = ({ {/* ADDITIONAL SETTINGS */} {alwaysAllowReadOnly && ( -
-
- -
{t("settings:autoApprove.readOnly.label")}
-
-
- - setCachedStateField("alwaysAllowReadOnlyOutsideWorkspace", e.target.checked) - } - data-testid="always-allow-readonly-outside-workspace-checkbox"> - - {t("settings:autoApprove.readOnly.outsideWorkspace.label")} - - -
- {t("settings:autoApprove.readOnly.outsideWorkspace.description")} -
-
-
- -
- {t("settings:autoApprove.readOnly.globalIgnore.description")} -
-
- -
- setGlobalIgnoreInput(e.target.value)} - onKeyDown={(e: any) => { - if (e.key === "Enter") { - e.preventDefault() - handleAddGlobalIgnorePattern() - } - }} - placeholder={t("settings:autoApprove.readOnly.globalIgnore.patternPlaceholder")} - className="grow" - data-testid="global-ignore-input" - /> - -
- -
- {(globallyIgnoredFiles ?? []).map((pattern, index) => ( - - ))} -
-
+ )} {alwaysAllowWrite && ( diff --git a/webview-ui/src/components/settings/GlobalIgnoreSettings.tsx b/webview-ui/src/components/settings/GlobalIgnoreSettings.tsx new file mode 100644 index 00000000000..ec8f48b19c9 --- /dev/null +++ b/webview-ui/src/components/settings/GlobalIgnoreSettings.tsx @@ -0,0 +1,103 @@ +import { X } from "lucide-react" +import { useAppTranslation } from "@/i18n/TranslationContext" +import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react" +import { vscode } from "@/utils/vscode" +import { Button, Input } from "@/components/ui" +import { useState } from "react" +import { SetCachedStateField } from "./types" + +interface GlobalIgnoreSettingsProps { + globallyIgnoredFiles?: string[] + alwaysAllowReadOnlyOutsideWorkspace?: boolean + setCachedStateField: SetCachedStateField<"globallyIgnoredFiles" | "alwaysAllowReadOnlyOutsideWorkspace"> +} + +export const GlobalIgnoreSettings = ({ + globallyIgnoredFiles, + alwaysAllowReadOnlyOutsideWorkspace, + setCachedStateField, +}: GlobalIgnoreSettingsProps) => { + const { t } = useAppTranslation() + const [globalIgnoreInput, setGlobalIgnoreInput] = useState("") + + const handleAddGlobalIgnorePattern = () => { + const currentPatterns = globallyIgnoredFiles ?? [] + + if (globalIgnoreInput && !currentPatterns.includes(globalIgnoreInput)) { + const newPatterns = [...currentPatterns, globalIgnoreInput] + setCachedStateField("globallyIgnoredFiles", newPatterns) + setGlobalIgnoreInput("") + vscode.postMessage({ type: "updateSettings", updatedSettings: { globallyIgnoredFiles: newPatterns } }) + } + } + + return ( +
+
+ +
{t("settings:autoApprove.readOnly.label")}
+
+
+ setCachedStateField("alwaysAllowReadOnlyOutsideWorkspace", e.target.checked)} + data-testid="always-allow-readonly-outside-workspace-checkbox"> + {t("settings:autoApprove.readOnly.outsideWorkspace.label")} + +
+ {t("settings:autoApprove.readOnly.outsideWorkspace.description")} +
+
+
+ +
+ {t("settings:autoApprove.readOnly.globalIgnore.description")} +
+
+ +
+ setGlobalIgnoreInput(e.target.value)} + onKeyDown={(e: any) => { + if (e.key === "Enter") { + e.preventDefault() + handleAddGlobalIgnorePattern() + } + }} + placeholder={t("settings:autoApprove.readOnly.globalIgnore.patternPlaceholder")} + className="grow" + data-testid="global-ignore-input" + /> + +
+ +
+ {(globallyIgnoredFiles ?? []).map((pattern, index) => ( + + ))} +
+
+ ) +} diff --git a/webview-ui/src/components/settings/__tests__/GlobalIgnoreSettings.spec.tsx b/webview-ui/src/components/settings/__tests__/GlobalIgnoreSettings.spec.tsx new file mode 100644 index 00000000000..435fab16008 --- /dev/null +++ b/webview-ui/src/components/settings/__tests__/GlobalIgnoreSettings.spec.tsx @@ -0,0 +1,90 @@ +import { render, screen, fireEvent } from "@/utils/test-utils" +import { GlobalIgnoreSettings } from "../GlobalIgnoreSettings" +import { vscode } from "@/utils/vscode" + +// Mock vscode +vi.mock("@/utils/vscode", () => ({ + vscode: { + postMessage: vi.fn(), + }, +})) + +describe("GlobalIgnoreSettings", () => { + const mockSetCachedStateField = vi.fn() + const defaultProps = { + globallyIgnoredFiles: [".env", "*.secret"], + alwaysAllowReadOnlyOutsideWorkspace: false, + setCachedStateField: mockSetCachedStateField, + } + + beforeEach(() => { + vi.clearAllMocks() + }) + + it("renders correctly", () => { + render() + expect(screen.getByText("settings:autoApprove.readOnly.label")).toBeInTheDocument() + expect(screen.getByTestId("global-ignore-input")).toBeInTheDocument() + expect(screen.getByText(".env")).toBeInTheDocument() + expect(screen.getByText("*.secret")).toBeInTheDocument() + }) + + it("adds a new global ignore pattern", () => { + render() + + const input = screen.getByTestId("global-ignore-input") + fireEvent.change(input, { target: { value: "*.pem" } }) + + const addButton = screen.getByTestId("add-global-ignore-button") + fireEvent.click(addButton) + + // Check if state update was called + expect(mockSetCachedStateField).toHaveBeenCalledWith("globallyIgnoredFiles", [".env", "*.secret", "*.pem"]) + + // Check if vscode message was sent + expect(vscode.postMessage).toHaveBeenCalledWith({ + type: "updateSettings", + updatedSettings: { + globallyIgnoredFiles: [".env", "*.secret", "*.pem"], + }, + }) + }) + + it("does not add duplicate patterns", () => { + render() + + const input = screen.getByTestId("global-ignore-input") + fireEvent.change(input, { target: { value: ".env" } }) // Already exists + + const addButton = screen.getByTestId("add-global-ignore-button") + fireEvent.click(addButton) + + expect(mockSetCachedStateField).not.toHaveBeenCalled() + expect(vscode.postMessage).not.toHaveBeenCalled() + }) + + it("removes a global ignore pattern", () => { + render() + + const removeButton = screen.getByTestId("remove-global-ignore-0") // Remove first item (.env) + fireEvent.click(removeButton) + + expect(mockSetCachedStateField).toHaveBeenCalledWith("globallyIgnoredFiles", ["*.secret"]) + + expect(vscode.postMessage).toHaveBeenCalledWith({ + type: "updateSettings", + updatedSettings: { + globallyIgnoredFiles: ["*.secret"], + }, + }) + }) + + it("toggles alwaysAllowReadOnlyOutsideWorkspace", () => { + render() + + const checkbox = screen.getByTestId("always-allow-readonly-outside-workspace-checkbox") + fireEvent.click(checkbox) + + expect(mockSetCachedStateField).toHaveBeenCalledWith("alwaysAllowReadOnlyOutsideWorkspace", true) + }) +})