Skip to content

sec: Prevent Arbitrary Command Injection in Terminal Execution Helpers (#3202) - #3281

Open
knoxiboy wants to merge 2 commits into
Karanjot786:mainfrom
knoxiboy:3202-sec-prevent-arbitrary-command-injection
Open

sec: Prevent Arbitrary Command Injection in Terminal Execution Helpers (#3202)#3281
knoxiboy wants to merge 2 commits into
Karanjot786:mainfrom
knoxiboy:3202-sec-prevent-arbitrary-command-injection

Conversation

@knoxiboy

@knoxiboy knoxiboy commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Description

This PR hardens command execution helpers (execFileAsync in @termuijs/data and useSubprocess in @termuijs/jsx) against command injection, null-byte injection, and dangerous shell execution.

Related Issue

Closes #3202

Which package(s)?

@termuijs/data, @termuijs/jsx

Type of Change

  • 🐛 Bug fix ( ype:bug)
  • ✨ Feature ( ype:feature)
  • 📝 Docs ( ype:docs)
  • 🧪 Tests ( ype:testing)
  • ♻️ Refactor ( ype:refactor)
  • 🎨 Design / UX ( ype:design)
  • ♿ Accessibility ( ype:accessibility)
  • ⚡ Performance ( ype:performance)
  • 🔧 DevOps / CI ( ype:devops)
  • 🔒 Security ( ype:security)

Checklist

  • ⭐ You starred the repo. The
    eeds-star check blocks your merge otherwise.
  • Tests pass locally: �un vitest run
  • Build passes: �un run build
  • Typecheck passes: �un run typecheck
  • You read CONTRIBUTING.md.
  • Your PR title follows ype: short description.
  • Widget state mutators call markDirty() (if your change affects rendering).
  • No new �ny types without an inline comment explaining why.
  • No unrelated refactors bundled into this PR.

GSSoC 2026 Participation

Notes for the Reviewer

Added null-byte and argument validation to execFileAsync and useSubprocess to prevent command injection and untrusted subprocess execution.

Summary by CodeRabbit

  • Bug Fixes

    • Added stricter validation for subprocess execution, including rejecting empty or falsy command inputs.
    • File paths and command arguments containing null bytes are now rejected with clear error messages.
    • Subprocess execution is now always performed without a shell, even if shell options are provided.
  • Tests

    • Expanded test coverage for null-byte validation and shell enforcement.
    • Improved subprocess test setup and strengthened mocked return typing.

@knoxiboy
knoxiboy requested a review from Karanjot786 as a code owner July 29, 2026 13:30
Copilot AI review requested due to automatic review settings July 29, 2026 13:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added area:jsx @termuijs/jsx area:data @termuijs/data type:testing +10 pts. Tests. labels Jul 29, 2026
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

execFileAsync and useSubprocess.run now validate subprocess inputs, reject null bytes, and ensure execFile uses shell: false. Tests cover the new validation errors, execution options, and tightened subprocess mocks.

Changes

Subprocess validation

Layer / File(s) Summary
execFileAsync validation and shell configuration
packages/data/src/hooks/_exec.ts, packages/data/src/hooks/_exec.test.ts
execFileAsync validates file and argument values, rejects null bytes, passes shell: false, and tests these behaviors.
useSubprocess command validation and test typing
packages/jsx/src/hooks/useSubprocess.ts, packages/jsx/src/hooks/useSubprocess.test.ts
useSubprocess.run rejects falsy, empty, and null-byte-containing commands; tests use a typed app stub and stricter spawn return types.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested labels: type:security, level:intermediate

Suggested reviewers: karanjot786

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR covers shell disabling and null-byte checks, but it misses strict alphanumeric argument validation and metacharacter regression tests required by #3202. Add strict alphanumeric validation for CLI flags and regression tests for |, ;, &, and $; align the implementation with the issue's scope.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, specific, and accurately summarizes the security hardening in the PR.
Description check ✅ Passed All required template sections are present, including description, related issue, packages, type, checklist, GSSoC, and notes.
Out of Scope Changes check ✅ Passed The test refactors and helper cleanup stay within the security hardening work and don't introduce unrelated changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/data/src/hooks/_exec.ts`:
- Line 18: Update the execFile options in the execution flow to spread caller
options before explicitly setting shell: false, ensuring opts.shell cannot
override the safety setting. Add a regression test covering opts.shell: true and
verify shell execution remains disabled.

In `@packages/jsx/src/hooks/useSubprocess.test.ts`:
- Line 25: Remove the unannotated any casts in the useSubprocess tests,
including the mockSpawn setup and the additional occurrences at the referenced
test cases. Use properly typed subprocess and application fixtures; if a type
assertion remains necessary, replace any with the narrowest type and add an
inline comment describing its specific purpose.
- Around line 40-50: Update the application fixtures in the affected hook tests
to instantiate the real Screen from `@termuijs/core` instead of providing fake
screen objects. Spy on the real Screen instance’s invalidate method while
preserving the existing terminal and requestRender test behavior in the relevant
test cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f3b39404-c17e-46e2-a044-db4c64536428

📥 Commits

Reviewing files that changed from the base of the PR and between 48f63a1 and ed3a9b6.

📒 Files selected for processing (4)
  • packages/data/src/hooks/_exec.test.ts
  • packages/data/src/hooks/_exec.ts
  • packages/jsx/src/hooks/useSubprocess.test.ts
  • packages/jsx/src/hooks/useSubprocess.ts

Comment thread packages/data/src/hooks/_exec.ts Outdated
Comment thread packages/jsx/src/hooks/useSubprocess.test.ts Outdated
Comment on lines +40 to +50
it('exits raw mode before spawning and restores the TUI after exit', async () => {
const app = {
terminal: {
exitRawMode: vi.fn(),
enterRawMode: vi.fn(),
},
screen: {
invalidate: vi.fn(),
},
requestRender: vi.fn(),
} as any;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Use the real Screen in these hook tests.

Both fixtures provide a fake screen object. Construct the application fixture with Screen from @termuijs/core and spy on its invalidate() method instead.

As per coding guidelines, package tests must use the real Screen and must not write fake screen objects.

Also applies to: 72-82

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/jsx/src/hooks/useSubprocess.test.ts` around lines 40 - 50, Update
the application fixtures in the affected hook tests to instantiate the real
Screen from `@termuijs/core` instead of providing fake screen objects. Spy on the
real Screen instance’s invalidate method while preserving the existing terminal
and requestRender test behavior in the relevant test cases.

Source: Coding guidelines

…jot786#3202)

- Fix critical spread order: change { shell: false, ...opts } to { ...opts, shell: false }
  so callers cannot override the shell: false security invariant
- Add regression test that verifies shell: true in opts is always overridden
- Replace raw 'any' casts in useSubprocess.test.ts with typed makeAppStub()
  factory using intentional 'unknown' cast (explained inline)
- Replace 'proc as any' with ReturnType<typeof spawn> cast

Addresses review comments from coderabbitai

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/jsx/src/hooks/useSubprocess.test.ts`:
- Around line 110-116: Add an assertion to the null-byte rejection test for
useSubprocess that verifies mockSpawn was never called after subprocess.run
rejects. Keep the existing error-message assertion unchanged and explicitly
protect the no-execution behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3f68d8bf-c465-4fcb-afd3-12dee0ae3fcf

📥 Commits

Reviewing files that changed from the base of the PR and between ed3a9b6 and e4fef25.

📒 Files selected for processing (3)
  • packages/data/src/hooks/_exec.test.ts
  • packages/data/src/hooks/_exec.ts
  • packages/jsx/src/hooks/useSubprocess.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/data/src/hooks/_exec.test.ts

Comment on lines +110 to +116
it('throws when command contains null bytes', async () => {
const subprocess = useSubprocess();

await expect(subprocess.run(['ls', 'dir\0malicious'])).rejects.toThrow(
'useSubprocess: command contains null bytes',
);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Assert that null-byte input never invokes spawn.

This test only checks rejection and the error message. Add expect(mockSpawn).not.toHaveBeenCalled() to protect the no-execution security invariant.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/jsx/src/hooks/useSubprocess.test.ts` around lines 110 - 116, Add an
assertion to the null-byte rejection test for useSubprocess that verifies
mockSpawn was never called after subprocess.run rejects. Keep the existing
error-message assertion unchanged and explicitly protect the no-execution
behavior.

@github-actions

Copy link
Copy Markdown

No activity on this PR for 14 days. Rebase, resolve conflicts, or comment to keep it open. It closes in 7 days otherwise.

@github-actions github-actions Bot added the stale No activity in 14 days. label Aug 13, 2026
@Karanjot786

Copy link
Copy Markdown
Owner

Defensive hardening mislabeled as a fix — no current injection vector. execFile/spawn already throw synchronously on null bytes (→ rejection inside the existing Promise), so the checks are redundant with Node; and shell:false guards a shell:true no call site uses. Same no-op shape as #2469. Reframe as defense-in-depth, not a vuln fix.

@Karanjot786 Karanjot786 added the quality:needs-work Needs changes before merge. label Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:data @termuijs/data area:jsx @termuijs/jsx quality:needs-work Needs changes before merge. stale No activity in 14 days. type:testing +10 pts. Tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security]: Prevent Arbitrary Command Injection in Terminal Execution Helpers

3 participants