Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/pretty-pants-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kilo-code": minor
---

Adds the concept of a global list of ignored file types for privacy and security
45 changes: 44 additions & 1 deletion apps/kilocode-docs/docs/features/auto-approving-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

<img src="/docs/img/auto-approving-actions/auto-approving-actions-5.png" alt="VSCode Problems pane showing diagnostic information" width="600" />
Expand Down
23 changes: 23 additions & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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,
Expand Down
39 changes: 32 additions & 7 deletions src/core/ignore/RooIgnoreController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down Expand Up @@ -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)

Expand All @@ -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)
// First check global ignore patterns (always active) // kilocode_change start
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
Expand Down
194 changes: 193 additions & 1 deletion src/core/ignore/__tests__/RooIgnoreController.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -526,4 +526,196 @@ 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
Loading