pilotignore 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2026 Valtteri Kerttula
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,152 @@
1
+ <p align="center">
2
+ <img src="assets/logo.svg" alt="pilotignore logo" width="120" height="156">
3
+ </p>
4
+
5
+ </p>
6
+
7
+ <h1 align="center">
8
+
9
+ `.pilotignore` — keep your secrets secret
10
+
11
+ </h1>
12
+
13
+ <p align="center">
14
+ <strong>like <code>.gitignore</code>, but for github copilot agents</strong>
15
+ </p>
16
+
17
+ > [!WARNING]
18
+ >
19
+ > **EARLY DEVELOPMENT/EXPERIMENTAL PHASE. USE AT YOUR OWN RISK.**
20
+ >
21
+ > This project is currently in active development and still contain bugs or unhandled edge cases.
22
+ >
23
+ > AI technologies and their capabilities are evolving rapidly and continuously, meaning new bypass methods could emerge. Use this tool strictly at your own risk. It is a supplementary security layer, not a silver bullet. Always follow best practices for secret management: use secure environment vaults, never commit hardcoded secrets to your repository, and **never expose data to an AI agent that you absolutely cannot afford to leak.** Be the boss of your agent, not the other way around.
24
+
25
+ > [!TIP]
26
+ > **Fun fact:** Some other AI coding assistants already have native, built-in features to prevent the AI from reading specific files. Until GitHub Copilot introduces an official, first-class `.copilotignore` feature, `pilotignore` is here to bridge the gap!
27
+
28
+ GitHub Copilot Cloud Agent and CLI are powerful tools that can navigate your codebase, read files, and execute commands. But sometimes, they shouldn't see everything. `pilotignore` is a lightweight CLI that sets up a programmatic firewall — actively blocking AI agents from reading `.env` files, API keys, or private configs.
29
+
30
+ One tool. One philosophy: **Agent boundaries should be simple.**
31
+
32
+ <p align="center">
33
+ <a href="https://www.npmjs.com/package/pilotignore"><img src="https://img.shields.io/npm/v/pilotignore?style=flat&color=000000" alt="NPM Version"></a>
34
+ <a href="https://www.npmjs.com/package/pilotignore"><img src="https://img.shields.io/npm/dt/pilotignore?style=flat&color=000000" alt="Downloads"></a>
35
+ <a href="LICENSE"><img src="https://img.shields.io/github/license/vkerttula/pilotignore?style=flat&color=000000" alt="License"></a>
36
+ </p>
37
+
38
+ ## Table of Contents
39
+
40
+ <p align="center">
41
+ <a href="#before--after">Before/After</a> •
42
+ <a href="#install">Install</a> •
43
+ <a href="#what-you-get">What You Get</a> •
44
+ <a href="#how-it-works">How It Works</a> •
45
+ <a href="#roadmap">Roadmap</a>
46
+ </p>
47
+
48
+ ---
49
+
50
+ ## Before / After
51
+
52
+ ### 🔓 Normal Copilot Agent
53
+
54
+ > **You:** "What is the database password inside config/secrets.json?"
55
+ > **Copilot:** "Looking at your `config/secrets.json` file, the database password is `super_secret_p4ssw0rd`. I can help you rotate this if you need!"
56
+
57
+ ### 🛡️ Copilot + pilotignore
58
+
59
+ > **You:** "What is the database password inside config/secrets.json?"
60
+ > **Copilot:** "I'm sorry, I cannot access `config/secrets.json` because my file-reading tool was denied by your `.pilotignore` rules."
61
+
62
+ <p align="center">
63
+ <img src="assets/copilot-vscode.png" alt="Copilot VS Code Chat blocked" width="49%">
64
+ &nbsp;
65
+ <img src="assets/copilot-cli.png" alt="Copilot CLI blocked" width="49%">
66
+ </p>
67
+
68
+ **Same agent. 0% leaked secrets. Peace of mind.**
69
+
70
+ ## Install
71
+
72
+ Install `pilotignore` as a development dependency. This ensures your project is locked to a specific version and makes updates seamless.
73
+
74
+ ```bash
75
+ # 1. Install the package
76
+ npm install -D pilotignore
77
+
78
+ # 2. Initialize the hooks and .pilotignore file
79
+ npx pilotignore init
80
+
81
+ ```
82
+
83
+ ## What You Get
84
+
85
+ After initialization, your project will have the following structure:
86
+
87
+ ```text
88
+ project-root/
89
+ ├── .github/
90
+ │ └── hooks/
91
+ │ └── pilotignore.json <-- Tells Copilot to run the firewall from node_modules
92
+ ├── .copilot/
93
+ │ └── logs/
94
+ │ └── pilotignore.log <-- Action logs accumulate here (add this to your .gitignore!)
95
+ ├── .pilotignore <-- Add your sensitive files/folders to this blocklist
96
+ └── package.json <-- pilotignore is installed as a devDependency
97
+
98
+ ```
99
+
100
+ Simply add your sensitive files to the generated `.pilotignore` file at the root of your project:
101
+
102
+ ```text
103
+ # Add files here to hide them from GitHub Copilot
104
+ # Examples:
105
+ # secrets.json
106
+ # .env
107
+ # Block from reading this file
108
+ .pilotignore
109
+
110
+ ```
111
+
112
+ Commit the `.pilotignore` file and the generated `.github/hooks/` directory configuration to Git. That's it.
113
+
114
+ | Target | Status | Note |
115
+ | -------------------- | -------------- | --------------------------------------------------------------------------------------------------- |
116
+ | **Copilot CLI** | 🛡️ Blocked | Local commands (`view`, `read_file`) are intercepted. |
117
+ | **IDE Chat** | 🛡️ Blocked | Agentic workspace reads are stopped at the tool level. |
118
+ | **Cloud Agent** | ⏳ Coming soon | Support for GitHub's secure cloud sandbox (see V2 roadmap). |
119
+ | **IDE Autocomplete** | ⏳ Coming soon | Ghost-text autocomplete does not use tools (see V3 roadmap). Already possible with vscode settings. |
120
+
121
+ ## How It Works
122
+
123
+ 1. You install the package and run `npx pilotignore init`.
124
+ 2. The tool drops an official Copilot Hooks API configuration script into `.github/hooks/pilotignore.json` that points to the local `node_modules` package.
125
+ 3. When a Copilot Agent tries to run a tool (like `view` to read a file), the `preToolUse` hook fires.
126
+ 4. If the target file matches an entry in your `.pilotignore`, the hook forcefully returns a `{"permissionDecision": "deny"}` JSON payload and a non-zero exit code.
127
+ 5. The Agent gracefully backs off. No files read.
128
+
129
+ > [!IMPORTANT]
130
+ > While `pilotignore` blocks native file-reading tools, an extremely determined AI could theoretically write a custom script to read a file and execute it via `bash`. **We block the front door, but complex workarounds are always a possibility with AI.** Use at your own risk, and ensure you understand the limitations of agentic boundaries. This is the closest to native, code-level blocking we've achieved so far — be the boss of your agent, not the other way around.
131
+
132
+ ## Roadmap
133
+
134
+ - [x] Block Copilot Cloud Agent & CLI tools via `preToolUse` hooks.
135
+ - [x] Basic exact-path and glob matching.
136
+ - [ ] **V1.1:** Add global installation support (`pilotignore init --global`) for user-level protection across all local repositories.
137
+ - [ ] **V2:** Cloud Agent support (syncing hooks to GitHub's secure cloud sandbox).
138
+ - [ ] **V2:** Inject `.pilotignore` rules into Copilot Chat's `custom instructions` for double redundancy.
139
+ - [ ] **V3:** Sync `.pilotignore` rules to `.vscode/settings.json` to automatically disable IDE ghost-text autocomplete for sensitive files.
140
+
141
+ ## Links
142
+
143
+ - [GitHub Copilot Hooks Reference](https://docs.github.com/en/copilot/reference/hooks-reference) — the underlying API
144
+ - [Issues](https://github.com/vkerttula/pilotignore/issues) — bug, feature, weird behavior
145
+
146
+ ## Star This Repo
147
+
148
+ `.pilotignore` keeps your secrets secret. Star cost zero. Fair trade. ⭐
149
+
150
+ ## License
151
+
152
+ MIT
Binary file
Binary file
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 130" width="120" height="156">
2
+ <path d="M 10,15
3
+ L 90,15
4
+ C 90,65 75,105 50,120
5
+ C 25,105 10,65 10,15
6
+ Z"
7
+ fill="#000000" />
8
+ </svg>
package/bin/cli.js ADDED
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "fs/promises";
4
+ import path from "path";
5
+ import { fileURLToPath } from "url";
6
+
7
+ // ESM equivalents for __dirname
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ async function init() {
12
+ const args = process.argv.slice(2);
13
+
14
+ if (args[0] !== "init") {
15
+ console.log("Usage: npx pilotignore init");
16
+ process.exit(1);
17
+ }
18
+
19
+ const targetDir = process.cwd();
20
+ const githubHooksDir = path.join(targetDir, ".github", "hooks");
21
+ const templatesDir = path.join(__dirname, "..", "templates");
22
+
23
+ try {
24
+ // 1. Create the directory structure
25
+ await fs.mkdir(githubHooksDir, { recursive: true });
26
+ console.log("✅ Ensured directory: .github/hooks");
27
+
28
+ // 2. Copy the configuration file
29
+ await fs.copyFile(
30
+ path.join(templatesDir, "pilotignore.json"),
31
+ path.join(githubHooksDir, "pilotignore.json"),
32
+ );
33
+ console.log("✅ Copied: .github/hooks/pilotignore.json");
34
+
35
+ // 3. Copy the actual firewall script
36
+ await fs.copyFile(
37
+ path.join(templatesDir, "pilotignore.js"),
38
+ path.join(githubHooksDir, "pilotignore.js"),
39
+ );
40
+ console.log("✅ Copied: .github/hooks/pilotignore.js");
41
+
42
+ // 4. Create .pilotignore only if it doesn't exist
43
+ const targetPilotignore = path.join(targetDir, ".pilotignore");
44
+ try {
45
+ await fs.access(targetPilotignore);
46
+ console.log("ℹ️ .pilotignore already exists, skipping creation.");
47
+ } catch {
48
+ await fs.copyFile(
49
+ path.join(templatesDir, "default.pilotignore"),
50
+ targetPilotignore,
51
+ );
52
+ console.log("✅ Created default file: .pilotignore");
53
+ }
54
+
55
+ console.log("\n🚀 pilotignore initialization complete!");
56
+ console.log(
57
+ "Add your sensitive files to the .pilotignore file at the root of your project.",
58
+ );
59
+ } catch (error) {
60
+ console.error("❌ Installation error:", error.message);
61
+ process.exit(1);
62
+ }
63
+ }
64
+
65
+ init();
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "pilotignore",
3
+ "version": "0.1.0",
4
+ "description": "Prevent Copilot from reading your secrets using .pilotignore. Works like .gitignore but for GitHub Copilot.",
5
+ "type": "module",
6
+ "main": "bin/cli.js",
7
+ "bin": {
8
+ "pilotignore": "./bin/cli.js"
9
+ },
10
+ "keywords": [
11
+ "copilot",
12
+ "github",
13
+ "hook",
14
+ "ignore",
15
+ "security",
16
+ "copilotignore",
17
+ "firewall",
18
+ "agent",
19
+ "pre-tool-use",
20
+ "copilot-hooks"
21
+ ],
22
+ "author": "Valtteri Kerttula",
23
+ "license": "MIT"
24
+ }
@@ -0,0 +1,6 @@
1
+ # Add files here to hide them from GitHub Copilot
2
+ # Examples:
3
+ # secrets.json
4
+ # .env
5
+ # Block from reading this file
6
+ .pilotignore
@@ -0,0 +1,168 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ // Helper function for logging
5
+ function appendLog(cwd, message) {
6
+ try {
7
+ const logDir = path.join(cwd, ".copilot", "logs");
8
+ const logFile = path.join(logDir, "pilotignore.log");
9
+
10
+ // Ensure the log directory exists
11
+ if (!fs.existsSync(logDir)) {
12
+ fs.mkdirSync(logDir, { recursive: true });
13
+ }
14
+
15
+ const timestamp = new Date().toISOString();
16
+ fs.appendFileSync(logFile, `[${timestamp}] ${message}\n`);
17
+ } catch (e) {
18
+ // If logging fails, do not crash the entire program
19
+ }
20
+ }
21
+
22
+ let input = "";
23
+ process.stdin.setEncoding("utf8");
24
+ process.stdin.on("data", (chunk) => {
25
+ input += chunk;
26
+ });
27
+
28
+ process.stdin.on("end", () => {
29
+ // Default cwd, updated if a better one is found in the payload
30
+ let currentCwd = process.cwd();
31
+ appendLog(currentCwd, `HOOK PROCESS STARTED (pid: ${process.pid})`);
32
+
33
+ try {
34
+ const payload = JSON.parse(input || "{}");
35
+ currentCwd = payload.cwd || currentCwd; // Use the project root
36
+
37
+ const candidates = [];
38
+ function collect(val) {
39
+ if (!val) return;
40
+ if (typeof val === "string") {
41
+ try {
42
+ const parsed = JSON.parse(val);
43
+ if (parsed && typeof parsed === "object") {
44
+ collect(parsed);
45
+ return;
46
+ }
47
+ } catch (e) {}
48
+ candidates.push(val);
49
+ return;
50
+ }
51
+ if (Array.isArray(val)) {
52
+ val.forEach(collect);
53
+ return;
54
+ }
55
+ if (typeof val === "object") {
56
+ Object.values(val).forEach(collect);
57
+ return;
58
+ }
59
+ candidates.push(String(val));
60
+ }
61
+
62
+ if (payload.tool_input) collect(payload.tool_input);
63
+ if (
64
+ payload.tool_input_as_string &&
65
+ typeof payload.tool_input_as_string === "string"
66
+ ) {
67
+ try {
68
+ collect(JSON.parse(payload.tool_input_as_string));
69
+ } catch (e) {}
70
+ }
71
+
72
+ if (payload.toolArgs) collect(payload.toolArgs);
73
+ if (payload.arguments) collect(payload.arguments);
74
+
75
+ if (candidates.length === 0) {
76
+ collect(payload);
77
+ }
78
+
79
+ [
80
+ "path",
81
+ "file",
82
+ "target",
83
+ "filePath",
84
+ "args",
85
+ "files",
86
+ "targetPath",
87
+ "file_path",
88
+ ].forEach((k) => collect(payload[k]));
89
+
90
+ appendLog(currentCwd, `Candidate paths: ${JSON.stringify(candidates)}`);
91
+
92
+ const ignoreRules = [];
93
+ const ignoreFilePath = path.join(currentCwd, ".pilotignore");
94
+ if (fs.existsSync(ignoreFilePath)) {
95
+ const content = fs.readFileSync(ignoreFilePath, "utf8");
96
+ content.split(/\r?\n/).forEach((line) => {
97
+ const trimmed = line.trim();
98
+ if (trimmed && !trimmed.startsWith("#")) ignoreRules.push(trimmed);
99
+ });
100
+ appendLog(
101
+ currentCwd,
102
+ `Loaded ${ignoreRules.length} rules from the .pilotignore file.`,
103
+ );
104
+ } else {
105
+ appendLog(
106
+ currentCwd,
107
+ `Warning: .pilotignore file not found at path ${ignoreFilePath}`,
108
+ );
109
+ }
110
+
111
+ const isBlocked = candidates.some((candidate) => {
112
+ if (!candidate.includes(path.sep) && !candidate.includes("/"))
113
+ return false;
114
+ const relative = path.relative(currentCwd, candidate).replace(/\\/g, "/");
115
+ return ignoreRules.some(
116
+ (rule) => relative.endsWith(rule) || relative === rule,
117
+ );
118
+ });
119
+
120
+ if (isBlocked) {
121
+ const reason = "File reading is blocked based on a .pilotignore rule.";
122
+ appendLog(
123
+ currentCwd,
124
+ `DECISION: DENY (Blocked) - Path found on the blocklist.`,
125
+ );
126
+
127
+ console.log(
128
+ JSON.stringify({
129
+ hookSpecificOutput: {
130
+ hookEventName: "PreToolUse",
131
+ permissionDecision: "deny",
132
+ permissionDecisionReason: reason,
133
+ },
134
+ }),
135
+ );
136
+
137
+ console.error(reason);
138
+
139
+ if (input.includes("vscode") || input.includes("copilot-chat")) {
140
+ process.exit(0); // copilot chat VS Code IDE
141
+ } else {
142
+ process.exit(2); // copilot CLI
143
+ }
144
+ }
145
+
146
+ appendLog(currentCwd, `DECISION: ALLOW`);
147
+ console.log(
148
+ JSON.stringify({
149
+ hookSpecificOutput: {
150
+ hookEventName: "PreToolUse",
151
+ permissionDecision: "allow",
152
+ },
153
+ }),
154
+ );
155
+ process.exit(0);
156
+ } catch (err) {
157
+ appendLog(currentCwd, `ERROR: ${err.message}`);
158
+ console.log(
159
+ JSON.stringify({
160
+ hookSpecificOutput: {
161
+ hookEventName: "PreToolUse",
162
+ permissionDecision: "allow",
163
+ },
164
+ }),
165
+ );
166
+ process.exit(0);
167
+ }
168
+ });
@@ -0,0 +1,15 @@
1
+ {
2
+ "version": 1,
3
+ "hooks": {
4
+ "preToolUse": [
5
+ {
6
+ "type": "command",
7
+ "bash": "node .github/hooks/pilotignore.js",
8
+ "powershell": "node .github\\hooks\\pilotignore.js",
9
+ "windows": "node .github\\hooks\\pilotignore.js",
10
+ "cwd": ".",
11
+ "timeoutSec": 5
12
+ }
13
+ ]
14
+ }
15
+ }
@@ -0,0 +1 @@
1
+ # Coming