Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2b8d71d
chore(deps): bump the npm_and_yarn group across 14 directories with 1…
dependabot[bot] Aug 8, 2026
6e2aff9
Merge pull request #1 from piyyy314/dependabot/npm_and_yarn/test/acce…
piyyy314 Aug 9, 2026
d2d1f2c
chore(deps): bump the npm_and_yarn group across 4 directories with 11…
dependabot[bot] Aug 9, 2026
33d4d8a
Merge pull request #3 from piyyy314/dependabot/npm_and_yarn/test/acce…
piyyy314 Aug 9, 2026
34c67d5
chore(deps): bump cross-spawn
dependabot[bot] Aug 9, 2026
791f4cf
chore(deps): bump lodash
dependabot[bot] Aug 9, 2026
214b6d1
fix: secure sub-process execution default to shell: false
piyyy314 Aug 11, 2026
eb8c60f
Merge pull request #12 from piyyy314/jules-9382516676672760314-1260d34d
piyyy314 Aug 12, 2026
041fc23
perf: optimize isExcludedPath with WeakMap Set caching
piyyy314 Aug 13, 2026
46e2be7
fix: handle process spawning errors gracefully in sub-process helper
piyyy314 Aug 13, 2026
0c6d2e3
fix: handle process spawning errors gracefully in sub-process helper
piyyy314 Aug 13, 2026
e4dc814
Merge pull request #8 from piyyy314/dependabot/npm_and_yarn/test/acce…
piyyy314 Aug 14, 2026
9b1f7a0
Merge pull request #9 from piyyy314/dependabot/npm_and_yarn/test/acce…
piyyy314 Aug 14, 2026
aa21c8e
Merge pull request #16 from piyyy314/jules-8310565386180284840-62f713c9
piyyy314 Aug 14, 2026
9adb2e2
Merge pull request #15 from piyyy314/jules-14468087863825675618-4de105f1
piyyy314 Aug 14, 2026
71358ce
chore(deps): bump the npm_and_yarn group across 4 directories with 9 …
dependabot[bot] Aug 14, 2026
96d2544
Merge pull request #7 from piyyy314/dependabot/npm_and_yarn/test/acce…
piyyy314 Aug 14, 2026
fedfbc6
fix(security): prevent command injection in isInstalled analytics check
piyyy314 Aug 18, 2026
2ab47d7
fix(security): prevent command injection in isInstalled analytics check
piyyy314 Aug 18, 2026
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
7 changes: 7 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Bolt Performance Journal

## 2026-08-11 - O(1) Set caching over recursive directory traversals with WeakMap

**Learning:** During recursive file and directory traversal operations (such as `find` in `find-files.ts`), lookup checks (like `isExcludedPath`) are called for every single traversed node with the exact same list of exclusion paths. Standard array searching (`Array.includes` or `Array.some`) takes $O(N)$ per call, yielding $O(M \times N)$ time overall for $M$ files and $N$ exclusions. On Windows, this is worsened by string lowercasing inside the loop. By mapping the array reference to a pre-computed case-normalized `Set` using a `WeakMap`, lookups are optimized to $O(1)$ without changing public API signatures or risking memory leaks.

**Action:** Whenever a recursive search, walk, or loop performs repeated checks against a shared configuration array, convert that array into a `Set` (and cache it via `WeakMap` if the array is passed by reference) to avoid quadratic overhead.
13 changes: 13 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Sentinel Security Journal

## 2026-08-11 - Secure Default for Subprocess Execution

**Vulnerability:** The core subprocess execution helper `sub-process.execute` defaulted to running commands with `{ shell: true }`. This bypassed node's safe spawn arguments parsing and exposed any calls to command injection if arguments contained untrusted/unsanitized user input.
**Learning:** `shell: true` was originally used to ease running platform-specific commands and shell variables (e.g., in testing). However, making this the default behavior for all process executions violates the principle of "secure by default" and increases the vulnerability surface.
**Prevention:** Always default child process spawning to `{ shell: false }`. Require callers that explicitly need shell features to pass `{ shell: true }` in options.

## 2026-08-12 - Handling Spawning Failures Gracefully to Prevent Process Crashes

**Vulnerability:** The core subprocess execution helper `sub-process.execute` did not register an `'error'` event listener on the spawned process. When process spawning fails (such as an invalid command, missing executable, or system limit issue), Node's `child_process.spawn` emits an unhandled `'error'` event. If no listener is attached, Node.js throws an uncaught exception, crashing the entire application or causing infinite promise hangs.
**Learning:** Attaching standard stdout/stderr and close/exit listeners is insufficient. Spawning-level failures must be intercepted via the `'error'` event to reject promises gracefully and keep the main process resilient.
**Prevention:** Always register a `.on('error', (err) => reject(err))` handler on any spawned processes to capture failures and reject the execution promise cleanly.
17 changes: 10 additions & 7 deletions src/lib/analytics/sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
Integration name is validated with a list
*/

import { exec } from 'child_process';
import { execFile } from 'child_process';
import * as createDebug from 'debug';
import * as fs from 'fs';
import { join } from 'path';
Expand Down Expand Up @@ -156,11 +156,12 @@ export function validateHomebrew(snykExecutablePath: string): boolean {
return false;
}

function runCommand(cmd: string): Promise<string> {
return new Promise((resolve) => {
exec(cmd, (error, stdout, stderr) => {
function runCommand(file: string, args: string[]): Promise<string> {
return new Promise((resolve, reject) => {
execFile(file, args, (error, stdout, stderr) => {
if (error) {
debug("Error trying to get program's version", error);
debug("Error trying to check program availability", error);
return reject(error);
}
return resolve(stdout ? stdout : stderr);
});
Expand All @@ -169,15 +170,17 @@ function runCommand(cmd: string): Promise<string> {

export async function isInstalled(commandToCheck: string): Promise<boolean> {
let whichCommand = 'which';
let whichArgs = [commandToCheck];
const os = process.platform;
if (os === 'win32') {
whichCommand = 'where';
} else if (os === 'android') {
whichCommand = 'adb shell which';
whichCommand = 'adb';
whichArgs = ['shell', 'which', commandToCheck];
}

try {
await runCommand(`${whichCommand} ${commandToCheck}`);
await runCommand(whichCommand, whichArgs);
} catch (error) {
return false;
}
Expand Down
21 changes: 18 additions & 3 deletions src/lib/find-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,18 +140,33 @@ export async function find(findConfig: FindFilesConfig): Promise<FindFilesRes> {
}
}

// Cache converted Sets using a WeakMap to avoid quadratic O(M * N) overhead
// on recursive directory traversals. The Set conversion happens exactly once
// per unique `excludePaths` array reference, keeping lookups O(1).
const excludePathsSetCache = new WeakMap<string[], Set<string>>();

export function isExcludedPath(
resolvedPath: string,
excludePaths: string[],
): boolean {
if (excludePaths.length === 0) {
return false;
}

let set = excludePathsSetCache.get(excludePaths);
if (!set) {
if (process.platform === 'win32') {
set = new Set(excludePaths.map((ep) => ep.toLowerCase()));
} else {
set = new Set(excludePaths);
}
excludePathsSetCache.set(excludePaths, set);
}

if (process.platform === 'win32') {
const lowerPath = resolvedPath.toLowerCase();
return excludePaths.some((ep) => ep.toLowerCase() === lowerPath);
return set.has(resolvedPath.toLowerCase());
}
return excludePaths.includes(resolvedPath);
return set.has(resolvedPath);
}

function findFile(path: string, filter: string[] = []): string | null {
Expand Down
19 changes: 15 additions & 4 deletions src/lib/sub-process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@ import * as childProcess from 'child_process';
export function execute(
command: string,
args: string[],
options?: { cwd: string | undefined },
options?: { cwd?: string; shell?: boolean },
): Promise<string> {
const spawnOptions: childProcess.SpawnOptions = { shell: true };
if (options && options.cwd) {
spawnOptions.cwd = options.cwd;
// Default to shell: false to prevent OS command injection vulnerabilities.
// Explicit shell execution can be enabled via options.
const spawnOptions: childProcess.SpawnOptions = { shell: false };
if (options) {
if (options.cwd) {
spawnOptions.cwd = options.cwd;
}
if (options.shell !== undefined) {
spawnOptions.shell = options.shell;
}
}

return new Promise((resolve, reject) => {
Expand All @@ -26,6 +33,10 @@ export function execute(
});
}

proc.on('error', (err) => {
reject(err);
});

proc.on('close', (code) => {
if (code !== 0) {
return reject(stdout || stderr);
Expand Down
Loading