Gathering detailed insights and metrics for asarmor
Gathering detailed insights and metrics for asarmor
Gathering detailed insights and metrics for asarmor
Gathering detailed insights and metrics for asarmor
npm install asarmor
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
242 Stars
232 Commits
25 Forks
6 Watching
4 Branches
7 Contributors
Updated on 27 Nov 2024
TypeScript (50.21%)
C (25.61%)
JavaScript (13.55%)
C++ (7.58%)
Python (1.48%)
CSS (1.05%)
EJS (0.23%)
HTML (0.23%)
Shell (0.05%)
Cumulative downloads
Total Downloads
Last day
170%
81
Compared to previous day
Last week
-11.4%
366
Compared to previous week
Last month
-10.2%
1,697
Compared to previous month
Last year
-2.2%
39,686
Compared to previous year
7
Protects asar files from extraction (with asar extract
).
The strategies provided by asarmor are not bulletproof, but can be useful as a first level of protection.
Maintenance of this project is made possible by all the lovely contributors and sponsors. If you'd like to sponsor this project and have your avatar or company logo appear in this section, click here. 💖
Asarmor can apply patches to your asar file to make it challenging to extract. The patches are applied in a way that doesn't affect the functionality of your application.
Unfortunately, at the time of writing, most of the 'old school' patches have been patched (pun not intended) by the asar
project itself. For that reason I strongly recommend enabling encryption as well.
Asarmor can encrypt all JavaScript files in your asar archive. No Electron recompilation required! Huge thanks to toyobayashi's wonderful electron-asar-encrypt-demo for making this possible. If you're interested in the details I highly recommend you check out the electron-asar-encrypt-demo repository.
You can use asarmor as a CLI tool or as a library in your project. The CLI tool is useful for quick and easy protection of your asar files or for trying out asarmor. The library is useful for more advanced use cases, such as integrating asarmor into your Electron project.
Installation:
npm install -g asarmor
Usage:
Usage: asarmor [options]
Options:
-V, --version output the version number
-a, --archive <archive> input asar file (required)
-o, --output <output> output asar file (required)
-b, --backup create backup
-r, --restore restore backup
-bl, --bloat [gigabytes] fill the drive with useless data on extraction attempt
-e, --encryption encrypt the JavaScript files stored in the archive
-h, --help display help for command
Examples:
$ asarmor -a app.asar -o asarmor.asar --backup --bloat 1000
$ asarmor -a plaintext.asar -o encrypted.asar --encryption
Installation:
npm install --save-dev asarmor
Usage:
1const asarmor = require('asarmor'); 2 3(async () => { 4 // Encrypt the JavaScript file contents stored within the asar file. 5 await asarmor.encrypt({ 6 src: './app.asar', // target asar file to encrypt 7 dst: './encrypted.asar', // output asar file 8 }); 9 10 // Read & parse the (optionally encrypted) asar file. 11 // This can take a while depending on the size of your file. 12 const archive = await asarmor.open('encrypted.asar'); 13 14 // Create a backup, which can be restored at any point in time through CLI or code. 15 await archive.createBackup({backupPath: '~/Documents/backups/encrypted.asar.backup'}); 16 17 // Apply customized bloat patch. 18 // The bloat patch by itself will write randomness to disk on extraction attempt. 19 archive.patch(asarmor.createBloatPatch(50)); // adds 50 GB of bloat in total 20 21 // Write changes back to disk. 22 const outputPath = await archive.write('app.asar'); 23 console.log('successfully wrote changes to ' + outputPath); 24})();
You can easily include asarmor in your packaging process using an afterPack hook:
1const asarmor = require('asarmor'); 2const { join } = require("path"); 3 4exports.default = async ({ appOutDir, packager }) => { 5 try { 6 const asarPath = join(packager.getResourcesDir(appOutDir), 'app.asar'); 7 console.log(`asarmor is applying patches to ${asarPath}`); 8 const archive = await asarmor.open(asarPath); 9 archive.patch(); // apply default patches 10 await archive.write(asarPath); 11 } catch (err) { 12 console.error(err); 13 } 14};
There's a few more steps involved to get encryption working. See example/electron-builder if you'd like to skip ahead to the code.
Steps:
1exports.default = async ({ appOutDir, packager }) => { 2 try { 3 const asarPath = join(packager.getResourcesDir(appOutDir), 'app.asar'); 4 5 console.log( 6 ` \x1B[34m•\x1B[0m asarmor encrypting contents of ${asarPath}` 7 ); 8 await encrypt({ 9 src: asarPath, 10 dst: asarPath, 11 }); 12 13 // then patch the header 14 console.log(` \x1B[34m•\x1B[0m asarmor applying patches to ${asarPath}`); 15 const archive = await asarmor.open(asarPath); 16 archive.patch(); // apply default patches 17 await archive.write(asarPath); 18 } catch (err) { 19 console.error(err); 20 } 21};
1const { join } = require('path'); 2const { copyFile } = require('fs/promises'); 3 4exports.default = async (context) => { 5 try { 6 console.log('copying native dependencies'); 7 8 const release = join(__dirname, '..', 'node_modules', 'asarmor', 'build', 'Release'); 9 10 // copy main.node from asarmor to our dist/build/release folder; this will become the entrypoint later on. 11 await copyFile( 12 join(release, 'main.node'), 13 join( 14 context.packager.info.projectDir, 15 'release', 16 'app', 17 'dist', 18 'main', 19 'main.node' 20 ) 21 ); 22 23 // copy renderer.node to our dist/build/release folder; the render process will be bootstrapped from the main process later on. 24 await copyFile( 25 join(release, 'renderer.node'), 26 join( 27 context.packager.info.projectDir, 28 'release', 29 'app', 30 'dist', 31 'renderer', 32 'renderer.node' 33 ) 34 ); 35 } catch (err) { 36 console.error(err); 37 } 38};
Don't forget to update package.json
as well:
1"afterPack": "./afterPack.js", 2+ "beforePack": "./beforePack.js",
1+ "main": "./dist/main/main.node", 2- "main": "./dist/main/main.js",
1// main.ts 2import { allowUnencrypted } from 'asarmor'; 3 4allowUnencrypted(['node_modules']); // enables resolution of non-encrypted dependencies from `node_modules.asar`
BrowserWindow.webPreferences
configuration settings:1const mainWindow = new BrowserWindow({ 2 // ... 3 webPreferences: { 4 nodeIntegration: true, // MUST BE ENABLED 5 contextIsolation: false, // MUST BE DISABLED 6 }, 7 });
1await mainWindow.webContents.executeJavaScript(`!function () { 2 require('../renderer/renderer.node'); 3 require('../renderer/renderer.js'); 4}()`);
1module.exports = function bootstrap(k: Uint8Array) { 2 // sanity check 3 if (!Array.isArray(k) || k.length === 0) { 4 throw new Error('Failed to bootstrap application.'); 5 } 6 7 // key should be valid at this point, but you can access it here to perform additional checks. 8 console.log('decryption key: ' + k); 9 10 // start the app 11 if (!process.env.ELECTRON_RUN_AS_NODE) { 12 app 13 .whenReady() 14 .then(() => { 15 createWindow(); 16 app.on('activate', () => { 17 if (mainWindow === null) createWindow(); 18 }); 19 }) 20 .catch(console.log); 21 } else { 22 console.error('failed to bootstrap main process'); 23 } 24};
The instructions below assume you're using the Vite + TypeScript configuration. You may need to adjust the instructions according to your setup.
You can easily include asarmor in your packaging process using a postPackage hook:
1import * as asarmor from 'asarmor'; 2 3const config = { 4 // ... 5 hooks: { 6 // ... 7 postPackage: async (forgeConfig, buildPath) => { 8 const asarPath = `${buildPath.outputPaths[0]}/resources/app.asar`; 9 console.log( 10 `asarmor is encrypting all JavaScript files stored in ${asarPath}` 11 ); 12 await asarmor.encrypt({ 13 src: asarPath, 14 dst: asarPath, 15 }); 16 console.log(`asarmor is applying patches to ${asarPath}`); 17 const archive = await asarmor.open(asarPath); 18 archive.patch(); 19 await archive.write(asarPath); 20 }, 21 }, 22};
There's a few more steps involved to get encryption working. See example/electron-forge if you'd like to skip ahead to the code.
Steps:
forge.config.ts
:1import { join } from 'path'; 2import { copyFile } from "fs/promises"; 3import * as asarmor from "asarmor"; 4 5const config = { 6 // ... 7 hooks: { 8 // ... 9 packageAfterCopy: async (forgeConfig, buildPath) => { 10 try { 11 console.log('copying native asarmor dependencies'); 12 13 const release = join(__dirname, 'node_modules', 'asarmor', 'build', 'Release'); 14 15 // copy main.node from asarmor to our build folder; this will become the entrypoint later on. 16 await copyFile( 17 join(release, 'main.node'), 18 join( 19 buildPath, 20 '.vite', // change this if you're not using Vite 21 'build', 22 'main.node' 23 ) 24 ); 25 26 // copy renderer.node to our build folder; the render process will be bootstrapped from the main process later on. 27 await copyFile( 28 join(release, 'renderer.node'), 29 join( 30 buildPath, 31 '.vite', // change this if you're not using Vite 32 'renderer', 33 'main_window', 34 'renderer.node' 35 ) 36 ); 37 38 // uncomment the line below to copy the final build directory for debugging purposes. 39 // await cp(buildPath, './tmp', {recursive: true, force: true}); 40 } catch (err) { 41 console.error(err); 42 } 43 }, 44 postPackage: async (forgeConfig, buildPath) => { 45 const asarPath = `${buildPath.outputPaths[0]}/resources/app.asar`; 46 console.log( 47 `asarmor is encrypting all JavaScript files stored in ${asarPath}` 48 ); 49 await asarmor.encrypt({ 50 src: asarPath, 51 dst: asarPath, 52 }); 53 console.log(`asarmor is applying patches to ${asarPath}`); 54 const archive = await asarmor.open(asarPath); 55 archive.patch(); 56 await archive.write(asarPath); 57 }, 58 }, 59};
1+ "main": ".vite/build/main.node", 2- "main": ".vite/build/main.js",
You may choose to skip this step or handle it differently. Whatever you do, make sure you know the import paths as you'll need them in step 5
. See below for more information.
1// vite.renderer.config.ts 2return { 3 // ... 4 build: { 5 // ... 6 // Add the following rollup configuration: 7 rollupOptions: { 8 output: { 9 // Disable chunk splitting. 10 inlineDynamicImports: true, 11 entryFileNames: 'assets/[name].js', 12 chunkFileNames: 'assets/[name].js', 13 assetFileNames: 'assets/[name].[ext]', 14 }, 15 }, 16 }, 17};
BrowserWindow.webPreferences
configuration settings:1const mainWindow = new BrowserWindow({
2 // ...
3 webPreferences: {
4 // preload: path.join(__dirname, 'preload.js'), // MUST BE DISABLED (preload scripts are not supported, see #40)
5 nodeIntegration: true, // MUST BE ENABLED
6 contextIsolation: false, // MUST BE DISABLED
7 },
8 });
1await mainWindow.webContents.executeJavaScript(`!function () { 2 require('./renderer.node'); 3 require('./assets/index.js'); 4}()`);
If you deviated from the default instructions in step 3
, replace ./assets/index.js
with the path to your renderer asset.
1export default function bootstrap(k: Uint8Array) { 2 // sanity check 3 if (!Array.isArray(k) || k.length === 0) { 4 throw new Error('Failed to bootstrap application.'); 5 } 6 7 // key should be valid at this point, but you can access it here to perform additional checks. 8 console.log('decryption key: ' + k); 9 10 // This method will be called when Electron has finished 11 // initialization and is ready to create browser windows. 12 // Some APIs can only be used after this event occurs. 13 app.on('ready', createWindow); 14 15 // Quit when all windows are closed, except on macOS. There, it's common 16 // for applications and their menu bar to stay active until the user quits 17 // explicitly with Cmd + Q. 18 app.on('window-all-closed', () => { 19 if (process.platform !== 'darwin') { 20 app.quit(); 21 } 22 }); 23 24 app.on('activate', () => { 25 // On OS X it's common to re-create a window in the app when the 26 // dock icon is clicked and there are no other windows open. 27 if (BrowserWindow.getAllWindows().length === 0) { 28 createWindow(); 29 } 30 }); 31}
Found a bug or have a question? Open an issue if it doesn't exist yet. Pull Requests are welcome, but please open an issue first if you're adding major changes!
A special thanks to the following projects for making this project possible:
No vulnerabilities found.
Reason
no dangerous workflow patterns detected
Reason
no binaries found in the repo
Reason
license file detected
Details
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
Found 0/4 approved changesets -- score normalized to 0
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Reason
45 existing vulnerabilities detected
Details
Score
Last Scanned on 2024-11-18
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More