Skip to content

Set a timeout on the rules fetch request - #43

Open
lbajsarowicz wants to merge 1 commit into
sansecio:mainfrom
lbajsarowicz:fix/rules-fetch-timeout
Open

Set a timeout on the rules fetch request#43
lbajsarowicz wants to merge 1 commit into
sansecio:mainfrom
lbajsarowicz:fix/rules-fetch-timeout

Conversation

@lbajsarowicz

@lbajsarowicz lbajsarowicz commented Sep 5, 2026

Copy link
Copy Markdown

Problem

Model/Rules.php::fetchRules() does:

$curl = $this->curlFactory->create();
$curl->setCredentials($this->config->getLicenseKey(), $this->config->getLicenseKey());
$curl->get(sprintf("%s?v=%d", $this->config->getRulesUrl(), self::PROTOCOL_VERSION));

Magento\Framework\HTTP\Client\Curl has no default timeout, so a connection that stalls (network partition, a slow or hung shield.sansec.io endpoint, a misconfigured proxy) blocks this call forever.

fetchRules() is reached from two places:

  • Cron\SyncRules, registered in the sansec cron group with use_separate_process="1" and a 2-minute schedule_lifetime. Magento's schedule-lifetime cleanup only prunes stale schedule rows after the fact; it doesn't kill the running process, so a hung fetch ties up the cron group's worker indefinitely rather than just failing one run.
  • The sansec:shield:sync-rules CLI command, where the same hang blocks the invoking shell/process (e.g. a deploy script) with no bound.

Model/Report.php:91 already guards its own Curl call the same way this module needs here:

$curl->setTimeout(5);

Change

Added a private constant and a setTimeout() call in Model/Rules.php:

private const RULES_FETCH_TIMEOUT = 30;
$curl = $this->curlFactory->create();
$curl->setCredentials($this->config->getLicenseKey(), $this->config->getLicenseKey());
$curl->setTimeout(self::RULES_FETCH_TIMEOUT);
$curl->get(sprintf("%s?v=%d", $this->config->getRulesUrl(), self::PROTOCOL_VERSION));

No new configuration option, matching how Report.php hardcodes its own timeout rather than exposing it.

Why 30 seconds

Report.php's setTimeout(5) fits a small JSON report POST where a slow response just means a dropped report — losing it is cheap. fetchRules() is different: it's the mechanism that keeps the WAF's ruleset current, its payload can run to a few hundred KB, and its caller is a cron job with a 2-minute schedule lifetime, not a request-path call. 30 seconds is generous enough to complete a normal fetch of that payload size over a slow connection, while still bounding the worst case to a small fraction of the cron schedule window and failing well within a typical deploy-script timeout, instead of hanging indefinitely.

Testing

  • Local test run: PHPUnit (57 tests, 62 assertions) with --fail-on-warning and xmllint on etc/*.xml both passed.
  • No unit test added for this change. Magento\Framework\HTTP\Client\CurlFactory is generated code (produced by bin/magento setup:di:compile), not a source file shipped in magento/framework, so a unit test for Rules::fetchRules()/syncRules() would need an inline stub of that class to satisfy the constructor's type hint. Upstream CI installs only magento/framework, so that stub would be needed there too, for a two-line change that mirrors the existing, untested $curl->setTimeout(5) in Report.php. Given that mismatch between test cost and change size, the safer call is to skip the test rather than add a brittle one.

fetchRules() issued its GET without a timeout, so a stalled connection to
the rules endpoint could hang the cron job or the sync-rules CLI command
indefinitely and block the sansec cron group. Set a 30 second timeout,
matching the pattern already used by Report::sendReport().
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