chore: add Husky pre-commit hooks + secret-scan scripts

This commit is contained in:
saravanakumardb1 2026-03-27 23:07:30 -07:00
parent 760afbbc41
commit 0a4c13b1d6
5 changed files with 172 additions and 1 deletions

13
.husky/pre-commit Executable file
View File

@ -0,0 +1,13 @@
#!/usr/bin/env sh
ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
if [ -n "$ROOT" ]; then
cd "$ROOT" || exit 1
fi
if [ "$HUSKY" = "0" ]; then
exit 0
fi
echo "🔐 Scanning staged changes for secrets..."
bash scripts/secret-scan-staged.sh

13
.husky/pre-push Executable file
View File

@ -0,0 +1,13 @@
#!/usr/bin/env sh
ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
if [ -n "$ROOT" ]; then
cd "$ROOT" || exit 1
fi
if [ "$HUSKY" = "0" ]; then
exit 0
fi
echo "🔐 Scanning tracked files for secrets before push..."
bash scripts/secret-scan-repo.sh

View File

@ -6,6 +6,10 @@
"typecheck": "pnpm --filter @notelett/backend run typecheck && pnpm --filter @notelett/web run typecheck && pnpm --filter @notelett/mobile run typecheck", "typecheck": "pnpm --filter @notelett/backend run typecheck && pnpm --filter @notelett/web run typecheck && pnpm --filter @notelett/mobile run typecheck",
"test": "pnpm --filter @notelett/backend run test && pnpm --filter @notelett/web run test && pnpm --filter @notelett/mobile run test", "test": "pnpm --filter @notelett/backend run test && pnpm --filter @notelett/web run test && pnpm --filter @notelett/mobile run test",
"build": "pnpm --filter @notelett/backend run build && pnpm --filter @notelett/web run build", "build": "pnpm --filter @notelett/backend run build && pnpm --filter @notelett/web run build",
"verify": "pnpm run typecheck && pnpm run test && pnpm run build" "verify": "pnpm run typecheck && pnpm run test && pnpm run build",
"prepare": "husky"
},
"devDependencies": {
"husky": "^9.0.0"
} }
} }

52
scripts/secret-scan-repo.sh Executable file
View File

@ -0,0 +1,52 @@
#!/usr/bin/env bash
#
# Scans tracked files for common secret patterns.
# Intended for manual use (or as part of quick-check). Avoids printing matching lines.
#
# Note: This does not scan git history. Use a dedicated tool (e.g. gitleaks) for history scanning.
set -euo pipefail
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
if [[ -z "${ROOT}" ]]; then
exit 0
fi
cd "${ROOT}"
fail=0
check() {
local name="$1"
local pattern="$2"
# -l prints only filenames (no secret material in output)
if git grep -l -E "${pattern}" -- . ':!*.example' ':!*.example.*' >/dev/null 2>&1; then
echo "${name}: potential matches found in:"
git grep -l -E "${pattern}" -- . ':!*.example' ':!*.example.*' | sed 's/^/ - /'
echo
fail=1
fi
}
check "Private key blocks" '-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----'
check "Azure storage AccountKey leaks" 'AccountKey=[A-Za-z0-9+/=_-]{20,}'
check "Azure SharedAccessKey leaks" 'SharedAccessKey=[A-Za-z0-9+/=_-]{20,}'
check "COSMOS_KEY assignment leaks" 'COSMOS_KEY[[:space:]]*=[[:space:]]*[A-Za-z0-9+/=_-]{20,}'
check "AZURE_OPENAI_KEY assignment leaks" 'AZURE_OPENAI_KEY[[:space:]]*=[[:space:]]*[A-Za-z0-9+/=_-]{20,}'
check "AZURE_SPEECH_KEY assignment leaks" 'AZURE_SPEECH_KEY[[:space:]]*=[[:space:]]*[A-Za-z0-9+/=_-]{20,}'
check "JWT_SECRET hex-like assignments" 'JWT_SECRET[[:space:]]*=[[:space:]]*[0-9a-fA-F]{32,}'
check "OpenAI API keys (sk-...)" 'sk-[A-Za-z0-9]{20,}'
check "Stripe secret keys (sk_live_/sk_test_)" 'sk_(live|test)_[A-Za-z0-9]{20,}'
check "Stripe webhook secrets (whsec_...)" 'whsec_[A-Za-z0-9]{20,}'
check "Perplexity API keys (pplx-...)" 'pplx-[A-Za-z0-9]{20,}'
check "AWS access key ids (AKIA...)" 'AKIA[0-9A-Z]{16}'
check "Google API keys (AIza...)" 'AIza[0-9A-Za-z\-_]{35}'
if [[ "${fail}" -ne 0 ]]; then
echo "Secret scan failed."
echo "Fix the files above (move values to Key Vault / env vars) and retry."
exit 1
fi
echo "✓ Secret scan passed (tracked files)"

89
scripts/secret-scan-staged.sh Executable file
View File

@ -0,0 +1,89 @@
#!/usr/bin/env bash
#
# Blocks commits that introduce obvious secrets (Azure keys, connection strings, private keys, etc.).
# This scans only staged changes (the git index) to avoid false positives from unstaged work.
#
# Note: Avoid printing matched lines to keep secrets out of terminal scrollback/logs.
set -euo pipefail
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
if [[ -z "${ROOT}" ]]; then
exit 0
fi
cd "${ROOT}"
# Nothing staged -> nothing to scan.
if git diff --cached --quiet; then
exit 0
fi
DIFF="$(git diff --cached --no-color --unified=0)"
if [[ -z "${DIFF}" ]]; then
exit 0
fi
perl -ne '
our ($file, %hits, $exit);
if (/^\+\+\+ b\/(.*)$/) {
$file = $1;
next;
}
next unless defined $file;
next unless /^\+(?!\+\+\+)(.*)$/;
my $line = $1;
my @checks = (
["PRIVATE_KEY_BLOCK", qr/-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/i],
# Azure connection strings / keys (high signal, low false positives)
["AZURE_STORAGE_ACCOUNT_KEY", qr/AccountKey=[A-Za-z0-9+\/=_-]{20,}/],
["AZURE_SHARED_ACCESS_KEY", qr/SharedAccessKey=[A-Za-z0-9+\/=_-]{20,}/],
# Common env var leaks
["COSMOS_KEY_ASSIGNMENT", qr/\bCOSMOS_KEY\s*=\s*[A-Za-z0-9+\/=_-]{20,}/],
["AZURE_OPENAI_KEY_ASSIGNMENT", qr/\bAZURE_OPENAI_KEY\s*=\s*[A-Za-z0-9+\/=_-]{20,}/],
["AZURE_SPEECH_KEY_ASSIGNMENT", qr/\bAZURE_SPEECH_KEY\s*=\s*[A-Za-z0-9+\/=_-]{20,}/],
["JWT_SECRET_HEX_ASSIGNMENT", qr/\bJWT_SECRET\s*=\s*[0-9a-fA-F]{32,}\b/],
# OpenAI / Stripe / Perplexity
["OPENAI_API_KEY", qr/\bOPENAI_API_KEY\s*=\s*sk-[A-Za-z0-9]{20,}/],
["OPENAI_KEY_LIKE", qr/\bsk-[A-Za-z0-9]{20,}\b/],
["STRIPE_SECRET_KEY", qr/\bsk_(?:live|test)_[A-Za-z0-9]{20,}\b/],
["STRIPE_WEBHOOK_SECRET", qr/\bwhsec_[A-Za-z0-9]{20,}\b/],
["PERPLEXITY_API_KEY", qr/\bpplx-[A-Za-z0-9]{20,}\b/],
# Cloud provider patterns
["AWS_ACCESS_KEY_ID", qr/\bAKIA[0-9A-Z]{16}\b/],
["GOOGLE_API_KEY", qr/\bAIza[0-9A-Za-z\-_]{35}\b/],
);
for my $check (@checks) {
my ($name, $re) = @$check;
if ($line =~ $re) {
$hits{$name}{$file} = 1;
$exit = 1;
}
}
END {
if (!$exit) {
exit 0;
}
print STDERR "✗ Potential secrets detected in staged changes:\n";
for my $name (sort keys %hits) {
for my $path (sort keys %{ $hits{$name} }) {
print STDERR " - ${name}: ${path}\n";
}
}
print STDERR "\nCommit aborted.\n";
print STDERR "Move secrets to Azure Key Vault (or env vars injected at deploy-time), then retry.\n";
print STDERR "If you believe this is a false positive, refactor the value into a placeholder.\n";
exit 1;
}
' <<< "${DIFF}"