Skip to content
Merged
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
66 changes: 66 additions & 0 deletions .github/risk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Rules of the deterministic risk classifier (.github/scripts/risk_tier.py).
# The `risk` workflow reads this file from the BASE branch: a PR cannot loosen its own rules.
# Any change to this file is T2-hard. Globs: `*` stays in one folder, `**` crosses folders,
# a pattern without `/` matches the name in any folder.
version: 1
repo: gabe4coding/claude-code-mode

# T0 candidates. A PR whose files are all docs or tests (and that removes no assertion) is T0.
docs: ['*.md', '*.mdx', '*.txt', 'docs/**', '**/assets/**', '*.svg', '*.png', '*.jpg', '*.gif', '*.webp']
# Markdown that a model or an agent reads is product text, not docs: T1 at least.
not_docs: ['AGENTS.md', 'CLAUDE.md', 'CODING_STANDARDS.md', 'hooks/**', 'hints/**', '.claude/**', 'scripts/**']
tests: ['tests/**', '.github/scripts/test_*.py']
# The check scripts are CI gates, not tests.
not_tests: ['tests/*.check.mjs', 'tests/check.mjs']
# Excluded from the size limits (lockfiles always are).
generated: []

# T2-hard: nothing lowers it.
hard:
- .github/risk.yml
- .github/scripts/risk_tier.py
- .github/scripts/general_review.py
- .github/workflows/review.yml
- .github/workflows/risk.yml
- '**/publish*.yml'
- '**/release*.yml'
- '**/tag*.yml'
- CODEOWNERS
- .github/CODEOWNERS
- docs/CODEOWNERS
- '**/ruleset*'
- LICENSE
- LICENSE.*
- .gitleaks.toml
- '.githooks/**'
# Reviewer config. A PR must not retune the hosted reviewer to look clean.
- greptile.json
- .greptile/**
- .coderabbit.yaml
- .coderabbit.yml
# T2-soft: Gabe, unless the LLM layer (not active yet) shows the change only tightens.
# The sandbox: the runner and the integration test that proves its layers (never weaken a sandbox layer).
soft: ['runtime/runner.mjs', 'tests/runner.integration.mjs']
plugin_manifests: ['.claude-plugin/plugin.json']
publish_on_version: []
dependency_manifests:
npm: ['package.json']
python: []
limits:
net_deleted_lines: 1000
changed_code_lines: 1500
changed_code_files: 40
history_rewrite_text: 'force[- ]?push|push --force|rewrite (the )?(git )?history|history rewrite|filter-(branch|repo)'

hold:
# Never install the plugins of these vendors: a path or an added line that names one goes to HOLD.
# sha256 of the lowercase name, so this public repo does not spell it (risk_tier.name_hashes).
forbidden_names_sha256:
- de61d34d2cf76f1208bb2cfd0b417d7e6827c66ff5ffbaa765d31c8a92f5cf41
- 219edac479105ee0f6e4b0323ed589d2523d1204767d11797b052eaf1fdb05cb
forbidden_text: [] # optional regexes, for text that is not a secret
# Comment-pruning PRs are on hold.
comment_pruning:
title_or_branch: '(prune|pruning|strip|remove|drop|trim|delete)[-_ a-z]{0,20}comments|comments?[-_ ]?(prune|pruning|cleanup|removal)'
min_removed_lines: 20
comment_share: 0.7
68 changes: 68 additions & 0 deletions .github/scripts/fixtures/pr47-reviews.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
[
{
"id": 4312996048,
"user": {
"login": "greptile-apps[bot]",
"type": "Bot"
},
"commit_id": "90f1fb98089860c577e53dfe3588868b852a74a3",
"path": "hooks/paths.ts",
"created_at": "2026-10-19T10:55:57Z",
"body": "<a href=\"#\"><img alt=\"P1\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9\" align=\"top\"></a> <a href=\"#\"><img alt=\"security\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/Security.svg?v=2\" align=\"top\"></a> **Parent paths bypass approval**\n\nIf `hints/a/b` exists, `D=a/b; printf x > ~/.claude/code-mode/hints/$D/../../x.md` passes the new check but writes `hints/x.md`. `pathsIn` removes both `$D` and `hints` before Bash expands `$D` into two directories. The old check denied this command because it contained `code-mode/hints`.\n\nPreserve the original protected prefix when folding paths, and add a regression test. This also violates the repository instruction to never weaken the hint-file guard.\n\n**How this was verified:** The path check removes both `$D` and `hints`, while Bash expands `$D` into two directories before resolving `..`; the Bash hook forwards commands when the check returns false.\n\n**Context Used:** AGENTS.md ([source](https://github.com/gabe4coding/claude-code-mode/blob/main/AGENTS.md))\n\n<a href=\"[trimmed-link]\"><picture><source media=\"(prefers-color-scheme: dark)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInCursorDark.svg?v=7\"><source media=\"(prefers-color-scheme: light)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInCursor.svg?v=7\"><img alt=\"Fix in Cursor\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInCursor.svg?v=7\"></picture></a> <a href=\"[trimmed-link]\"><picture><source media=\"(prefers-color-scheme: dark)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInClaudeDark.svg?v=7\"><source media=\"(prefers-color-scheme: light)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInClaude.svg?v=7\"><img alt=\"Fix in Claude Code\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInClaude.svg?v=7\"></picture></a>\n"
},
{
"id": 4312996059,
"user": {
"login": "greptile-apps[bot]",
"type": "Bot"
},
"commit_id": "90f1fb98089860c577e53dfe3588868b852a74a3",
"path": "hooks/paths.ts",
"created_at": "2026-10-19T10:55:57Z",
"body": "<a href=\"#\"><img alt=\"P1\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9\" align=\"top\"></a> <a href=\"#\"><img alt=\"security\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/Security.svg?v=2\" align=\"top\"></a> **Character classes bypass approval**\n\n`globRegex` does not read Bash character classes correctly. For example, `cp f.md ~/.claude/code-mode/[[:alpha:]]ints/a.md` can write into `hints`, but the check stops at the first `]` and produces a JavaScript pattern that does not match `hints`. The new glob protection therefore misses a standard Bash spelling.\n\nSupport these classes or deny patterns that cannot be translated safely, and add this case to the spelling tests.\n\n**How this was verified:** Bash's `[[:alpha:]]ints` matches `hints`, but the bracket tokenizer produces a different pattern, so the Bash hook forwards the protected write.\n\n<a href=\"[trimmed-link]\"><picture><source media=\"(prefers-color-scheme: dark)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInCursorDark.svg?v=7\"><source media=\"(prefers-color-scheme: light)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInCursor.svg?v=7\"><img alt=\"Fix in Cursor\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInCursor.svg?v=7\"></picture></a> <a href=\"[trimmed-link]\"><picture><source media=\"(prefers-color-scheme: dark)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInClaudeDark.svg?v=7\"><source media=\"(prefers-color-scheme: light)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInClaude.svg?v=7\"><img alt=\"Fix in Claude Code\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInClaude.svg?v=7\"></picture></a>\n"
},
{
"id": 4312996067,
"user": {
"login": "greptile-apps[bot]",
"type": "Bot"
},
"commit_id": "90f1fb98089860c577e53dfe3588868b852a74a3",
"path": "hooks/paths.ts",
"created_at": "2026-10-19T10:55:58Z",
"body": "<a href=\"#\"><img alt=\"P2\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p2.svg?v=9\" align=\"top\"></a> **Shell patterns crash the check**\n\n`globRegex` can throw on command text instead of returning a result. For example, `ls ~/.claude/code-mode/[z-a]` builds an invalid JavaScript character range and throws `SyntaxError`. Previously this command passed through the guard; now it fails inside the plugin instead of reaching the shell.\n\nCatch unsupported patterns and handle them conservatively so a shell spelling cannot crash the check.\n\n<a href=\"[trimmed-link]\"><picture><source media=\"(prefers-color-scheme: dark)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInCursorDark.svg?v=7\"><source media=\"(prefers-color-scheme: light)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInCursor.svg?v=7\"><img alt=\"Fix in Cursor\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInCursor.svg?v=7\"></picture></a> <a href=\"[trimmed-link]\"><picture><source media=\"(prefers-color-scheme: dark)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInClaudeDark.svg?v=7\"><source media=\"(prefers-color-scheme: light)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInClaude.svg?v=7\"><img alt=\"Fix in Claude Code\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInClaude.svg?v=7\"></picture></a>\n"
},
{
"id": 4313241737,
"user": {
"login": "greptile-apps[bot]",
"type": "Bot"
},
"commit_id": "5075dbc6918a73c32ed780236d3812bd007318f7",
"path": "hooks/paths.ts",
"created_at": "2026-10-19T11:25:08Z",
"body": "<a href=\"#\"><img alt=\"P1\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9\" align=\"top\"></a> <a href=\"#\"><img alt=\"security\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/Security.svg?v=2\" align=\"top\"></a> **Copy commands skip approval**\n\n`verbBefore` treats an argument named `ls` or `du` as the command. If local source folders `hints` and `ls` exist, `cp -r hints ls ~/.claude/code-mode/` returns false from `namesGuardedDir` and copies files into the protected hint folder without approval. The previous check denied this command.\n\nLimit the exception to the actual command, not the nearest bare argument. This violates the repository requirement that Bash commands naming protected folders be denied.\n\n**How this was verified:** The backward scan returns the source argument `ls`, the read-only exception skips denial, and the Bash hook forwards the copy.\n\n**Context Used:** AGENTS.md ([source](https://github.com/gabe4coding/claude-code-mode/blob/main/AGENTS.md))\n\n<a href=\"[trimmed-link]\"><picture><source media=\"(prefers-color-scheme: dark)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInCursorDark.svg?v=7\"><source media=\"(prefers-color-scheme: light)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInCursor.svg?v=7\"><img alt=\"Fix in Cursor\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInCursor.svg?v=7\"></picture></a> <a href=\"[trimmed-link]\"><picture><source media=\"(prefers-color-scheme: dark)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInClaudeDark.svg?v=7\"><source media=\"(prefers-color-scheme: light)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInClaude.svg?v=7\"><img alt=\"Fix in Claude Code\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInClaude.svg?v=7\"></picture></a>\n"
},
{
"id": 4313241748,
"user": {
"login": "greptile-apps[bot]",
"type": "Bot"
},
"commit_id": "5075dbc6918a73c32ed780236d3812bd007318f7",
"path": "hooks/paths.ts",
"created_at": "2026-10-19T11:25:08Z",
"body": "<a href=\"#\"><img alt=\"P1\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9\" align=\"top\"></a> <a href=\"#\"><img alt=\"security\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/Security.svg?v=2\" align=\"top\"></a> **Brace spellings skip approval**\n\nSplitting on braces breaks protected names that contain a brace expansion. For example, `printf x | tee ~/.claude/code-mode/hi{nts,other}/x.md` writes `hints/x.md`, but the check sees separate words ending in `hi`, `nts`, and `other`. It returns false and forwards the write. The previous check kept `hi{nts,other}` together and matched `hints`.\n\nPreserve brace alternatives with their surrounding path text. This violates the repository requirement that Bash commands naming protected folders be denied.\n\n**How this was verified:** Brace splitting removes the protected name before the path check, while Bash expands the same argument to include `hints/x.md`; the hook forwards the missed command.\n\n**Context Used:** AGENTS.md ([source](https://github.com/gabe4coding/claude-code-mode/blob/main/AGENTS.md))\n\n<a href=\"[trimmed-link]\"><picture><source media=\"(prefers-color-scheme: dark)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInCursorDark.svg?v=7\"><source media=\"(prefers-color-scheme: light)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInCursor.svg?v=7\"><img alt=\"Fix in Cursor\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInCursor.svg?v=7\"></picture></a> <a href=\"[trimmed-link]\"><picture><source media=\"(prefers-color-scheme: dark)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInClaudeDark.svg?v=7\"><source media=\"(prefers-color-scheme: light)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInClaude.svg?v=7\"><img alt=\"Fix in Claude Code\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInClaude.svg?v=7\"></picture></a>\n"
},
{
"id": 4313241756,
"user": {
"login": "greptile-apps[bot]",
"type": "Bot"
},
"commit_id": "5075dbc6918a73c32ed780236d3812bd007318f7",
"path": "hooks/paths.ts",
"created_at": "2026-10-19T11:25:08Z",
"body": "<a href=\"#\"><img alt=\"P1\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9\" align=\"top\"></a> <a href=\"#\"><img alt=\"security\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/Security.svg?v=2\" align=\"top\"></a> **Relative deletions skip approval**\n\nThe new check skips a relative path whenever the previous word starts with `-`. If `~/.claude/code-mode/files` exists, `cd ~/.claude/code-mode/files && rm -f ../hints/x.md` therefore passes the guard and deletes a protected hint without approval. The previous check denied it.\n\nDo not skip paths merely because they follow an option. This violates the repository requirement that Bash commands naming protected folders be denied.\n\n**How this was verified:** The `cd` target is recognized, but `-f` makes the relative hint path get skipped; no remaining path triggers denial, so the Bash hook forwards the deletion.\n\n**Context Used:** AGENTS.md ([source](https://github.com/gabe4coding/claude-code-mode/blob/main/AGENTS.md))\n\n<a href=\"[trimmed-link]\"><picture><source media=\"(prefers-color-scheme: dark)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInCursorDark.svg?v=7\"><source media=\"(prefers-color-scheme: light)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInCursor.svg?v=7\"><img alt=\"Fix in Cursor\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInCursor.svg?v=7\"></picture></a> <a href=\"[trimmed-link]\"><picture><source media=\"(prefers-color-scheme: dark)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInClaudeDark.svg?v=7\"><source media=\"(prefers-color-scheme: light)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInClaude.svg?v=7\"><img alt=\"Fix in Claude Code\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixInClaude.svg?v=7\"></picture></a>\n"
}
]
12 changes: 12 additions & 0 deletions .github/scripts/fixtures/pr47-summary.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[
{
"id": 6265579780,
"user": {
"login": "greptile-apps[bot]",
"type": "Bot"
},
"created_at": "2026-10-19T10:55:54Z",
"updated_at": "2026-10-19T11:25:14Z",
"body": "<!-- greptile_summary -->\n\n<h2><a href=\"https://app.greptile.com/api/retrigger?id=83531479\"><picture><source media=\"(prefers-color-scheme: dark)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/RetriggerDark.svg?v=2\"><source media=\"(prefers-color-scheme: light)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/Retrigger.svg?v=2\"><img alt=\"Retrigger\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/Retrigger.svg?v=2\" align=\"right\"></picture></a><a href=\"https://app.greptile.com/gabe4coding/github/gabe4coding/claude-code-mode/-/pull/47\"><picture><source media=\"(prefers-color-scheme: dark)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/ViewInGreptileDark.svg?v=2\"><source media=\"(prefers-color-scheme: light)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/ViewInGreptile.svg?v=2\"><img alt=\"View in Greptile\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/ViewInGreptile.svg?v=2\" align=\"right\"></picture></a>Confidence Score: 0/5</h2>\n\n**[Critical impact]** Rewrites the bash command guard that protects hint folders\\.\n\nDo not merge until the three new gaps in the hint-file guard are fixed.\n\n<h2><a href=\"[trimmed-link]\"><picture><source media=\"(prefers-color-scheme: dark)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixAllInCursorDark.svg?v=7\"><source media=\"(prefers-color-scheme: light)\" srcset=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixAllInCursor.svg?v=7\"><img alt=\"Fix All in Cursor\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/FixAllInCursor.svg?v=7\" align=\"right\"></picture></a>Findings</h2>\n\n1. <img alt=\"P1\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9\" align=\"top\">&nbsp;<img alt=\"Security\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/Security.svg?v=2\" align=\"top\">&nbsp;**Copy commands skip approval** <a href=\"https://github.com/gabe4coding/claude-code-mode/pull/47#discussion_r4313241737\">\u25b6</a>\n2. <img alt=\"P1\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9\" align=\"top\">&nbsp;<img alt=\"Security\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/Security.svg?v=2\" align=\"top\">&nbsp;**Brace spellings skip approval** <a href=\"https://github.com/gabe4coding/claude-code-mode/pull/47#discussion_r4313241748\">\u25b6</a>\n3. <img alt=\"P1\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9\" align=\"top\">&nbsp;<img alt=\"Security\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/Security.svg?v=2\" align=\"top\">&nbsp;**Relative deletions skip approval** <a href=\"https://github.com/gabe4coding/claude-code-mode/pull/47#discussion_r4313241756\">\u25b6</a>\n\n<details open><summary><h3>Summary</h3></summary>\n\nThe PR broadens the Bash guard to catch common shell spellings and bumps the plugin to `0.12.6`.\n- Bash commands now catch common spellings of protected hint and function folders.\n</details>\n\n<details open><summary><h3>Diagram</h3></summary>\n\n```mermaid\n%%{init: {'theme': 'neutral'}}%%\nflowchart TD\n A[Bash command] --> B[Split words and fold paths]\n B --> C[Check relative paths after cd]\n C --> D[Check protected names and read-only exception]\n D --> E{Protected path found?}\n E -->|Yes| F[Deny command]\n E -->|No| G[Forward command to Bash]\n```\n</details>\n\n<!-- greptile_confidence_score:0 -->\n\n<sub>Reviews (2) \u00b7 Last reviewed commit: [\"Bash guard: close the spellings the revi...\"](https://github.com/gabe4coding/claude-code-mode/commit/5075dbc6918a73c32ed780236d3812bd007318f7) \u00b7 [Reviewed by Greptile](https://www.greptile.com/?utm_source=greptile_review&utm_medium=github&utm_campaign=review_public&utm_content=gabe4coding/claude-code-mode)</sub>\n"
}
]
Loading
Loading