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

daycry / jobs / 26886467550

03 Jun 2026 01:01PM UTC coverage: 88.948% (+14.0%) from 74.974%
26886467550

push

github

web-flow
v3.0: single clean architecture (remove V1, lease-based queues, secure-by-default)

Complete v3.0 rewrite into a single, clean architecture. The v1 API and the V2\ scaffolding
are removed (no facade, no dual code); the package passes PHPStan level 6 + strict-rules +
codeigniter with NO baseline.

- Definition: Jobs::define()->...->dispatch() fluent builder -> immutable JobDefinition.
- Handlers decoupled from the god-object (JobHandlerInterface / AbstractJobHandler / TypedJobHandler + JobContext).
- One QueueBackend contract (enqueue/fetch(lease)/ack/nack(delay)/abandon/reapExpired) with 5 backends:
  Sync, Database, Redis, Beanstalk, ServiceBus.
- Runtime: one attempt per fetch; real interrupting Timeout; opt-in idempotency; single-instance lock.
- Worker/Cron: jobs:queue:work, jobs:queue:reap, jobs:cronjob:run, jobs:queue:purge.
- Secure-by-default: HMAC-signed envelopes, per-queue handler allowlist, ShellHandler deny-by-default,
  EventHandler allowlist, UrlHandler anti-SSRF.

Resolves audit findings #1,#2,#3,#4,#5,#6,#7,#8,#10,#12,#13,#17,#18,#19,#20,#22.
Tests: 359 (Beanstalk live); line coverage 88.9%; PHPStan/Psalm/Rector/cs green on PHP 8.2-8.5.

BREAKING CHANGE: v1 API removed. See docs/MIGRATION-v1-to-v3.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

983 of 1103 new or added lines in 43 files covered. (89.12%)

15 existing lines in 3 files now uncovered.

1497 of 1683 relevant lines covered (88.95%)

7.55 hits per line

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

92.5
/src/Handlers/ShellHandler.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\Handlers;
15

16
use Daycry\Jobs\Exceptions\JobException;
17
use Daycry\Jobs\Execution\JobContext;
18

19
/**
20
 * Executes an OS command via proc_open with an argv array (never through /bin/sh -c),
21
 * which removes the shell metacharacter attack surface entirely.
22
 *
23
 * Deny-by-default: with an empty allowlist (Config\Jobs::$allowedShellCommands) and
24
 * $allowAllShellCommands = false, execution is refused. Configure the allowlist with
25
 * absolute paths, or opt out explicitly via $allowAllShellCommands = true.
26
 *
27
 * Payload accepts either a string (split on whitespace) or, preferably, a list<string>
28
 * argv array so arguments with spaces survive intact.
29
 */
30
final class ShellHandler extends AbstractJobHandler
31
{
32
    public function handle(JobContext $ctx): mixed
33
    {
34
        $argv = $this->toArgv($ctx->payload);
9✔
35
        if ($argv === []) {
8✔
36
            throw JobException::validationError('ShellHandler payload must be a non-empty command.');
1✔
37
        }
38

39
        $this->authorize($argv[0]);
7✔
40

41
        $descriptors = [
3✔
42
            1 => ['pipe', 'w'],
3✔
43
            2 => ['pipe', 'w'],
3✔
44
        ];
3✔
45

46
        $process = proc_open($argv, $descriptors, $pipes);
3✔
47
        if (! is_resource($process)) {
3✔
NEW
48
            throw JobException::validationError('ShellHandler could not start the process.');
×
49
        }
50

51
        $stdout = stream_get_contents($pipes[1]);
3✔
52
        $stdout = $stdout === false ? '' : $stdout;
3✔
53
        fclose($pipes[1]);
3✔
54
        fclose($pipes[2]);
3✔
55
        proc_close($process);
3✔
56

57
        // Preserve the v1-style array-of-output-lines return shape.
58
        $lines = explode("\n", str_replace("\r\n", "\n", $stdout));
3✔
59

60
        return array_values(array_filter($lines, static fn (string $line): bool => $line !== ''));
3✔
61
    }
62

63
    /**
64
     * @return list<string>
65
     */
66
    private function toArgv(mixed $payload): array
67
    {
68
        if (is_array($payload)) {
9✔
69
            return array_values(array_map(static fn ($v): string => (string) $v, $payload));
4✔
70
        }
71

72
        if (is_string($payload)) {
5✔
73
            $parts = preg_split('/\s+/', trim($payload));
4✔
74
            if ($parts === false) {
4✔
NEW
75
                $parts = [];
×
76
            }
77

78
            return array_values(array_filter($parts, static fn (string $p): bool => $p !== ''));
4✔
79
        }
80

81
        throw JobException::validationError('ShellHandler payload must be a string or an argv array.');
1✔
82
    }
83

84
    private function authorize(string $binary): void
85
    {
86
        $cfg     = config('Jobs');
7✔
87
        $allowed = $cfg->allowedShellCommands ?? [];
7✔
88

89
        if ($allowed === []) {
7✔
90
            if (($cfg->allowAllShellCommands ?? false) === true) {
5✔
91
                return;
3✔
92
            }
93

94
            throw JobException::forShellCommandsNotConfigured();
2✔
95
        }
96

97
        $candidate = realpath($binary);
2✔
98
        $candidate = $candidate === false ? $binary : $candidate;
2✔
99

100
        foreach ($allowed as $entry) {
2✔
101
            $resolved = realpath((string) $entry);
2✔
102
            $resolved = $resolved === false ? (string) $entry : $resolved;
2✔
103
            if ($candidate === $resolved) {
2✔
NEW
104
                return;
×
105
            }
106
        }
107

108
        throw JobException::forShellCommandNotAllowed($binary);
2✔
109
    }
110
}
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