From dad67954c4c84b7dc5249758505371d6d92e123a Mon Sep 17 00:00:00 2001 From: jdalton Date: Sun, 19 Apr 2026 22:39:04 -0400 Subject: [PATCH 1/4] chore(claude.md): warn against parallel-Claude-hostile git operations Multiple Claude sessions can run concurrently against the same checkout, parallel worktrees, or sibling clones. Document which git ops break that contract (stash, add -A, branch switching, reset --hard non-HEAD) and the safe alternatives (worktrees, surgical add). --- CLAUDE.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) 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. From f0e84ab4c238b8b2725c3c0491c99ea214464619 Mon Sep 17 00:00:00 2001 From: jdalton Date: Mon, 20 Apr 2026 09:06:54 -0400 Subject: [PATCH 2/4] fix(hook): adopt fleet-gold check-new-deps improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two improvements from the socket-repo-template gold standard: - Canonical isMainModule: fileURLToPath(import.meta.url) === path.resolve(process.argv[1]) — widest cross-platform support. import.meta.filename is undefined under Node <20.11 ESM loaders and edge cases with symlinks. - Local errorMessage() helper: replaces (e as Error).message casts which print 'undefined' when the caught value isn't an Error instance. Mirrors build-infra/lib/error-utils — inlined so the hook stays workspace-dep-free. --- .claude/hooks/check-new-deps/index.mts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.claude/hooks/check-new-deps/index.mts b/.claude/hooks/check-new-deps/index.mts index c61e73f07..f7158aed3 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,13 @@ import { import { SocketSdk } from '@socketsecurity/sdk' import type { MalwareCheckPackage } from '@socketsecurity/sdk' +// Local mirror of build-infra/lib/error-utils#errorMessage. Hook runs +// standalone (no workspace deps beyond @socketsecurity/*) so we can't import +// the shared helper, but the contract is identical. +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 +283,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 +412,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 From 306ca4f1f9c510c0d99b3641acd2fedc5fe873af Mon Sep 17 00:00:00 2001 From: jdalton Date: Mon, 20 Apr 2026 14:36:59 -0400 Subject: [PATCH 3/4] chore(hook): fix stale comment reference on errorMessage helper --- .claude/hooks/check-new-deps/index.mts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.claude/hooks/check-new-deps/index.mts b/.claude/hooks/check-new-deps/index.mts index f7158aed3..b143ecbb1 100644 --- a/.claude/hooks/check-new-deps/index.mts +++ b/.claude/hooks/check-new-deps/index.mts @@ -35,9 +35,8 @@ import { import { SocketSdk } from '@socketsecurity/sdk' import type { MalwareCheckPackage } from '@socketsecurity/sdk' -// Local mirror of build-infra/lib/error-utils#errorMessage. Hook runs -// standalone (no workspace deps beyond @socketsecurity/*) so we can't import -// the shared helper, but the contract is identical. +// 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) } From c251996f2521bfd7b6bcf0bab2f4ce4c9f3a9176 Mon Sep 17 00:00:00 2001 From: jdalton Date: Mon, 20 Apr 2026 18:18:58 -0400 Subject: [PATCH 4/4] chore(hooks): extract _helpers.sh, block Linear refs in commit-msg De-inline the API-key allowlist into a new .git-hooks/_helpers.sh so this repo matches the fleet's shared helper convention. Widen the .env file check to match the fleet pattern (allows .env.example and .env.test, blocks the rest). Add a commit-msg check that rejects messages containing Socket Linear team keys or linear.app/... URLs. --- .git-hooks/_helpers.sh | 43 ++++++++++++++++++++++++++++++++++++++++++ .git-hooks/commit-msg | 32 +++++++++++++++++++++---------- .git-hooks/pre-push | 13 ++++--------- 3 files changed, 69 insertions(+), 19 deletions(-) create mode 100644 .git-hooks/_helpers.sh 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