The Joomla fork diverged from upstream around Dec 2023 (commit 7c2c58c5) and has 18 Joomla-specific commits on top. Meanwhile, upstream has continued with 25+ commits including major refactoring. The two have diverged significantly.
File: src/Client/Updater.php
Joomla added lines to recreate the SignatureVerifier and UniversalVerifier after each root metadata update in the updateRoot() loop (between steps 5.3.7 and 5.3.8 of the TUF spec).
Upstream does not do this — it creates the verifier once before calling updateRoot() and never recreates it during the root rotation loop. This means upstream validates intermediate root versions using the original root's keys, while Joomla validates each new root using the previous root's keys (as the TUF spec arguably requires for signature verification with key rotation).
This is the most significant divergence and could be a real bug in upstream or a difference in spec interpretation.
// In Joomla's updateRoot() loop, after § 5.3.7:
+ // Recreate Verifier with new root metadata
+ $this->signatureVerifier = SignatureVerifier::createFromRootMetadata($rootData);
+ $this->universalVerifier = new UniversalVerifier($this->storage, $this->signatureVerifier, $this->metadataExpiration);File: src/CanonicalJsonTrait.php
Joomla moved recursive sortKeys() calls before the array_is_list() check and changed visibility from private to protected. The upstream version recurses after sorting the current level; Joomla recurses first, then sorts.
This changes behavior for nested arrays inside indexed (list) arrays — Joomla ensures sub-arrays inside lists still get their keys sorted, even though the list itself doesn't need sorting. Upstream does the same thing but in a different order (recurse after sort vs before).
Note: upstream later refactored this in March 2024 to a one-liner (if (!array_is_list($data) && !ksort(...))), with recursion always happening after.
- private static function sortKeys(array &$data): void
+ protected static function sortKeys(array &$data): void
{
+ // Apply recursively on potential subarrays
+ foreach ($data as $key => $value) {
+ if (is_array($value)) {
+ static::sortKeys($data[$key]);
+ }
+ }
+
// If $data is numerically indexed, the keys are already sorted
if (array_is_list($data)) {
return;
}
if (!ksort($data, SORT_STRING)) {
throw new \RuntimeException("Failure sorting keys.");
}
- foreach ($data as $key => $value) {
- if (is_array($value)) {
- static::sortKeys($data[$key]);
- }
- }
}File: src/Metadata/MetadataBase.php
- Joomla: Regex
/^1(\.[0-9]+){1,2}$/— accepts1.0,1.0.31, and even1.31 - Upstream: Uses
AtLeastOneOfwithIdenticalTo('1.0')ORRegex('/^1\.[0-9]+\.[0-9]+$/')— accepts1.0and1.x.ybut not1.31
Both address the same issue (the original regex rejected 1.0), but the upstream solution is stricter.
Files: src/Metadata/MetadataBase.php, src/Metadata/TargetsMetadata.php
Joomla removed the toNormalizedArray() method entirely and inlined the logic directly into toCanonicalJson(). Upstream kept toNormalizedArray() as a separate method that toCanonicalJson() calls — it's used as an override point in TargetsMetadata.
Joomla's TargetsMetadata::toCanonicalJson() also explicitly calls self::sortKeys() before encoding, while upstream relies on encodeJson() to handle sorting.
Files: Multiple exception classes, Repository.php, DelegatedRole.php, SnapshotVerifier.php, TargetsVerifier.php
Joomla added ?Type nullable syntax to replace the deprecated implicit nullable (Type $param = null) across ~8 files. Upstream addressed PHP 8.4 compat separately in their own test matrix update (May 2025).
File: composer.json
Joomla allows ^1.5 || ^2.0. Upstream's current composer.json likely handles this differently (they've had broader dependency updates).
Since the fork point, upstream has added:
- Static caching of
createFromJson()for performance - Performance optimizations for delegated role lookups
- Symfony 7.3 deprecation fixes
- PHP 8.4 and 8.5 test coverage
- Complete removal of Prophecy (testing framework migration)
- Read-only properties replacing getter methods
- TUF spec update to 1.0.33
- Removal of
keyid_hash_algorithmssupport - Various code quality improvements
| Change | Joomla Fork | Upstream |
|---|---|---|
| Verifier recreation on root rotation | Yes (security fix) | No |
| Canonical JSON sort order | Recurse-first | Sort-first (refactored) |
spec_version accepts 1.0 |
Via relaxed regex | Via AtLeastOneOf |
toNormalizedArray() |
Removed | Kept |
| PHP 8.4 nullable syntax | Backported | Handled separately |
| guzzle/promises v2 | ^1.5 || ^2.0 |
Updated independently |
| Performance optimizations | None | Static cache, delegated role optimization |
| TUF spec version | 1.0.32 | 1.0.33 |
The verifier recreation fix is the most interesting — it's arguably a spec compliance fix that upstream hasn't adopted. The canonical JSON changes also address real bugs with nested array sorting that upstream fixed differently.