Skip to content

fix(plugin-manager): shell-escape hook arguments in pre/post_hooks#2697

Open
elibosley wants to merge 1 commit into
masterfrom
fix/plugin-manager-hook-arg-quoting
Open

fix(plugin-manager): shell-escape hook arguments in pre/post_hooks#2697
elibosley wants to merge 1 commit into
masterfrom
fix/plugin-manager-hook-arg-quoting

Conversation

@elibosley

@elibosley elibosley commented Jul 15, 2026

Copy link
Copy Markdown
Member

Fixes OS-610.

Summary

pre_hooks() and post_hooks() build the hook command by concatenating unquoted values into a string that run() executes via sh -c (popen):

run("$hook plugin $method $plugin $error");

A normal Plugin Manager error contains an apostrophe — XML file doesn't exist or xml parse error — so the shell treats it as the start of a quoted string and aborts:

sh: -c: line 1: unexpected EOF while looking for matching `'`

The hook never executes.

Impact

This doesn't cause the original plugin failure, but it prevents Plugin Manager from completing its failure-handling contract — exactly when those hooks matter most:

  • The pending marker created by pre_plugin_checks is never removed (/tmp/plugins/pluginPending/<name>), so the UI can be left showing a stuck "pending" state.
  • Post-hook cleanup and notification behavior is skipped.
  • A misleading secondary error obscures the actual failure.
  • An error containing spaces is split into multiple arguments, so hooks don't receive the documented complete error value even when the command stays syntactically valid.

Fix

Keep the existing run() implementation and escape each interpolated argument at the call site with escapeshellarg(), in both pre_hooks() and post_hooks(). Surgical, and preserves the existing execution model. post_hooks() keeps passing $error (escaped) so post_plugin_checks' if ($error) break; success/failure signal is retained.

Verification

Using the reproduction from OS-610 (a deliberately malformed .plg):

Before

plugin: XML file doesn't exist or xml parse error
Executing hook script: post_plugin_checks
sh: -c: line 1: unexpected EOF while looking for matching `'`
exit_status=1
pending marker remains

After

plugin: XML file doesn't exist or xml parse error
Executing hook script: post_plugin_checks
exit_status=1
pending marker cleared

The original XML failure and nonzero status are unchanged — post_plugin_checks now executes, clears the pending marker, and emits no secondary shell error. Separately confirmed the hook receives the full error as a single argument ($argv[4] = XML file doesn't exist or xml parse error). php -l clean.

Security context

This is not claiming a demonstrated privilege escalation — the inputs here come from a plugin operation that is already privileged. But Plugin Manager runs these commands with elevated authority, so passing values through sh -c without argument escaping is undesirable; escaping is worthwhile defense-in-depth alongside the failure-handling fix.

Notes

Independent of the dangling-symlink fix in #2696 (that error string is what surfaced this bug). The two touch different functions and can merge in either order.

pre_hooks() and post_hooks() built the hook command by interpolating the plugin
name, method, and error text straight into a string that run() executes via
`sh -c` (popen):

    run("$hook plugin $method $plugin $error");

An error containing an apostrophe - e.g. "XML file doesn't exist or xml parse
error" - makes the shell treat it as an unterminated quote and abort:

    sh: -c: line 1: unexpected EOF while looking for matching `'`

so the hook never runs. For post-hooks that means post_plugin_checks is skipped,
and normal failure cleanup (e.g. removing the plugin's pending-status marker) is
missed. An error (or name) containing spaces is also split into multiple args,
truncating the message the hook receives.

Escape every interpolated value with escapeshellarg() so each is passed as a
single, literal argument regardless of quotes or whitespace. run()'s streaming
behavior is unchanged. Verified: with an apostrophe-containing error the hook
now runs and receives the full error string as one argument, where before it
failed with the sh EOF error and produced no output.
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@elibosley, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 47 seconds

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5a3c7bb4-1de7-4b58-abf2-a22282b00f7b

📥 Commits

Reviewing files that changed from the base of the PR and between 369f0b2 and c893fc7.

📒 Files selected for processing (1)
  • emhttp/plugins/dynamix.plugin.manager/scripts/plugin
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/plugin-manager-hook-arg-quoting

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.

@github-actions

Copy link
Copy Markdown

🔧 PR Test Plugin Available

A test plugin has been generated for this PR that includes the modified files.

Version: 2026.07.15.1727
Build: View Workflow Run

📥 Installation Instructions:

Install via Unraid Web UI:

  1. Go to Plugins → Install Plugin
  2. Copy and paste this URL:
https://preview.dl.unraid.net/pr-plugins/pr-2697/webgui-pr-2697.plg
  1. Click Install

Alternative: Direct Download

⚠️ Important Notes:

  • Testing only: This plugin is for testing PR changes
  • Backup included: Original files are automatically backed up
  • Easy removal: Files are restored when plugin is removed
  • Conflicts: Remove this plugin before installing production updates
  • Post-merge behavior: This preview stays available after merge until preview storage expires or it is manually cleaned up

📝 Modified Files:

Click to expand file list
emhttp/plugins/dynamix.plugin.manager/scripts/plugin

🔄 To Remove:

Navigate to Plugins → Installed Plugins and remove webgui-pr-2697, or run:

plugin remove webgui-pr-2697

🤖 This comment is automatically generated and will be updated with each new push to this PR.

@unraid-bot unraid-bot added 7.4.0 Approved for release 7.4.0 (auto-managed by notification-worker) labels Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

7.4.0 Approved for release 7.4.0 (auto-managed by notification-worker)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants