seabox 0.1.0-beta.2 → 0.1.0-beta.4

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.
@@ -0,0 +1,210 @@
1
+ /**
2
+ * native-scanner.mjs
3
+ * Deep scanning of node_modules for native modules and automatic detection.
4
+ */
5
+
6
+ import fs from 'fs/promises';
7
+ import fsSync from 'fs';
8
+ import path from 'path';
9
+
10
+ /**
11
+ * @typedef {Object} NativeModuleMetadata
12
+ * @property {string} name - Module name
13
+ * @property {string} path - Absolute path to module root
14
+ * @property {string} version - Module version
15
+ * @property {boolean} hasBindingGyp - Whether binding.gyp exists
16
+ * @property {string[]} binaryFiles - Detected .node files
17
+ */
18
+
19
+ /**
20
+ * Scan node_modules directory for native modules
21
+ * @param {string} projectRoot - Project root directory
22
+ * @param {boolean} verbose - Enable verbose logging
23
+ * @returns {Promise<NativeModuleMetadata[]>}
24
+ */
25
+ export async function scanDependenciesForNativeModules(projectRoot, verbose = false) {
26
+ const nativeModules = [];
27
+ const nodeModulesPath = path.join(projectRoot, 'node_modules');
28
+
29
+ if (!fsSync.existsSync(nodeModulesPath)) {
30
+ if (verbose) {
31
+ console.log('[NativeScanner] No node_modules directory found');
32
+ }
33
+ return [];
34
+ }
35
+
36
+ if (verbose) {
37
+ console.log('[NativeScanner] Scanning node_modules for native modules');
38
+ }
39
+
40
+ /**
41
+ * Recursively scan a directory for packages
42
+ */
43
+ async function scanDir(dir, isScoped = false) {
44
+ try {
45
+ const entries = await fs.readdir(dir, { withFileTypes: true });
46
+
47
+ for (const entry of entries) {
48
+ // Skip hidden directories and bin
49
+ if (entry.name.startsWith('.') || entry.name === '.bin') {
50
+ continue;
51
+ }
52
+
53
+ const fullPath = path.join(dir, entry.name);
54
+
55
+ if (entry.isDirectory()) {
56
+ const pkgPath = path.join(fullPath, 'package.json');
57
+
58
+ // Check if this is a scoped package directory
59
+ if (entry.name.startsWith('@')) {
60
+ await scanDir(fullPath, true);
61
+ continue;
62
+ }
63
+
64
+ // Check if package.json exists
65
+ if (fsSync.existsSync(pkgPath)) {
66
+ const moduleInfo = await analyzePackage(fullPath, pkgPath);
67
+ if (moduleInfo) {
68
+ nativeModules.push(moduleInfo);
69
+ }
70
+ }
71
+ }
72
+ }
73
+ } catch (err) {
74
+ if (verbose) {
75
+ console.warn('[NativeScanner] Error scanning directory:', dir, err.message);
76
+ }
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Analyze a package to determine if it's a native module
82
+ */
83
+ async function analyzePackage(modulePath, pkgPath) {
84
+ try {
85
+ const pkgContent = await fs.readFile(pkgPath, 'utf8');
86
+ const pkg = JSON.parse(pkgContent);
87
+
88
+ // Check for native module indicators
89
+ const hasBindingGyp = fsSync.existsSync(path.join(modulePath, 'binding.gyp'));
90
+ const hasGypfile = pkg.gypfile === true;
91
+ const hasBinaryField = pkg.binary != null;
92
+
93
+ if (!hasBindingGyp && !hasGypfile && !hasBinaryField) {
94
+ return null;
95
+ }
96
+
97
+ // Find .node files
98
+ const binaryFiles = await findNodeFiles(modulePath);
99
+
100
+ if (hasBindingGyp || hasGypfile || binaryFiles.length > 0) {
101
+ return {
102
+ name: pkg.name,
103
+ path: modulePath,
104
+ version: pkg.version,
105
+ hasBindingGyp: hasBindingGyp,
106
+ binaryFiles: binaryFiles
107
+ };
108
+ }
109
+
110
+ return null;
111
+ } catch (err) {
112
+ // Ignore packages with invalid package.json
113
+ return null;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Find all .node files in a directory tree
119
+ */
120
+ async function findNodeFiles(dir, maxDepth = 5, currentDepth = 0) {
121
+ const nodeFiles = [];
122
+
123
+ if (currentDepth > maxDepth) {
124
+ return nodeFiles;
125
+ }
126
+
127
+ try {
128
+ const entries = await fs.readdir(dir, { withFileTypes: true });
129
+
130
+ for (const entry of entries) {
131
+ const fullPath = path.join(dir, entry.name);
132
+
133
+ if (entry.isDirectory()) {
134
+ // Skip node_modules subdirectories
135
+ if (entry.name === 'node_modules') {
136
+ continue;
137
+ }
138
+
139
+ // Recurse into subdirectories
140
+ const subFiles = await findNodeFiles(fullPath, maxDepth, currentDepth + 1);
141
+ nodeFiles.push(...subFiles);
142
+ } else if (entry.name.endsWith('.node')) {
143
+ nodeFiles.push(fullPath);
144
+ }
145
+ }
146
+ } catch (err) {
147
+ // Ignore read errors
148
+ }
149
+
150
+ return nodeFiles;
151
+ }
152
+
153
+ await scanDir(nodeModulesPath);
154
+
155
+ if (verbose) {
156
+ console.log(`[NativeScanner] Found ${nativeModules.length} native modules`);
157
+ for (const mod of nativeModules) {
158
+ console.log(` - ${mod.name}@${mod.version} (${mod.binaryFiles.length} binaries)`);
159
+ }
160
+ }
161
+
162
+ return nativeModules;
163
+ }
164
+
165
+ /**
166
+ * Find the build output path for a native module
167
+ * @param {string} moduleRoot - Root directory of the native module
168
+ * @param {string} target - Build target (e.g., node24.11.0-win32-x64)
169
+ * @returns {Promise<string|null>}
170
+ */
171
+ export async function findNativeModuleBuildPath(moduleRoot, target) {
172
+ const { platform, arch } = parseTarget(target);
173
+
174
+ // Common build output locations
175
+ const searchPaths = [
176
+ path.join(moduleRoot, 'build/Release'),
177
+ path.join(moduleRoot, 'build/Debug'),
178
+ path.join(moduleRoot, 'lib/binding', `${platform}-${arch}`),
179
+ path.join(moduleRoot, 'prebuilds', `${platform}-${arch}`)
180
+ ];
181
+
182
+ for (const searchPath of searchPaths) {
183
+ if (fsSync.existsSync(searchPath)) {
184
+ // Look for .node files
185
+ const files = await fs.readdir(searchPath);
186
+ const nodeFile = files.find(f => f.endsWith('.node'));
187
+
188
+ if (nodeFile) {
189
+ return path.join(searchPath, nodeFile);
190
+ }
191
+ }
192
+ }
193
+
194
+ return null;
195
+ }
196
+
197
+ /**
198
+ * Parse a target string into components
199
+ */
200
+ function parseTarget(target) {
201
+ const match = target.match(/^node(\d+\.\d+\.\d+)-(\w+)-(\w+)$/);
202
+ if (!match) {
203
+ throw new Error(`Cannot parse target: ${target}`);
204
+ }
205
+ return {
206
+ nodeVersion: match[1],
207
+ platform: match[2],
208
+ arch: match[3]
209
+ };
210
+ }
@@ -1,7 +1,11 @@
1
1
  /**
2
- * @file Obfuscate bootstrap code to protect encryption keys and decryption logic
2
+ * obfuscate.mjs
3
+ * Obfuscate bootstrap code to protect encryption keys and decryption logic
3
4
  */
4
5
 
6
+ import Module from 'module';
7
+
8
+ const require = Module.createRequire(import.meta.url);
5
9
  const JavaScriptObfuscator = require('javascript-obfuscator');
6
10
 
7
11
  /**
@@ -10,11 +14,9 @@ const JavaScriptObfuscator = require('javascript-obfuscator');
10
14
  * @param {string} bootstrapCode - The bootstrap JavaScript code to obfuscate
11
15
  * @returns {string} Obfuscated JavaScript code
12
16
  */
13
- function obfuscateBootstrap(bootstrapCode) {
17
+ export function obfuscateBootstrap(bootstrapCode) {
14
18
  const obfuscationResult = JavaScriptObfuscator.obfuscate(bootstrapCode, {
15
19
  // Maximum protection settings for encryption key and decryption logic
16
-
17
- // String encoding
18
20
  stringArray: true,
19
21
  stringArrayThreshold: 1,
20
22
  stringArrayEncoding: ['rc4'],
@@ -25,49 +27,25 @@ function obfuscateBootstrap(bootstrapCode) {
25
27
  stringArrayWrappersChainedCalls: true,
26
28
  stringArrayWrappersParametersMaxCount: 5,
27
29
  stringArrayWrappersType: 'function',
28
-
29
- // Control flow
30
30
  controlFlowFlattening: true,
31
31
  controlFlowFlatteningThreshold: 1,
32
32
  deadCodeInjection: true,
33
33
  deadCodeInjectionThreshold: 0.4,
34
-
35
- // Code transformations
36
34
  transformObjectKeys: true,
37
35
  splitStrings: true,
38
36
  splitStringsChunkLength: 10,
39
-
40
- // Identifiers
41
37
  identifierNamesGenerator: 'hexadecimal',
42
38
  identifiersPrefix: '',
43
- renameGlobals: false, // Keep false - we need to preserve global scope
44
- renameProperties: false, // Keep false - breaks sea.getAsset patching
45
-
46
- // Self-defending
39
+ renameGlobals: false,
40
+ renameProperties: false,
47
41
  selfDefending: true,
48
-
49
- // Compact output
50
42
  compact: true,
51
-
52
- // Additional obfuscation
53
43
  numbersToExpressions: true,
54
44
  simplify: true,
55
-
56
- // Disable source maps (we don't want them)
57
- sourceMap: false,
58
-
59
- // Performance vs protection tradeoff
60
- // (these settings prioritize protection over performance)
61
45
  target: 'node',
62
46
  ignoreImports: true,
63
-
64
- // Comments removal
65
- // (handled automatically by compact: true)
47
+ sourceMap: false
66
48
  });
67
49
 
68
50
  return obfuscationResult.getObfuscatedCode();
69
51
  }
70
-
71
- module.exports = {
72
- obfuscateBootstrap
73
- };
@@ -0,0 +1,111 @@
1
+ /**
2
+ * require-shim.js
3
+ * SEA-aware require replacement that intercepts .node module loads
4
+ */
5
+
6
+ /**
7
+ * Generate the __requireSeabox shim code
8
+ * @returns {string} - The shim code to inject
9
+ */
10
+ export function generateRequireShim() {
11
+ return `
12
+ // SEA-aware require replacement
13
+ const __originalRequire = require;
14
+ function __requireSeabox(id) {
15
+ // Check if this is a native module request (either .node extension or asset key)
16
+ if (typeof id === 'string' && (id.endsWith('.node') || id.startsWith('native/'))) {
17
+ // Check if we're in SEA mode
18
+ let isSEA = false;
19
+ try {
20
+ const sea = __originalRequire('node:sea');
21
+ isSEA = sea.isSea();
22
+ } catch (e) {
23
+ // Not in SEA mode
24
+ }
25
+
26
+ if (isSEA && global.__seaNativeModuleMap) {
27
+ const path = __originalRequire('path');
28
+
29
+ // Try multiple resolution strategies
30
+ const basename = path.basename(id);
31
+ const nameWithoutExt = basename.replace(/\\.node$/, '');
32
+
33
+ // 1. Try the ID as-is (asset key)
34
+ let resolvedPath = global.__seaNativeModuleMap[id];
35
+
36
+ // 2. Try basename
37
+ if (!resolvedPath) {
38
+ resolvedPath = global.__seaNativeModuleMap[basename];
39
+ }
40
+
41
+ // 3. Try name without extension
42
+ if (!resolvedPath) {
43
+ resolvedPath = global.__seaNativeModuleMap[nameWithoutExt];
44
+ }
45
+
46
+ // 4. Try searching for matching keys
47
+ if (!resolvedPath) {
48
+ for (const [key, value] of Object.entries(global.__seaNativeModuleMap)) {
49
+ if (key.endsWith(basename) || key.endsWith(nameWithoutExt)) {
50
+ resolvedPath = value;
51
+ break;
52
+ }
53
+ }
54
+ }
55
+
56
+ if (resolvedPath) {
57
+ const module = { exports: {} };
58
+ process.dlopen(module, resolvedPath);
59
+ return module.exports;
60
+ } else {
61
+ console.error('[__requireSeabox] ✗ Native module not found in map');
62
+ console.error('[__requireSeabox] Requested:', id);
63
+ console.error('[__requireSeabox] Available:', Object.keys(global.__seaNativeModuleMap));
64
+ }
65
+ }
66
+ }
67
+
68
+ // Handle bindings module - return a shim that uses our native module map
69
+ if (id === 'bindings') {
70
+ return function(name) {
71
+ if (!name.endsWith('.node')) {
72
+ name += '.node';
73
+ }
74
+ return __requireSeabox(name);
75
+ };
76
+ }
77
+
78
+ // Fall back to original require
79
+ return __originalRequire(id);
80
+ }
81
+ `;
82
+ }
83
+
84
+ /**
85
+ * Replace all require() calls with __requireSeabox() in source code
86
+ * @param {string} sourceCode - The source code to transform
87
+ * @param {boolean} verbose - Enable verbose logging
88
+ * @returns {Object} - { code: string, count: number }
89
+ */
90
+ export function replaceRequireCalls(sourceCode, verbose = false) {
91
+ if (verbose) console.log('Replacing require() calls with __requireSeabox()');
92
+
93
+ const requirePattern = /\brequire\s*\(/g;
94
+ let replacementCount = 0;
95
+
96
+ const transformedCode = sourceCode.replace(requirePattern, (match) => {
97
+ replacementCount++;
98
+ return '__requireSeabox(';
99
+ });
100
+
101
+ if (verbose) console.log('Replaced ' + replacementCount + ' require() calls');
102
+
103
+ return {
104
+ code: transformedCode,
105
+ count: replacementCount
106
+ };
107
+ }
108
+
109
+
110
+
111
+