Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions knip.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ const config: KnipConfig = {
// Optional security tooling invoked from npm scripts; installed on demand.
'semgrep',
'gitleaks',
// Shell builtin, not a binary: the build script sets `umask 022` so files the
// packaging targets generate are world-readable (see scripts/after-pack.cjs).
'umask',
],
// Test files are allowed to have unused exports (test helpers, fixtures).
ignoreExportsUsedInFile: true,
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"build:mcp": "esbuild dist-electron/mcp/server.js --bundle --platform=node --format=cjs --outfile=dist-electron/mcp-server.cjs",
"build:frontend": "NODE_OPTIONS='--max-old-space-size=4096' vite build --config electron/vite.config.electron.ts",
"build:remote": "vite build --config src/remote/vite.config.ts",
"build": "npm run build:frontend && npm run build:remote && npm run compile && npm run build:mcp && electron-builder",
"build": "umask 022 && npm run build:frontend && npm run build:remote && npm run compile && npm run build:mcp && electron-builder",
"serve": "vite preview --config electron/vite.config.electron.ts",
"lint": "eslint . --max-warnings 0",
"lint:fix": "eslint . --fix",
Expand Down Expand Up @@ -130,6 +130,7 @@
"build": {
"appId": "com.parallel-code.app",
"productName": "Parallel Code",
"afterPack": "./scripts/after-pack.cjs",
"directories": {
"buildResources": "build",
"output": "release"
Expand Down
46 changes: 46 additions & 0 deletions scripts/after-pack.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// electron-builder afterPack hook: make the packaged Linux app world-readable.
//
// Files installed or generated under a restrictive umask keep modes without
// world-read: node_modules as npm installed them (the asar-unpacked native
// modules), build output, and the directories electron-builder creates. dpkg
// installs those modes verbatim, which leaves a `/opt/Parallel Code` the desktop
// user cannot enter.
//
// Runs after the app is packed and before the deb and AppImage targets read it.
// Files the targets generate themselves (the desktop entry, changelog) come
// later and are covered by the `umask 022` in the `build` script instead.

const fs = require('fs');
const path = require('path');

// Directories 755; files 644, or 755 where the owner could already execute them,
// so no file becomes executable that its owner could not already execute. This
// clears setuid, setgid and sticky bits; the packed tree has none (chrome-sandbox
// is packed 0755, and the deb's postinst sets 4755 only on systems without user
// namespaces). Symlinks carry no mode of their own and lchmod is not portable, so
// they are left alone. A mode that is already right is not rewritten.
function normalizePermissions(target) {
const stat = fs.lstatSync(target);
if (stat.isSymbolicLink()) return;

const wanted = stat.isDirectory() || stat.mode & 0o100 ? 0o755 : 0o644;
if ((stat.mode & 0o7777) !== wanted) fs.chmodSync(target, wanted);

if (stat.isDirectory()) {
for (const entry of fs.readdirSync(target)) {
normalizePermissions(path.join(target, entry));
}
}
}

exports.default = async function afterPack(context) {
if (context.electronPlatformName !== 'linux') return;

normalizePermissions(context.appOutDir);

// fpm reads the menu icon from the build resources directory directly, so its
// modes reach the package too. This changes modes in the checkout's build/;
// git records only the executable bit, which is kept, so no diff results.
const buildResources = context.packager.buildResourcesDir;
if (buildResources && fs.existsSync(buildResources)) normalizePermissions(buildResources);
};
Loading