diff --git a/.github/workflows/sanity-check.yml b/.github/workflows/sanity-check.yml new file mode 100644 index 00000000..4b45e99d --- /dev/null +++ b/.github/workflows/sanity-check.yml @@ -0,0 +1,102 @@ +name: PR Sanity Check + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + pull-requests: write + contents: read + +jobs: + sanity-check: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Fetch base branch + run: git fetch origin ${{ github.base_ref }} + + - name: Block disallowed files by extension + run: | + echo "Checking for disallowed file extensions..." + CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) + + # Block by extension + BLOCKED=$(echo "$CHANGED_FILES" | grep -E '\.(exe|dll|out|ezdb|db|sqlite|sqlite3)$' || true) + if [ -n "$BLOCKED" ]; then + echo "::error::Disallowed file extensions detected: $BLOCKED" + exit 1 + fi + + # Block IDE/OS junk folders + JUNK=$(echo "$CHANGED_FILES" | grep -E '^(\.idea/|\.vs/|__pycache__/)' || true) + if [ -n "$JUNK" ]; then + echo "::error::IDE/OS junk detected: $JUNK" + exit 1 + fi + + # Block OS junk files + OS_JUNK=$(echo "$CHANGED_FILES" | grep -E '(Thumbs\.db|Desktop\.ini|\.DS_Store)$' || true) + if [ -n "$OS_JUNK" ]; then + echo "::error::OS junk files detected: $OS_JUNK" + exit 1 + fi + + echo "No disallowed file extensions detected." + + - name: Block meta/config files from other ecosystems + run: | + echo "Checking for ecosystem files..." + CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) + + # Block meta/config files from other language ecosystems + SUSPICIOUS=$(echo "$CHANGED_FILES" | grep -E '^(package\.json|package-lock\.json|yarn\.lock|pnpm-lock\.yaml|bun\.lockb|bunfig\.toml|deno\.json|deno\.jsonc|tsconfig\.json|jsconfig\.json|requirements\.txt|Pipfile|Gemfile|Cargo\.toml|Cargo\.lock)$|node_modules/' || true) + + if [ -n "$SUSPICIOUS" ]; then + echo "::error::Ecosystem config files detected: $SUSPICIOUS" + exit 1 + fi + echo "No suspicious ecosystem files detected." + + - name: Reject binary files except images + run: | + echo "Checking for binary files..." + CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) + + for file in $CHANGED_FILES; do + if [ -f "$file" ]; then + MIME=$(file --mime-type -b "$file") + + # Allow common image formats + if [[ "$MIME" =~ ^image/(jpeg|png|gif|svg\+xml|webp)$ ]]; then + echo "Allowed image file: $file ($MIME)" + continue + fi + + # Reject executables and archives + if file "$file" | grep -qE "executable|binary|archive|compressed"; then + echo "::error::Binary/executable file detected: $file" + exit 1 + fi + + # Also check charset=binary for other binary types (but not images) + if file --mime "$file" | grep -q "charset=binary" && [[ ! "$MIME" =~ ^image/ ]]; then + echo "::error::Binary file detected: $file ($MIME)" + exit 1 + fi + fi + done + echo "No disallowed binary files detected." + + - name: Close PR if checks fail + if: failure() + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh pr close ${{ github.event.pull_request.number }} \ + --repo ${{ github.repository }} \ + --comment "This PR has been automatically closed because it contains disallowed files (binaries, executables, archives, or meta/config files from other ecosystems). Please remove these files and open a new PR."