@blogic-cz/agent-tools 1.11.1 → 1.13.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/README.md +18 -43
- package/dist/credential-guard/bash.d.ts +3 -0
- package/dist/credential-guard/bash.d.ts.map +1 -0
- package/dist/credential-guard/claude-hook.d.ts.map +1 -1
- package/dist/credential-guard/engine.test.d.ts +5 -0
- package/dist/credential-guard/engine.test.d.ts.map +1 -0
- package/dist/credential-guard/index.d.ts +8 -35
- package/dist/credential-guard/index.d.ts.map +1 -1
- package/dist/credential-guard/ir.d.ts +150 -0
- package/dist/credential-guard/ir.d.ts.map +1 -0
- package/dist/credential-guard/literal-output.d.ts +2 -0
- package/dist/credential-guard/literal-output.d.ts.map +1 -0
- package/dist/credential-guard/passive-awk.d.ts +2 -0
- package/dist/credential-guard/passive-awk.d.ts.map +1 -0
- package/dist/credential-guard/passive-jq.d.ts.map +1 -1
- package/dist/credential-guard/passive-sed.d.ts +2 -0
- package/dist/credential-guard/passive-sed.d.ts.map +1 -0
- package/dist/credential-guard/passive.test.d.ts +2 -0
- package/dist/credential-guard/passive.test.d.ts.map +1 -0
- package/dist/credential-guard/paths.d.ts +14 -0
- package/dist/credential-guard/paths.d.ts.map +1 -0
- package/dist/credential-guard/policy.d.ts +47 -0
- package/dist/credential-guard/policy.d.ts.map +1 -0
- package/dist/credential-guard/powershell.d.ts +3 -0
- package/dist/credential-guard/powershell.d.ts.map +1 -0
- package/dist/credential-guard/registry.d.ts +89 -0
- package/dist/credential-guard/registry.d.ts.map +1 -0
- package/dist/credential-guard/review4.test.d.ts +2 -0
- package/dist/credential-guard/review4.test.d.ts.map +1 -0
- package/dist/credential-guard/review7.test.d.ts +2 -0
- package/dist/credential-guard/review7.test.d.ts.map +1 -0
- package/dist/credential-guard/shell-oracle.d.ts +2 -0
- package/dist/credential-guard/shell-oracle.d.ts.map +1 -0
- package/dist/credential-guard/xargs.d.ts +7 -0
- package/dist/credential-guard/xargs.d.ts.map +1 -0
- package/dist/gh-tool/pr/checkbox-markdown.d.ts +22 -0
- package/dist/gh-tool/pr/checkbox-markdown.d.ts.map +1 -0
- package/dist/gh-tool/pr/checkbox.d.ts +21 -0
- package/dist/gh-tool/pr/checkbox.d.ts.map +1 -0
- package/dist/gh-tool/pr/commands.d.ts +9 -0
- package/dist/gh-tool/pr/commands.d.ts.map +1 -1
- package/dist/gh-tool/pr/index.d.ts +1 -1
- package/dist/gh-tool/pr/index.d.ts.map +1 -1
- package/dist/gh-tool/read/api.d.ts +8 -0
- package/dist/gh-tool/read/api.d.ts.map +1 -1
- package/dist/gh-tool/read/cache.d.ts.map +1 -1
- package/dist/gh-tool/read/commands.d.ts +5 -0
- package/dist/gh-tool/read/commands.d.ts.map +1 -1
- package/dist/gh-tool/read/pulls.d.ts +1 -0
- package/dist/gh-tool/read/pulls.d.ts.map +1 -1
- package/dist/gh-tool/read/reads.d.ts +39 -0
- package/dist/gh-tool/read/reads.d.ts.map +1 -1
- package/dist/gh-tool/read/telemetry-command.d.ts +7 -0
- package/dist/gh-tool/read/telemetry-command.d.ts.map +1 -0
- package/dist/gh-tool/read/telemetry-store.d.ts +3 -0
- package/dist/gh-tool/read/telemetry-store.d.ts.map +1 -0
- package/dist/gh-tool/read/telemetry.d.ts +59 -0
- package/dist/gh-tool/read/telemetry.d.ts.map +1 -0
- package/dist/gh-tool/read/transport.d.ts +9 -0
- package/dist/gh-tool/read/transport.d.ts.map +1 -1
- package/dist/gh-tool/read/writes.d.ts +2 -1
- package/dist/gh-tool/read/writes.d.ts.map +1 -1
- package/package.json +2 -1
- package/schemas/agent-tools.schema.json +1 -1
- package/src/credential-guard/bash.ts +769 -0
- package/src/credential-guard/claude-hook.ts +14 -14
- package/src/credential-guard/engine.test.ts +2050 -0
- package/src/credential-guard/index.ts +285 -4268
- package/src/credential-guard/ir.ts +139 -0
- package/src/credential-guard/literal-output.ts +8 -0
- package/src/credential-guard/passive-awk.ts +239 -0
- package/src/credential-guard/passive-jq.ts +183 -24
- package/src/credential-guard/passive-sed.ts +78 -0
- package/src/credential-guard/passive.test.ts +141 -0
- package/src/credential-guard/paths.ts +108 -0
- package/src/credential-guard/policy.ts +1576 -0
- package/src/credential-guard/powershell.ts +220 -0
- package/src/credential-guard/registry.ts +4720 -0
- package/src/credential-guard/review4.test.ts +83 -0
- package/src/credential-guard/review7.test.ts +51 -0
- package/src/credential-guard/rg-types.json +389 -0
- package/src/credential-guard/shell-oracle.ts +187 -0
- package/src/credential-guard/xargs.ts +65 -0
- package/src/gh-tool/main.ts +32 -6
- package/src/gh-tool/pr/checkbox-markdown.ts +317 -0
- package/src/gh-tool/pr/checkbox.ts +113 -0
- package/src/gh-tool/pr/commands.ts +55 -2
- package/src/gh-tool/pr/core.ts +2 -2
- package/src/gh-tool/pr/index.ts +1 -0
- package/src/gh-tool/read/api.ts +73 -6
- package/src/gh-tool/read/cache.ts +13 -4
- package/src/gh-tool/read/commands.ts +17 -1
- package/src/gh-tool/read/pulls.ts +19 -0
- package/src/gh-tool/read/reads.ts +86 -2
- package/src/gh-tool/read/telemetry-command.ts +45 -0
- package/src/gh-tool/read/telemetry-store.ts +85 -0
- package/src/gh-tool/read/telemetry.ts +191 -0
- package/src/gh-tool/read/transport.ts +95 -6
- package/src/gh-tool/read/writes.ts +19 -2
- package/src/gh-tool/workflow.ts +2 -2
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export function isPassiveSedProgram(text: string): boolean {
|
|
2
|
+
let position = 0;
|
|
3
|
+
let commands = 0;
|
|
4
|
+
let groups = 0;
|
|
5
|
+
const spaces = (): void => {
|
|
6
|
+
while (position < text.length && /[ \t\r]/.test(text[position] ?? "")) position++;
|
|
7
|
+
};
|
|
8
|
+
const delimited = (delimiter: string): boolean => {
|
|
9
|
+
while (position < text.length) {
|
|
10
|
+
const ch = text[position++];
|
|
11
|
+
if (ch === "\\") {
|
|
12
|
+
if (position >= text.length || text[position] === "\n") return false;
|
|
13
|
+
position++;
|
|
14
|
+
} else if (ch === delimiter) return true;
|
|
15
|
+
else if (ch === "\n") return false;
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
};
|
|
19
|
+
const address = (): boolean => {
|
|
20
|
+
const number = /^[0-9]+/.exec(text.slice(position));
|
|
21
|
+
if (number) {
|
|
22
|
+
position += number[0].length;
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
if (text[position] === "$") {
|
|
26
|
+
position++;
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
if (text[position] !== "/") return false;
|
|
30
|
+
position++;
|
|
31
|
+
return delimited("/");
|
|
32
|
+
};
|
|
33
|
+
while (position < text.length) {
|
|
34
|
+
spaces();
|
|
35
|
+
if (text[position] === ";" || text[position] === "\n") {
|
|
36
|
+
position++;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (text[position] === "}") {
|
|
40
|
+
if (groups === 0) return false;
|
|
41
|
+
groups--;
|
|
42
|
+
position++;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (position === text.length) break;
|
|
46
|
+
if (/[0-9$/]/.test(text[position] ?? "")) {
|
|
47
|
+
if (!address()) return false;
|
|
48
|
+
spaces();
|
|
49
|
+
if (text[position] === ",") {
|
|
50
|
+
position++;
|
|
51
|
+
spaces();
|
|
52
|
+
const relative = /^\+[0-9]+/.exec(text.slice(position));
|
|
53
|
+
if (relative) position += relative[0].length;
|
|
54
|
+
else if (!address()) return false;
|
|
55
|
+
spaces();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (text[position] === "!") {
|
|
59
|
+
position++;
|
|
60
|
+
spaces();
|
|
61
|
+
}
|
|
62
|
+
const command = text[position++];
|
|
63
|
+
if (command === "{") {
|
|
64
|
+
groups++;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (command === "s") {
|
|
68
|
+
const delimiter = text[position++];
|
|
69
|
+
if (!delimiter || /[\\\n\r]/.test(delimiter)) return false;
|
|
70
|
+
if (!delimited(delimiter) || !delimited(delimiter)) return false;
|
|
71
|
+
while (position < text.length && /[gp0-9iImM]/.test(text[position] ?? "")) position++;
|
|
72
|
+
} else if (!command || !"pPdDnNgGhHx=".includes(command)) return false;
|
|
73
|
+
commands++;
|
|
74
|
+
spaces();
|
|
75
|
+
if (position < text.length && ![";", "\n", "}"].includes(text[position] ?? "")) return false;
|
|
76
|
+
}
|
|
77
|
+
return commands > 0 && groups === 0;
|
|
78
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createCredentialGuard } from "#guard";
|
|
3
|
+
import { isPassiveJqFilter } from "./passive-jq";
|
|
4
|
+
|
|
5
|
+
describe("passive jq independent parser limits", () => {
|
|
6
|
+
it("enforces the byte limit", () => {
|
|
7
|
+
expect(isPassiveJqFilter("." + " ".repeat(65_535))).toBe(true);
|
|
8
|
+
expect(isPassiveJqFilter("." + " ".repeat(65_536))).toBe(false);
|
|
9
|
+
expect(isPassiveJqFilter('"' + "é".repeat(32_767) + '"')).toBe(true);
|
|
10
|
+
expect(isPassiveJqFilter('"' + "é".repeat(32_768) + '"')).toBe(false);
|
|
11
|
+
});
|
|
12
|
+
it("enforces the token limit without shallow-chain recursion", () => {
|
|
13
|
+
expect(isPassiveJqFilter(Array(2048).fill(".").join("|"))).toBe(true);
|
|
14
|
+
expect(isPassiveJqFilter(Array(2048).fill(".").join("|") + "?")).toBe(true);
|
|
15
|
+
expect(isPassiveJqFilter(Array(2049).fill(".").join("|"))).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
it("enforces the depth limit", () => {
|
|
18
|
+
expect(isPassiveJqFilter("(".repeat(64) + "." + ")".repeat(64))).toBe(true);
|
|
19
|
+
expect(isPassiveJqFilter("(".repeat(65) + "." + ")".repeat(65))).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
it.each([
|
|
22
|
+
". |",
|
|
23
|
+
"{a:}",
|
|
24
|
+
"{a",
|
|
25
|
+
"map(.) trailing",
|
|
26
|
+
'"unterminated',
|
|
27
|
+
'"\\(env)"',
|
|
28
|
+
"unknown_filter",
|
|
29
|
+
".[]? | env",
|
|
30
|
+
".[env]",
|
|
31
|
+
"map(env)",
|
|
32
|
+
'include "helper"; .',
|
|
33
|
+
])("refuses incomplete and unsupported syntax: %s", (filter) => {
|
|
34
|
+
expect(isPassiveJqFilter(filter)).toBe(false);
|
|
35
|
+
expect(createCredentialGuard().isDangerousBashCommand(`jq '${filter}' sample.json`)).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("credential guard file tools", () => {
|
|
40
|
+
it("keeps content scanning on Write and Edit", () => {
|
|
41
|
+
const content = "ghp_" + "A".repeat(36);
|
|
42
|
+
for (const tool of ["Write", "Edit"]) {
|
|
43
|
+
expect(() =>
|
|
44
|
+
createCredentialGuard().handleToolExecuteBefore(
|
|
45
|
+
{ tool },
|
|
46
|
+
{ args: { filePath: "docs/example.md", content } },
|
|
47
|
+
),
|
|
48
|
+
).toThrow("Secret detected");
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
it("protects kubeconfig artifacts without matching unrelated path words", () => {
|
|
52
|
+
const guard = createCredentialGuard();
|
|
53
|
+
for (const path of [
|
|
54
|
+
"config/kubeconfig",
|
|
55
|
+
"config/kubeconfig-prod.yaml",
|
|
56
|
+
"config/cluster.kubeconfig",
|
|
57
|
+
"config/team-kubeconfig-prod.yaml",
|
|
58
|
+
"config/prod-kubeconfig-test.json",
|
|
59
|
+
"config/TEAM-KUBECONFIG-PROD.YAML",
|
|
60
|
+
"config/team-kubeconfig.backup",
|
|
61
|
+
"config/kubeconfig.md",
|
|
62
|
+
"config/kubeconfig/cluster.json",
|
|
63
|
+
"prod-kubeconfig/admin.conf",
|
|
64
|
+
"/tmp/team_kubeconfig/config",
|
|
65
|
+
"mykubeconfig",
|
|
66
|
+
"kubeconfig2",
|
|
67
|
+
"config/MYKUBECONFIG2.backup",
|
|
68
|
+
"prod-kubeconfig",
|
|
69
|
+
"/tmp/team_kubeconfig/",
|
|
70
|
+
"docs/kubeconfig-notes/readme.md",
|
|
71
|
+
"docs/team-kubeconfig-prod/readme.md",
|
|
72
|
+
"/tmp/team_kubeconfig/nested/check-kubeconfig-topology.md",
|
|
73
|
+
"docs/cluster-kubeconfig-notes.md/readme.md",
|
|
74
|
+
"config/kubeconfig2.MD",
|
|
75
|
+
"docs/check-kubeconfig.md\n",
|
|
76
|
+
"docs/check-kubeconfig.md\r\n",
|
|
77
|
+
])
|
|
78
|
+
expect(guard.isPathBlocked(path)).toBe(true);
|
|
79
|
+
for (const path of [
|
|
80
|
+
"docs/check-kubeconfig-topology.md",
|
|
81
|
+
"docs/team-kubeconfig-prod.md",
|
|
82
|
+
"docs/TEAM-KUBECONFIG-PROD.MD",
|
|
83
|
+
"docs/cluster-kubeconfig-notes.md",
|
|
84
|
+
])
|
|
85
|
+
expect(guard.isPathBlocked(path)).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
it.each([
|
|
88
|
+
["prod-kubeconfig/admin.conf", true],
|
|
89
|
+
["/tmp/team_kubeconfig/config", true],
|
|
90
|
+
["mykubeconfig", true],
|
|
91
|
+
["kubeconfig2", true],
|
|
92
|
+
["docs/kubeconfig-notes/readme.md", true],
|
|
93
|
+
["/tmp/team_kubeconfig/check-kubeconfig-topology.md", true],
|
|
94
|
+
["docs/cluster-kubeconfig-notes.md/readme.md", true],
|
|
95
|
+
["config/kubeconfig.md", true],
|
|
96
|
+
["docs/check-kubeconfig-topology.md", false],
|
|
97
|
+
["docs/cluster-kubeconfig-notes.md", false],
|
|
98
|
+
["docs/TEAM-KUBECONFIG-PROD.MD", false],
|
|
99
|
+
["docs/cluster-topology.md", false],
|
|
100
|
+
["docs/check-kubeconfig.md\n", true],
|
|
101
|
+
["docs/check-kubeconfig.md\r\n", true],
|
|
102
|
+
["prod-kubeconfig/.env.example", false],
|
|
103
|
+
])("preserves kubeconfig segment policy across file tools: %s", (path, blocked) => {
|
|
104
|
+
for (const guard of [
|
|
105
|
+
createCredentialGuard(),
|
|
106
|
+
createCredentialGuard({ additionalBlockedPaths: ["ordinary-private\\.md$"] }),
|
|
107
|
+
]) {
|
|
108
|
+
expect(guard.isPathBlocked(path)).toBe(blocked);
|
|
109
|
+
for (const tool of ["Read", "Write", "Edit"]) {
|
|
110
|
+
const invoke = () =>
|
|
111
|
+
guard.handleToolExecuteBefore({ tool }, { args: { filePath: path, content: "Notes." } });
|
|
112
|
+
if (blocked) expect(invoke).toThrow("Access blocked");
|
|
113
|
+
else expect(invoke).not.toThrow();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
it("retains configured protection for ordinary kubeconfig Markdown basenames", () => {
|
|
118
|
+
const path = "docs/cluster-kubeconfig-notes.md";
|
|
119
|
+
const guard = createCredentialGuard({
|
|
120
|
+
additionalBlockedPaths: ["cluster-kubeconfig-notes\\.md$"],
|
|
121
|
+
});
|
|
122
|
+
expect(guard.isPathBlocked(path)).toBe(true);
|
|
123
|
+
for (const tool of ["Read", "Write", "Edit"])
|
|
124
|
+
expect(() =>
|
|
125
|
+
guard.handleToolExecuteBefore({ tool }, { args: { file_path: path, content: "Notes." } }),
|
|
126
|
+
).toThrow("Access blocked");
|
|
127
|
+
});
|
|
128
|
+
it("preserves existing explicit allowed-path precedence for kubeconfig segments", () => {
|
|
129
|
+
const path = "prod-kubeconfig/admin.conf";
|
|
130
|
+
const guard = createCredentialGuard({
|
|
131
|
+
additionalAllowedPaths: ["^prod-kubeconfig/admin\\.conf$"],
|
|
132
|
+
additionalBlockedPaths: ["admin\\.conf$"],
|
|
133
|
+
});
|
|
134
|
+
expect(guard.isPathBlocked(path)).toBe(false);
|
|
135
|
+
for (const tool of ["Read", "Write", "Edit"])
|
|
136
|
+
expect(() =>
|
|
137
|
+
guard.handleToolExecuteBefore({ tool }, { args: { file_path: path, content: "Notes." } }),
|
|
138
|
+
).not.toThrow();
|
|
139
|
+
expect(guard.isDangerousBashCommand(`cat ${path}`)).toBe(false);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { posix } from "node:path";
|
|
2
|
+
|
|
3
|
+
export const CODE_OR_PROSE_EXTENSION =
|
|
4
|
+
/\.(?:ts|tsx|mts|cts|js|jsx|mjs|cjs|vue|cs|py|go|rs|java|kt|rb|php|sh|md|mdx)$/i;
|
|
5
|
+
|
|
6
|
+
export const DEFAULT_BLOCKED_PATH_PATTERNS: RegExp[] = [
|
|
7
|
+
/(?:^|\/)id_(?:rsa|dsa|ecdsa|ed25519)(?:_sk)?(?![^/]*\.pub$)(?:[-_.][^/]*|~)?$/i,
|
|
8
|
+
/(?:^|\/)\.htpasswd(?:~|\.bak|\.orig)?$/i,
|
|
9
|
+
/(?:^|\/)service-account[^/]*\.json$/i,
|
|
10
|
+
/\.env$/,
|
|
11
|
+
/\.env\.[^./]+$/,
|
|
12
|
+
/(?:^|\/)\.env(?!\.(?:example|sample|template))[^/]*(?:\/|$)/i,
|
|
13
|
+
/(?:^|\/)(?:\.netrc|_netrc|\.npmrc|\.pypirc|\.pgpass|\.my\.cnf|\.vault-token|\.sentryclirc)(?:~|\.bak|\.orig)?$/i,
|
|
14
|
+
/(?:^|\/)\.docker\/config\.json(?:~|\.bak|\.orig)?$/i,
|
|
15
|
+
/(?:^|\/)\.config\/(?:gh\/hosts\.yml|hub)(?:~|\.bak|\.orig)?$/i,
|
|
16
|
+
/(?:^|\/)\.m2\/settings\.xml(?:~|\.bak|\.orig)?$/i,
|
|
17
|
+
/(?:^|\/)\.composer\/auth\.json(?:~|\.bak|\.orig)?$/i,
|
|
18
|
+
/\.(?:pem|key|p12|pfx)(?:~|\.bak|\.orig)?$/i,
|
|
19
|
+
/(?:^|\/)(?:secrets?|credentials?)(?:\/|$)/i,
|
|
20
|
+
/(?:^|\/)\.(?:aws|ssh|kube|gnupg|azure)(?:\/|$)/i,
|
|
21
|
+
/(?:^|\/)(?:kubeconfig[^/]*|(?![^/]+\.md(?![\s\S]))[^/]*kubeconfig[^/]*)(?:\/|$)/i,
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export const DEFAULT_ALLOWED_PATH_PATTERNS: RegExp[] = [
|
|
25
|
+
/\.env\.(?:example|template|sample)[^/]*$/i,
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const PROCESS_ENVIRONMENT_PATH =
|
|
29
|
+
/^(?:\/|(?:\.\.\/)*)proc\/(?:self|thread-self|[0-9]+|\$\$|\$\{\$\})(?:\/task\/(?:[0-9]+|\$\$|\$\{\$\}))?\/environ$/;
|
|
30
|
+
|
|
31
|
+
export type PathContext = {
|
|
32
|
+
readonly home: string;
|
|
33
|
+
readonly cwd: string;
|
|
34
|
+
readonly contentRead?: boolean;
|
|
35
|
+
readonly additionalBlockedPaths?: readonly RegExp[];
|
|
36
|
+
readonly additionalAllowedPaths?: readonly RegExp[];
|
|
37
|
+
};
|
|
38
|
+
export function normalizePath(path: string, home = "~", cwd = "."): string {
|
|
39
|
+
let value = path.replace(/\\/g, "/").replace(/^file:\/\//i, "");
|
|
40
|
+
value = value.replace(
|
|
41
|
+
/^(?:~(?=\/|$)|%USERPROFILE%|%HOME%|\$HOME|\$\{HOME\}|\$env:USERPROFILE)/i,
|
|
42
|
+
home.replace(/\\/g, "/"),
|
|
43
|
+
);
|
|
44
|
+
if (/^(?:env:|(?:microsoft\.powershell\.core\/)?environment::)/i.test(value))
|
|
45
|
+
return value.toLowerCase();
|
|
46
|
+
value = value
|
|
47
|
+
.split("/")
|
|
48
|
+
.map((segment, index) => {
|
|
49
|
+
if (segment === "." || segment === ".." || (index === 0 && /^[A-Za-z]:$/.test(segment)))
|
|
50
|
+
return segment;
|
|
51
|
+
return segment.split(":")[0]?.replace(/[. ]+$/, "") ?? "";
|
|
52
|
+
})
|
|
53
|
+
.join("/");
|
|
54
|
+
if (!value.startsWith("/") && !/^[A-Za-z]:\//.test(value))
|
|
55
|
+
value = `${cwd.replace(/\\/g, "/")}/${value}`;
|
|
56
|
+
return posix.normalize(value).toLowerCase();
|
|
57
|
+
}
|
|
58
|
+
function matches(pattern: RegExp, value: string): boolean {
|
|
59
|
+
return new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, "")).test(value);
|
|
60
|
+
}
|
|
61
|
+
export function isSensitivePath(path: string, context: PathContext): boolean {
|
|
62
|
+
const normalized = normalizePath(path, context.home, context.cwd);
|
|
63
|
+
if (
|
|
64
|
+
/^(?:env:|(?:microsoft\.powershell\.core\/)?environment::)/i.test(normalized) ||
|
|
65
|
+
PROCESS_ENVIRONMENT_PATH.test(normalized) ||
|
|
66
|
+
PROCESS_ENVIRONMENT_PATH.test(
|
|
67
|
+
path
|
|
68
|
+
.replace(/\\/g, "/")
|
|
69
|
+
.replace(/^(?:\.\/)+/, "")
|
|
70
|
+
.toLowerCase(),
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
return true;
|
|
74
|
+
const allowed = [...DEFAULT_ALLOWED_PATH_PATTERNS, ...(context.additionalAllowedPaths ?? [])];
|
|
75
|
+
const raw = path.replace(/\\/g, "/");
|
|
76
|
+
if (allowed.some((pattern) => matches(pattern, normalized) || matches(pattern, raw)))
|
|
77
|
+
return false;
|
|
78
|
+
const basename = raw.split("/").at(-1) ?? "";
|
|
79
|
+
if (
|
|
80
|
+
context.contentRead !== false &&
|
|
81
|
+
/secret|credential|token|password|passwd|api[-_]?key|private[-_]key/i.test(basename) &&
|
|
82
|
+
!CODE_OR_PROSE_EXTENSION.test(basename)
|
|
83
|
+
)
|
|
84
|
+
return true;
|
|
85
|
+
return [...DEFAULT_BLOCKED_PATH_PATTERNS, ...(context.additionalBlockedPaths ?? [])].some(
|
|
86
|
+
(pattern) => matches(pattern, normalized) || matches(pattern, raw),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function expandGlobAlternatives(pattern: string): readonly string[] | null {
|
|
91
|
+
if (/[[]/.test(pattern)) return null;
|
|
92
|
+
let variants = [""];
|
|
93
|
+
let rest = pattern;
|
|
94
|
+
while (rest.includes("{")) {
|
|
95
|
+
const start = rest.indexOf("{");
|
|
96
|
+
const end = rest.indexOf("}", start);
|
|
97
|
+
if (end < 0) return null;
|
|
98
|
+
const inner = rest.slice(start + 1, end);
|
|
99
|
+
if (inner.includes("{") || !inner.includes(",")) return null;
|
|
100
|
+
const choices = inner.split(",");
|
|
101
|
+
if (variants.length * choices.length > 64) return null;
|
|
102
|
+
const prefix = rest.slice(0, start);
|
|
103
|
+
variants = variants.flatMap((value) => choices.map((choice) => value + prefix + choice));
|
|
104
|
+
rest = rest.slice(end + 1);
|
|
105
|
+
}
|
|
106
|
+
const result = variants.map((value) => value + rest);
|
|
107
|
+
return result.some((value) => /[{}[]/.test(value)) ? null : result;
|
|
108
|
+
}
|