Skip to content

Commit 851f2f1

Browse files
committed
feat(secrets): block access to common files containing secrets
1 parent fe6b4ca commit 851f2f1

File tree

2 files changed

+524
-0
lines changed

2 files changed

+524
-0
lines changed

command-blocker.test.ts

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,6 +1197,215 @@ describe("Command Blocker", () => {
11971197
});
11981198
});
11991199

1200+
describe("checkSecretFileRead", () => {
1201+
let plugin: any;
1202+
let mockApp: any;
1203+
let mockClient: any;
1204+
let mock$: any;
1205+
1206+
beforeEach(async () => {
1207+
mockApp = {};
1208+
mockClient = {};
1209+
mock$ = {};
1210+
plugin = await CommandBlocker({
1211+
app: mockApp,
1212+
client: mockClient,
1213+
$: mock$,
1214+
});
1215+
});
1216+
1217+
it("should block reading .env files", async () => {
1218+
const input1 = { tool: "read" };
1219+
const output1 = { args: { filePath: ".env" } };
1220+
await expect(
1221+
plugin["tool.execute.before"](input1, output1)
1222+
).rejects.toThrow(
1223+
"`Reading secret files is blocked to prevent exposure of sensitive data including API keys, credentials, and configuration.`"
1224+
);
1225+
1226+
const input2 = { tool: "read" };
1227+
const output2 = { args: { filePath: ".envrc" } };
1228+
await expect(
1229+
plugin["tool.execute.before"](input2, output2)
1230+
).rejects.toThrow(
1231+
"`Reading secret files is blocked to prevent exposure of sensitive data including API keys, credentials, and configuration.`"
1232+
);
1233+
1234+
const input3 = { tool: "read" };
1235+
const output3 = { args: { filePath: ".env.local" } };
1236+
await expect(
1237+
plugin["tool.execute.before"](input3, output3)
1238+
).rejects.toThrow(
1239+
"`Reading secret files is blocked to prevent exposure of sensitive data including API keys, credentials, and configuration.`"
1240+
);
1241+
});
1242+
1243+
it("should block reading secrets files", async () => {
1244+
const input1 = { tool: "read" };
1245+
const output1 = { args: { filePath: "secrets.json" } };
1246+
await expect(
1247+
plugin["tool.execute.before"](input1, output1)
1248+
).rejects.toThrow(
1249+
"`Reading secret files is blocked to prevent exposure of sensitive data including API keys, credentials, and configuration.`"
1250+
);
1251+
1252+
const input2 = { tool: "read" };
1253+
const output2 = { args: { filePath: "config/secrets.json" } };
1254+
await expect(
1255+
plugin["tool.execute.before"](input2, output2)
1256+
).rejects.toThrow(
1257+
"`Reading secret files is blocked to prevent exposure of sensitive data including API keys, credentials, and configuration.`"
1258+
);
1259+
1260+
const input3 = { tool: "read" };
1261+
const output3 = { args: { filePath: ".secrets" } };
1262+
await expect(
1263+
plugin["tool.execute.before"](input3, output3)
1264+
).rejects.toThrow(
1265+
"`Reading secret files is blocked to prevent exposure of sensitive data including API keys, credentials, and configuration.`"
1266+
);
1267+
});
1268+
1269+
it("should block reading SSH key files", async () => {
1270+
const input1 = { tool: "read" };
1271+
const output1 = { args: { filePath: ".ssh/id_rsa" } };
1272+
await expect(
1273+
plugin["tool.execute.before"](input1, output1)
1274+
).rejects.toThrow(
1275+
"`Reading secret files is blocked to prevent exposure of sensitive data including API keys, credentials, and configuration.`"
1276+
);
1277+
1278+
const input2 = { tool: "read" };
1279+
const output2 = { args: { filePath: "id_rsa" } };
1280+
await expect(
1281+
plugin["tool.execute.before"](input2, output2)
1282+
).rejects.toThrow(
1283+
"`Reading secret files is blocked to prevent exposure of sensitive data including API keys, credentials, and configuration.`"
1284+
);
1285+
});
1286+
1287+
it("should block reading AWS credentials", async () => {
1288+
const input1 = { tool: "read" };
1289+
const output1 = { args: { filePath: ".aws/credentials" } };
1290+
await expect(
1291+
plugin["tool.execute.before"](input1, output1)
1292+
).rejects.toThrow(
1293+
"`Reading secret files is blocked to prevent exposure of sensitive data including API keys, credentials, and configuration.`"
1294+
);
1295+
1296+
const input2 = { tool: "read" };
1297+
const output2 = { args: { filePath: ".aws/config" } };
1298+
await expect(
1299+
plugin["tool.execute.before"](input2, output2)
1300+
).rejects.toThrow(
1301+
"`Reading secret files is blocked to prevent exposure of sensitive data including API keys, credentials, and configuration.`"
1302+
);
1303+
});
1304+
1305+
it("should allow reading other files", async () => {
1306+
const input1 = { tool: "read" };
1307+
const output1 = { args: { filePath: "package.json" } };
1308+
await expect(async () => {
1309+
await plugin["tool.execute.before"](input1, output1);
1310+
}).not.toThrow();
1311+
1312+
const input2 = { tool: "read" };
1313+
const output2 = { args: { filePath: "src/main.ts" } };
1314+
await expect(async () => {
1315+
await plugin["tool.execute.before"](input2, output2);
1316+
}).not.toThrow();
1317+
1318+
const input3 = { tool: "read" };
1319+
const output3 = { args: { filePath: "README.md" } };
1320+
await expect(async () => {
1321+
await plugin["tool.execute.before"](input3, output3);
1322+
}).not.toThrow();
1323+
});
1324+
1325+
it("should handle empty file path", async () => {
1326+
const input1 = { tool: "read" };
1327+
const output1 = { args: { filePath: "" } };
1328+
await expect(async () => {
1329+
await plugin["tool.execute.before"](input1, output1);
1330+
}).not.toThrow();
1331+
1332+
const input2 = { tool: "read" };
1333+
const output2 = { args: { filePath: undefined } };
1334+
await expect(async () => {
1335+
await plugin["tool.execute.before"](input2, output2);
1336+
}).not.toThrow();
1337+
});
1338+
1339+
it("should handle file paths with query parameters and fragments", async () => {
1340+
const input1 = { tool: "read" };
1341+
const output1 = { args: { filePath: ".env?version=1" } };
1342+
await expect(
1343+
plugin["tool.execute.before"](input1, output1)
1344+
).rejects.toThrow();
1345+
1346+
const input2 = { tool: "read" };
1347+
const output2 = { args: { filePath: "secrets.json#section" } };
1348+
await expect(
1349+
plugin["tool.execute.before"](input2, output2)
1350+
).rejects.toThrow();
1351+
});
1352+
1353+
it("should block reading certificate and key files", async () => {
1354+
const inputs = [
1355+
{ tool: "read", args: { filePath: "certificate.pem" } },
1356+
{ tool: "read", args: { filePath: "private.key" } },
1357+
{ tool: "read", args: { filePath: "server.crt" } },
1358+
{ tool: "read", args: { filePath: "keystore.jks" } },
1359+
{ tool: "read", args: { filePath: "config/cert.p12" } },
1360+
];
1361+
1362+
for (const input of inputs) {
1363+
await expect(
1364+
plugin["tool.execute.before"]({ tool: input.tool }, { args: input.args })
1365+
).rejects.toThrow(
1366+
"`Reading secret files is blocked to prevent exposure of sensitive data including API keys, credentials, and configuration.`"
1367+
);
1368+
}
1369+
});
1370+
1371+
it("should block reading authentication and token files", async () => {
1372+
const inputs = [
1373+
{ tool: "read", args: { filePath: "auth.json" } },
1374+
{ tool: "read", args: { filePath: "token.txt" } },
1375+
{ tool: "read", args: { filePath: "passwords.yml" } },
1376+
{ tool: "read", args: { filePath: ".npmrc" } },
1377+
{ tool: "read", args: { filePath: ".git-credentials" } },
1378+
{ tool: "read", args: { filePath: ".vault-token" } },
1379+
];
1380+
1381+
for (const input of inputs) {
1382+
await expect(
1383+
plugin["tool.execute.before"]({ tool: input.tool }, { args: input.args })
1384+
).rejects.toThrow(
1385+
"`Reading secret files is blocked to prevent exposure of sensitive data including API keys, credentials, and configuration.`"
1386+
);
1387+
}
1388+
});
1389+
1390+
it("should block reading config files that may contain secrets", async () => {
1391+
const inputs = [
1392+
{ tool: "read", args: { filePath: "config.json" } },
1393+
{ tool: "read", args: { filePath: "settings.yml" } },
1394+
{ tool: "read", args: { filePath: ".kube/config" } },
1395+
{ tool: "read", args: { filePath: ".docker/config.json" } },
1396+
{ tool: "read", args: { filePath: ".terraformrc" } },
1397+
];
1398+
1399+
for (const input of inputs) {
1400+
await expect(
1401+
plugin["tool.execute.before"]({ tool: input.tool }, { args: input.args })
1402+
).rejects.toThrow(
1403+
"`Reading secret files is blocked to prevent exposure of sensitive data including API keys, credentials, and configuration.`"
1404+
);
1405+
}
1406+
});
1407+
});
1408+
12001409
describe("checkTypeScriptAnyType", () => {
12011410
let plugin: any;
12021411
let mockApp: any;
@@ -1534,6 +1743,115 @@ describe("Command Blocker", () => {
15341743
}).not.toThrow();
15351744
});
15361745

1746+
it("should block any shell commands that reference secret files", async () => {
1747+
const plugin = await CommandBlocker({
1748+
app: mockApp,
1749+
client: mockClient,
1750+
$: mock$,
1751+
});
1752+
1753+
const secretFileCommands = [
1754+
{ tool: "bash", args: { command: "cat .envrc" } },
1755+
{ tool: "bash", args: { command: "less secrets.json" } },
1756+
{ tool: "bash", args: { command: "head .ssh/id_rsa" } },
1757+
{ tool: "bash", args: { command: "tail .aws/credentials" } },
1758+
{ tool: "bash", args: { command: "grep password config.json" } },
1759+
{ tool: "bash", args: { command: "vi .npmrc" } },
1760+
{ tool: "bash", args: { command: "vim .git-credentials" } },
1761+
{ tool: "bash", args: { command: "nano .vault-token" } },
1762+
{ tool: "bash", args: { command: "emacs .kube/config" } },
1763+
{ tool: "bash", args: { command: "view certificate.pem" } },
1764+
{ tool: "bash", args: { command: "hexdump private.key" } },
1765+
// Test non-file-reading commands that reference secret files
1766+
{ tool: "bash", args: { command: "cp .envrc backup.env" } },
1767+
{ tool: "bash", args: { command: "mv secrets.json secrets.bak" } },
1768+
{ tool: "bash", args: { command: "rm .ssh/id_rsa" } },
1769+
{ tool: "bash", args: { command: "chmod 600 .aws/credentials" } },
1770+
{ tool: "bash", args: { command: "chown user .npmrc" } },
1771+
{ tool: "bash", args: { command: "ls -la .git-credentials" } },
1772+
{ tool: "bash", args: { command: "touch .vault-token" } },
1773+
];
1774+
1775+
for (const input of secretFileCommands) {
1776+
await expect(
1777+
plugin["tool.execute.before"](input, { args: input.args })
1778+
).rejects.toThrow(
1779+
"`Reading secret files is blocked to prevent exposure of sensitive data including API keys, credentials, and configuration.`"
1780+
);
1781+
}
1782+
});
1783+
1784+
it("should block shell commands with secret files in complex structures", async () => {
1785+
const plugin = await CommandBlocker({
1786+
app: mockApp,
1787+
client: mockClient,
1788+
$: mock$,
1789+
});
1790+
1791+
const complexCommands = [
1792+
{ tool: "bash", args: { command: 'cat $(echo ".envrc")' } },
1793+
{ tool: "bash", args: { command: 'less `echo secrets.json`' } },
1794+
{ tool: "bash", args: { command: 'head ".ssh/id_rsa"' } },
1795+
{ tool: "bash", args: { command: "grep token $HOME/.vault-token" } },
1796+
{ tool: "bash", args: { command: 'vi ".kube/config"' } },
1797+
];
1798+
1799+
for (const input of complexCommands) {
1800+
await expect(
1801+
plugin["tool.execute.before"](input, { args: input.args })
1802+
).rejects.toThrow(
1803+
"`Reading secret files is blocked to prevent exposure of sensitive data including API keys, credentials, and configuration.`"
1804+
);
1805+
}
1806+
});
1807+
1808+
it("should allow shell commands that don't access secret files", async () => {
1809+
const plugin = await CommandBlocker({
1810+
app: mockApp,
1811+
client: mockClient,
1812+
$: mock$,
1813+
});
1814+
1815+
const safeCommands = [
1816+
{ tool: "bash", args: { command: "cat package.json" } },
1817+
{ tool: "bash", args: { command: "less README.md" } },
1818+
{ tool: "bash", args: { command: "head src/main.ts" } },
1819+
{ tool: "bash", args: { command: "tail .gitignore" } },
1820+
{ tool: "bash", args: { command: "grep function src/" } },
1821+
{ tool: "bash", args: { command: "vi tsconfig.json" } },
1822+
{ tool: "bash", args: { command: "ls -la" } },
1823+
{ tool: "bash", args: { command: "pwd" } },
1824+
{ tool: "bash", args: { command: "echo hello" } },
1825+
{ tool: "bash", args: { command: "mkdir .kube" } }, // Directory name, not a secret file
1826+
];
1827+
1828+
for (const input of safeCommands) {
1829+
await expect(async () => {
1830+
await plugin["tool.execute.before"](input, { args: input.args });
1831+
}).not.toThrow();
1832+
}
1833+
});
1834+
1835+
it("should handle shell commands with flags correctly", async () => {
1836+
const plugin = await CommandBlocker({
1837+
app: mockApp,
1838+
client: mockClient,
1839+
$: mock$,
1840+
});
1841+
1842+
// Should block even with flags
1843+
const blockedCommand = { tool: "bash", args: { command: "cat -n .envrc" } };
1844+
await expect(
1845+
plugin["tool.execute.before"](blockedCommand, { args: blockedCommand.args })
1846+
).rejects.toThrow();
1847+
1848+
// Should allow safe commands with flags
1849+
const safeCommand = { tool: "bash", args: { command: "cat -n package.json" } };
1850+
await expect(async () => {
1851+
await plugin["tool.execute.before"](safeCommand, { args: safeCommand.args });
1852+
}).not.toThrow();
1853+
});
1854+
15371855
it("should allow safe bash commands with semicolons", async () => {
15381856
const plugin = await CommandBlocker({
15391857
app: mockApp,

0 commit comments

Comments
 (0)