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
26 changes: 14 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,24 +129,26 @@ A bulk read asks L1 for the full batch, asks later tiers only for remaining keys
Payload and runtime policy is provided at construction and never stored globally:

```php
use Infocyph\CacheLayer\Cache\Cache;
use Infocyph\CacheLayer\Cache\CacheOptions;

$options = new CacheOptions(
integrityKey: $_ENV['CACHE_INTEGRITY_KEY'],
maxPayloadBytes: 8_388_608,
compressionThreshold: 4096,
compressionLevel: 6,
allowClosures: false,
allowObjects: false,
failOpen: true,
);

$cache = Cache::redis('app', options: $options);
function createCache(string $integrityKey): Cache
{
return Cache::redis('app', options: new CacheOptions(
integrityKey: $integrityKey,
maxPayloadBytes: 8_388_608,
compressionThreshold: 4096,
compressionLevel: 6,
allowClosures: false,
allowObjects: false,
failOpen: true,
));
}
```

Records use only the CacheLayer v2 markers `cl2:`, `cl2-gz:`, and `cl2-sig:`. Compression is threshold-based and retained only when smaller. HMAC verification, payload bounds, bounded decompression, and deserialization policy are isolated per cache instance. Corrupt payloads are safe misses.

Construction and configuration errors throw. Runtime backend failures default to fail-open: reads become misses, writes/deletes return `false`, and `backend_failure` is recorded. Set `failOpen: false` to propagate runtime failures. `CacheOptions::fromEnvironment()` explicitly reads `CACHELAYER_PAYLOAD_INTEGRITY_KEY` and `CACHELAYER_MAX_PAYLOAD_BYTES`; environment state is never read implicitly.
Construction and configuration errors throw. Runtime backend failures default to fail-open: reads become misses, writes/deletes return `false`, and `backend_failure` is recorded. Set `failOpen: false` to propagate runtime failures. Pass deploy-varying values from the application's composition root; CacheLayer never reads process environment state.

## Node Cache

Expand Down
28 changes: 15 additions & 13 deletions docs/cache.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,21 @@ Immutable options
use Infocyph\CacheLayer\Cache\Cache;
use Infocyph\CacheLayer\Cache\CacheOptions;

$cache = Cache::redis('app', options: new CacheOptions(
integrityKey: $_ENV['CACHE_INTEGRITY_KEY'],
maxPayloadBytes: 8_388_608,
compressionThreshold: 4096,
allowClosures: false,
allowObjects: false,
failOpen: true,
));

``CacheOptions::fromEnvironment()`` is the explicit opt-in for
``CACHELAYER_PAYLOAD_INTEGRITY_KEY`` and ``CACHELAYER_MAX_PAYLOAD_BYTES``.
Options are isolated per instance and cannot be changed after record processing
begins.
function createCache(string $integrityKey): Cache
{
return Cache::redis('app', options: new CacheOptions(
integrityKey: $integrityKey,
maxPayloadBytes: 8_388_608,
compressionThreshold: 4096,
allowClosures: false,
allowObjects: false,
failOpen: true,
));
}

Pass deploy-varying values from the application's composition root. CacheLayer
does not read process environment state. Options are isolated per instance and
cannot be changed after record processing begins.

Runtime failure policy
----------------------
Expand Down
26 changes: 14 additions & 12 deletions docs/security.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,18 @@ Per-instance construction API:
use Infocyph\CacheLayer\Cache\Cache;
use Infocyph\CacheLayer\Cache\CacheOptions;

$cache = Cache::redis('app', options: new CacheOptions(
integrityKey: 'replace-with-strong-secret',
maxPayloadBytes: 8_388_608,
allowClosures: false,
allowObjects: false,
));

``CacheOptions::fromEnvironment()`` explicitly reads:

* ``CACHELAYER_PAYLOAD_INTEGRITY_KEY``
* ``CACHELAYER_MAX_PAYLOAD_BYTES``
function createCache(string $integrityKey): Cache
{
return Cache::redis('app', options: new CacheOptions(
integrityKey: $integrityKey,
maxPayloadBytes: 8_388_608,
allowClosures: false,
allowObjects: false,
));
}

Pass secret material from the application's composition root. CacheLayer does
not read process environment state.

2) ``phpFiles`` Adapter Guardrails
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -98,7 +99,8 @@ different timeout, TLS, retry, or socket-context settings.
Recommended Production Profile
------------------------------

1. Set ``CACHELAYER_PAYLOAD_INTEGRITY_KEY`` to a strong random secret.
1. Pass a strong random secret as ``CacheOptions::$integrityKey`` from the
application's composition root.
2. Disable closure/object payloads unless explicitly required.
3. Use explicit, private cache directories outside shared temp space.
4. Prefer non-executable file storage adapters over ``phpFiles`` where
Expand Down
12 changes: 0 additions & 12 deletions src/Cache/CacheOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,4 @@ public function __construct(
}
}

public static function fromEnvironment(): self
{
$integrityKey = getenv('CACHELAYER_PAYLOAD_INTEGRITY_KEY');
$maxPayloadBytes = getenv('CACHELAYER_MAX_PAYLOAD_BYTES');

return new self(
integrityKey: is_string($integrityKey) && $integrityKey !== '' ? $integrityKey : null,
maxPayloadBytes: is_string($maxPayloadBytes) && ctype_digit($maxPayloadBytes)
? (int) $maxPayloadBytes
: 8_388_608,
);
}
}
4 changes: 4 additions & 0 deletions tests/Cache/ArchitectureHardeningTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,10 @@ public function resetOperationCounts(): void
->and(Cache::memory('tenant-a.v1'))->toBeInstanceOf(Cache::class);
});

test('cache options do not load process environment state', function () {
expect(method_exists(CacheOptions::class, 'fromEnvironment'))->toBeFalse();
});

test('missing tag metadata cannot resurrect a tagged record', function () {
$adapter = new \Infocyph\CacheLayer\Cache\Adapter\ArrayCacheAdapter('generation-loss');
$cache = new Cache($adapter);
Expand Down