apigraveyard 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,203 @@
1
+ #!/bin/bash
2
+ #
3
+ # APIgraveyard Pre-commit Hook
4
+ # Prevents committing files containing exposed API keys
5
+ #
6
+ # Installation:
7
+ # cp hooks/pre-commit .git/hooks/pre-commit
8
+ # chmod +x .git/hooks/pre-commit
9
+ #
10
+
11
+ # Colors for output
12
+ RED='\033[0;31m'
13
+ YELLOW='\033[0;33m'
14
+ GREEN='\033[0;32m'
15
+ CYAN='\033[0;36m'
16
+ DIM='\033[2m'
17
+ BOLD='\033[1m'
18
+ NC='\033[0m' # No Color
19
+
20
+ # API Key patterns (same as scanner.js)
21
+ PATTERNS=(
22
+ # OpenAI
23
+ 'sk-[A-Za-z0-9]{48}'
24
+ # Groq
25
+ 'gsk_[A-Za-z0-9]{52}'
26
+ # GitHub
27
+ 'gh[ps]_[A-Za-z0-9]{36}'
28
+ # Stripe
29
+ 'sk_(live|test)_[A-Za-z0-9]{24}'
30
+ # Google/Firebase
31
+ 'AIza[A-Za-z0-9_-]{35}'
32
+ # AWS
33
+ 'AKIA[A-Z0-9]{16}'
34
+ # Anthropic
35
+ 'sk-ant-[A-Za-z0-9_-]{95}'
36
+ # Hugging Face
37
+ 'hf_[A-Za-z0-9]{34}'
38
+ )
39
+
40
+ # Service names corresponding to patterns
41
+ SERVICE_NAMES=(
42
+ "OpenAI"
43
+ "Groq"
44
+ "GitHub"
45
+ "Stripe"
46
+ "Google/Firebase"
47
+ "AWS"
48
+ "Anthropic"
49
+ "Hugging Face"
50
+ )
51
+
52
+ # Dangerous file patterns
53
+ DANGEROUS_FILES=(
54
+ '.env'
55
+ '.env.local'
56
+ '.env.production'
57
+ '.env.development'
58
+ '*.pem'
59
+ '*.key'
60
+ '*secret*'
61
+ '*credentials*'
62
+ )
63
+
64
+ # Track if we found any issues
65
+ FOUND_KEYS=0
66
+ FOUND_DANGEROUS=0
67
+ BLOCKED_FILES=""
68
+
69
+ # Function to mask a key for display
70
+ mask_key() {
71
+ local key="$1"
72
+ local len=${#key}
73
+ if [ $len -le 8 ]; then
74
+ echo "****"
75
+ else
76
+ echo "${key:0:4}***...***${key: -4}"
77
+ fi
78
+ }
79
+
80
+ # Function to get service name for a pattern index
81
+ get_service_name() {
82
+ echo "${SERVICE_NAMES[$1]}"
83
+ }
84
+
85
+ # Print banner
86
+ echo -e "${DIM}🪦 APIgraveyard: Scanning staged files for API keys...${NC}"
87
+ echo ""
88
+
89
+ # Get list of staged files (Added, Copied, Modified)
90
+ STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null)
91
+
92
+ if [ -z "$STAGED_FILES" ]; then
93
+ echo -e "${GREEN}✓ No staged files to check.${NC}"
94
+ exit 0
95
+ fi
96
+
97
+ # Check for dangerous files being committed
98
+ for file in $STAGED_FILES; do
99
+ filename=$(basename "$file")
100
+
101
+ # Check against dangerous patterns
102
+ for pattern in "${DANGEROUS_FILES[@]}"; do
103
+ case "$filename" in
104
+ $pattern)
105
+ if [ $FOUND_DANGEROUS -eq 0 ]; then
106
+ echo -e "${YELLOW}⚠️ WARNING: Potentially sensitive files staged${NC}"
107
+ echo ""
108
+ fi
109
+ echo -e " ${YELLOW}⚠️ $file${NC}"
110
+ FOUND_DANGEROUS=1
111
+ ;;
112
+ esac
113
+ done
114
+ done
115
+
116
+ if [ $FOUND_DANGEROUS -eq 1 ]; then
117
+ echo ""
118
+ fi
119
+
120
+ # Scan each staged file for API keys
121
+ for file in $STAGED_FILES; do
122
+ # Skip binary files and non-existent files
123
+ if [ ! -f "$file" ]; then
124
+ continue
125
+ fi
126
+
127
+ # Skip files that are likely binary
128
+ if file "$file" | grep -q "binary"; then
129
+ continue
130
+ fi
131
+
132
+ # Read file content from the staged version
133
+ content=$(git show :"$file" 2>/dev/null)
134
+
135
+ if [ -z "$content" ]; then
136
+ continue
137
+ fi
138
+
139
+ # Check each pattern
140
+ for i in "${!PATTERNS[@]}"; do
141
+ pattern="${PATTERNS[$i]}"
142
+ service=$(get_service_name $i)
143
+
144
+ # Find matches with line numbers
145
+ line_num=0
146
+ while IFS= read -r line; do
147
+ line_num=$((line_num + 1))
148
+
149
+ # Check if line matches pattern
150
+ if echo "$line" | grep -qE "$pattern"; then
151
+ # Extract the matched key
152
+ matched_key=$(echo "$line" | grep -oE "$pattern" | head -1)
153
+ masked=$(mask_key "$matched_key")
154
+
155
+ if [ $FOUND_KEYS -eq 0 ]; then
156
+ echo -e "${RED}${BOLD}❌ COMMIT BLOCKED - API Keys Detected!${NC}"
157
+ echo ""
158
+ fi
159
+
160
+ echo -e "${RED}Found in:${NC} ${CYAN}$file${NC} ${DIM}(line $line_num)${NC}"
161
+ echo -e " ${YELLOW}$service key:${NC} $masked"
162
+ echo ""
163
+
164
+ FOUND_KEYS=1
165
+ BLOCKED_FILES="$BLOCKED_FILES $file"
166
+ fi
167
+ done <<< "$content"
168
+ done
169
+ done
170
+
171
+ # If keys were found, show suggestions and exit with error
172
+ if [ $FOUND_KEYS -eq 1 ]; then
173
+ echo -e "${DIM}─────────────────────────────────────────────────${NC}"
174
+ echo ""
175
+ echo -e "${BOLD}Suggested fixes:${NC}"
176
+ echo -e " ${GREEN}1.${NC} Move keys to ${CYAN}.env${NC} file"
177
+ echo -e " ${GREEN}2.${NC} Add ${CYAN}.env${NC} to ${CYAN}.gitignore${NC}"
178
+ echo -e " ${GREEN}3.${NC} Unstage the file(s):"
179
+
180
+ # Show unique files
181
+ unique_files=$(echo "$BLOCKED_FILES" | tr ' ' '\n' | sort -u)
182
+ for f in $unique_files; do
183
+ if [ -n "$f" ]; then
184
+ echo -e " ${DIM}git reset HEAD $f${NC}"
185
+ fi
186
+ done
187
+
188
+ echo ""
189
+ echo -e "${DIM}To bypass this check (NOT recommended):${NC}"
190
+ echo -e " ${DIM}git commit --no-verify${NC}"
191
+ echo ""
192
+
193
+ exit 1
194
+ fi
195
+
196
+ # Check if only dangerous files warning (not blocking)
197
+ if [ $FOUND_DANGEROUS -eq 1 ]; then
198
+ echo -e "${YELLOW}Consider adding sensitive files to .gitignore${NC}"
199
+ echo ""
200
+ fi
201
+
202
+ echo -e "${GREEN}✓ No API keys detected in staged files.${NC}"
203
+ exit 0
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "apigraveyard",
3
+ "version": "1.0.0",
4
+ "description": "A CLI tool to detect and manage deprecated APIs in your codebase",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "apigraveyard": "./bin/apigraveyard.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node bin/apigraveyard.js",
12
+ "test": "echo \"Error: no test specified\" && exit 1",
13
+ "postinstall": "node scripts/install-hooks.js",
14
+ "install-hooks": "node scripts/install-hooks.js"
15
+ },
16
+ "keywords": [
17
+ "api",
18
+ "deprecated",
19
+ "cli",
20
+ "scanner",
21
+ "graveyard"
22
+ ],
23
+ "author": "",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "commander": "^11.0.0",
27
+ "chalk": "^5.3.0",
28
+ "axios": "^1.6.0",
29
+ "ora": "^7.0.0",
30
+ "cli-table3": "^0.6.3",
31
+ "glob": "^10.3.0",
32
+ "dotenv": "^16.3.0"
33
+ }
34
+ }
@@ -0,0 +1,182 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * APIgraveyard Hook Installer
5
+ * Installs git hooks for API key detection
6
+ *
7
+ * Usage:
8
+ * npm run install-hooks
9
+ * node scripts/install-hooks.js
10
+ */
11
+
12
+ import fs from 'fs';
13
+ import path from 'path';
14
+ import { fileURLToPath } from 'url';
15
+ import { execSync } from 'child_process';
16
+
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = path.dirname(__filename);
19
+
20
+ // Colors for console output
21
+ const colors = {
22
+ reset: '\x1b[0m',
23
+ red: '\x1b[31m',
24
+ green: '\x1b[32m',
25
+ yellow: '\x1b[33m',
26
+ cyan: '\x1b[36m',
27
+ dim: '\x1b[2m'
28
+ };
29
+
30
+ /**
31
+ * Logs a message with color
32
+ */
33
+ function log(message, color = 'reset') {
34
+ console.log(`${colors[color]}${message}${colors.reset}`);
35
+ }
36
+
37
+ /**
38
+ * Finds the git root directory
39
+ */
40
+ function findGitRoot() {
41
+ try {
42
+ const gitRoot = execSync('git rev-parse --show-toplevel', {
43
+ encoding: 'utf-8',
44
+ stdio: ['pipe', 'pipe', 'pipe']
45
+ }).trim();
46
+ return gitRoot;
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Main installation function
54
+ */
55
+ function installHooks() {
56
+ log('\n🪦 APIgraveyard Hook Installer\n', 'cyan');
57
+
58
+ // Find git root
59
+ const gitRoot = findGitRoot();
60
+
61
+ if (!gitRoot) {
62
+ log('⚠️ Not a git repository. Skipping hook installation.', 'yellow');
63
+ log(' Run "git init" first, then "npm run install-hooks"\n', 'dim');
64
+ return;
65
+ }
66
+
67
+ const hooksDir = path.join(gitRoot, '.git', 'hooks');
68
+ const sourceHook = path.join(__dirname, '..', 'hooks', 'pre-commit');
69
+ const targetHook = path.join(hooksDir, 'pre-commit');
70
+
71
+ // Check if source hook exists
72
+ if (!fs.existsSync(sourceHook)) {
73
+ log('❌ Source hook not found: ' + sourceHook, 'red');
74
+ process.exit(1);
75
+ }
76
+
77
+ // Ensure hooks directory exists
78
+ if (!fs.existsSync(hooksDir)) {
79
+ fs.mkdirSync(hooksDir, { recursive: true });
80
+ log('📁 Created hooks directory', 'dim');
81
+ }
82
+
83
+ // Check if hook already exists
84
+ if (fs.existsSync(targetHook)) {
85
+ // Read existing hook to check if it's ours
86
+ const existingContent = fs.readFileSync(targetHook, 'utf-8');
87
+
88
+ if (existingContent.includes('APIgraveyard')) {
89
+ log('✓ Pre-commit hook already installed', 'green');
90
+
91
+ // Update it anyway in case of changes
92
+ const sourceContent = fs.readFileSync(sourceHook, 'utf-8');
93
+ fs.writeFileSync(targetHook, sourceContent, { mode: 0o755 });
94
+ log(' Updated to latest version', 'dim');
95
+ } else {
96
+ // There's an existing hook that's not ours
97
+ log('⚠️ Existing pre-commit hook found', 'yellow');
98
+ log(' Backing up to pre-commit.backup', 'dim');
99
+
100
+ // Backup existing hook
101
+ fs.copyFileSync(targetHook, targetHook + '.backup');
102
+
103
+ // Install our hook
104
+ const sourceContent = fs.readFileSync(sourceHook, 'utf-8');
105
+ fs.writeFileSync(targetHook, sourceContent, { mode: 0o755 });
106
+ log('✓ Pre-commit hook installed', 'green');
107
+ log(' Your old hook was saved to pre-commit.backup', 'dim');
108
+ }
109
+ } else {
110
+ // No existing hook, just copy
111
+ const sourceContent = fs.readFileSync(sourceHook, 'utf-8');
112
+ fs.writeFileSync(targetHook, sourceContent, { mode: 0o755 });
113
+ log('✓ Pre-commit hook installed', 'green');
114
+ }
115
+
116
+ // Make executable on Unix systems
117
+ try {
118
+ if (process.platform !== 'win32') {
119
+ fs.chmodSync(targetHook, 0o755);
120
+ log(' Made hook executable', 'dim');
121
+ }
122
+ } catch (err) {
123
+ log('⚠️ Could not set executable permission: ' + err.message, 'yellow');
124
+ }
125
+
126
+ log('\n📍 Hook installed at: ' + targetHook, 'dim');
127
+ log('\n✅ APIgraveyard will now scan for API keys before each commit!\n', 'green');
128
+
129
+ // Show how to bypass
130
+ log('To bypass the check (not recommended):', 'dim');
131
+ log(' git commit --no-verify\n', 'dim');
132
+ }
133
+
134
+ /**
135
+ * Uninstall function
136
+ */
137
+ function uninstallHooks() {
138
+ log('\n🪦 APIgraveyard Hook Uninstaller\n', 'cyan');
139
+
140
+ const gitRoot = findGitRoot();
141
+
142
+ if (!gitRoot) {
143
+ log('⚠️ Not a git repository.', 'yellow');
144
+ return;
145
+ }
146
+
147
+ const targetHook = path.join(gitRoot, '.git', 'hooks', 'pre-commit');
148
+ const backupHook = targetHook + '.backup';
149
+
150
+ if (!fs.existsSync(targetHook)) {
151
+ log('ℹ️ No pre-commit hook installed', 'dim');
152
+ return;
153
+ }
154
+
155
+ const content = fs.readFileSync(targetHook, 'utf-8');
156
+
157
+ if (!content.includes('APIgraveyard')) {
158
+ log('⚠️ Pre-commit hook is not from APIgraveyard', 'yellow');
159
+ return;
160
+ }
161
+
162
+ // Remove our hook
163
+ fs.unlinkSync(targetHook);
164
+ log('✓ Pre-commit hook removed', 'green');
165
+
166
+ // Restore backup if exists
167
+ if (fs.existsSync(backupHook)) {
168
+ fs.renameSync(backupHook, targetHook);
169
+ log(' Restored previous hook from backup', 'dim');
170
+ }
171
+
172
+ log('\n✅ APIgraveyard hooks uninstalled\n', 'green');
173
+ }
174
+
175
+ // Check command line arguments
176
+ const args = process.argv.slice(2);
177
+
178
+ if (args.includes('--uninstall') || args.includes('-u')) {
179
+ uninstallHooks();
180
+ } else {
181
+ installHooks();
182
+ }