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.
- package/.github/FUNDING.yml +4 -0
- package/.prettierrc +7 -0
- package/README.md +240 -0
- package/commitlint.config.cjs +3 -0
- package/extensions/pi-var/__tests__/integration/smoke.test.ts +308 -0
- package/extensions/pi-var/__tests__/unit/command.test.ts +322 -0
- package/extensions/pi-var/__tests__/unit/cow-detection.test.ts +340 -0
- package/extensions/pi-var/__tests__/unit/cow-git-merge.test.ts +404 -0
- package/extensions/pi-var/__tests__/unit/edr.test.ts +119 -0
- package/extensions/pi-var/__tests__/unit/environment.test.ts +369 -0
- package/extensions/pi-var/__tests__/unit/file-redirect.test.ts +295 -0
- package/extensions/pi-var/__tests__/unit/store.test.ts +489 -0
- package/extensions/pi-var/__tests__/unit/tools.test.ts +133 -0
- package/extensions/pi-var/__tests__/unit/variations.test.ts +352 -0
- package/extensions/pi-var/index.ts +111 -0
- package/extensions/pi-var/src/config.ts +30 -0
- package/extensions/pi-var/src/cow/clone.ts +46 -0
- package/extensions/pi-var/src/cow/detection.ts +215 -0
- package/extensions/pi-var/src/cow/index.ts +6 -0
- package/extensions/pi-var/src/edr/constants.ts +199 -0
- package/extensions/pi-var/src/edr/crowdstrike.ts +62 -0
- package/extensions/pi-var/src/edr/detection.ts +97 -0
- package/extensions/pi-var/src/edr/index.ts +6 -0
- package/extensions/pi-var/src/environment/copy-env.ts +79 -0
- package/extensions/pi-var/src/environment/detect.ts +91 -0
- package/extensions/pi-var/src/environment/index.ts +54 -0
- package/extensions/pi-var/src/environment/marker.ts +42 -0
- package/extensions/pi-var/src/environment/symlink.ts +82 -0
- package/extensions/pi-var/src/gitignore/manager.ts +77 -0
- package/extensions/pi-var/src/state/index.ts +7 -0
- package/extensions/pi-var/src/state/keys.ts +14 -0
- package/extensions/pi-var/src/state/store.ts +110 -0
- package/extensions/pi-var/src/state/types.ts +70 -0
- package/extensions/pi-var/src/tools/command/clean.ts +87 -0
- package/extensions/pi-var/src/tools/command/index.ts +95 -0
- package/extensions/pi-var/src/tools/command/status.ts +58 -0
- package/extensions/pi-var/src/tools/command/stop.ts +37 -0
- package/extensions/pi-var/src/tools/file-redirect.ts +126 -0
- package/extensions/pi-var/src/tools/redirect/bash.ts +75 -0
- package/extensions/pi-var/src/tools/redirect/edit.ts +60 -0
- package/extensions/pi-var/src/tools/redirect/index.ts +64 -0
- package/extensions/pi-var/src/tools/redirect/read.ts +61 -0
- package/extensions/pi-var/src/tools/redirect/write.ts +60 -0
- package/extensions/pi-var/src/tools/variation/create.ts +108 -0
- package/extensions/pi-var/src/tools/variation/merge.ts +80 -0
- package/extensions/pi-var/src/types/index.ts +139 -0
- package/extensions/pi-var/src/utils/names.ts +120 -0
- package/extensions/pi-var/src/variation/create-copy.ts +35 -0
- package/extensions/pi-var/src/variation/create-cow.ts +81 -0
- package/extensions/pi-var/src/variation/create-worktree.ts +31 -0
- package/extensions/pi-var/src/variation/create.ts +127 -0
- package/extensions/pi-var/src/variation/index.ts +9 -0
- package/extensions/pi-var/src/variation/merge/copy.ts +151 -0
- package/extensions/pi-var/src/variation/merge/git.ts +176 -0
- package/extensions/pi-var/src/variation/merge/index.ts +36 -0
- package/extensions/pi-var/src/variation/merge/rsync.ts +74 -0
- package/extensions/pi-var/src/variation/merge/strategy.ts +62 -0
- package/extensions/pi-var/src/variation/paths.ts +26 -0
- package/extensions/pi-var/src/variation/remove.ts +79 -0
- package/npm-shrinkwrap.json +6908 -0
- package/package.json +63 -0
- package/skills/variations/SKILL.md +182 -0
- package/tsconfig.json +25 -0
- 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,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,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
|
+
}
|