Skip to content

Commit e2b3cc0

Browse files
committed
fix(miner): close deny-check bypasses
1 parent f8c4c35 commit e2b3cc0

3 files changed

Lines changed: 27 additions & 5 deletions

File tree

packages/gittensory-miner/lib/deny-hooks.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,24 @@ function collectInputStrings(input) {
6060
* rather than relying on a later caller to split the command first — a bare path-valued field still matches via
6161
* the whole-value candidate.
6262
*/
63+
function normalizePathCandidate(candidate) {
64+
const normalized = candidate
65+
.replace(/\\/g, "/")
66+
.replace(/\/\.(?=\/|$)/g, "")
67+
.replace(/^(?:\.\/)+/, "");
68+
return normalized || candidate;
69+
}
70+
6371
function pathCandidates(value) {
64-
const candidates = [value];
72+
const candidates = new Set([value, normalizePathCandidate(value)]);
6573
for (const token of value.split(/\s+/)) {
6674
const trimmed = token.replace(/^["']+|["']+$/g, "");
67-
if (trimmed && trimmed !== value) candidates.push(trimmed);
75+
if (trimmed) {
76+
candidates.add(trimmed);
77+
candidates.add(normalizePathCandidate(trimmed));
78+
}
6879
}
69-
return candidates;
80+
return [...candidates];
7081
}
7182

7283
function matcherMatches(matcher, toolName) {
@@ -93,14 +104,15 @@ function ruleMatches(rule, toolName, inputStrings) {
93104
/**
94105
* The built-in house-rule deny set — a non-empty starting example a later phase can extend or replace. Mirrors the
95106
* forbidden-path regex in `scripts/check-mcp-package.mjs` (CI workflows, env files, secret-bearing paths, private
96-
* key material) and adds a conservative git force-push guard (a command carrying both `push` and `--force`).
107+
* key material) and adds conservative git force-push guards (a command carrying `push` plus a force flag).
97108
*/
98109
export const DEFAULT_DENY_RULES = [
99110
{ matcher: "*", pathPattern: ".github/workflows/**", reason: "Never modify CI workflows (.github/workflows/**)." },
100111
{ matcher: "*", pathPattern: "**/.env*", reason: "Never read or write environment files (.env*)." },
101112
{ matcher: "*", pathPattern: "**/secret*/**", reason: "Never touch secret-bearing paths (**/secret*/**)." },
102113
{ matcher: "*", pathPattern: "**/*private*key*", reason: "Never touch private key material (**/*private*key*)." },
103114
{ matcher: "*", inputIncludesAll: ["push", "--force"], reason: "Never force-push (git push --force)." },
115+
{ matcher: "*", inputIncludesAll: ["push", "-f"], reason: "Never force-push (git push -f)." },
104116
];
105117

106118
/**

test/unit/miner-cli-deny-check.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ describe("gittensory-miner hooks check command", () => {
3737
'{"file_path":".github/workflows/ci.yml"}',
3838
]),
3939
).toBe(1);
40+
expect(
41+
runDenyCheck([
42+
"--tool",
43+
"Write",
44+
"--input",
45+
'{"file_path":"./.github/workflows/ci.yml"}',
46+
]),
47+
).toBe(1);
4048
expect(error).toHaveBeenCalledWith(expect.stringContaining("CI workflows"));
4149
});
4250

test/unit/miner-deny-hooks.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ describe("evaluateDenyHooks — built-in DEFAULT_DENY_RULES", () => {
1313
expect(DEFAULT_DENY_RULES.length).toBeGreaterThan(0);
1414
});
1515

16-
it("blocks writing a CI workflow, allows an ordinary source path", () => {
16+
it("blocks writing a CI workflow, including dot-prefixed equivalents, and allows an ordinary source path", () => {
1717
const blocked = evaluateDenyHooks({ name: "Write", input: { file_path: ".github/workflows/ci.yml" } });
1818
expect(blocked.allowed).toBe(false);
1919
expect(blocked.blockedBy?.reason).toContain("CI workflows");
20+
expect(evaluateDenyHooks({ name: "Write", input: { file_path: "./.github/workflows/ci.yml" } }).allowed).toBe(false);
2021
expect(evaluateDenyHooks({ name: "Write", input: { file_path: "src/review/rag.ts" } }).allowed).toBe(true);
2122
});
2223

@@ -46,6 +47,7 @@ describe("evaluateDenyHooks — built-in DEFAULT_DENY_RULES", () => {
4647

4748
it("blocks a git force-push regardless of flag/subcommand order, allows a normal push", () => {
4849
expect(evaluateDenyHooks({ name: "Bash", input: { command: "git push --force origin main" } }).allowed).toBe(false);
50+
expect(evaluateDenyHooks({ name: "Bash", input: { command: "git push -f origin main" } }).allowed).toBe(false);
4951
// Order-independent: both substrings present in either order.
5052
expect(evaluateDenyHooks({ name: "Bash", input: { command: "git --force-with-lease push" } }).allowed).toBe(false);
5153
expect(evaluateDenyHooks({ name: "Bash", input: { command: "git push origin main" } }).allowed).toBe(true);

0 commit comments

Comments
 (0)