seabox 0.1.0-beta.1 → 0.1.0-beta.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/README.md CHANGED
@@ -13,6 +13,11 @@ A reusable tool for building Node.js Single Executable Applications (SEA) with n
13
13
  - Automatic code signature removal before injection
14
14
  - Simple configuration via package.json
15
15
 
16
+ ## Use case
17
+ This tooling was created as an alternative to pkg, which is unfortunatly deprecated, and where forks were running foul of virus checkers. By using node's SEA, the executables are directly from nodejs's distribution source, and built using node's native Single Executable Application solution. Unfortunatly this does mean native modules embedded within the exe cannot run directly and must be extracted to a location on the disk on first run - This tooling automates that process for you, while providing arbitrary asset embedding. Embedded assets are _not_ extracted and access to them is handled by intercepting require and fs.
18
+
19
+ Note: **V8 snapshot includes and embedds the original source**, this is currently a limitation of Node's SEA tooling as far as I can tell; thus the snapshot is only useful for faster startup.
20
+
16
21
  ## Installation
17
22
 
18
23
  ```bash
@@ -63,7 +68,8 @@ Add a `sea` configuration to your `package.json`:
63
68
  - **useCodeCache**: Enable V8 code cache (default: false)
64
69
  - **encryptAssets**: Enable encryption for assets (default: false)
65
70
  - **encryptExclude**: Patterns to exclude from encryption (e.g., `['*.txt']`)
66
- - **exclude**: *(Deprecated)* Use `!` prefix in assets array instead
71
+ - **rebuild**: Automatically rebuild native modules for the target platform before building the SEA (default: false)
72
+ - **cacheLocation**: Custom cache directory for extracted binaries (default: `'./.sea-cache'`). Supports environment variable expansion (e.g., `'%LOCALAPPDATA%\\myapp-cache'` on Windows or `'$HOME/.cache/myapp'` on Unix)
67
73
 
68
74
  ## Usage
69
75
 
@@ -94,8 +100,12 @@ npm run build:exe
94
100
  # Build using package.json configuration
95
101
  npx seabox
96
102
 
103
+ # Build using a standalone config file (alternative to package.json)
104
+ npx seabox --config sea-config.json
105
+
97
106
  # Verbose output
98
107
  npx seabox --verbose
108
+
99
109
  ```
100
110
 
101
111
  ### Programmatic API
@@ -126,7 +136,35 @@ Native modules (`.node`, `.dll`, `.so`, `.dylib`) are automatically:
126
136
  - Integrity-checked using SHA-256 hashes
127
137
  - Loaded via custom module resolution
128
138
 
129
- Cache location: `%LOCALAPPDATA%\.sea-cache\<appname>\<version>-<platform>-<arch>`
139
+ ### Cache Location
140
+
141
+ By default, binaries are extracted to: `./.sea-cache/<appname>/<version>-<platform>-<arch>`
142
+
143
+ You can customize the cache location in your configuration:
144
+
145
+ ```json
146
+ {
147
+ "sea": {
148
+ "cacheLocation": "%LOCALAPPDATA%\\myapp-cache"
149
+ }
150
+ }
151
+ ```
152
+
153
+ The cache location supports environment variable expansion:
154
+ - **Windows**: `%LOCALAPPDATA%`, `%APPDATA%`, `%TEMP%`, etc.
155
+ - **Unix/Linux/macOS**: `$HOME`, `$TMPDIR`, `${XDG_CACHE_HOME}`, etc.
156
+
157
+ **Override at runtime**: Set the `SEACACHE` environment variable to override the configured location:
158
+
159
+ ```bash
160
+ # Windows
161
+ set SEACACHE=C:\custom\cache\path
162
+ myapp.exe
163
+
164
+ # Unix/Linux/macOS
165
+ export SEACACHE=/custom/cache/path
166
+ ./myapp
167
+ ```
130
168
 
131
169
  ## Native Module Rebuilding
132
170
 
@@ -169,33 +207,17 @@ seabox supports optional AES-256-GCM encryption of embedded assets to protect yo
169
207
  1. **Build Time**: A random 256-bit encryption key is generated
170
208
  2. **Asset Encryption**: Non-binary assets are encrypted using AES-256-GCM
171
209
  3. **Key Embedding**: The encryption key is obfuscated and embedded in the bootstrap code
172
- 4. **Snapshot Obfuscation**: When `useSnapshot: true`, the key becomes part of V8 bytecode
210
+ 4. **Key Obfuscation**: the bootstrap and key code are obfuscated, but not removed
173
211
  5. **Runtime Decryption**: Assets are transparently decrypted when accessed
174
212
 
175
- ### Security Considerations
213
+ ### Considerations
176
214
 
177
215
  - **Binary files** (`.node`, `.dll`, `.so`, `.dylib`) are **never encrypted** as they must be extracted as-is
178
216
  - The manifest (`sea-manifest.json`) is **not encrypted** to allow bootstrap initialization
217
+ - **V8 snapshot includes the original source**, this is currently a limitation of Node's SEA.
179
218
  - Encryption provides **obfuscation**, not cryptographic security against determined attackers
180
- - When combined with V8 snapshots, the encryption key is compiled into bytecode, making extraction significantly harder
181
- - Use `encryptExclude` to skip encryption for non-sensitive assets (e.g., public files, documentation)
182
-
183
- ### Best Practices
184
-
185
- ```json
186
- {
187
- "sea": {
188
- "encryptAssets": true,
189
- "useSnapshot": true, // Strongly recommended with encryption
190
- "assets": [
191
- "./out/**/*",
192
- "!**/*.md", // Exclude documentation
193
- "!**/public/**", // Exclude public assets
194
- "!**/LICENSE" // Exclude license files
195
- ]
196
- }
197
- }
198
- ```
219
+ - The bootloader code, that includes the encryption key, is obfuscated in the source embedded by Node's SEA
220
+
199
221
 
200
222
  ## Platform Support
201
223
 
package/bin/seabox.js CHANGED
@@ -32,7 +32,7 @@ async function main() {
32
32
  // Show help
33
33
  if (showHelp) {
34
34
  console.log(`
35
- SEA Builder - Node.js Single Executable Application Builder
35
+ SeaBox - Node.js Single Executable Application Builder
36
36
 
37
37
  Usage:
38
38
  seabox [options]
package/lib/bootstrap.js CHANGED
@@ -202,18 +202,56 @@ function getAsset(assetKey, encoding) {
202
202
  }
203
203
 
204
204
 
205
+ /**
206
+ * Resolve environment variables in a path string.
207
+ * Supports %VAR% on Windows and $VAR or ${VAR} on Unix-like systems.
208
+ * @param {string} pathStr - Path string that may contain environment variables
209
+ * @returns {string} - Path with environment variables expanded
210
+ */
211
+ function resolveEnvVars(pathStr) {
212
+ if (!pathStr) return pathStr;
213
+
214
+ // Replace Windows-style %VAR%
215
+ let resolved = pathStr.replace(/%([^%]+)%/g, (match, varName) => {
216
+ return process.env[varName] || match;
217
+ });
218
+
219
+ // Replace Unix-style $VAR and ${VAR}
220
+ resolved = resolved.replace(/\$\{([^}]+)\}/g, (match, varName) => {
221
+ return process.env[varName] || match;
222
+ });
223
+ resolved = resolved.replace(/\$([A-Za-z_][A-Za-z0-9_]*)/g, (match, varName) => {
224
+ return process.env[varName] || match;
225
+ });
226
+
227
+ return resolved;
228
+ }
229
+
205
230
  /**
206
231
  * Get the extraction cache directory for this application.
207
232
  * @param {string} appName
208
233
  * @param {string} appVersion
209
234
  * @param {string} platform
210
235
  * @param {string} arch
236
+ * @param {string} [configuredCacheLocation] - Optional configured cache location from manifest
211
237
  * @returns {string}
212
238
  */
213
- function getExtractionCacheDir(appName, appVersion, platform, arch) {
239
+ function getExtractionCacheDir(appName, appVersion, platform, arch, configuredCacheLocation) {
240
+ // Priority: SEACACHE env var > configured location > default
214
241
  if(process.env.SEACACHE) return process.env.SEACACHE;
215
- const localAppData = process.env.LOCALAPPDATA || process.env.HOME || process.cwd();
216
- return path.join(localAppData, '.sea-cache', appName, `${appVersion}-${platform}-${arch}`);
242
+
243
+ if (configuredCacheLocation) {
244
+ // Resolve environment variables in the configured path
245
+ const resolvedBase = resolveEnvVars(configuredCacheLocation);
246
+ // Make relative paths absolute (relative to cwd)
247
+ const absoluteBase = path.isAbsolute(resolvedBase) ? resolvedBase : path.resolve(process.cwd(), resolvedBase);
248
+ return path.join(absoluteBase, appName, `${appVersion}-${platform}-${arch}`);
249
+ }
250
+
251
+ // Default behavior
252
+ const defaultBase = './.sea-cache';
253
+ const absoluteBase = path.resolve(process.cwd(), defaultBase);
254
+ return path.join(absoluteBase, appName, `${appVersion}-${platform}-${arch}`);
217
255
  }
218
256
 
219
257
  /**
@@ -400,6 +438,9 @@ function bootstrap() {
400
438
  console.log(` App: ${manifest.appName} v${manifest.appVersion}`);
401
439
  console.log(` Platform: ${manifest.platform}-${manifest.arch}`);
402
440
  console.log(` Binaries to extract: ${manifest.binaries.length}`);
441
+ if (manifest.cacheLocation) {
442
+ console.log(` Configured cache location: ${manifest.cacheLocation}`);
443
+ }
403
444
  }
404
445
 
405
446
  // Filter binaries for current platform
@@ -415,7 +456,8 @@ function bootstrap() {
415
456
  manifest.appName,
416
457
  manifest.appVersion,
417
458
  manifest.platform,
418
- manifest.arch
459
+ manifest.arch,
460
+ manifest.cacheLocation
419
461
  );
420
462
 
421
463
  const nativeModuleMap = {};
@@ -0,0 +1,56 @@
1
+ /*MIT License
2
+
3
+ Copyright (c) 2018 Osama Abbas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.*/
22
+
23
+ module.exports.fixBytecode = function (bytecodeBuffer) {
24
+ if (!isBuffer(bytecodeBuffer)) {
25
+ throw new Error('bytecodeBuffer must be a buffer object.');
26
+ }
27
+
28
+ const dummyBytecode = compileCode('"ಠ_ಠ"');
29
+ const version = parseFloat(process.version.slice(1, 5));
30
+
31
+ if (process.version.startsWith('v8.8') || process.version.startsWith('v8.9')) {
32
+ // Node is v8.8.x or v8.9.x
33
+ dummyBytecode.subarray(16, 20).copy(bytecodeBuffer, 16);
34
+ dummyBytecode.subarray(20, 24).copy(bytecodeBuffer, 20);
35
+ } else if (version >= 12 && version <= 23) {
36
+ dummyBytecode.subarray(12, 16).copy(bytecodeBuffer, 12);
37
+ } else {
38
+ dummyBytecode.subarray(12, 16).copy(bytecodeBuffer, 12);
39
+ dummyBytecode.subarray(16, 20).copy(bytecodeBuffer, 16);
40
+ }
41
+ };
42
+
43
+ module.exports.readSourceHash = function (bytecodeBuffer) {
44
+ if (!isBuffer(bytecodeBuffer)) {
45
+ throw new Error('bytecodeBuffer must be a buffer object.');
46
+ }
47
+
48
+ if (process.version.startsWith('v8.8') || process.version.startsWith('v8.9')) {
49
+ // Node is v8.8.x or v8.9.x
50
+ // eslint-disable-next-line no-return-assign
51
+ return bytecodeBuffer.subarray(12, 16).reduce((sum, number, power) => sum += number * Math.pow(256, power), 0);
52
+ } else {
53
+ // eslint-disable-next-line no-return-assign
54
+ return bytecodeBuffer.subarray(8, 12).reduce((sum, number, power) => sum += number * Math.pow(256, power), 0);
55
+ }
56
+ };
package/lib/config.js CHANGED
@@ -22,6 +22,7 @@ const path = require('path');
22
22
  * @property {string[]} [encryptExclude] - Asset patterns to exclude from encryption
23
23
  * @property {boolean} [rebuild] - Automatically rebuild native modules for target platform (default: false)
24
24
  * @property {boolean} [verbose] - Enable diagnostic logging
25
+ * @property {string} [cacheLocation] - Cache directory for extracted binaries (default: './.sea-cache', supports env vars like '%LOCALAPPDATA%\\path')
25
26
  */
26
27
 
27
28
  /**
package/lib/manifest.js CHANGED
@@ -23,6 +23,7 @@ const path = require('path');
23
23
  * @property {string} arch - Target architecture
24
24
  * @property {BinaryManifestEntry[]} binaries - Binary extraction rules
25
25
  * @property {string[]} allAssetKeys - All embedded asset keys
26
+ * @property {string} [cacheLocation] - Configured cache location (optional)
26
27
  */
27
28
 
28
29
  /**
@@ -48,7 +49,7 @@ function generateManifest(assets, config, targetPlatform, targetArch) {
48
49
  };
49
50
  });
50
51
 
51
- return {
52
+ const manifest = {
52
53
  appName: config._packageName || 'app',
53
54
  appVersion: config._packageVersion || '1.0.0',
54
55
  platform: targetPlatform,
@@ -56,6 +57,13 @@ function generateManifest(assets, config, targetPlatform, targetArch) {
56
57
  binaries,
57
58
  allAssetKeys: assets.map(a => a.assetKey)
58
59
  };
60
+
61
+ // Include cacheLocation if configured
62
+ if (config.cacheLocation) {
63
+ manifest.cacheLocation = config.cacheLocation;
64
+ }
65
+
66
+ return manifest;
59
67
  }
60
68
 
61
69
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seabox",
3
- "version": "0.1.0-beta.1",
3
+ "version": "0.1.0-beta.2",
4
4
  "description": "Node.js Single Executable Application (SEA) builder tool with native and library extraction",
5
5
  "main": "lib/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -28,6 +28,11 @@
28
28
  ],
29
29
  "author": "Meirion Hughes",
30
30
  "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/MeirionHughes/seabox"
34
+ },
35
+ "homepage": "https://github.com/MeirionHughes/seabox",
31
36
  "dependencies": {
32
37
  "adm-zip": "^0.5.16",
33
38
  "glob": "^10.0.0",