Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/security.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Implemented Hardening
* Signed payloads are rejected when integrity verification fails.
* When an integrity key is configured, unsigned payloads are rejected.
* Maximum payload size can be enforced at decode time.
* Compressed payload expansion is capped before deserialization.
* ``ValueSerializer`` supports strict mode:

* block closure payloads
Expand Down Expand Up @@ -76,6 +77,17 @@ subdirectories:

These paths are created with restrictive permissions and world-writable checks.

The shared-memory adapter also stores its ``ftok`` token in a private
``cachelayer/shared-memory`` directory, creates the segment for the current
user only, and serializes read-modify-write operations with a filesystem lock.

4) Network Timeouts
~~~~~~~~~~~~~~~~~~~

Redis/Valkey connections created from a DSN use bounded one-second connect and
read timeouts. Inject a preconfigured client when an application needs
different timeout, TLS, retry, or socket-context settings.

Recommended Production Profile
------------------------------

Expand Down
2 changes: 1 addition & 1 deletion src/Cache/Adapter/AbstractCacheAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public function commit(): bool
{
$ok = true;
foreach ($this->deferred as $key => $item) {
$ok = $ok && $this->save($item);
$ok = $this->save($item) && $ok;
unset($this->deferred[$key]);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Cache/Adapter/ApcuCacheAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public function deleteItems(array $keys): bool
{
$ok = true;
foreach ($keys as $k) {
$ok = $ok && $this->deleteItem($k);
$ok = $this->deleteItem($k) && $ok;
}

return $ok;
Expand Down
20 changes: 16 additions & 4 deletions src/Cache/Adapter/CachePayloadCodec.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ public static function decode(string $blob): ?array
}

$expanded = self::expandIfCompressed($verifiedBlob);
if ($expanded === null) {
return null;
}
if (self::isPayloadTooLarge($expanded)) {
return null;
}
Expand Down Expand Up @@ -216,7 +219,7 @@ private static function decodeFormattedPayload(array $decoded): ?array
];
}

private static function expandIfCompressed(string $blob): string
private static function expandIfCompressed(string $blob): ?string
{
if (!str_starts_with($blob, self::COMPRESSED_PREFIX)) {
return $blob;
Expand All @@ -225,12 +228,21 @@ private static function expandIfCompressed(string $blob): string
$payload = substr($blob, strlen(self::COMPRESSED_PREFIX));
$raw = base64_decode($payload, true);
if ($raw === false || !function_exists('gzdecode')) {
return $blob;
return null;
}

$decoded = gzdecode($raw);
$maximumLength = self::$maxPayloadBytes === null
? 0
: min(self::$maxPayloadBytes, PHP_INT_MAX - 1) + 1;
set_error_handler(static fn(): bool => true);

try {
$decoded = gzdecode($raw, $maximumLength);
} finally {
restore_error_handler();
}

return is_string($decoded) ? $decoded : $blob;
return is_string($decoded) ? $decoded : null;
}

private static function isPayloadTooLarge(string $blob): bool
Expand Down
6 changes: 3 additions & 3 deletions src/Cache/Adapter/FileCacheAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public function clear(): bool
$files = [];
}
foreach ($files as $f) {
$ok = $ok && (!is_file($f) || unlink($f));
$ok = (!is_file($f) || unlink($f)) && $ok;
}
$this->deferred = [];

Expand All @@ -75,7 +75,7 @@ public function deleteItems(array $keys): bool
{
$ok = true;
foreach ($keys as $k) {
$ok = $ok && $this->deleteItem($k);
$ok = $this->deleteItem($k) && $ok;
}

return $ok;
Expand Down Expand Up @@ -128,7 +128,7 @@ public function save(CacheItemInterface $item): bool
return false;
}

if (file_put_contents($tmp, $blob) === false) {
if (file_put_contents($tmp, $blob, LOCK_EX) === false) {
if (is_file($tmp)) {
unlink($tmp);
}
Expand Down
32 changes: 32 additions & 0 deletions src/Cache/Adapter/GenericCacheItemPoolBehavior.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Infocyph\CacheLayer\Cache\Adapter;

use Infocyph\CacheLayer\Cache\Item\GenericCacheItem;
use Psr\Cache\CacheItemInterface;

/** @internal */
trait GenericCacheItemPoolBehavior
{
public function hasItem(string $key): bool
{
return $this->getItem($key)->isHit();
}

/**
* @param array $keys The keys argument.
* @phpstan-param list<string> $keys
* @phpstan-return array<string, GenericCacheItem>
*/
public function multiFetch(array $keys): array
{
return $this->multiFetchItems($keys, $this->getItem(...));
}

protected function supportsItem(CacheItemInterface $item): bool
{
return $item instanceof GenericCacheItem;
}
}
2 changes: 1 addition & 1 deletion src/Cache/Adapter/PhpFilesCacheAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public function save(CacheItemInterface $item): bool
return false;
}

if (file_put_contents($tmp, $code) === false) {
if (file_put_contents($tmp, $code, LOCK_EX) === false) {
if (is_file($tmp)) {
unlink($tmp);
}
Expand Down
Loading