ship-safe 5.0.0 → 5.0.1

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.
@@ -36,6 +36,7 @@ import {
36
36
  SECURITY_PATTERNS,
37
37
  SKIP_DIRS,
38
38
  SKIP_EXTENSIONS,
39
+ SKIP_FILENAMES,
39
40
  TEST_FILE_PATTERNS,
40
41
  MAX_FILE_SIZE
41
42
  } from '../utils/patterns.js';
@@ -335,6 +336,7 @@ async function findFiles(rootPath) {
335
336
  for (const file of files) {
336
337
  const ext = path.extname(file).toLowerCase();
337
338
  if (SKIP_EXTENSIONS.has(ext)) continue;
339
+ if (SKIP_FILENAMES.has(path.basename(file))) continue;
338
340
 
339
341
  const basename = path.basename(file);
340
342
  if (basename.endsWith('.min.js') || basename.endsWith('.min.css')) continue;
@@ -1,160 +1,161 @@
1
- /**
2
- * Watch Command
3
- * ==============
4
- *
5
- * Continuous file monitoring mode. Watches for file changes
6
- * and incrementally scans modified files.
7
- *
8
- * USAGE:
9
- * npx ship-safe watch [path] Start watching for changes
10
- * npx ship-safe watch . --poll Use polling (for network drives)
11
- */
12
-
13
- import fs from 'fs';
14
- import path from 'path';
15
- import chalk from 'chalk';
16
- import { SKIP_DIRS, SKIP_EXTENSIONS, SECRET_PATTERNS, SECURITY_PATTERNS } from '../utils/patterns.js';
17
- import { isHighEntropyMatch, getConfidence } from '../utils/entropy.js';
18
- import * as output from '../utils/output.js';
19
-
20
- export async function watchCommand(targetPath = '.', options = {}) {
21
- const absolutePath = path.resolve(targetPath);
22
-
23
- if (!fs.existsSync(absolutePath)) {
24
- output.error(`Path does not exist: ${absolutePath}`);
25
- process.exit(1);
26
- }
27
-
28
- console.log();
29
- output.header('Ship Safe — Watch Mode');
30
- console.log();
31
- console.log(chalk.cyan(' Watching for file changes...'));
32
- console.log(chalk.gray(' Press Ctrl+C to stop'));
33
- console.log();
34
-
35
- const allPatterns = [...SECRET_PATTERNS, ...SECURITY_PATTERNS];
36
- const skipDirSet = SKIP_DIRS;
37
- let debounceTimer = null;
38
- const pendingFiles = new Set();
39
-
40
- // Use fs.watch recursively
41
- try {
42
- const watcher = fs.watch(absolutePath, { recursive: true }, (eventType, filename) => {
43
- if (!filename) return;
44
-
45
- const fullPath = path.join(absolutePath, filename); // ship-safe-ignore — filename from fs.watch, not user input
46
- const relPath = filename.replace(/\\/g, '/');
47
-
48
- // Skip directories we don't care about
49
- for (const skipDir of skipDirSet) {
50
- if (relPath.includes(`${skipDir}/`) || relPath.startsWith(`${skipDir}/`)) return;
51
- }
52
-
53
- // Skip non-code files
54
- const ext = path.extname(filename).toLowerCase();
55
- if (SKIP_EXTENSIONS.has(ext)) return;
56
- if (filename.endsWith('.min.js') || filename.endsWith('.min.css')) return;
57
-
58
- // Add to pending and debounce
59
- pendingFiles.add(fullPath);
60
-
61
- if (debounceTimer) clearTimeout(debounceTimer);
62
- debounceTimer = setTimeout(() => {
63
- const filesToScan = [...pendingFiles];
64
- pendingFiles.clear();
65
- scanChangedFiles(filesToScan, allPatterns, absolutePath);
66
- }, 300);
67
- });
68
-
69
- // Keep the process alive
70
- process.on('SIGINT', () => {
71
- watcher.close();
72
- console.log();
73
- output.info('Watch mode stopped.');
74
- process.exit(0);
75
- });
76
-
77
- // Prevent Node from exiting
78
- setInterval(() => {}, 1000 * 60 * 60);
79
-
80
- } catch (err) {
81
- output.error(`Watch failed: ${err.message}`);
82
- console.log(chalk.gray(' Try: npx ship-safe watch . --poll'));
83
- process.exit(1);
84
- }
85
- }
86
-
87
- function scanChangedFiles(files, patterns, rootPath) {
88
- const timestamp = new Date().toLocaleTimeString();
89
- let totalFindings = 0;
90
-
91
- for (const filePath of files) {
92
- if (!fs.existsSync(filePath)) continue;
93
-
94
- try {
95
- const stats = fs.statSync(filePath);
96
- if (stats.size > 1_000_000) continue;
97
- } catch {
98
- continue;
99
- }
100
-
101
- const findings = scanFile(filePath, patterns);
102
- if (findings.length > 0) {
103
- totalFindings += findings.length;
104
- const relPath = path.relative(rootPath, filePath);
105
-
106
- for (const f of findings) {
107
- const sevColor = f.severity === 'critical' ? chalk.red.bold
108
- : f.severity === 'high' ? chalk.yellow
109
- : chalk.blue;
110
-
111
- console.log(
112
- chalk.gray(` [${timestamp}] `) +
113
- sevColor(`[${f.severity.toUpperCase()}]`) +
114
- chalk.white(` ${relPath}:${f.line} `) +
115
- chalk.gray(f.patternName)
116
- );
117
- }
118
- }
119
- }
120
-
121
- if (totalFindings === 0 && files.length > 0) {
122
- console.log(chalk.gray(` [${timestamp}] ${files.length} file(s) scanned — clean`));
123
- }
124
- }
125
-
126
- function scanFile(filePath, patterns) {
127
- const findings = [];
128
- try {
129
- const content = fs.readFileSync(filePath, 'utf-8');
130
- const lines = content.split('\n');
131
-
132
- for (let i = 0; i < lines.length; i++) {
133
- const line = lines[i];
134
- if (/ship-safe-ignore/i.test(line)) continue;
135
-
136
- for (const pattern of patterns) {
137
- pattern.pattern.lastIndex = 0;
138
- let match;
139
- while ((match = pattern.pattern.exec(line)) !== null) {
140
- if (pattern.requiresEntropyCheck && !isHighEntropyMatch(match[0])) continue;
141
- findings.push({
142
- line: i + 1,
143
- patternName: pattern.name,
144
- severity: pattern.severity,
145
- matched: match[0],
146
- category: pattern.category || 'secret',
147
- });
148
- }
149
- }
150
- }
151
- } catch { /* skip */ }
152
-
153
- const seen = new Set();
154
- return findings.filter(f => {
155
- const key = `${f.line}:${f.matched}`;
156
- if (seen.has(key)) return false;
157
- seen.add(key);
158
- return true;
159
- });
160
- }
1
+ /**
2
+ * Watch Command
3
+ * ==============
4
+ *
5
+ * Continuous file monitoring mode. Watches for file changes
6
+ * and incrementally scans modified files.
7
+ *
8
+ * USAGE:
9
+ * npx ship-safe watch [path] Start watching for changes
10
+ * npx ship-safe watch . --poll Use polling (for network drives)
11
+ */
12
+
13
+ import fs from 'fs';
14
+ import path from 'path';
15
+ import chalk from 'chalk';
16
+ import { SKIP_DIRS, SKIP_EXTENSIONS, SKIP_FILENAMES, SECRET_PATTERNS, SECURITY_PATTERNS } from '../utils/patterns.js';
17
+ import { isHighEntropyMatch, getConfidence } from '../utils/entropy.js';
18
+ import * as output from '../utils/output.js';
19
+
20
+ export async function watchCommand(targetPath = '.', options = {}) {
21
+ const absolutePath = path.resolve(targetPath);
22
+
23
+ if (!fs.existsSync(absolutePath)) {
24
+ output.error(`Path does not exist: ${absolutePath}`);
25
+ process.exit(1);
26
+ }
27
+
28
+ console.log();
29
+ output.header('Ship Safe — Watch Mode');
30
+ console.log();
31
+ console.log(chalk.cyan(' Watching for file changes...'));
32
+ console.log(chalk.gray(' Press Ctrl+C to stop'));
33
+ console.log();
34
+
35
+ const allPatterns = [...SECRET_PATTERNS, ...SECURITY_PATTERNS];
36
+ const skipDirSet = SKIP_DIRS;
37
+ let debounceTimer = null;
38
+ const pendingFiles = new Set();
39
+
40
+ // Use fs.watch recursively
41
+ try {
42
+ const watcher = fs.watch(absolutePath, { recursive: true }, (eventType, filename) => {
43
+ if (!filename) return;
44
+
45
+ const fullPath = path.join(absolutePath, filename); // ship-safe-ignore — filename from fs.watch, not user input
46
+ const relPath = filename.replace(/\\/g, '/');
47
+
48
+ // Skip directories we don't care about
49
+ for (const skipDir of skipDirSet) {
50
+ if (relPath.includes(`${skipDir}/`) || relPath.startsWith(`${skipDir}/`)) return;
51
+ }
52
+
53
+ // Skip non-code files
54
+ const ext = path.extname(filename).toLowerCase();
55
+ if (SKIP_EXTENSIONS.has(ext)) return;
56
+ if (SKIP_FILENAMES.has(path.basename(filename))) return;
57
+ if (filename.endsWith('.min.js') || filename.endsWith('.min.css')) return;
58
+
59
+ // Add to pending and debounce
60
+ pendingFiles.add(fullPath);
61
+
62
+ if (debounceTimer) clearTimeout(debounceTimer);
63
+ debounceTimer = setTimeout(() => {
64
+ const filesToScan = [...pendingFiles];
65
+ pendingFiles.clear();
66
+ scanChangedFiles(filesToScan, allPatterns, absolutePath);
67
+ }, 300);
68
+ });
69
+
70
+ // Keep the process alive
71
+ process.on('SIGINT', () => {
72
+ watcher.close();
73
+ console.log();
74
+ output.info('Watch mode stopped.');
75
+ process.exit(0);
76
+ });
77
+
78
+ // Prevent Node from exiting
79
+ setInterval(() => {}, 1000 * 60 * 60);
80
+
81
+ } catch (err) {
82
+ output.error(`Watch failed: ${err.message}`);
83
+ console.log(chalk.gray(' Try: npx ship-safe watch . --poll'));
84
+ process.exit(1);
85
+ }
86
+ }
87
+
88
+ function scanChangedFiles(files, patterns, rootPath) {
89
+ const timestamp = new Date().toLocaleTimeString();
90
+ let totalFindings = 0;
91
+
92
+ for (const filePath of files) {
93
+ if (!fs.existsSync(filePath)) continue;
94
+
95
+ try {
96
+ const stats = fs.statSync(filePath);
97
+ if (stats.size > 1_000_000) continue;
98
+ } catch {
99
+ continue;
100
+ }
101
+
102
+ const findings = scanFile(filePath, patterns);
103
+ if (findings.length > 0) {
104
+ totalFindings += findings.length;
105
+ const relPath = path.relative(rootPath, filePath);
106
+
107
+ for (const f of findings) {
108
+ const sevColor = f.severity === 'critical' ? chalk.red.bold
109
+ : f.severity === 'high' ? chalk.yellow
110
+ : chalk.blue;
111
+
112
+ console.log(
113
+ chalk.gray(` [${timestamp}] `) +
114
+ sevColor(`[${f.severity.toUpperCase()}]`) +
115
+ chalk.white(` ${relPath}:${f.line} `) +
116
+ chalk.gray(f.patternName)
117
+ );
118
+ }
119
+ }
120
+ }
121
+
122
+ if (totalFindings === 0 && files.length > 0) {
123
+ console.log(chalk.gray(` [${timestamp}] ${files.length} file(s) scanned — clean`));
124
+ }
125
+ }
126
+
127
+ function scanFile(filePath, patterns) {
128
+ const findings = [];
129
+ try {
130
+ const content = fs.readFileSync(filePath, 'utf-8');
131
+ const lines = content.split('\n');
132
+
133
+ for (let i = 0; i < lines.length; i++) {
134
+ const line = lines[i];
135
+ if (/ship-safe-ignore/i.test(line)) continue;
136
+
137
+ for (const pattern of patterns) {
138
+ pattern.pattern.lastIndex = 0;
139
+ let match;
140
+ while ((match = pattern.pattern.exec(line)) !== null) {
141
+ if (pattern.requiresEntropyCheck && !isHighEntropyMatch(match[0])) continue;
142
+ findings.push({
143
+ line: i + 1,
144
+ patternName: pattern.name,
145
+ severity: pattern.severity,
146
+ matched: match[0],
147
+ category: pattern.category || 'secret',
148
+ });
149
+ }
150
+ }
151
+ }
152
+ } catch { /* skip */ }
153
+
154
+ const seen = new Set();
155
+ return findings.filter(f => {
156
+ const key = `${f.line}:${f.matched}`;
157
+ if (seen.has(key)) return false;
158
+ seen.add(key);
159
+ return true;
160
+ });
161
+ }
package/cli/index.js CHANGED
@@ -26,7 +26,7 @@ export { doctorCommand } from './commands/doctor.js';
26
26
  export { baselineCommand } from './commands/baseline.js';
27
27
 
28
28
  // ── Patterns ──────────────────────────────────────────────────────────────────
29
- export { SECRET_PATTERNS, SECURITY_PATTERNS, SKIP_DIRS, SKIP_EXTENSIONS } from './utils/patterns.js';
29
+ export { SECRET_PATTERNS, SECURITY_PATTERNS, SKIP_DIRS, SKIP_EXTENSIONS, SKIP_FILENAMES } from './utils/patterns.js';
30
30
 
31
31
  // ── Agent Framework ───────────────────────────────────────────────────────────
32
32
  export { BaseAgent, createFinding } from './agents/base-agent.js';