Skip to content

Commit 9ba1a0d

Browse files
always encrypt data
1 parent 6975c73 commit 9ba1a0d

File tree

1 file changed

+170
-0
lines changed

1 file changed

+170
-0
lines changed

CLAUDE.md

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
This is a Laravel package that enables privacy-preserving encrypted search functionality for Eloquent models. It allows searching encrypted data using deterministic hashed tokens without exposing plaintext values.
8+
9+
**Core Concept**: When sensitive data is encrypted (e.g., using Laravel's `encrypted` cast), it becomes unsearchable. This package solves that by maintaining a separate search index of SHA-256 hashed tokens that can be queried without compromising security.
10+
11+
## Architecture
12+
13+
### Token Generation Flow
14+
15+
1. **Normalization** (`src/Support/Normalizer.php`): Text is normalized (lowercased, diacritics removed)
16+
2. **Token Generation** (`src/Support/Tokens.php`): SHA-256 hashes are created with a secret pepper
17+
- **Exact tokens**: Hash of full normalized value (for exact match queries)
18+
- **Prefix tokens**: Multiple hashes for progressive prefixes (for "starts with" queries)
19+
3. **Storage**: Tokens stored in either:
20+
- Database table `encrypted_search_index` (default)
21+
- Elasticsearch index (when enabled)
22+
23+
### Key Components
24+
25+
**Trait: `HasEncryptedSearchIndex`** (`src/Traits/HasEncryptedSearchIndex.php`)
26+
- Applied to Eloquent models to enable encrypted search
27+
- Hooks into model lifecycle events (created, updated, deleted, restored)
28+
- Provides query scopes: `encryptedExact()` and `encryptedPrefix()`
29+
- Configuration resolution priority: 1) `$encryptedSearch` property, 2) PHP attributes, 3) auto-detected casts
30+
31+
**Service Provider** (`src/EncryptedSearchServiceProvider.php`)
32+
- Registers package configuration and migrations
33+
- Registers Artisan command `encryption:index-rebuild`
34+
- Attaches global observer for Eloquent events
35+
36+
**Elasticsearch Integration** (`src/Services/ElasticsearchService.php`)
37+
- Lightweight HTTP-based wrapper around Elasticsearch REST API
38+
- Used when `ENCRYPTED_SEARCH_ELASTIC_ENABLED=true`
39+
- Stores tokens in ES instead of database for horizontal scalability
40+
41+
**Model Configuration**
42+
Models can specify searchable fields via:
43+
```php
44+
// Method 1: Property array
45+
protected array $encryptedSearch = [
46+
'first_names' => ['exact' => true, 'prefix' => true],
47+
'last_names' => ['exact' => true, 'prefix' => true],
48+
];
49+
50+
// Method 2: PHP Attributes (overrides property)
51+
#[EncryptedSearch(exact: true, prefix: true)]
52+
public string $last_names;
53+
54+
// Method 3: Auto-detection (enabled by default)
55+
// Any field with 'encrypted' cast is automatically indexed for exact search
56+
```
57+
58+
### Database Structure
59+
60+
The `encrypted_search_index` table stores:
61+
- `model_type`: Fully qualified model class name
62+
- `model_id`: Primary key of the model
63+
- `field`: Name of the encrypted field
64+
- `type`: Either 'exact' or 'prefix'
65+
- `token`: SHA-256 hash (64-char hex string)
66+
67+
## Common Commands
68+
69+
### Testing
70+
```bash
71+
# Run all tests
72+
vendor/bin/phpunit
73+
74+
# Run with detailed output
75+
vendor/bin/phpunit --testdox --colors=always
76+
77+
# Run single test
78+
vendor/bin/phpunit --filter EncryptedSearchIntegrationTest
79+
```
80+
81+
### Development Setup
82+
```bash
83+
# Install dependencies
84+
composer install
85+
86+
# Run tests for specific Laravel version
87+
composer require "illuminate/support:^11.0" "orchestra/testbench:^9.0" --no-update
88+
composer update
89+
```
90+
91+
### Index Management
92+
```bash
93+
# Rebuild index for a model
94+
php artisan encryption:index-rebuild "App\\Models\\Client"
95+
96+
# Short form (auto-resolves to App\Models namespace)
97+
php artisan encryption:index-rebuild Client
98+
99+
# Process in smaller chunks (default is 100)
100+
php artisan encryption:index-rebuild Client --chunk=50
101+
```
102+
103+
## Configuration
104+
105+
Located in `config/encrypted-search.php`:
106+
107+
- `search_pepper`: Secret value added to all hashes (CRITICAL: must be in `.env`)
108+
- `max_prefix_depth`: Maximum prefix length for prefix tokens (default: 6)
109+
- `auto_index_encrypted_casts`: Auto-detect and index fields with `encrypted` cast (default: true)
110+
- `elasticsearch.enabled`: Use Elasticsearch instead of database (default: false)
111+
- `elasticsearch.host`: ES connection URL
112+
- `elasticsearch.index`: ES index name for tokens
113+
114+
## Testing Strategy
115+
116+
Tests use Orchestra Testbench to simulate a full Laravel environment with in-memory SQLite database. The test suite covers:
117+
- Token generation (exact and prefix)
118+
- Model lifecycle events (create, update, delete, restore)
119+
- Query scopes (`encryptedExact`, `encryptedPrefix`)
120+
- Configuration resolution (attributes, properties, auto-detection)
121+
122+
Test environment variables set in `phpunit.xml.dist`:
123+
- `SEARCH_PEPPER=test-pepper-secret`
124+
- `DB_CONNECTION=sqlite`
125+
- `DB_DATABASE=:memory:`
126+
127+
## Multi-Version Compatibility
128+
129+
The package supports Laravel 9-12 and PHP 8.1-8.4. The CI matrix (`.github/workflows/tests.yml`) tests all combinations:
130+
- Laravel 9 + PHP 8.1
131+
- Laravel 10 + PHP 8.2
132+
- Laravel 11 + PHP 8.3
133+
- Laravel 12 + PHP 8.4
134+
135+
When making changes, ensure compatibility across all versions. The package uses only features available in Laravel 9+.
136+
137+
## Security Model
138+
139+
- **Tokens are deterministic**: Same input always produces same hash (required for searching)
140+
- **Pepper prevents rainbow tables**: Even with token dump, plaintext cannot be recovered without pepper
141+
- **Detached index**: Search tokens stored separately from encrypted data
142+
- **No blind indexes**: Primary tables contain no searchable metadata
143+
- **One-way hashing**: SHA-256 is cryptographically secure and irreversible
144+
145+
## Important Implementation Notes
146+
147+
1. **Elasticsearch Mode**: When enabled, database writes to `encrypted_search_index` are skipped entirely. The trait automatically routes to `ElasticsearchService` instead.
148+
149+
2. **Index Rebuild Command**: The command (`RebuildIndex`) supports short model names and auto-resolves them under `App\Models` namespace if not fully qualified.
150+
151+
3. **SoftDeletes Support**: The trait checks for `SoftDeletes` and hooks into `restored` and `forceDeleted` events appropriately.
152+
153+
4. **Query Scopes**: Both `encryptedExact()` and `encryptedPrefix()` use subqueries with `whereIn()` for efficient database-level filtering. When Elasticsearch is enabled, these need to be modified to query ES instead (currently database-only).
154+
155+
5. **Normalization**: All text is normalized before hashing (see `Normalizer::normalize()`). This ensures consistent matching regardless of case or diacritics.
156+
157+
## Package Publishing
158+
159+
When publishing this package, ensure:
160+
- Configuration published: `--tag=config`
161+
- Migration published: `--tag=migrations`
162+
- Migration filename includes timestamp for proper ordering
163+
164+
Installation flow:
165+
```bash
166+
composer require ginkelsoft/laravel-encrypted-search-index
167+
php artisan vendor:publish --provider="Ginkelsoft\EncryptedSearch\EncryptedSearchServiceProvider" --tag=config
168+
php artisan vendor:publish --provider="Ginkelsoft\EncryptedSearch\EncryptedSearchServiceProvider" --tag=migrations
169+
php artisan migrate
170+
```

0 commit comments

Comments
 (0)