diff --git a/.claude/hooks/check-new-deps/index.mts b/.claude/hooks/check-new-deps/index.mts index c61e73f07..b143ecbb1 100644 --- a/.claude/hooks/check-new-deps/index.mts +++ b/.claude/hooks/check-new-deps/index.mts @@ -17,6 +17,9 @@ // 0 = allow (no new deps, all clean, or non-dep file) // 2 = block (malware detected by Socket.dev) +import path from 'node:path' +import { fileURLToPath } from 'node:url' + import { parseNpmSpecifier, stringify, @@ -32,6 +35,12 @@ import { import { SocketSdk } from '@socketsecurity/sdk' import type { MalwareCheckPackage } from '@socketsecurity/sdk' +// Hook runs standalone with only @socketsecurity/* deps, so this +// one-liner lives here instead of importing a shared helper. +function errorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error) +} + const logger = getDefaultLogger() // Per-request timeout (ms) to avoid blocking the hook on slow responses. @@ -273,7 +282,7 @@ const extractors: Record = { // --- main (only when executed directly, not imported) --- -if (import.meta.filename === process.argv[1]) { +if (fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) { // Read the full JSON blob from stdin (piped by Claude Code). let input = '' for await (const chunk of process.stdin) input += chunk @@ -402,10 +411,7 @@ async function checkDepsBatch( } } catch (e) { // Network failure — log and allow all deps through. - logger.warn( - `Socket: network error` - + ` (${(e as Error).message}), allowing all` - ) + logger.warn(`Socket: network error (${errorMessage(e)}), allowing all`) } return blocked diff --git a/.git-hooks/_helpers.sh b/.git-hooks/_helpers.sh new file mode 100644 index 000000000..15e9a4083 --- /dev/null +++ b/.git-hooks/_helpers.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# Shared helpers for git hooks. +# Sourced by .git-hooks/commit-msg, pre-commit, pre-push. +# +# Constants +# --------- +# ALLOWED_PUBLIC_KEY Real public API key shipped in socket-lib test +# fixtures. Safe to appear in commits anywhere. +# FAKE_TOKEN_MARKER Substring marker used in fleet test fixtures. +# FAKE_TOKEN_LEGACY Legacy lib-scoped marker — accepted during the +# rename from `socket-lib-test-fake-token` to +# `socket-test-fake-token`. Drop when socket-lib's +# fixture rename PR lands. +# SOCKET_SECURITY_ENV Env var name used in shell examples; not a token. +# +# Functions +# --------- +# filter_allowed_api_keys Reads stdin, drops allowlist matches (public +# key, fake-token markers, env var name, +# `.example` paths), prints the rest. +# +# Colors +# ------ +# RED, GREEN, YELLOW, NC + +# shellcheck disable=SC2034 # constants sourced by other hooks +ALLOWED_PUBLIC_KEY="sktsec_t_--RAN5U4ivauy4w37-6aoKyYPDt5ZbaT5JBVMqiwKo_api" +FAKE_TOKEN_MARKER="socket-test-fake-token" +FAKE_TOKEN_LEGACY="socket-lib-test-fake-token" +SOCKET_SECURITY_ENV="SOCKET_SECURITY_API_KEY=" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +filter_allowed_api_keys() { + grep -v "$ALLOWED_PUBLIC_KEY" \ + | grep -v "$FAKE_TOKEN_MARKER" \ + | grep -v "$FAKE_TOKEN_LEGACY" \ + | grep -v "$SOCKET_SECURITY_ENV" \ + | grep -v '\.example' +} diff --git a/.git-hooks/commit-msg b/.git-hooks/commit-msg index f637d0310..7acf4c56b 100755 --- a/.git-hooks/commit-msg +++ b/.git-hooks/commit-msg @@ -4,13 +4,8 @@ set -e -# Colors for output. -RED='\033[0;31m' -GREEN='\033[0;32m' -NC='\033[0m' - -# Allowed public API key (used in socket-lib). -ALLOWED_PUBLIC_KEY="sktsec_t_--RAN5U4ivauy4w37-6aoKyYPDt5ZbaT5JBVMqiwKo_api" +# shellcheck source=./_helpers.sh +. "$(dirname "$0")/_helpers.sh" ERRORS=0 @@ -22,14 +17,14 @@ if [ -n "$COMMITTED_FILES" ]; then for file in $COMMITTED_FILES; do if [ -f "$file" ]; then # Check for Socket API keys (except allowed). - if grep -E 'sktsec_[a-zA-Z0-9_-]+' "$file" 2>/dev/null | grep -v "$ALLOWED_PUBLIC_KEY" | grep -v 'your_api_key_here' | grep -v 'fake-token' | grep -v 'test-token' | grep -v '\.example' | grep -q .; then + if grep -E 'sktsec_[a-zA-Z0-9_-]+' "$file" 2>/dev/null | filter_allowed_api_keys | grep -q .; then printf "${RED}✗ SECURITY: Potential API key detected in commit!${NC}\n" printf "File: %s\n" "$file" ERRORS=$((ERRORS + 1)) fi # Check for .env files. - if echo "$file" | grep -qE '^\.env(\.local)?$'; then + if echo "$file" | grep -qE '^\.env(\.[^/]+)?$' && ! echo "$file" | grep -qE '^\.env\.(example|test)$'; then printf "${RED}✗ SECURITY: .env file in commit!${NC}\n" ERRORS=$((ERRORS + 1)) fi @@ -37,8 +32,25 @@ if [ -n "$COMMITTED_FILES" ]; then done fi -# Auto-strip AI attribution from commit message. +# Block Linear issue references in the commit message. +# Linear tracking lives in Linear; keep commit history tool-agnostic. +# Team keys enumerated from the Socket workspace. PATCH listed before PAT so +# the engine matches the longer prefix first on strings like "PATCH-123". COMMIT_MSG_FILE="$1" +LINEAR_TEAM_KEYS='ASK|AUTO|BOT|CE|CORE|DAT|DES|DEV|ENG|INFRA|LAB|MAR|MET|OPS|PAR|PATCH|PAT|PLAT|REA|SALES|SBOM|SEC|SMO|SUP|TES|TI|WEB' +if [ -f "$COMMIT_MSG_FILE" ]; then + LINEAR_HITS=$(grep -vE '^#' "$COMMIT_MSG_FILE" 2>/dev/null \ + | grep -oE "(^|[^A-Za-z0-9_])($LINEAR_TEAM_KEYS)-[0-9]+($|[^A-Za-z0-9_])|linear\.app/[A-Za-z0-9/_-]+" \ + | head -5 || true) + if [ -n "$LINEAR_HITS" ]; then + printf "${RED}✗ Commit message references Linear issue(s):${NC}\n" + printf '%s\n' "$LINEAR_HITS" | sed 's/^/ /' + printf "${RED}Linear tracking lives in Linear. Remove the reference from the commit message.${NC}\n" + ERRORS=$((ERRORS + 1)) + fi +fi + +# Auto-strip AI attribution from commit message. if [ -f "$COMMIT_MSG_FILE" ]; then # Create a temporary file to store the cleaned message. TEMP_FILE=$(mktemp) || { diff --git a/.git-hooks/pre-push b/.git-hooks/pre-push index 96f159284..8f8637b88 100755 --- a/.git-hooks/pre-push +++ b/.git-hooks/pre-push @@ -15,16 +15,11 @@ set -e -# Colors for output. -RED='\033[0;31m' -GREEN='\033[0;32m' -NC='\033[0m' +# shellcheck source=./_helpers.sh +. "$(dirname "$0")/_helpers.sh" printf "${GREEN}Running mandatory pre-push validation...${NC}\n" -# Allowed public API key (used in socket-lib test fixtures). -ALLOWED_PUBLIC_KEY="sktsec_t_--RAN5U4ivauy4w37-6aoKyYPDt5ZbaT5JBVMqiwKo_api" - # Get the remote name and URL from git (passed as arguments to pre-push hooks). remote="$1" url="$2" @@ -162,9 +157,9 @@ while read local_ref local_sha remote_ref remote_sha; do fi # Socket API keys (except allowed public key and test placeholders). - 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 + if echo "$file_text" | grep -E 'sktsec_[a-zA-Z0-9_-]+' | filter_allowed_api_keys | grep -q .; then 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 + echo "$file_text" | grep -n 'sktsec_' | filter_allowed_api_keys | head -3 ERRORS=$((ERRORS + 1)) fi diff --git a/CLAUDE.md b/CLAUDE.md index 9f2a673c1..e207a404d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,6 +7,31 @@ - Identify users by git credentials (commit author, GitHub account). Use their actual name, never "the user". - Use "you/your" when speaking directly; use their name when discussing their work. +## PARALLEL CLAUDE SESSIONS - WORKTREE REQUIRED + +**This repo may have multiple Claude sessions running concurrently against the same checkout, against parallel git worktrees, or against sibling clones.** Several common git operations are hostile to that and silently destroy or hijack the other session's work. + +- **FORBIDDEN in the primary checkout** (the one another Claude may be editing): + - `git stash` — shared stash store; another session can `pop` yours. + - `git add -A` / `git add .` — sweeps files belonging to other sessions. + - `git checkout ` / `git switch ` — yanks the working tree out from under another session. + - `git reset --hard` against a non-HEAD ref — discards another session's commits. +- **REQUIRED for branch work**: spawn a worktree instead of switching branches in place. Each worktree has its own HEAD, so branch operations inside it are safe. + + ```bash + # From the primary checkout — does NOT touch the working tree here. + git worktree add -b ../- main + cd ../- + # edit, commit, push from here; the primary checkout is untouched. + cd - + git worktree remove ../- + ``` + +- **REQUIRED for staging**: surgical `git add […]` with explicit paths. Never `-A` / `.`. +- **If you need a quick WIP save**: commit on a new branch from inside a worktree, not a stash. + +The umbrella rule: never run a git command that mutates state belonging to a path other than the file you just edited. + ## PRE-ACTION PROTOCOL **MANDATORY**: Review CLAUDE.md before any action. No exceptions.