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
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ composer require infocyph/cachelayer
```php
use Infocyph\CacheLayer\Cache\Cache;

$cache = Cache::pdo('app'); // defaults to sqlite file in sys temp dir
$cache = Cache::pdo('app'); // defaults to sqlite file under sys temp cachelayer/pdo

$cache->setTagged('user:1', ['name' => 'Ada'], ['users'], 300);

Expand All @@ -68,6 +68,29 @@ $cache->invalidateTag('users');
$metrics = $cache->exportMetrics();
```

## Security Hardening

CacheLayer includes optional payload/serialization hardening controls:

```php
$cache
->configurePayloadSecurity(
integrityKey: 'replace-with-strong-secret',
maxPayloadBytes: 8_388_608,
)
->configureSerializationSecurity(
allowClosurePayloads: false,
allowObjectPayloads: false,
);
```

You can also set:

- `CACHELAYER_PAYLOAD_INTEGRITY_KEY`
- `CACHELAYER_MAX_PAYLOAD_BYTES`

See `SECURITY.md` for deployment guidance and threat model notes.

## Documentation

https://docs.infocyph.com/projects/CacheLayer
Expand Down
78 changes: 78 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Security Guide

This document captures CacheLayer hardening guidance and rollout options.

## Threat Model

CacheLayer stores serialized payloads in backends that may be writable by local
or network-adjacent actors if infrastructure is misconfigured. Main risks:

- Deserialization abuse when payloads are tampered.
- Executable cache-file abuse in `phpFiles` adapter.
- Insecure default temp-directory usage in shared environments.

## Implemented Hardening

### 1) Serialization and Payload Hardening

- `CachePayloadCodec` supports signed payloads (HMAC-SHA256).
- 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.
- `ValueSerializer` supports strict mode:
- block closure payloads
- block object payloads
- Native scalar/array serialization paths now decode with
`allowed_classes => false`.

### Runtime API

```php
$cache
->configurePayloadSecurity(
integrityKey: 'replace-with-strong-secret',
maxPayloadBytes: 8_388_608,
)
->configureSerializationSecurity(
allowClosurePayloads: false,
allowObjectPayloads: false,
);
```

### Environment Variables

- `CACHELAYER_PAYLOAD_INTEGRITY_KEY`
- `CACHELAYER_MAX_PAYLOAD_BYTES`

### 2) `phpFiles` Adapter Guardrails

`phpFiles` keeps executable `.php` cache files for performance, so strict
directory controls are required. Runtime checks now reject:

- symlinked cache directories
- world-writable cache directories

Use `phpFiles` only on trusted hosts and private directories.

### 3) Temp-Directory Hardening

Default filesystem locations are now scoped under dedicated cachelayer temp
subdirectories:

- file adapter default base: `sys_get_temp_dir()/cachelayer/files`
- php-files adapter default base: `sys_get_temp_dir()/cachelayer/phpfiles`
- PDO SQLite default: `sys_get_temp_dir()/cachelayer/pdo/cache_<ns>.sqlite`

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

## Recommended Production Profile

1. Set `CACHELAYER_PAYLOAD_INTEGRITY_KEY` to a strong random secret.
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 possible.

## Disclosure

If you discover a security issue, please open a private report to project
maintainers before public disclosure.
2 changes: 1 addition & 1 deletion docs/adapters/file.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Stores one cache payload per file under a namespace directory.

Path layout:

* base dir: provided ``$dir`` or ``sys_get_temp_dir()``
* base dir: provided ``$dir`` or ``sys_get_temp_dir() . '/cachelayer/files'``
* namespace dir: ``cache_<sanitized-namespace>``
* file name: ``hash('xxh128', $key) . '.cache'``

Expand Down
2 changes: 1 addition & 1 deletion docs/adapters/pdo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Examples:

.. code-block:: php

// Default sqlite file under sys temp dir
// Default sqlite file under sys temp: /tmp/cachelayer/pdo/cache_<namespace>.sqlite
$cacheDefault = Cache::pdo('app');

// MySQL / MariaDB
Expand Down
4 changes: 3 additions & 1 deletion docs/adapters/php-files.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Persists cache records as PHP files that return payload arrays.

Path layout:

* base dir: provided ``$dir`` or ``sys_get_temp_dir()``
* base dir: provided ``$dir`` or ``sys_get_temp_dir() . '/cachelayer/phpfiles'``
* namespace dir: ``phpcache_<sanitized-namespace>``
* file name: ``hash('xxh128', $key) . '.php'``

Expand All @@ -21,6 +21,8 @@ Highlights:
* ``setNamespaceAndDirectory()`` supported

Good for environments where opcode cache integration is desired.
Use only in trusted environments, since cache entries are stored as executable
PHP files.

Example
-------
Expand Down
2 changes: 1 addition & 1 deletion docs/adapters/sqlite.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Factory: ``Cache::sqlite(string $namespace = 'default', ?string $file = null)``
Equivalent behavior:

* ``Cache::sqlite($namespace, $file)`` forwards to ``Cache::pdo($namespace, 'sqlite:' . $file)``
* when ``$file`` is ``null``, default path is ``sys_get_temp_dir() . "/cache_<namespace>.sqlite"``
* when ``$file`` is ``null``, default path is ``sys_get_temp_dir() . "/cachelayer/pdo/cache_<namespace>.sqlite"``

Use ``Cache::pdo(...)`` directly if you want to switch to MySQL/MariaDB/PostgreSQL
without changing the rest of your cache usage pattern.
Expand Down
27 changes: 27 additions & 0 deletions docs/cache.rst
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,33 @@ Notes:
* Requires ``gzencode``/``gzdecode`` functions.
* Compression configuration is global (``CachePayloadCodec`` static state).

Payload and Serialization Security
----------------------------------

Methods:

* ``configurePayloadSecurity(?string $integrityKey = null, ?int $maxPayloadBytes = 8388608): self``
* ``configureSerializationSecurity(bool $allowClosurePayloads = true, bool $allowObjectPayloads = true): self``

Example:

.. code-block:: php

$cache
->configurePayloadSecurity(
integrityKey: 'replace-with-strong-secret',
maxPayloadBytes: 8_388_608,
)
->configureSerializationSecurity(
allowClosurePayloads: false,
allowObjectPayloads: false,
);

Environment variables:

* ``CACHELAYER_PAYLOAD_INTEGRITY_KEY``
* ``CACHELAYER_MAX_PAYLOAD_BYTES``

Convenience Features
--------------------

Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Quick Start
adapters/index
cookbook
metrics-and-locking
security
serializer
memoize
functions
29 changes: 29 additions & 0 deletions docs/security.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.. _security:

========
Security
========

CacheLayer includes optional hardening controls for payload integrity,
serialization policy, and filesystem defaults.

Quick hardening setup:

.. code-block:: php

$cache
->configurePayloadSecurity(
integrityKey: 'replace-with-strong-secret',
maxPayloadBytes: 8_388_608,
)
->configureSerializationSecurity(
allowClosurePayloads: false,
allowObjectPayloads: false,
);

Environment variables:

* ``CACHELAYER_PAYLOAD_INTEGRITY_KEY``
* ``CACHELAYER_MAX_PAYLOAD_BYTES``

For detailed policy and rollout guidance, see project root ``SECURITY.md``.
Loading
Loading