agent-recon 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/.claude/hooks/send-event-wsl.py +339 -0
- package/.claude/hooks/send-event.py +334 -0
- package/CHANGELOG.md +66 -0
- package/CONTRIBUTING.md +70 -0
- package/EULA.md +223 -0
- package/INSTALL.md +193 -0
- package/LICENSE +287 -0
- package/LICENSE-COMMERCIAL +241 -0
- package/PRIVACY.md +115 -0
- package/README.md +182 -0
- package/SECURITY.md +63 -0
- package/TERMS.md +233 -0
- package/install-service.ps1 +302 -0
- package/installer/cli.js +177 -0
- package/installer/detect.js +355 -0
- package/installer/install.js +195 -0
- package/installer/manifest.js +140 -0
- package/installer/package.json +12 -0
- package/installer/steps/api-keys.js +59 -0
- package/installer/steps/directory.js +41 -0
- package/installer/steps/env-report.js +48 -0
- package/installer/steps/hooks.js +149 -0
- package/installer/steps/service.js +159 -0
- package/installer/steps/tls.js +104 -0
- package/installer/steps/verify.js +117 -0
- package/installer/steps/welcome.js +46 -0
- package/installer/ui.js +133 -0
- package/installer/uninstall.js +233 -0
- package/installer/upgrade.js +289 -0
- package/package.json +58 -0
- package/public/index.html +13953 -0
- package/server/fixtures/allowlist-profiles.json +185 -0
- package/server/package.json +34 -0
- package/server/platform.js +270 -0
- package/server/rules/gitleaks.toml +3214 -0
- package/server/rules/security.yara +579 -0
- package/server/start.js +178 -0
- package/service/agent-recon.service +30 -0
- package/service/com.agent-recon.server.plist +56 -0
- package/setup-linux.sh +259 -0
- package/setup-macos.sh +264 -0
- package/setup-wsl.sh +248 -0
- package/setup.ps1 +171 -0
- package/start-agent-recon.bat +4 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
{
|
|
2
|
+
"standard-dev": {
|
|
3
|
+
"label": "Standard Development",
|
|
4
|
+
"description": "Suppresses common dev workflow false positives: package managers, build subshells, commit heredocs, credential manager patterns",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"scope_cat": "exec",
|
|
8
|
+
"scope_subcat": "priv-escalation",
|
|
9
|
+
"pattern": "sudo (apt|apt-get|brew|dnf|yum|pacman|apk|snap|port) ",
|
|
10
|
+
"pattern_mode": "regex",
|
|
11
|
+
"action": "suppress",
|
|
12
|
+
"priority": 10,
|
|
13
|
+
"reason": "Package manager installs are routine system administration"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"scope_cat": "inject",
|
|
17
|
+
"scope_subcat": "pipe-injection",
|
|
18
|
+
"pattern": "git commit.*\\$(cat",
|
|
19
|
+
"pattern_mode": "regex",
|
|
20
|
+
"action": "suppress",
|
|
21
|
+
"priority": 10,
|
|
22
|
+
"reason": "Claude Code standard commit pattern using heredoc"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"scope_cat": "inject",
|
|
26
|
+
"scope_subcat": "pipe-injection",
|
|
27
|
+
"pattern": "GH_TOKEN=\\$(pass ",
|
|
28
|
+
"pattern_mode": "regex",
|
|
29
|
+
"action": "suppress",
|
|
30
|
+
"priority": 10,
|
|
31
|
+
"reason": "Standard credential manager retrieval via pass"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"scope_cat": "inject",
|
|
35
|
+
"pattern": "\\$(az (keyvault|account|functionapp|monitor|webapp|storage)",
|
|
36
|
+
"pattern_mode": "regex",
|
|
37
|
+
"action": "suppress",
|
|
38
|
+
"priority": 10,
|
|
39
|
+
"reason": "Azure CLI subshell patterns are standard DevOps workflow"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"scope_cat": "inject",
|
|
43
|
+
"scope_subcat": "pipe-injection",
|
|
44
|
+
"pattern": "\\$((npm bin|which|command -v|nvm |rbenv |pyenv )",
|
|
45
|
+
"pattern_mode": "regex",
|
|
46
|
+
"action": "suppress",
|
|
47
|
+
"priority": 20,
|
|
48
|
+
"reason": "Standard tool path resolution subshells"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"scope_cat": "inject",
|
|
52
|
+
"scope_subcat": "pipe-injection",
|
|
53
|
+
"pattern": "\\$((cat |echo |printf |date|basename|dirname|pwd|hostname)",
|
|
54
|
+
"pattern_mode": "regex",
|
|
55
|
+
"action": "suppress",
|
|
56
|
+
"priority": 20,
|
|
57
|
+
"reason": "Benign utility command substitutions"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"scope_cat": "data",
|
|
61
|
+
"scope_subcat": "exfiltration",
|
|
62
|
+
"pattern": "curl.*-[dX].*POST.*(localhost|127\\.0\\.0\\.1|agent-recon)",
|
|
63
|
+
"pattern_mode": "regex",
|
|
64
|
+
"action": "suppress",
|
|
65
|
+
"priority": 15,
|
|
66
|
+
"reason": "Testing own API endpoints is not exfiltration"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"scope_cat": "exec",
|
|
70
|
+
"scope_subcat": "priv-escalation",
|
|
71
|
+
"pattern": "chmod (644|755|700|600|400)\\b",
|
|
72
|
+
"pattern_mode": "regex",
|
|
73
|
+
"action": "suppress",
|
|
74
|
+
"priority": 10,
|
|
75
|
+
"reason": "Standard file permissions are not privilege escalation"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"scope_cat": "inject",
|
|
79
|
+
"scope_subcat": "code-injection",
|
|
80
|
+
"pattern": "node -e.*(console\\.|require\\(|JSON\\.|process\\.)",
|
|
81
|
+
"pattern_mode": "regex",
|
|
82
|
+
"action": "downgrade",
|
|
83
|
+
"target_risk": "medium",
|
|
84
|
+
"priority": 30,
|
|
85
|
+
"reason": "Simple Node.js one-liners for testing module imports and logging"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"scope_cat": "file",
|
|
89
|
+
"pattern": "\\.env\\.(example|test|sample|template|development|local)",
|
|
90
|
+
"pattern_mode": "regex",
|
|
91
|
+
"action": "suppress",
|
|
92
|
+
"priority": 15,
|
|
93
|
+
"reason": "Template and example environment files are not sensitive"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"scope_cat": "net",
|
|
97
|
+
"scope_subcat": "network-call",
|
|
98
|
+
"pattern": "curl",
|
|
99
|
+
"pattern_mode": "contains",
|
|
100
|
+
"action": "downgrade",
|
|
101
|
+
"target_risk": "low",
|
|
102
|
+
"priority": 50,
|
|
103
|
+
"reason": "Curl for dev API testing; exfiltration patterns caught by higher-priority rules"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"scope_cat": "data",
|
|
107
|
+
"scope_subcat": "exfiltration",
|
|
108
|
+
"pattern": "base64 -d",
|
|
109
|
+
"pattern_mode": "contains",
|
|
110
|
+
"action": "downgrade",
|
|
111
|
+
"target_risk": "low",
|
|
112
|
+
"priority": 40,
|
|
113
|
+
"reason": "Base64 decode for debugging (no pipe to shell)"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"scope_cat": "pii",
|
|
117
|
+
"pattern": "\\.(test|spec|mock)\\.|__tests__|__mocks__|fixtures|testdata",
|
|
118
|
+
"pattern_mode": "regex",
|
|
119
|
+
"action": "downgrade",
|
|
120
|
+
"target_risk": "medium",
|
|
121
|
+
"priority": 15,
|
|
122
|
+
"reason": "PII in test/fixture files is typically synthetic test data"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"scope_cat": "pii",
|
|
126
|
+
"pattern": "\\.env\\.(example|test|sample|template)",
|
|
127
|
+
"pattern_mode": "regex",
|
|
128
|
+
"action": "downgrade",
|
|
129
|
+
"target_risk": "medium",
|
|
130
|
+
"priority": 15,
|
|
131
|
+
"reason": "PII in environment template files is placeholder data"
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
},
|
|
135
|
+
"devops": {
|
|
136
|
+
"label": "DevOps / Infrastructure",
|
|
137
|
+
"description": "Extends Standard Development with additional suppressions for SSH, chmod, and infrastructure tooling",
|
|
138
|
+
"rules": [
|
|
139
|
+
{
|
|
140
|
+
"scope_cat": "net",
|
|
141
|
+
"scope_subcat": "ssh-connection",
|
|
142
|
+
"pattern": "ssh",
|
|
143
|
+
"pattern_mode": "contains",
|
|
144
|
+
"action": "downgrade",
|
|
145
|
+
"target_risk": "low",
|
|
146
|
+
"priority": 15,
|
|
147
|
+
"reason": "SSH connections expected in infrastructure work"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"scope_cat": "exec",
|
|
151
|
+
"scope_subcat": "priv-escalation",
|
|
152
|
+
"pattern": "chmod ",
|
|
153
|
+
"pattern_mode": "contains",
|
|
154
|
+
"action": "suppress",
|
|
155
|
+
"priority": 10,
|
|
156
|
+
"reason": "Standard infrastructure permissions management"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"scope_cat": "net",
|
|
160
|
+
"scope_subcat": "network-call",
|
|
161
|
+
"pattern": "rsync|scp",
|
|
162
|
+
"pattern_mode": "regex",
|
|
163
|
+
"action": "downgrade",
|
|
164
|
+
"target_risk": "low",
|
|
165
|
+
"priority": 15,
|
|
166
|
+
"reason": "Standard infrastructure file transfer tools"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"scope_cat": "exec",
|
|
170
|
+
"scope_subcat": "priv-escalation",
|
|
171
|
+
"pattern": "sudo ",
|
|
172
|
+
"pattern_mode": "contains",
|
|
173
|
+
"action": "downgrade",
|
|
174
|
+
"target_risk": "medium",
|
|
175
|
+
"priority": 50,
|
|
176
|
+
"reason": "Sudo expected in infrastructure contexts"
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
},
|
|
180
|
+
"strict": {
|
|
181
|
+
"label": "Strict (No Suppressions)",
|
|
182
|
+
"description": "All events shown at original classification - no suppressions applied",
|
|
183
|
+
"rules": []
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-recon-server",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Real-time agent observability dashboard",
|
|
5
|
+
"main": "server.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node start.js",
|
|
8
|
+
"test": "node --test tests/unit/*.test.js tests/frontend/*.test.js tests/integration/*.test.js",
|
|
9
|
+
"test:unit": "node --test tests/unit/*.test.js",
|
|
10
|
+
"test:fe": "node --test tests/frontend/*.test.js",
|
|
11
|
+
"test:int": "node --test tests/integration/*.test.js",
|
|
12
|
+
"test:coverage": "c8 node --test tests/unit/*.test.js tests/frontend/*.test.js",
|
|
13
|
+
"setup:hooks": "node -e \"const fs=require('fs'),p=require('path'),s=p.join(__dirname,'pre-commit-hook.sh'),d=p.join(__dirname,'../.git/hooks/pre-commit');fs.copyFileSync(s,d);fs.chmodSync(d,0o755);console.log('Pre-commit hook installed.');\""
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=22.0.0"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@litko/yara-x": "^0.5.0",
|
|
20
|
+
"better-sqlite3": "^12.8.0",
|
|
21
|
+
"express": "^5.2.1",
|
|
22
|
+
"ws": "^8.20.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"c8": "^11.0.0",
|
|
26
|
+
"terser": "^5.46.1"
|
|
27
|
+
},
|
|
28
|
+
"optionalDependencies": {
|
|
29
|
+
"@litko/yara-x-darwin-arm64": "0.5.0",
|
|
30
|
+
"@litko/yara-x-darwin-x64": "0.5.0",
|
|
31
|
+
"@litko/yara-x-linux-x64-gnu": "0.5.0",
|
|
32
|
+
"@litko/yara-x-win32-x64-msvc": "^0.5.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
// Copyright 2026 PNW Great Loop LLC. All rights reserved.
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 — see LICENSE for terms.
|
|
3
|
+
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Agent Recon — Cross-platform utility module
|
|
8
|
+
*
|
|
9
|
+
* Centralises all platform detection, binary identification, and
|
|
10
|
+
* OS-specific path logic so that other modules (start.js, credential-store.js,
|
|
11
|
+
* process-monitor.js, server.js) can import a single source of truth.
|
|
12
|
+
*
|
|
13
|
+
* Exported:
|
|
14
|
+
* detectOS, homedir, isNtfs, credentialBackend,
|
|
15
|
+
* detectBinaryType, needsRebuild, backupSuffix
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const os = require('os');
|
|
20
|
+
|
|
21
|
+
// ── OS detection ────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
/** Cached result — OS does not change at runtime. */
|
|
24
|
+
let _cachedOS;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Detect the operating system environment.
|
|
28
|
+
* @returns {'windows'|'macos'|'linux'|'wsl'}
|
|
29
|
+
*/
|
|
30
|
+
function detectOS() {
|
|
31
|
+
if (_cachedOS) return _cachedOS;
|
|
32
|
+
|
|
33
|
+
if (process.platform === 'win32') {
|
|
34
|
+
_cachedOS = 'windows';
|
|
35
|
+
} else if (process.platform === 'darwin') {
|
|
36
|
+
_cachedOS = 'macos';
|
|
37
|
+
} else {
|
|
38
|
+
// Linux — but might be WSL
|
|
39
|
+
try {
|
|
40
|
+
const ver = fs.readFileSync('/proc/version', 'utf8');
|
|
41
|
+
_cachedOS = /microsoft|wsl/i.test(ver) ? 'wsl' : 'linux';
|
|
42
|
+
} catch {
|
|
43
|
+
_cachedOS = 'linux';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return _cachedOS;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Reset cached OS (for tests only).
|
|
51
|
+
* @private
|
|
52
|
+
*/
|
|
53
|
+
function _resetCache() { _cachedOS = undefined; }
|
|
54
|
+
|
|
55
|
+
// ── Home directory ──────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Platform-appropriate home directory.
|
|
59
|
+
* On Windows uses USERPROFILE, everywhere else uses os.homedir().
|
|
60
|
+
* @returns {string}
|
|
61
|
+
*/
|
|
62
|
+
function homedir() {
|
|
63
|
+
if (process.platform === 'win32') {
|
|
64
|
+
return process.env.USERPROFILE || os.homedir();
|
|
65
|
+
}
|
|
66
|
+
return process.env.HOME || os.homedir();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── Config directory ─────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
const path = require('path');
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Platform-appropriate configuration directory for Agent Recon.
|
|
75
|
+
*
|
|
76
|
+
* - macOS: ~/Library/Application Support/agent-recon
|
|
77
|
+
* - Windows: %APPDATA%\agent-recon (falls back to ~/.config/agent-recon)
|
|
78
|
+
* - Linux/WSL: ~/.config/agent-recon
|
|
79
|
+
*
|
|
80
|
+
* @returns {string}
|
|
81
|
+
*/
|
|
82
|
+
function configDir() {
|
|
83
|
+
const osType = detectOS();
|
|
84
|
+
const home = homedir();
|
|
85
|
+
switch (osType) {
|
|
86
|
+
case 'macos':
|
|
87
|
+
return path.join(home, 'Library', 'Application Support', 'agent-recon');
|
|
88
|
+
case 'windows':
|
|
89
|
+
return path.join(process.env.APPDATA || path.join(home, '.config'), 'agent-recon');
|
|
90
|
+
default:
|
|
91
|
+
return path.join(home, '.config', 'agent-recon');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── NTFS / DrvFs detection ──────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check whether a file path resides on an NTFS/DrvFs mount (WSL2).
|
|
99
|
+
* Reads /proc/mounts and looks for drvfs or 9p filesystem types covering
|
|
100
|
+
* the given path. Always returns false on non-Linux platforms.
|
|
101
|
+
*
|
|
102
|
+
* @param {string} filePath — absolute path to check
|
|
103
|
+
* @returns {boolean}
|
|
104
|
+
*/
|
|
105
|
+
function isNtfs(filePath) {
|
|
106
|
+
if (process.platform === 'win32' || process.platform === 'darwin') return false;
|
|
107
|
+
try {
|
|
108
|
+
const mounts = fs.readFileSync('/proc/mounts', 'utf8');
|
|
109
|
+
return _isNtfsFromMounts(mounts, filePath);
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Pure-logic NTFS detection from mount content — testable without /proc.
|
|
117
|
+
* @param {string} mountsContent — raw contents of /proc/mounts
|
|
118
|
+
* @param {string} filePath — absolute path to check
|
|
119
|
+
* @returns {boolean}
|
|
120
|
+
*/
|
|
121
|
+
function _isNtfsFromMounts(mountsContent, filePath) {
|
|
122
|
+
for (const line of mountsContent.split('\n')) {
|
|
123
|
+
const parts = line.split(' ');
|
|
124
|
+
if (parts[2] === 'drvfs' || parts[2] === '9p') {
|
|
125
|
+
const mountPoint = parts[1];
|
|
126
|
+
if (mountPoint && filePath.startsWith(mountPoint)) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── Credential backend selection ────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Determine which credential storage backend to use.
|
|
138
|
+
*
|
|
139
|
+
* Priority: env var override → platform-native store → fallback.
|
|
140
|
+
* macOS and libsecret backends are stubs until Group B implements them.
|
|
141
|
+
*
|
|
142
|
+
* @returns {'env'|'dpapi'|'keychain'|'libsecret'|'pbkdf2'}
|
|
143
|
+
*/
|
|
144
|
+
function credentialBackend() {
|
|
145
|
+
if (process.env.AGENT_RECON_MASTER_KEY) return 'env';
|
|
146
|
+
const osType = detectOS();
|
|
147
|
+
switch (osType) {
|
|
148
|
+
case 'windows': return 'dpapi';
|
|
149
|
+
case 'wsl': return 'dpapi'; // WSL uses DPAPI via PowerShell interop
|
|
150
|
+
case 'macos': return 'keychain'; // Stub — implemented in Group B (Phase 7.2)
|
|
151
|
+
case 'linux': return 'libsecret'; // Stub — implemented in Group B (Phase 7.3)
|
|
152
|
+
default: return 'pbkdf2';
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ── Binary type detection ───────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Identify a native binary's platform from its magic bytes.
|
|
160
|
+
*
|
|
161
|
+
* Supported formats:
|
|
162
|
+
* PE (Windows) — 0x4D 0x5A ("MZ")
|
|
163
|
+
* ELF (Linux) — 0x7F 0x45 0x4C 0x46
|
|
164
|
+
* Mach-O x64 (macOS) — 0xCF 0xFA 0xED 0xFE
|
|
165
|
+
* Mach-O arm64 — 0xFE 0xED 0xFA 0xCE (actually — see note below)
|
|
166
|
+
*
|
|
167
|
+
* Note: Mach-O uses two magic numbers depending on byte order:
|
|
168
|
+
* Little-endian (x86-64): 0xCF FA ED FE (MH_MAGIC_64 reversed)
|
|
169
|
+
* Little-endian (arm64): same 0xCF FA ED FE (arm64 macOS is also LE)
|
|
170
|
+
* Big-endian: 0xFE ED FA CE (MH_MAGIC)
|
|
171
|
+
* Universal binaries (fat): 0xCA FE BA BE
|
|
172
|
+
*
|
|
173
|
+
* Since both x64 and arm64 macOS use little-endian Mach-O 64-bit,
|
|
174
|
+
* we distinguish them by the cputype field at offset 4:
|
|
175
|
+
* x86-64: cputype = 0x01000007 → bytes[4] = 0x07
|
|
176
|
+
* arm64: cputype = 0x0100000C → bytes[4] = 0x0C
|
|
177
|
+
*
|
|
178
|
+
* @param {Buffer} headerBytes — at least 5 bytes from start of file
|
|
179
|
+
* @returns {'pe'|'elf'|'macho-x64'|'macho-arm64'|'macho-universal'|'unknown'}
|
|
180
|
+
*/
|
|
181
|
+
function detectBinaryType(headerBytes) {
|
|
182
|
+
if (!headerBytes || headerBytes.length < 4) return 'unknown';
|
|
183
|
+
|
|
184
|
+
// PE (Windows) — "MZ"
|
|
185
|
+
if (headerBytes[0] === 0x4D && headerBytes[1] === 0x5A) return 'pe';
|
|
186
|
+
|
|
187
|
+
// ELF (Linux) — 0x7F "ELF"
|
|
188
|
+
if (headerBytes[0] === 0x7F && headerBytes[1] === 0x45 &&
|
|
189
|
+
headerBytes[2] === 0x4C && headerBytes[3] === 0x46) return 'elf';
|
|
190
|
+
|
|
191
|
+
// Mach-O 64-bit little-endian — 0xCF FA ED FE
|
|
192
|
+
// Both x64 and arm64 macOS use little-endian Mach-O 64-bit, so they share
|
|
193
|
+
// the same 4-byte magic. We must inspect the cputype field at byte offset 4
|
|
194
|
+
// to tell them apart:
|
|
195
|
+
// 0x0C = CPU_TYPE_ARM64 (Apple Silicon)
|
|
196
|
+
// 0x07 = CPU_TYPE_X86_64 (Intel Mac)
|
|
197
|
+
if (headerBytes[0] === 0xCF && headerBytes[1] === 0xFA &&
|
|
198
|
+
headerBytes[2] === 0xED && headerBytes[3] === 0xFE) {
|
|
199
|
+
if (headerBytes.length >= 5) {
|
|
200
|
+
if (headerBytes[4] === 0x0C) return 'macho-arm64';
|
|
201
|
+
if (headerBytes[4] === 0x07) return 'macho-x64';
|
|
202
|
+
}
|
|
203
|
+
return 'macho-x64'; // default if cputype byte unavailable
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Mach-O 32-bit big-endian — 0xFE ED FA CE (rare, but handle it)
|
|
207
|
+
if (headerBytes[0] === 0xFE && headerBytes[1] === 0xED &&
|
|
208
|
+
headerBytes[2] === 0xFA && headerBytes[3] === 0xCE) return 'macho-x64';
|
|
209
|
+
|
|
210
|
+
// Universal/fat binary — 0xCA FE BA BE
|
|
211
|
+
if (headerBytes[0] === 0xCA && headerBytes[1] === 0xFE &&
|
|
212
|
+
headerBytes[2] === 0xBA && headerBytes[3] === 0xBE) return 'macho-universal';
|
|
213
|
+
|
|
214
|
+
return 'unknown';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Determine whether the current platform needs a native binary rebuild.
|
|
219
|
+
*
|
|
220
|
+
* @param {string} binaryType — return value of detectBinaryType()
|
|
221
|
+
* @returns {boolean}
|
|
222
|
+
*/
|
|
223
|
+
function needsRebuild(binaryType) {
|
|
224
|
+
if (binaryType === 'unknown') return false;
|
|
225
|
+
const osType = detectOS();
|
|
226
|
+
switch (osType) {
|
|
227
|
+
case 'windows':
|
|
228
|
+
return binaryType !== 'pe';
|
|
229
|
+
case 'macos':
|
|
230
|
+
// Any Mach-O variant is acceptable on macOS (x64, arm64, universal)
|
|
231
|
+
return !binaryType.startsWith('macho');
|
|
232
|
+
case 'linux':
|
|
233
|
+
case 'wsl':
|
|
234
|
+
return binaryType !== 'elf';
|
|
235
|
+
default:
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Get the backup file suffix for a mismatched binary.
|
|
242
|
+
*
|
|
243
|
+
* @param {string} binaryType — return value of detectBinaryType()
|
|
244
|
+
* @returns {string} e.g. '.win32', '.linux', '.macho-arm64'
|
|
245
|
+
*/
|
|
246
|
+
function backupSuffix(binaryType) {
|
|
247
|
+
switch (binaryType) {
|
|
248
|
+
case 'pe': return '.win32';
|
|
249
|
+
case 'elf': return '.linux';
|
|
250
|
+
case 'macho-x64': return '.macos-x64';
|
|
251
|
+
case 'macho-arm64': return '.macos-arm64';
|
|
252
|
+
case 'macho-universal': return '.macos-universal';
|
|
253
|
+
default: return '.unknown';
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ── Exports ─────────────────────────────────────────────────────────────────
|
|
258
|
+
|
|
259
|
+
module.exports = {
|
|
260
|
+
detectOS,
|
|
261
|
+
homedir,
|
|
262
|
+
configDir,
|
|
263
|
+
isNtfs,
|
|
264
|
+
_isNtfsFromMounts,
|
|
265
|
+
credentialBackend,
|
|
266
|
+
detectBinaryType,
|
|
267
|
+
needsRebuild,
|
|
268
|
+
backupSuffix,
|
|
269
|
+
_resetCache,
|
|
270
|
+
};
|