diff --git a/.git-hooks/pre-push b/.git-hooks/pre-push index 1913411..2c56abd 100755 --- a/.git-hooks/pre-push +++ b/.git-hooks/pre-push @@ -1,13 +1,13 @@ #!/bin/bash # Socket Security Pre-push Hook -# MANDATORY ENFORCEMENT LAYER - Cannot be bypassed with --no-verify. +# Security enforcement layer for all pushes. # Validates all commits being pushed for security issues and AI attribution. +# NOTE: Security checks parallel .husky/security-checks.sh — keep in sync. set -e # Colors for output. RED='\033[0;31m' -YELLOW='\033[1;33m' GREEN='\033[0;32m' NC='\033[0m' @@ -24,28 +24,46 @@ TOTAL_ERRORS=0 # Read stdin for refs being pushed. while read local_ref local_sha remote_ref remote_sha; do + # Skip tag pushes: tags point to existing commits already validated. + if echo "$local_ref" | grep -q '^refs/tags/'; then + printf "${GREEN}Skipping tag push: %s${NC}\n" "$local_ref" + continue + fi + + # Skip delete pushes. + if [ "$local_sha" = "0000000000000000000000000000000000000000" ]; then + continue + fi + # Get the range of commits being pushed. if [ "$remote_sha" = "0000000000000000000000000000000000000000" ]; then - # New branch - find the latest published release tag to limit scope. - latest_release=$(git tag --list 'v*' --sort=-version:refname --merged "$local_sha" | head -1) - if [ -n "$latest_release" ]; then - # Check commits since the latest published release. - range="$latest_release..$local_sha" - else - # No release tags found - check all commits. - range="$local_sha" + # New branch - only check commits not on the default remote branch. + default_branch=$(git symbolic-ref "refs/remotes/$remote/HEAD" 2>/dev/null | sed "s@^refs/remotes/$remote/@@") + if [ -z "$default_branch" ]; then + default_branch="main" fi - else - # Existing branch - check new commits since remote. - # Limit scope to commits after the latest published release on this branch. - latest_release=$(git tag --list 'v*' --sort=-version:refname --merged "$remote_sha" | head -1) - if [ -n "$latest_release" ]; then - # Only check commits after the latest release that are being pushed. - range="$latest_release..$local_sha" + if git rev-parse "$remote/$default_branch" >/dev/null 2>&1; then + range="$remote/$default_branch..$local_sha" else - # No release tags found - check new commits only. - range="$remote_sha..$local_sha" + # No remote default branch, fall back to release tag. + latest_release=$(git tag --list 'v*' --sort=-version:refname --merged "$local_sha" | head -1) + if [ -n "$latest_release" ]; then + range="$latest_release..$local_sha" + else + # No remote branch or tags — skip scan to avoid walking entire history. + printf "${GREEN}✓ Skipping validation (no baseline to compare against)${NC}\n" + continue + fi fi + else + # Existing branch — only check commits not yet on the remote. + range="$remote_sha..$local_sha" + fi + + # Validate the computed range before using it. + if ! git rev-list "$range" >/dev/null 2>&1; then + printf "${RED}✗ Invalid commit range: %s${NC}\n" "$range" >&2 + exit 1 fi ERRORS=0 @@ -64,7 +82,7 @@ while read local_ref local_sha remote_ref remote_sha; do printf "${RED}✗ BLOCKED: AI attribution found in commit messages!${NC}\n" printf "Commits with AI attribution:\n" fi - printf " - $(git log -1 --oneline "$commit_sha")\n" + printf " - %s\n" "$(git log -1 --oneline "$commit_sha")" ERRORS=$((ERRORS + 1)) fi done < <(git rev-list "$range") @@ -74,8 +92,9 @@ while read local_ref local_sha remote_ref remote_sha; do printf "These commits were likely created with --no-verify, bypassing the\n" printf "commit-msg hook that strips AI attribution.\n" printf "\n" + range_base="${range%%\.\.*}" printf "To fix:\n" - printf " git rebase -i $remote_sha\n" + printf " git rebase -i %s\n" "$range_base" printf " Mark commits as 'reword', remove AI attribution, save\n" printf " git push\n" fi @@ -86,32 +105,32 @@ while read local_ref local_sha remote_ref remote_sha; do printf "Checking files for security issues...\n" # Get all files changed in these commits. - CHANGED_FILES=$(git diff --name-only "$range" 2>/dev/null || printf "\n") + CHANGED_FILES=$(git diff --name-only "$range" 2>/dev/null || echo "") if [ -n "$CHANGED_FILES" ]; then # Check for sensitive files. if echo "$CHANGED_FILES" | grep -qE '^\.env(\.local)?$'; then printf "${RED}✗ BLOCKED: Attempting to push .env file!${NC}\n" - printf "Files: $(echo "$CHANGED_FILES" | grep -E '^\.env(\.local)?$')\n" + printf "Files: %s\n" "$(echo "$CHANGED_FILES" | grep -E '^\.env(\.local)?$')" ERRORS=$((ERRORS + 1)) fi # Check for .DS_Store. if echo "$CHANGED_FILES" | grep -q '\.DS_Store'; then printf "${RED}✗ BLOCKED: .DS_Store file in push!${NC}\n" - printf "Files: $(echo "$CHANGED_FILES" | grep '\.DS_Store')\n" + printf "Files: %s\n" "$(echo "$CHANGED_FILES" | grep '\.DS_Store')" ERRORS=$((ERRORS + 1)) fi # Check for log files. if echo "$CHANGED_FILES" | grep -E '\.log$' | grep -v 'test.*\.log' | grep -q .; then printf "${RED}✗ BLOCKED: Log file in push!${NC}\n" - printf "Files: $(echo "$CHANGED_FILES" | grep -E '\.log$' | grep -v 'test.*\.log')\n" + printf "Files: %s\n" "$(echo "$CHANGED_FILES" | grep -E '\.log$' | grep -v 'test.*\.log')" ERRORS=$((ERRORS + 1)) fi # Check file contents for secrets. - for file in $CHANGED_FILES; do + while IFS= read -r file; do if [ -f "$file" ] && [ ! -d "$file" ]; then # Skip test files, example files, and hook scripts. if echo "$file" | grep -qE '\.(test|spec)\.(m?[jt]s|tsx?)$|\.example$|/test/|/tests/|fixtures/|\.git-hooks/|\.husky/'; then @@ -128,46 +147,46 @@ while read local_ref local_sha remote_ref remote_sha; do fi if [ "$is_binary" = true ]; then - file_text=$(strings "$file" 2>/dev/null || echo "") + file_text=$(strings "$file" 2>/dev/null) else - file_text=$(cat "$file" 2>/dev/null || echo "") + file_text=$(cat "$file" 2>/dev/null) fi # Check for hardcoded user paths. if echo "$file_text" | grep -qE '(/Users/[^/\s]+/|/home/[^/\s]+/|C:\\Users\\[^\\]+\\)'; then - printf "${RED}✗ BLOCKED: Hardcoded personal path found in: $file${NC}\n" + printf "${RED}✗ BLOCKED: Hardcoded personal path found in: %s${NC}\n" "$file" echo "$file_text" | grep -nE '(/Users/[^/\s]+/|/home/[^/\s]+/|C:\\Users\\[^\\]+\\)' | head -3 ERRORS=$((ERRORS + 1)) fi # Check for Socket API keys. if echo "$file_text" | grep -E 'sktsec_[a-zA-Z0-9_-]+' | grep -v "$ALLOWED_PUBLIC_KEY" | grep -v 'your_api_key_here' | grep -v 'SOCKET_SECURITY_API_KEY=' | grep -v 'fake-token' | grep -v 'test-token' | grep -q .; then - printf "${RED}✗ BLOCKED: Real API key detected in: $file${NC}\n" + printf "${RED}✗ BLOCKED: Real API key detected in: %s${NC}\n" "$file" echo "$file_text" | grep -n 'sktsec_' | grep -v "$ALLOWED_PUBLIC_KEY" | grep -v 'your_api_key_here' | grep -v 'fake-token' | grep -v 'test-token' | head -3 ERRORS=$((ERRORS + 1)) fi # Check for AWS keys. if echo "$file_text" | grep -iqE '(aws_access_key|aws_secret|AKIA[0-9A-Z]{16})'; then - printf "${RED}✗ BLOCKED: Potential AWS credentials found in: $file${NC}\n" + printf "${RED}✗ BLOCKED: Potential AWS credentials found in: %s${NC}\n" "$file" echo "$file_text" | grep -niE '(aws_access_key|aws_secret|AKIA[0-9A-Z]{16})' | head -3 ERRORS=$((ERRORS + 1)) fi # Check for GitHub tokens. if echo "$file_text" | grep -qE 'gh[ps]_[a-zA-Z0-9]{36}'; then - printf "${RED}✗ BLOCKED: Potential GitHub token found in: $file${NC}\n" + printf "${RED}✗ BLOCKED: Potential GitHub token found in: %s${NC}\n" "$file" echo "$file_text" | grep -nE 'gh[ps]_[a-zA-Z0-9]{36}' | head -3 ERRORS=$((ERRORS + 1)) fi # Check for private keys. if echo "$file_text" | grep -qE -- '-----BEGIN (RSA |EC |DSA )?PRIVATE KEY-----'; then - printf "${RED}✗ BLOCKED: Private key found in: $file${NC}\n" + printf "${RED}✗ BLOCKED: Private key found in: %s${NC}\n" "$file" ERRORS=$((ERRORS + 1)) fi fi - done + done <<< "$CHANGED_FILES" fi TOTAL_ERRORS=$((TOTAL_ERRORS + ERRORS)) diff --git a/.npmrc b/.npmrc index 5f1d8d9..9c7382b 100644 --- a/.npmrc +++ b/.npmrc @@ -1,5 +1,3 @@ -# Minimum release age for npm v11+ (days). +# npm v11+ settings (not pnpm — pnpm v11 only reads auth/registry from .npmrc). +ignore-scripts=true min-release-age=7 - -# Trust policy - prevent downgrade attacks -trust-policy=no-downgrade diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 908589b..cc747e1 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,5 @@ -ignoreDependencyScripts: true -linkWorkspacePackages: false resolutionMode: highest +trustPolicy: no-downgrade allowBuilds: esbuild: true