Skip to content
Open
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
16 changes: 11 additions & 5 deletions .claude/hooks/check-new-deps/index.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.
Expand Down Expand Up @@ -273,7 +282,7 @@ const extractors: Record<string, Extractor> = {

// --- 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
Expand Down Expand Up @@ -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
Expand Down
43 changes: 43 additions & 0 deletions .git-hooks/_helpers.sh
Original file line number Diff line number Diff line change
@@ -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'
}
32 changes: 22 additions & 10 deletions .git-hooks/commit-msg
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -22,23 +17,40 @@ 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
fi
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) || {
Expand Down
13 changes: 4 additions & 9 deletions .git-hooks/pre-push
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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

Expand Down
25 changes: 25 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <branch>` / `git switch <branch>` — 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 <task-branch> ../<repo>-<task> main
cd ../<repo>-<task>
# edit, commit, push from here; the primary checkout is untouched.
cd -
git worktree remove ../<repo>-<task>
```

- **REQUIRED for staging**: surgical `git add <specific-file> [<file>…]` 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.
Expand Down