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
87 changes: 53 additions & 34 deletions .git-hooks/pre-push
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -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
Expand All @@ -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")
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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))
Expand Down
6 changes: 2 additions & 4 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -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
3 changes: 1 addition & 2 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
ignoreDependencyScripts: true
linkWorkspacePackages: false
resolutionMode: highest
trustPolicy: no-downgrade

allowBuilds:
esbuild: true
Expand Down