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
39 changes: 39 additions & 0 deletions .github/scripts/safe-glob.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# safe-glob — sandboxed file finder for Claude Code Review
# Prevents directory traversal outside the repository root.
# Usage: safe-glob <pattern> [search_path]
set -euo pipefail

REPO_DIR="${REPO_DIR:?REPO_DIR must be set}"
PATTERN="${1:-}"
SEARCH_PATH="${2:-}"

if [[ -z "$PATTERN" ]]; then
echo "ERROR: Pattern required"
exit 1
fi

if [[ -n "$SEARCH_PATH" ]]; then
RESOLVED=$(realpath -m "$SEARCH_PATH" 2>/dev/null)
if [[ "$RESOLVED" != "$REPO_DIR" && "$RESOLVED" != "$REPO_DIR"/* ]]; then
echo "ERROR: Access denied. Can only search within the repository."
exit 1
fi
TARGET="$RESOLVED"
else
TARGET="$REPO_DIR"
fi

if [[ ! -d "$TARGET" ]]; then
echo "ERROR: Directory not found: ${SEARCH_PATH:-$REPO_DIR}"
exit 1
fi

find "$TARGET" \
-not -path '*/.git/*' \
-not -name '.env' \
-not -name '.env.*' \
-name "$PATTERN" \
-type f \
2>/dev/null \
| head -500 | sort || true
37 changes: 37 additions & 0 deletions .github/scripts/safe-grep.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# safe-grep — sandboxed grep for Claude Code Review
# Prevents directory traversal outside the repository root.
# Usage: safe-grep <pattern> [search_path]
set -euo pipefail

REPO_DIR="${REPO_DIR:?REPO_DIR must be set}"
PATTERN="${1:-}"
SEARCH_PATH="${2:-}"

if [[ -z "$PATTERN" ]]; then
echo "ERROR: Pattern required"
exit 1
fi

if [[ -n "$SEARCH_PATH" ]]; then
RESOLVED=$(realpath -m "$SEARCH_PATH" 2>/dev/null)
if [[ "$RESOLVED" != "$REPO_DIR" && "$RESOLVED" != "$REPO_DIR"/* ]]; then
echo "ERROR: Access denied. Can only search within the repository."
exit 1
fi
TARGET="$RESOLVED"
else
TARGET="$REPO_DIR"
fi

if [[ ! -e "$TARGET" ]]; then
echo "ERROR: Path not found: $SEARCH_PATH"
exit 1
fi

grep -rn \
--exclude-dir=".git" \
--exclude=".env" \
--exclude=".env.*" \
-- "$PATTERN" "$TARGET" 2>/dev/null \
| head -500 || true
33 changes: 33 additions & 0 deletions .github/scripts/safe-read.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# safe-read — sandboxed file reader for Claude Code Review
# Prevents directory traversal outside the repository root.
# Usage: safe-read <file_path>
set -euo pipefail

REPO_DIR="${REPO_DIR:?REPO_DIR must be set}"
FILE_PATH="$1"

if [[ -z "$FILE_PATH" ]]; then
echo "ERROR: File path required"
exit 1
fi

RESOLVED=$(realpath -m "$FILE_PATH" 2>/dev/null)

if [[ "$RESOLVED" != "$REPO_DIR" && "$RESOLVED" != "$REPO_DIR"/* ]]; then
echo "ERROR: Access denied. Can only read files within the repository."
exit 1
fi

BASENAME=$(basename "$RESOLVED")
if [[ "$BASENAME" == .env || "$BASENAME" == .env.* ]]; then
echo "ERROR: Access denied. Cannot read environment files."
exit 1
fi

if [[ ! -f "$RESOLVED" ]]; then
echo "ERROR: File not found: $FILE_PATH"
exit 1
fi

cat -n "$RESOLVED"
27 changes: 24 additions & 3 deletions .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,15 @@ jobs:
permissions:
contents: read
pull-requests: write
id-token: write

concurrency:
group: claude-review-${{ github.event.pull_request.number || inputs.pr_number }}
group: claude-review-${{ github.event.pull_request.number || inputs.pr_number }}-${{ github.event.label.name || 'manual' }}
cancel-in-progress: true

env:
REPO_DIR: ${{ github.workspace }}

steps:
- name: Resolve PR number
id: pr
Expand All @@ -40,25 +44,42 @@ jobs:
- name: Checkout base branch (never PR code — prevents prompt injection via attacker files)
uses: actions/checkout@v4
with:
persist-credentials: false
fetch-depth: 1
ref: ${{ github.event.pull_request.base.sha || github.sha }}

- name: Set up sandboxed tools
run: |
chmod +x .github/scripts/safe-read.sh .github/scripts/safe-grep.sh .github/scripts/safe-glob.sh
ln -s "$GITHUB_WORKSPACE/.github/scripts/safe-read.sh" /usr/local/bin/safe-read
ln -s "$GITHUB_WORKSPACE/.github/scripts/safe-grep.sh" /usr/local/bin/safe-grep
ln -s "$GITHUB_WORKSPACE/.github/scripts/safe-glob.sh" /usr/local/bin/safe-glob

- name: Run Claude Code Review
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: '--max-turns 20 --allowed-tools "Bash(gh pr diff:*),Bash(gh pr view:*),Read, Glob, Grep"'
claude_args: '--max-turns 20 --tools "Bash" --allowed-tools "Bash(gh pr diff:*),Bash(gh pr view:*),Bash(safe-read:*),Bash(safe-grep:*),Bash(safe-glob:*)"'
prompt: |
You are reviewing a pull request for Dialtone, Dialpad's public design system monorepo.
Breaking changes here have blast radius across all Dialpad products and external consumers.

PR NUMBER: ${{ steps.pr.outputs.number }}
REPO: ${{ github.repository }}

## Tools

You have sandboxed file-access tools that only work within the repository:
- `safe-read <file>` — read a file (like cat -n)
- `safe-grep <pattern> [path]` — search file contents (like grep -rn)
- `safe-glob <pattern> [path]` — find files by name pattern (like find -name)

Do NOT attempt to use Read, Glob, or Grep directly. Use the safe-* commands above.

## Steps

1. Get the diff: `gh pr diff ${{ steps.pr.outputs.number }}`
2. Read relevant source files from the base branch for full context on changed code
2. Read relevant source files using `safe-read <file>` for full context on changed code
3. Post inline review comments using the PR Review API (see format below)
4. If the PR has NO issues, post nothing. Do not comment just to say "looks good".

Expand Down
Loading