• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

daycry / jobs / 24850441053

23 Apr 2026 05:54PM UTC coverage: 52.404% (-1.5%) from 53.938%
24850441053

push

github

daycry
Fixes

104 of 219 new or added lines in 42 files covered. (47.49%)

14 existing lines in 9 files now uncovered.

1210 of 2309 relevant lines covered (52.4%)

4.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.44
/src/Jobs/ShellJob.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of Daycry Queues.
7
 *
8
 * (c) Daycry <daycry9@proton.me>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace Daycry\Jobs\Jobs;
15

16
use Daycry\Jobs\Exceptions\JobException;
17
use Daycry\Jobs\Interfaces\JobInterface;
18
use Daycry\Jobs\Job;
19
use Daycry\Jobs\Traits\InteractsWithCurrentJob;
20

21
/**
22
 * Executes a shell command using exec().
23
 * Payload: string command to execute. Returns captured output array.
24
 * NOTE: Commands are validated against whitelist if configured.
25
 */
26
class ShellJob extends Job implements JobInterface
27
{
28
    use InteractsWithCurrentJob;
29

30
    public function handle(mixed $payload): mixed
31
    {
32
        if (! is_string($payload)) {
8✔
NEW
33
            throw JobException::validationError('ShellJob payload must be a string command.');
×
34
        }
35

36
        $this->validateCommand($payload);
8✔
37

38
        // Use escapeshellcmd on the full command, then escapeshellarg on each argument
39
        $parts   = preg_split('/\s+/', trim($payload));
7✔
40
        $command = escapeshellarg(array_shift($parts));
7✔
41

42
        foreach ($parts as $arg) {
7✔
43
            $command .= ' ' . escapeshellarg($arg);
7✔
44
        }
45

46
        exec($command, $output);
7✔
47

48
        return $output;
7✔
49
    }
50

51
    /**
52
     * Validate command against whitelist if configured.
53
     */
54
    private function validateCommand(string $command): void
55
    {
56
        $config  = config('Jobs');
8✔
57
        $allowed = $config->allowedShellCommands ?? [];
8✔
58

59
        // Empty whitelist = allow all (backward compatible)
60
        if (empty($allowed)) {
8✔
61
            return;
2✔
62
        }
63

64
        // Extract command name (first token) and get basename to prevent path-based bypass
65
        $parts   = preg_split('/\\s+/', trim($command), 2);
6✔
66
        $cmdName = basename($parts[0] ?? '');
6✔
67

68
        // Normalize whitelist to lowercase for case-insensitive comparison
69
        $allowedLower = array_map(strtolower(...), array_map(basename(...), $allowed));
6✔
70

71
        // Check if command is in whitelist (case-insensitive)
72
        if (! in_array(strtolower($cmdName), $allowedLower, true)) {
6✔
73
            throw JobException::forShellCommandNotAllowed($cmdName);
1✔
74
        }
75
    }
76
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc