diff --git a/.changeset/pretty-pants-end.md b/.changeset/pretty-pants-end.md new file mode 100644 index 00000000000..a760cecbff9 --- /dev/null +++ b/.changeset/pretty-pants-end.md @@ -0,0 +1,5 @@ +--- +"kilo-code": minor +--- + +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..e7d2d63b63b 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 initial layer of protection for sensitive files. + +**Default patterns include:** + +- 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` + +**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 @@ -102,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 f78682c3b33..c4d5b048f83 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -45,6 +45,27 @@ 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 + */ +export const DEFAULT_GLOBALLY_IGNORED_FILES = [ + ".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 +] // kilocode_change end + /** * GlobalSettings */ @@ -72,6 +93,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 +356,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..dc95bbbbbe0 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 // kilocode_change + private globallyIgnoredFiles: string[] // kilocode_change private disposables: vscode.Disposable[] = [] rooIgnoreContent: string | undefined - constructor(cwd: string) { + constructor(cwd: string, globallyIgnoredFiles: string[] = []) { // kilocode_change this.cwd = cwd this.ignoreInstance = ignore() + this.globalIgnoreInstance = ignore() // kilocode_change this.rooIgnoreContent = undefined + 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() } @@ -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) @@ -107,14 +111,35 @@ export class RooIgnoreController { // Convert real path to relative for .rooignore checking const relativePath = path.relative(this.cwd, realPath).toPosix() - // Check if the real path is ignored - return !this.ignoreInstance.ignores(relativePath) + // kilocode_change start: 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 // kilocode_change end } catch (error) { // Allow access to files outside cwd or on errors (backward compatibility) return true } } + /** kilocode_change start + * 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) + } + } // kilocode_change end + /** * 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/ignore/__tests__/RooIgnoreController.spec.ts b/src/core/ignore/__tests__/RooIgnoreController.spec.ts index 9e630d5f20b..f4a0d5a96c2 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", () => { // kilocode_change start + /** + * 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) + }) + }) +}) // kilocode_change end 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/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 80cdbba24c5..3dd693b344c 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, // kilocode_change 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/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 79a68504e58..b4a48efdae5 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" // kilocode_change | "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/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 4cf3fcc8a1c..eecd7f66210 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" // kilocode_change 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} diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 2f699a5c843..95e0c0d5376 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.", + "addButton": "Add Pattern", + "patternPlaceholder": "Enter glob pattern (e.g., '*.key', '.env.*')" } }, "write": {