Skip to content

Add human-in-the-loop confirmation for risky package install scripts#348

Open
lmajano wants to merge 1 commit into
developmentfrom
claude/boxlang-security-prompts-s20wjh
Open

Add human-in-the-loop confirmation for risky package install scripts#348
lmajano wants to merge 1 commit into
developmentfrom
claude/boxlang-security-prompts-s20wjh

Conversation

@lmajano

@lmajano lmajano commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Package box.json scripts tied to install-lifecycle interception points
(preInstall, onInstall, postInstall, etc.) are fired automatically and
run arbitrary commands via shell.callCommand. Installing a third-party
package could therefore silently execute attacker-controlled commands.

This adds a yes/no confirmation before CommandBox auto-runs one of these
install-lifecycle scripts, printing the exact commands first so the user
can decline untrusted code.

  • PackageService.runScript gains an automatic flag; only interceptor-
    driven runs (PackageScripts.cfc) set it, so explicit run-script
    invocations are never prompted.
  • Gating applies only to install/uninstall lifecycle events, not benign
    high-frequency points (preCommand, prePrompt, etc.).
  • On by default. Non-interactive shells (CI/no TTY) deny by default unless
    trusted via config scripts.trustInstallScriptsNonInteractive, env var
    COMMANDBOX_TRUST_INSTALL_SCRIPTS, or install --trustScripts.
  • Master switch scripts.confirmInstallScripts (default true) can disable.

Reuses shell.confirm() and shell.isTerminalInteractive().

Package box.json scripts tied to install-lifecycle interception points
(preInstall, onInstall, postInstall, etc.) are fired automatically and
run arbitrary commands via shell.callCommand. Installing a third-party
package could therefore silently execute attacker-controlled commands.

This adds a yes/no confirmation before CommandBox auto-runs one of these
install-lifecycle scripts, printing the exact commands first so the user
can decline untrusted code.

- PackageService.runScript gains an `automatic` flag; only interceptor-
  driven runs (PackageScripts.cfc) set it, so explicit `run-script`
  invocations are never prompted.
- Gating applies only to install/uninstall lifecycle events, not benign
  high-frequency points (preCommand, prePrompt, etc.).
- On by default. Non-interactive shells (CI/no TTY) deny by default unless
  trusted via config `scripts.trustInstallScriptsNonInteractive`, env var
  COMMANDBOX_TRUST_INSTALL_SCRIPTS, or `install --trustScripts`.
- Master switch `scripts.confirmInstallScripts` (default true) can disable.

Reuses shell.confirm() and shell.isTerminalInteractive().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015g9LPH1Ct6zP17DoDEsBUp
@bdw429s

bdw429s commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

We don't make up random CommandBox env vars generally. We already have an existing system for overriding config settings. So COMMANDBOX_TRUST_INSTALL_SCRIPTS should actually be called box_config_scripts_trustInstallScriptsNonInteractive which is our existing convention. And we shouldn't be checking the values of env vars directly in the code. Just check the config settings. The env var will naturally override the config setting as needed all on its own. When the install command delegates to a recursive install command, it should pass along its trust scripts arg to propagate, and the command arg should just default to the config setting.

I also don't understand the need for both scripts.trustInstallScriptsNonInteractive and scripts.confirmInstallScripts. Can't we just have a single trust flag?

The PackageService isn't really the correct place to put an isTruthy() helper. At best, perhaps in the config service since we'd likely want to use this for any boolean config setting which could have been set to a non-boolean value. That said, we've never messed with this level of validation before. If people put a crap value in, we error out instead of just silently ignoring it (and treating it has false). I'm not a fan of the silent treatment. In this specific case, treating an unknown value as false may be the "safe" option, but in another scenario, it could be the opposite.

Also, all of this needs sent to the boxlang branch as well. Either in another pull, or by cherry picking the commit afterward.

@bdw429s bdw429s self-requested a review June 25, 2026 22:41

@bdw429s bdw429s left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

stupid GitHub won't let me submit this dumb form without a comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants