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
2 changes: 1 addition & 1 deletion .php-cs-fixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@
'visibility_required' => true,
'native_function_invocation' => true,
'native_constant_invocation' => true,
'mb_str_functions' => true,
'mb_str_functions' => false, // Disabled: we handle binary data (sodium keys) that requires strlen() not mb_strlen()
'modernize_strpos' => true,
]);
266 changes: 266 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
# AGENTS.md - TOR-PHP

## 🎯 Core Concept

**TOR-PHP** is a PHP library providing **Tor network integration** for anonymous HTTP requests and ControlPort management with a type-safe API.

### Solution

Two main components:
- **TorHttpClient**: Symfony HttpClient implementation for HTTP requests through Tor SOCKS5 proxy
- **TorControlClient**: Socket-based client for Tor Control Protocol (circuit management, onion services, configuration)

---

## 🏗️ Architecture

### Main Components

#### 1. **TorHttpClient** (HTTP via Tor)
Implements `HttpClientInterface`, wraps Symfony HttpClient with SOCKS5 proxy configuration.

Key methods:
- `request()`: HTTP requests through Tor
- `newIdentity()`: Request new Tor circuit
- `getExitNodes()`: Get list of exit nodes (Generator)

#### 2. **TorControlClient** (Tor Control Protocol)
Manages Tor via ControlPort socket connection.

Key methods:
- `getCircuits()`, `signalNewnym()`, `waitForCircuitBuild()`: Circuit management
- `getConfigValue()`, `setConfigValue()`: Configuration
- `addOnionService()`, `deleteOnionService()`, `listOnionServices()`: Onion services
- `setEvents()`: Subscribe to Tor events

#### 3. **TorSocketClient** (Transport)
Low-level socket communication with Tor:
- `connect()`, `close()`: Connection management
- `writeLine()`, `readLine()`, `readUntil()`: Protocol I/O

#### 4. **Model Layer** (readonly classes)
- `Circuit`: Circuit information
- `OnionService`: Hidden service data
- `PortMapping`: Port mapping for onion services
- `ExitNode`: Exit node information

#### 5. **Exceptions**
- `SocketException`: Socket errors (connection, timeout, I/O)

---

## 💡 Design Patterns

- **Facade**: Clients hide protocol complexity
- **Decorator**: TorHttpClient decorates HttpClientInterface
- **Immutable Value Objects**: All models are `readonly`
- **Generator**: `getExitNodes()` yields lazily for memory efficiency
- **Dependency Injection**: Constructor injection for testability
- **Sensitive Parameter Protection**: `#[\SensitiveParameter]` for credentials

---

## 🧪 Testing

### Requirements
Most tests require running Tor with ControlPort enabled:
```bash
# Install: brew install tor (macOS) or sudo apt install tor (Linux)
# Configure /etc/tor/torrc:
ControlPort 9051
CookieAuthentication 0 # or HashedControlPassword <hash>
```

### Running Tests
```bash
make test # Run tests
make phpstan # Static analysis (level 8)
make phpcs # Linting/formatting
```

### Mocking
```php
$mockClient = $this->createMock(HttpClientInterface::class);
$torClient = new TorHttpClient(httpClient: $mockClient);
```

---

## 🎨 Code Style & Standards

### PHP Version & Standards
- **PHP 8.4+** required
- **PSR-12** code style (enforced by php-cs-fixer)
- **PHPStan level 8** (no baseline, all issues must be fixed)
- **Strict types**: Every file starts with `declare(strict_types=1);`

### Key Rules
- `@PSR12` and `@Symfony` rulesets
- `declare_strict_types`: Enforced
- `trailing_comma_in_multiline`: Required
- `native_function_invocation`: Use native functions with backslash
- `yoda_style`: Disabled (natural comparisons)

### Type Safety
```php
// ✅ Correct
declare(strict_types=1);

public function request(string $method, string $url, array $options = []): ResponseInterface
readonly class Circuit { ... }
#[\SensitiveParameter] private readonly ?string $password = null
public function getExitNodes(): \Generator

// ❌ Wrong
public function request($method, $url, $options = []) // No types
class Circuit { public string $id; } // Not readonly for value object
private ?string $password = null // Missing SensitiveParameter
```

### Naming Conventions
```php
private const string TOR_DEFAULT_HOST = '127.0.0.1'; // Constants: UPPER_SNAKE_CASE with type
public function signalNewnym(): void // Methods: camelCase
private readonly TorSocketClient $socketClient; // Properties: camelCase
```

### Documentation
```php
// ✅ Class-level PHPDoc always
/**
* A client for interacting with Tor via the ControlPort.
*
* @author Edouard Courty
*/
class TorControlClient

// ✅ Method PHPDoc only for array structures
/** @param array<string, mixed> $options */
public function request(string $method, string $url, array $options = []): ResponseInterface

// ✅ No PHPDoc when types are clear
public function close(): void
```

### Error Handling
```php
// ✅ Specific exceptions
try {
$response->getStatusCode();
} catch (TransportExceptionInterface $e) {
// Handle
}

// ✅ Meaningful messages
throw new SocketException("Failed to connect to Tor at {$host}:{$port}");
```

---

## ⚡ Performance & Security

### Performance
- Reuse client instances (avoid creating new instances per request)
- `TorControlClient`: Call `close()` in finally blocks
- `newIdentity()` is expensive (3-10s circuit build time)
- `getExitNodes()` returns Generator (don't use `iterator_to_array()`)

### Security
- All credentials use `#[\SensitiveParameter]` (prevents stack trace leaks)
- Store onion private keys securely: `chmod 600 onion.key`
- Never log passwords or private keys
- Authentication priority: password > cookie > none

---

## 📋 Developer Checklist

### Adding Features
1. ✅ Implement in appropriate client class
2. ✅ Add readonly model classes for new entities
3. ✅ Write tests (may need running Tor)
4. ✅ Run `make phpcs` (linting)
5. ✅ Run `make phpstan` (static analysis)
6. ✅ Update README.md, CHANGELOG.md, add example to `examples/`

### Fixing Bugs
1. ✅ Write failing test reproducing bug
2. ✅ Fix with minimal changes
3. ✅ Run `make test`
4. ✅ Run `make phpcs` and `make phpstan`
5. ✅ Update CHANGELOG.md

### Before Committing
1. ✅ All tests pass: `make test`
2. ✅ No PHPStan errors: `make phpstan`
3. ✅ Code formatted: `make phpcs`
4. ✅ Commit message: `[type] description` (types: feat, fix, docs, refactor, test, chore)

### Debugging
- Tor running? → `ps aux | grep tor`
- ControlPort enabled? → Check `/etc/tor/torrc` for `ControlPort 9051`
- Authentication? → Verify password or cookie
- Timeout? → Increase timeout in constructor
- Wrong response? → Check Tor logs: `tail -f /var/log/tor/notices.log`

---

## Remarks for AI Agents

### Critical Rules
- **NEVER commit or push** unless explicitly instructed
- **Always run** `make phpcs`, `make phpstan`, `make test` before completing work
- **Maintain backward compatibility** unless explicitly breaking change
- **PHPStan level 8** must pass with no baseline

### When Adding Features
1. Check Tor control-spec for feature support
2. Add readonly model classes for new entities
3. Write tests (may require Tor instance)
4. Update README.md, CHANGELOG.md, add example

### When Fixing Bugs
1. Reproduce with test first
2. Fix with minimal changes
3. Don't fix unrelated issues

### When Refactoring
1. Tests must pass before refactoring
2. Tests must still pass after without modification
3. If tests need changes, refactoring may break API

### Common Pitfalls
- ❌ Missing `declare(strict_types=1)`
- ❌ Missing type hints
- ❌ Using mutable classes for value objects (use `readonly`)
- ❌ Missing `#[\SensitiveParameter]` for credentials
- ❌ Ignoring PHPStan errors
- ❌ Breaking backward compatibility
- ❌ Adding unnecessary dependencies

### Code Review Checklist
- [ ] `declare(strict_types=1)` at top of file
- [ ] All parameters and return types declared
- [ ] PHPStan level 8 passes
- [ ] PHP-CS-Fixer passes
- [ ] Tests written and passing
- [ ] No sensitive data logged
- [ ] `readonly` used for value objects
- [ ] `#[\SensitiveParameter]` for passwords/keys
- [ ] Generator used for large datasets
- [ ] Exceptions properly typed

---

## 📚 Quick Reference

### Useful Resources
- Tor Control Protocol: https://spec.torproject.org/control-spec/
- Tor Manual: https://2019.www.torproject.org/docs/tor-manual.html.en
- Symfony HttpClient: https://symfony.com/doc/current/http_client.html

### Project Files
- Source: `/src` (TorHttpClient, TorControlClient, Transport, Model, Helper, Exception)
- Tests: `/tests` (mirror structure of `/src`)
- Examples: `/examples`
- Config: `.php-cs-fixer.php`, `phpstan.neon`, `Makefile`
25 changes: 24 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,34 @@
This file contains information about every addition, update and deletion in the `ecourty/tor-php` library.
It is recommended to read this file before updating the library to a new version.

## v1.2.0

#### Additions

- Added [`KeyPairGenerator`](./src/Helper/KeyPairGenerator.php) for generating ED25519-V3 keypairs for Tor onion services
- Uses native PHP `sodium` extension for cryptographically secure key generation
- Derives `.onion` service IDs according to Tor v3 specification (base32 encoding with SHA3-256 checksum)
- Keys are properly expanded to match Tor's expected format
- Added [`KeyPair`](./src/Model/KeyPair.php) readonly model class
- Stores private key, public key, and derived service ID
- Provides multiple key format methods: raw, base64, and Tor-formatted (uses `PrivateKeyHelper`)
- Includes `__debugInfo()` to prevent sensitive data leaks in dumps
- Added comprehensive unit tests for key generation and model
- Added integration tests validating generated keys work with actual Tor instances
- Added `selective/base32` dependency for RFC 4648 compliant base32 encoding
- Added `ext-sodium` to `suggest` in composer.json (required for `KeyPairGenerator`, but not for other features)

#### Updates

- Updated `.php-cs-fixer.php`: disabled `mb_str_functions` rule (required for binary-safe string operations with cryptographic keys)
- Updated README.md with key generation examples
- `KeyPair::getPrivateKeyFormatted()` now uses `PrivateKeyHelper::parsePrivateKey()` for consistency

## v1.1.0

#### Additions

- Added the [`TorHttpClient::getAllPeers`](./src/TorHttpClient.php) method which returns all current peers on the Tor network in a Generator.
- Added the [`TorHttpClient::getExitNodes`](./src/TorHttpClient.php) method which returns all current peers on the Tor network in a Generator.
- Added [`ExitNodeHelper`](./src/Helper/ExitNodesHelper.php) helper to extract data from the Tor response.
- Added tests for this method.
- Added a [code example](./examples/get_exit_nodes.php) for getting exit nodes.
Expand Down
22 changes: 16 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,31 @@ I aim to maintain clean, consistent, and readable code throughout `tor-php`. Bef
2. **Running Tests Locally**
- Ensure all existing and new tests pass before submitting a PR:
```bash
make test
composer test
```
- Make sure tests are passing and coverage is acceptable.

## Linting & Static Analysis

1. **Linting**
- Run `php-cs-fixer` to catch syntax or formatting issues:
1. **Code Fixing**
- Run `php-cs-fixer` to automatically fix syntax or formatting issues:
```bash
make phpcs
composer cs-fix
```
2. **Static Analysis**
2. **Code Checking**
- Run `php-cs-fixer` in check mode to verify formatting without making changes:
```bash
composer cs-check
```
3. **Static Analysis**
- Run `phpstan` to catch potential bugs or incorrect assumptions:
```bash
make phpstan
composer phpstan
```
4. **Run All Quality Checks**
- Run all quality assurance checks (static analysis, code style check, and tests):
```bash
composer qa
```

Any Pull Request that does not pass **all** of these checks (linting, static analysis, and tests) in the CI pipeline will not be reviewed.
Expand Down
19 changes: 0 additions & 19 deletions Makefile

This file was deleted.

Loading