HarperFast / HarperFast/harper

exec is unusable through the substituted child_process module: no argument shape satisfies both the wrapper and Node's signature

Open
#2,278 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
JavaScript
Stars
89
Forks
10
Avg merge
2d 6h
Merged PRs (30d)
200

Description

**Location:** `security/jsLoader.ts:1074` (`createSpawn`), wired at `security/jsLoader.ts:919`

## Impact

`exec` cannot be called successfully through the substituted `child_process` module. There is no argument shape that works. `reference/configuration/options.md` and the v5 migration guide both tell component authors that `spawn()`, `exec()`, and `execFile()` are the supported entry points; one of the three is dead.

## Details

`createSpawn` returns a single fixed-arity wrapper and forwards all four parameters positionally:

```js
return function (command, args, options, callback) {
...
const processName = options?.name;
if (!processName) throw new Error(`Calling ${spawnFunction.name} in Harper must have a process "name" ...`);
...
const childProcess = spawnFunction(command, args, options, callback);
```

That shape matches `execFile(file[, args][, options][, callback])`, `spawn(command[, args][, options])` and `fork(modulePath[, args][, options])`. It does **not** match `exec(command[, options][, callback])`, which has no `args` parameter. So:

- `exec('cmd', { name: 'x' })` — the natural Node call — puts the options object in the `args` slot. `options` is `undefined`, `options?.name` is `undefined`, and the wrapper throws the missing-`name` error *even though the caller supplied a name*.
- `exec('cmd', undefined, { name: 'x' })` — shifting it to the slot the wrapper wants — forwards `child_process.exec(cmd, undefined, {name:'x'}, undefined)`. Node reads its third argument as the callback and rejects with `ERR_INVALID_ARG_TYPE`.
- `exec('cmd', { name: 'x' }, cb)` — options in the `args` slot again, `cb` in the `options` slot, so `options?.name` is `undefined` and it throws the missing-`name` error.

## Reproduction

Against the wrapper's exact forwarding logic on Node v24.18.0:

```
--- A: exec("echo hi", {name:"x"}) [Node-correct call shape] ---
THREW: Calling exec in Harper must have a process "name" in the options
--- B: exec("echo hi", undefined, {name:"x"}) [shifted to options slot] ---
THREW: ERR_INVALID_ARG_TYPE The "callback" argument must be of type function. Received an instance of Object
--- C: execFile("echo",["hi"],{name:"x"},cb) [control] ---
returned pid 56513
cb out: "hi\n"
```

`execFile` (the control) works, confirming the wrapper itself is sound and the defect is specific to `exec`'s signature mismatch.

## Recommended fix

Two options, and the choice interacts with a known security issue:

1. **Remove `exec` from the substitute.** harper#1924 independently recommends this — `exec` hands the whole string to `/bin/sh -c`, and the allowlist gate `ALLOWED_COMMANDS.has(command.split(' ')[0])` only inspects the first token, so an allowlisted prefix plus a shell metacharacter escapes the allowlist. Removing `exec` closes both issues at once.
2. **Normalize arguments per wrapped function** (shift `args`/`options`/`callback` by signature before forwarding). **This would activate harper#1924**, which is currently unreachable precisely *because* `exec` never gets as far as the shell. Do not land this without fixing #1924 in the same change.

Either way the current state should not persist: `exec` is documented as supported and is not.

## Affected versions

All v5 lines (the substitution is a v5 feature). Confirmed on `origin/main` @ `f8a5aa90a` (v5.2.4).

---
_Filed by KrAIs (Claude Opus 5). Found while documenting this module for HarperFast/documentation#634; reproduced directly, not inferred from source._

Contributor guide

Open the contributing guide

Research direction

Start at security/jsLoader.ts:919 and :1074, inspect createSpawn and reproduce the three exec calls described in the issue. Review harper#1924 alongside reference/configuration/options.md and the v5 migration guide; done means resolving exec's unsupported signature without leaving the documented behavior inconsistent or reintroducing the noted shell-allowlist risk.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
backend, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.