pi-var 1.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.
Files changed (64) hide show
  1. package/.github/FUNDING.yml +4 -0
  2. package/.prettierrc +7 -0
  3. package/README.md +240 -0
  4. package/commitlint.config.cjs +3 -0
  5. package/extensions/pi-var/__tests__/integration/smoke.test.ts +308 -0
  6. package/extensions/pi-var/__tests__/unit/command.test.ts +322 -0
  7. package/extensions/pi-var/__tests__/unit/cow-detection.test.ts +340 -0
  8. package/extensions/pi-var/__tests__/unit/cow-git-merge.test.ts +404 -0
  9. package/extensions/pi-var/__tests__/unit/edr.test.ts +119 -0
  10. package/extensions/pi-var/__tests__/unit/environment.test.ts +369 -0
  11. package/extensions/pi-var/__tests__/unit/file-redirect.test.ts +295 -0
  12. package/extensions/pi-var/__tests__/unit/store.test.ts +489 -0
  13. package/extensions/pi-var/__tests__/unit/tools.test.ts +133 -0
  14. package/extensions/pi-var/__tests__/unit/variations.test.ts +352 -0
  15. package/extensions/pi-var/index.ts +111 -0
  16. package/extensions/pi-var/src/config.ts +30 -0
  17. package/extensions/pi-var/src/cow/clone.ts +46 -0
  18. package/extensions/pi-var/src/cow/detection.ts +215 -0
  19. package/extensions/pi-var/src/cow/index.ts +6 -0
  20. package/extensions/pi-var/src/edr/constants.ts +199 -0
  21. package/extensions/pi-var/src/edr/crowdstrike.ts +62 -0
  22. package/extensions/pi-var/src/edr/detection.ts +97 -0
  23. package/extensions/pi-var/src/edr/index.ts +6 -0
  24. package/extensions/pi-var/src/environment/copy-env.ts +79 -0
  25. package/extensions/pi-var/src/environment/detect.ts +91 -0
  26. package/extensions/pi-var/src/environment/index.ts +54 -0
  27. package/extensions/pi-var/src/environment/marker.ts +42 -0
  28. package/extensions/pi-var/src/environment/symlink.ts +82 -0
  29. package/extensions/pi-var/src/gitignore/manager.ts +77 -0
  30. package/extensions/pi-var/src/state/index.ts +7 -0
  31. package/extensions/pi-var/src/state/keys.ts +14 -0
  32. package/extensions/pi-var/src/state/store.ts +110 -0
  33. package/extensions/pi-var/src/state/types.ts +70 -0
  34. package/extensions/pi-var/src/tools/command/clean.ts +87 -0
  35. package/extensions/pi-var/src/tools/command/index.ts +95 -0
  36. package/extensions/pi-var/src/tools/command/status.ts +58 -0
  37. package/extensions/pi-var/src/tools/command/stop.ts +37 -0
  38. package/extensions/pi-var/src/tools/file-redirect.ts +126 -0
  39. package/extensions/pi-var/src/tools/redirect/bash.ts +75 -0
  40. package/extensions/pi-var/src/tools/redirect/edit.ts +60 -0
  41. package/extensions/pi-var/src/tools/redirect/index.ts +64 -0
  42. package/extensions/pi-var/src/tools/redirect/read.ts +61 -0
  43. package/extensions/pi-var/src/tools/redirect/write.ts +60 -0
  44. package/extensions/pi-var/src/tools/variation/create.ts +108 -0
  45. package/extensions/pi-var/src/tools/variation/merge.ts +80 -0
  46. package/extensions/pi-var/src/types/index.ts +139 -0
  47. package/extensions/pi-var/src/utils/names.ts +120 -0
  48. package/extensions/pi-var/src/variation/create-copy.ts +35 -0
  49. package/extensions/pi-var/src/variation/create-cow.ts +81 -0
  50. package/extensions/pi-var/src/variation/create-worktree.ts +31 -0
  51. package/extensions/pi-var/src/variation/create.ts +127 -0
  52. package/extensions/pi-var/src/variation/index.ts +9 -0
  53. package/extensions/pi-var/src/variation/merge/copy.ts +151 -0
  54. package/extensions/pi-var/src/variation/merge/git.ts +176 -0
  55. package/extensions/pi-var/src/variation/merge/index.ts +36 -0
  56. package/extensions/pi-var/src/variation/merge/rsync.ts +74 -0
  57. package/extensions/pi-var/src/variation/merge/strategy.ts +62 -0
  58. package/extensions/pi-var/src/variation/paths.ts +26 -0
  59. package/extensions/pi-var/src/variation/remove.ts +79 -0
  60. package/npm-shrinkwrap.json +6908 -0
  61. package/package.json +63 -0
  62. package/skills/variations/SKILL.md +182 -0
  63. package/tsconfig.json +25 -0
  64. package/vitest.config.ts +19 -0
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Copy-on-Write detection and performance testing
3
+ */
4
+
5
+ import { exec, execSync } from 'child_process';
6
+ import { promisify } from 'util';
7
+ import * as os from 'os';
8
+ import * as path from 'path';
9
+ import { promises as fs } from 'fs';
10
+ import type { CoWDetectionResult, EDRDetectionResult } from '../types/index';
11
+ import { detectEDR, detectCrowdStrike, hasSlowCoWEDR } from '../edr/index';
12
+
13
+ const execAsync = promisify(exec);
14
+
15
+ /**
16
+ * Detect Copy-on-Write support on the current platform
17
+ * Tests actual CoW functionality by creating test files
18
+ * Includes EDR detection and multi-iteration performance testing
19
+ *
20
+ * Strategy:
21
+ * 1. Known slow EDR detected (CrowdStrike, SentinelOne, etc.) → worktree
22
+ * 2. No slow EDR detected + fast CoW timing → cow
23
+ * 3. Uncertain cases → worktree (conservative)
24
+ */
25
+ export async function detectCoWSupport(testDir: string): Promise<CoWDetectionResult> {
26
+ // Ensure test directory exists
27
+ await fs.mkdir(testDir, { recursive: true });
28
+
29
+ const testFile = path.join(testDir, `.cow-test-${Date.now()}`);
30
+ const clonedFile = path.join(testDir, `.cow-clone-${Date.now()}`);
31
+
32
+ // Detect EDR software that may impact CoW performance
33
+ let edrResult: CoWDetectionResult['edr'] = {
34
+ detected: false,
35
+ products: [],
36
+ hasSlowCoWEDR: false,
37
+ };
38
+ let hasCrowdStrike = false;
39
+
40
+ try {
41
+ const [edrDetection, crowdstrikeDetection] = await Promise.all([
42
+ detectEDR(),
43
+ detectCrowdStrike(),
44
+ ]);
45
+
46
+ hasCrowdStrike = crowdstrikeDetection;
47
+ edrResult = {
48
+ detected: edrDetection.detected,
49
+ products: edrDetection.details.map((d) => d.product),
50
+ hasSlowCoWEDR: hasSlowCoWEDR(edrDetection) || hasCrowdStrike,
51
+ };
52
+ } catch {
53
+ // EDR detection failed, continue without EDR info
54
+ }
55
+
56
+ // If we detected a known slow CoW EDR, we can short-circuit to worktree
57
+ // without needing timing validation (high confidence)
58
+ if (edrResult.hasSlowCoWEDR) {
59
+ return {
60
+ supported: true, // CoW technically works, just slow
61
+ edr: edrResult,
62
+ recommendedType: 'worktree',
63
+ };
64
+ }
65
+
66
+ try {
67
+ const platform = os.platform();
68
+
69
+ // Skip CoW detection entirely on Windows (no native CoW support)
70
+ if (platform === 'win32') {
71
+ return {
72
+ supported: false,
73
+ edr: edrResult,
74
+ recommendedType: 'worktree',
75
+ };
76
+ }
77
+
78
+ // Create test files of varying sizes
79
+ // Larger files more likely to trigger EDR scanning
80
+ const testSizes = [1 * 1024 * 1024, 5 * 1024 * 1024]; // 1MB and 5MB
81
+ let cowSupported = false;
82
+ let cowMethod: 'clonefile' | 'reflink' | undefined;
83
+ const timings: number[] = [];
84
+
85
+ for (const size of testSizes) {
86
+ const dataFile = `${testFile}-${size}`;
87
+ const cloneTarget = `${clonedFile}-${size}`;
88
+
89
+ try {
90
+ const testData = Buffer.alloc(size, 'x');
91
+ await fs.writeFile(dataFile, testData);
92
+
93
+ if (platform === 'darwin') {
94
+ // macOS: Try cp -c (clonefile)
95
+ const startTime = performance.now();
96
+ await execAsync(`cp -c "${dataFile}" "${cloneTarget}"`);
97
+ const duration = performance.now() - startTime;
98
+ timings.push(duration);
99
+
100
+ const [origStat, cloneStat] = await Promise.all([
101
+ fs.stat(dataFile),
102
+ fs.stat(cloneTarget),
103
+ ]);
104
+
105
+ if (origStat.size === cloneStat.size) {
106
+ cowSupported = true;
107
+ cowMethod = 'clonefile';
108
+ }
109
+ } else if (platform === 'linux') {
110
+ // Linux: Try cp --reflink=auto
111
+ const startTime = performance.now();
112
+ await execAsync(`cp -a --reflink=auto "${dataFile}" "${cloneTarget}"`);
113
+ const duration = performance.now() - startTime;
114
+ timings.push(duration);
115
+
116
+ const [origStat, cloneStat] = await Promise.all([
117
+ fs.stat(dataFile),
118
+ fs.stat(cloneTarget),
119
+ ]);
120
+
121
+ if (origStat.ino === cloneStat.ino) {
122
+ cowSupported = true;
123
+ cowMethod = 'reflink';
124
+ } else {
125
+ // Check filesystem type
126
+ try {
127
+ const { stdout } = await execAsync(`findmnt -T "${testDir}" -o FSTYPE --noheadings`);
128
+ const fsType = stdout.trim();
129
+ if (fsType === 'btrfs' || fsType.includes('xfs')) {
130
+ cowSupported = true;
131
+ cowMethod = 'reflink';
132
+ }
133
+ } catch {
134
+ // findmnt failed
135
+ }
136
+ }
137
+ }
138
+
139
+ // Cleanup this iteration's files
140
+ await fs.unlink(dataFile).catch(() => {});
141
+ await fs.unlink(cloneTarget).catch(() => {});
142
+ } catch {
143
+ // This size test failed, continue to next
144
+ }
145
+ }
146
+
147
+ // Analyze timing results
148
+ const avgTiming = timings.length > 0 ? timings.reduce((a, b) => a + b, 0) / timings.length : 0;
149
+ const maxTiming = timings.length > 0 ? Math.max(...timings) : 0;
150
+
151
+ // Conservative thresholds:
152
+ // - < 20ms: Definitely fast (no EDR interference)
153
+ // - 20-100ms: Uncertain (might be EDR, might be system load)
154
+ // - > 100ms: Definitely slow (likely EDR or very slow system)
155
+ const fastThreshold = 20;
156
+ const slowThreshold = 100;
157
+
158
+ let cowFast: boolean;
159
+ let confidence: 'high' | 'medium' | 'low';
160
+
161
+ if (avgTiming < fastThreshold) {
162
+ cowFast = true;
163
+ confidence = 'high';
164
+ } else if (avgTiming > slowThreshold) {
165
+ cowFast = false;
166
+ confidence = 'high';
167
+ } else {
168
+ // Gray zone - medium confidence
169
+ cowFast = false;
170
+ confidence = 'medium';
171
+ }
172
+
173
+ // Additional heuristics for gray zone:
174
+ // If max timing is significantly higher than average, might be intermittent EDR
175
+ const hasSpike = maxTiming > avgTiming * 3 && maxTiming > slowThreshold;
176
+
177
+ // Decision logic - priority order matters!
178
+ let recommendedType: 'cow' | 'worktree' | 'copy';
179
+
180
+ if (!cowSupported) {
181
+ recommendedType = 'worktree';
182
+ } else if (hasSpike || confidence === 'medium') {
183
+ // Uncertain or saw spikes - be conservative with worktree
184
+ // This check MUST come before the fast+high check to catch spikes
185
+ recommendedType = 'worktree';
186
+ } else if (cowFast && confidence === 'high') {
187
+ // Fast and confident - use CoW
188
+ recommendedType = 'cow';
189
+ } else {
190
+ // Slow but confident - definitely worktree
191
+ recommendedType = 'worktree';
192
+ }
193
+
194
+ return {
195
+ supported: cowSupported,
196
+ method: cowMethod,
197
+ edr: edrResult,
198
+ performance: {
199
+ fast: cowFast,
200
+ durationMs: avgTiming,
201
+ samples: timings.length,
202
+ maxDurationMs: maxTiming,
203
+ confidence,
204
+ },
205
+ recommendedType,
206
+ };
207
+ } catch {
208
+ // CoW detection failed entirely
209
+ return {
210
+ supported: false,
211
+ edr: edrResult,
212
+ recommendedType: 'worktree',
213
+ };
214
+ }
215
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Copy-on-Write (CoW) module
3
+ * Exports CoW detection functionality
4
+ */
5
+
6
+ export { detectCoWSupport } from './detection';
@@ -0,0 +1,199 @@
1
+ /**
2
+ * EDR (Endpoint Detection and Response) constants
3
+ */
4
+
5
+ /**
6
+ * Known EDR/AV process names by platform
7
+ */
8
+ export const EDR_PROCESSES: Record<string, string[]> = {
9
+ darwin: [
10
+ 'Falcon', // CrowdStrike Falcon
11
+ 'falconctl', // CrowdStrike CLI
12
+ 'SentinelAgent', // SentinelOne
13
+ 'SentinelOne', // SentinelOne alternative
14
+ 'CylanceSvc', // Cylance
15
+ 'CylanceUI', // Cylance UI
16
+ 'eset_daemon', // ESET
17
+ 'eset_nod32', // ESET NOD32
18
+ 'kav', // Kaspersky
19
+ 'kaspersky', // Kaspersky
20
+ 'SophosScanD', // Sophos
21
+ 'SophosMRT', // Sophos MRT
22
+ 'sophosscan', // Sophos
23
+ 'com.symantec.nfm', // Symantec/Norton
24
+ 'norton', // Norton
25
+ 'mcshield', // McAfee
26
+ 'McAfee', // McAfee
27
+ '的趋势', // Trend Micro (UTF-8)
28
+ 'TrendMicro', // Trend Micro
29
+ 'tmux', // Trend Micro
30
+ 'CbDefense', // Carbon Black/VMware
31
+ 'cbdaemon', // Carbon Black daemon
32
+ 'xagt', // FireEye/XAGENT
33
+ 'osqueryd', // osquery (often used for EDR)
34
+ 'elastic-agent', // Elastic Security
35
+ 'elastic-endpoint', // Elastic Endpoint
36
+ 'jamf', // Jamf (MDM+security)
37
+ 'defense', // Apple XProtect
38
+ 'RTProtectionDaemon', // Various macOS AV
39
+ ],
40
+ linux: [
41
+ 'falcon-sensor', // CrowdStrike Falcon
42
+ 'falconctl', // CrowdStrike CLI
43
+ 'sentinel-agent', // SentinelOne
44
+ 'sentinelone', // SentinelOne alternative
45
+ 'cylancesvc', // Cylance
46
+ 'cylanceui', // Cylance UI
47
+ 'eset_daemon', // ESET
48
+ 'klnagent', // Kaspersky
49
+ 'kav', // Kaspersky
50
+ 'savscand', // Sophos
51
+ 'sophos', // Sophos
52
+ 'nscd', // Symantec
53
+ 'symcscan', // Symantec
54
+ 'mcshield', // McAfee
55
+ 'nailsd', // McAfee Linux
56
+ 'TrendMicro', // Trend Micro
57
+ 'ds_agent', // Trend Micro Deep Security
58
+ 'cbdaemon', // Carbon Black
59
+ 'xagt', // FireEye
60
+ 'osqueryd', // osquery
61
+ 'elastic-agent', // Elastic Security
62
+ 'elastic-endpoint', // Elastic Endpoint
63
+ 'filebeat', // Elastic Filebeat (often used with security)
64
+ 'auditd', // Linux audit daemon (security monitoring)
65
+ 'apparmor', // AppArmor
66
+ 'selinux', // SELinux userspace tools
67
+ 'clamd', // ClamAV
68
+ 'freshclam', // ClamAV updater
69
+ ],
70
+ win32: [
71
+ 'CSAgent', // CrowdStrike (csagent service)
72
+ 'CSFalconService', // CrowdStrike service
73
+ 'SentinelAgent', // SentinelOne
74
+ 'SentinelServiceHost', // SentinelOne service
75
+ 'CylanceSvc', // Cylance
76
+ 'CylanceUI', // Cylance UI
77
+ 'ekrn', // ESET
78
+ 'avp', // Kaspersky
79
+ 'avpui', // Kaspersky UI
80
+ 'avps', // Kaspersky service
81
+ 'SavService', // Sophos
82
+ 'SophosSafestore64', // Sophos
83
+ 'SymCorpUI', // Symantec
84
+ 'ccSvcHst', // Symantec service
85
+ 'mcshield', // McAfee
86
+ 'ModuleCoreService', // McAfee
87
+ 'mcsvc', // McAfee
88
+ 'TmListen', // Trend Micro
89
+ 'TmProxy', // Trend Micro
90
+ 'TmCCSF', // Trend Micro
91
+ 'CbDefense', // Carbon Black
92
+ 'repwx', // Carbon Black
93
+ 'xagt', // FireEye
94
+ 'osqueryd', // osquery
95
+ 'elastic-agent', // Elastic Security
96
+ 'elastic-endpoint', // Elastic Endpoint
97
+ 'MsMpEng', // Microsoft Defender
98
+ 'MsSense', // Microsoft Defender for Endpoint
99
+ 'Sense', // Microsoft Defender for Endpoint
100
+ 'WdFilter', // Windows Defender filter driver
101
+ 'clamav', // ClamAV
102
+ 'ImmunetProtect', // Cisco Immunet
103
+ 'cyserver', // Cylance
104
+ ],
105
+ };
106
+
107
+ // Mapping of process names to product names and CoW impact
108
+ export const EDR_INFO: Record<string, { product: string; knownSlowCoW: boolean }> = {
109
+ // CrowdStrike - major CoW impact
110
+ Falcon: { product: 'CrowdStrike Falcon', knownSlowCoW: true },
111
+ falconctl: { product: 'CrowdStrike Falcon', knownSlowCoW: true },
112
+ 'falcon-sensor': { product: 'CrowdStrike Falcon', knownSlowCoW: true },
113
+ CSAgent: { product: 'CrowdStrike Falcon', knownSlowCoW: true },
114
+ CSFalconService: { product: 'CrowdStrike Falcon', knownSlowCoW: true },
115
+
116
+ // SentinelOne - major CoW impact
117
+ SentinelAgent: { product: 'SentinelOne', knownSlowCoW: true },
118
+ SentinelOne: { product: 'SentinelOne', knownSlowCoW: true },
119
+ SentinelServiceHost: { product: 'SentinelOne', knownSlowCoW: true },
120
+ 'sentinel-agent': { product: 'SentinelOne', knownSlowCoW: true },
121
+ sentinelone: { product: 'SentinelOne', knownSlowCoW: true },
122
+
123
+ // Cylance - moderate to high CoW impact
124
+ CylanceSvc: { product: 'Cylance', knownSlowCoW: true },
125
+ CylanceUI: { product: 'Cylance', knownSlowCoW: true },
126
+ cylancesvc: { product: 'Cylance', knownSlowCoW: true },
127
+ cylanceui: { product: 'Cylance', knownSlowCoW: true },
128
+ cyserver: { product: 'Cylance', knownSlowCoW: true },
129
+
130
+ // Carbon Black - moderate CoW impact
131
+ CbDefense: { product: 'Carbon Black/VMware', knownSlowCoW: true },
132
+ cbdaemon: { product: 'Carbon Black/VMware', knownSlowCoW: true },
133
+ repwx: { product: 'Carbon Black/VMware', knownSlowCoW: true },
134
+
135
+ // Microsoft Defender for Endpoint - moderate CoW impact
136
+ MsSense: { product: 'Microsoft Defender for Endpoint', knownSlowCoW: true },
137
+ Sense: { product: 'Microsoft Defender for Endpoint', knownSlowCoW: true },
138
+
139
+ // FireEye - high CoW impact
140
+ xagt: { product: 'FireEye/XAGENT', knownSlowCoW: true },
141
+
142
+ // Traditional AV - generally lower CoW impact but still present
143
+ eset_daemon: { product: 'ESET', knownSlowCoW: false },
144
+ eset_nod32: { product: 'ESET', knownSlowCoW: false },
145
+ ekrn: { product: 'ESET', knownSlowCoW: false },
146
+ kav: { product: 'Kaspersky', knownSlowCoW: false },
147
+ kaspersky: { product: 'Kaspersky', knownSlowCoW: false },
148
+ klnagent: { product: 'Kaspersky', knownSlowCoW: false },
149
+ avp: { product: 'Kaspersky', knownSlowCoW: false },
150
+ avpui: { product: 'Kaspersky', knownSlowCoW: false },
151
+ avps: { product: 'Kaspersky', knownSlowCoW: false },
152
+ SophosScanD: { product: 'Sophos', knownSlowCoW: false },
153
+ SophosMRT: { product: 'Sophos', knownSlowCoW: false },
154
+ sophosscan: { product: 'Sophos', knownSlowCoW: false },
155
+ savscand: { product: 'Sophos', knownSlowCoW: false },
156
+ sophos: { product: 'Sophos', knownSlowCoW: false },
157
+ SavService: { product: 'Sophos', knownSlowCoW: false },
158
+ SophosSafestore64: { product: 'Sophos', knownSlowCoW: false },
159
+ 'com.symantec.nfm': { product: 'Symantec/Norton', knownSlowCoW: false },
160
+ norton: { product: 'Symantec/Norton', knownSlowCoW: false },
161
+ SymCorpUI: { product: 'Symantec', knownSlowCoW: false },
162
+ ccSvcHst: { product: 'Symantec', knownSlowCoW: false },
163
+ nscd: { product: 'Symantec', knownSlowCoW: false },
164
+ symcscan: { product: 'Symantec', knownSlowCoW: false },
165
+ mcshield: { product: 'McAfee', knownSlowCoW: false },
166
+ mcsvc: { product: 'McAfee', knownSlowCoW: false },
167
+ ModuleCoreService: { product: 'McAfee', knownSlowCoW: false },
168
+ nailsd: { product: 'McAfee', knownSlowCoW: false },
169
+ 的趋势: { product: 'Trend Micro', knownSlowCoW: false },
170
+ TrendMicro: { product: 'Trend Micro', knownSlowCoW: false },
171
+ tmux: { product: 'Trend Micro', knownSlowCoW: false },
172
+ ds_agent: { product: 'Trend Micro Deep Security', knownSlowCoW: false },
173
+ TmListen: { product: 'Trend Micro', knownSlowCoW: false },
174
+ TmProxy: { product: 'Trend Micro', knownSlowCoW: false },
175
+ TmCCSF: { product: 'Trend Micro', knownSlowCoW: false },
176
+
177
+ // Microsoft Defender (standard) - generally lower impact
178
+ MsMpEng: { product: 'Microsoft Defender', knownSlowCoW: false },
179
+ WdFilter: { product: 'Windows Defender', knownSlowCoW: false },
180
+
181
+ // Open source/enterprise tools - variable impact
182
+ osqueryd: { product: 'osquery', knownSlowCoW: false },
183
+ 'elastic-agent': { product: 'Elastic Security', knownSlowCoW: false },
184
+ 'elastic-endpoint': { product: 'Elastic Security', knownSlowCoW: false },
185
+ filebeat: { product: 'Elastic Filebeat', knownSlowCoW: false },
186
+ clamd: { product: 'ClamAV', knownSlowCoW: false },
187
+ freshclam: { product: 'ClamAV', knownSlowCoW: false },
188
+ ImmunetProtect: { product: 'Cisco Immunet', knownSlowCoW: false },
189
+
190
+ // Linux security tools
191
+ auditd: { product: 'Linux audit daemon', knownSlowCoW: false },
192
+ apparmor: { product: 'AppArmor', knownSlowCoW: false },
193
+ selinux: { product: 'SELinux', knownSlowCoW: false },
194
+
195
+ // macOS specific
196
+ defense: { product: 'Apple XProtect', knownSlowCoW: false },
197
+ RTProtectionDaemon: { product: 'macOS RTProtection', knownSlowCoW: false },
198
+ jamf: { product: 'Jamf', knownSlowCoW: false },
199
+ };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * CrowdStrike Falcon specific detection
3
+ */
4
+
5
+ import { exec } from 'child_process';
6
+ import { promisify } from 'util';
7
+ import * as os from 'os';
8
+
9
+ const execAsync = promisify(exec);
10
+
11
+ /**
12
+ * Check specifically for CrowdStrike Falcon (most common CoW blocker)
13
+ * Uses platform-specific methods for reliable detection
14
+ */
15
+ export async function detectCrowdStrike(): Promise<boolean> {
16
+ const platform = os.platform();
17
+
18
+ try {
19
+ if (platform === 'darwin') {
20
+ // Check for falconctl binary
21
+ try {
22
+ await execAsync('which falconctl', { timeout: 5000 });
23
+ return true;
24
+ } catch {
25
+ // falconctl not in PATH, check for the app
26
+ const { execSync } = await import('child_process');
27
+ try {
28
+ execSync('test -d /Applications/Falcon.app', { stdio: 'ignore' });
29
+ return true;
30
+ } catch {
31
+ // App not found
32
+ }
33
+ }
34
+ } else if (platform === 'linux') {
35
+ // Check for falcon-sensor service or binary
36
+ try {
37
+ await execAsync('which falconctl', { timeout: 5000 });
38
+ return true;
39
+ } catch {
40
+ // Check systemctl
41
+ try {
42
+ await execAsync('systemctl is-active falcon-sensor', { timeout: 5000 });
43
+ return true;
44
+ } catch {
45
+ // Service not active
46
+ }
47
+ }
48
+ } else if (platform === 'win32') {
49
+ // Check for csagent service
50
+ try {
51
+ await execAsync('sc query csagent', { timeout: 5000 });
52
+ return true;
53
+ } catch {
54
+ // Service not found
55
+ }
56
+ }
57
+ } catch {
58
+ // Any error means not detected
59
+ }
60
+
61
+ return false;
62
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * EDR detection utilities
3
+ */
4
+
5
+ import { exec } from 'child_process';
6
+ import { promisify } from 'util';
7
+ import * as os from 'os';
8
+ import { EDR_PROCESSES, EDR_INFO } from './constants';
9
+ import type { EDRDetectionResult, EDRDetails } from '../types/index';
10
+
11
+ const execAsync = promisify(exec);
12
+
13
+ /**
14
+ * Check if a specific process is running
15
+ */
16
+ async function checkProcessRunning(processName: string, platform: string): Promise<boolean> {
17
+ try {
18
+ if (platform === 'darwin' || platform === 'linux') {
19
+ // Use pgrep for Unix-like systems
20
+ await execAsync(`pgrep -x "${processName}"`, { timeout: 5000 });
21
+ return true;
22
+ } else if (platform === 'win32') {
23
+ // Use tasklist for Windows
24
+ await execAsync(`tasklist /FI "IMAGENAME eq ${processName}.exe" /NH`, { timeout: 5000 });
25
+ return true;
26
+ }
27
+ return false;
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Detect EDR/AV software running on the system
35
+ * Uses multiple detection methods for robustness
36
+ */
37
+ export async function detectEDR(): Promise<EDRDetectionResult> {
38
+ const platform = os.platform();
39
+ const processes = EDR_PROCESSES[platform] || [];
40
+ const found: string[] = [];
41
+ const details: EDRDetails[] = [];
42
+
43
+ for (const proc of processes) {
44
+ try {
45
+ const isRunning = await checkProcessRunning(proc, platform);
46
+ if (isRunning && !found.includes(proc)) {
47
+ found.push(proc);
48
+ const info = EDR_INFO[proc] || { product: proc, knownSlowCoW: false };
49
+ details.push({
50
+ process: proc,
51
+ product: info.product,
52
+ knownSlowCoW: info.knownSlowCoW,
53
+ });
54
+ }
55
+ } catch {
56
+ // Ignore errors for individual process checks
57
+ }
58
+ }
59
+
60
+ return {
61
+ detected: found.length > 0,
62
+ found,
63
+ details,
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Check if any known CoW-slowing EDR is present
69
+ */
70
+ export function hasSlowCoWEDR(result: EDRDetectionResult): boolean {
71
+ return result.details.some((d) => d.knownSlowCoW);
72
+ }
73
+
74
+ /**
75
+ * Get human-readable EDR summary for UI display
76
+ */
77
+ export function getEDRSummary(result: EDRDetectionResult): string {
78
+ if (!result.detected) {
79
+ return 'No EDR/AV software detected';
80
+ }
81
+
82
+ const slowEdrs = result.details.filter((d) => d.knownSlowCoW);
83
+ const otherEdrs = result.details.filter((d) => !d.knownSlowCoW);
84
+
85
+ let summary = '';
86
+
87
+ if (slowEdrs.length > 0) {
88
+ summary += `CoW-impacting EDR detected: ${slowEdrs.map((d) => d.product).join(', ')}`;
89
+ }
90
+
91
+ if (otherEdrs.length > 0) {
92
+ if (summary) summary += '; ';
93
+ summary += `Other security software: ${otherEdrs.map((d) => d.product).join(', ')}`;
94
+ }
95
+
96
+ return summary;
97
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * EDR (Endpoint Detection and Response) module
3
+ */
4
+
5
+ export { detectEDR, hasSlowCoWEDR, getEDRSummary } from './detection.js';
6
+ export { detectCrowdStrike } from './crowdstrike.js';
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Environment variable and file copying utilities
3
+ */
4
+
5
+ import { promises as fs } from 'fs';
6
+ import * as path from 'path';
7
+
8
+ /**
9
+ * Copy a single file if it exists
10
+ */
11
+ async function copySingleFile(source: string, variation: string, filename: string): Promise<void> {
12
+ const srcPath = path.join(source, filename);
13
+ const destPath = path.join(variation, filename);
14
+
15
+ try {
16
+ await fs.access(srcPath);
17
+ await fs.mkdir(path.dirname(destPath), { recursive: true });
18
+ await fs.copyFile(srcPath, destPath);
19
+ } catch (err) {
20
+ if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
21
+ throw err;
22
+ }
23
+ // File doesn't exist in source, skip
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Copy files matching a glob pattern
29
+ */
30
+ async function copyGlobPattern(source: string, variation: string, pattern: string): Promise<void> {
31
+ // Simple glob implementation - split pattern and match
32
+ const [baseName, ...extParts] = pattern.split('*');
33
+ const ext = extParts.join('*');
34
+
35
+ try {
36
+ const entries = await fs.readdir(source, { withFileTypes: true });
37
+
38
+ for (const entry of entries) {
39
+ if (entry.isFile()) {
40
+ const name = entry.name;
41
+
42
+ // Check if file matches pattern
43
+ if (name.startsWith(baseName) && name.endsWith(ext)) {
44
+ // Additional check: ensure middle part exists for patterns like .env.*
45
+ if (pattern.includes('.*') && !name.slice(baseName.length, -ext.length || undefined)) {
46
+ continue;
47
+ }
48
+
49
+ await copySingleFile(source, variation, name);
50
+ }
51
+ }
52
+ }
53
+ } catch (err) {
54
+ // Directory doesn't exist or can't be read, skip
55
+ if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
56
+ throw err;
57
+ }
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Copy environment files from source to variation
63
+ * Supports glob patterns like .env.*
64
+ */
65
+ export async function copyEnvFiles(
66
+ source: string,
67
+ variation: string,
68
+ patterns: string[]
69
+ ): Promise<void> {
70
+ for (const pattern of patterns) {
71
+ if (pattern.includes('*')) {
72
+ // Handle glob patterns
73
+ await copyGlobPattern(source, variation, pattern);
74
+ } else {
75
+ // Handle exact file paths
76
+ await copySingleFile(source, variation, pattern);
77
+ }
78
+ }
79
+ }