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