From 5e859224b4b9faa61d9343fa8e4c2eccef6bf8b2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 27 Dec 2025 00:41:34 +0000 Subject: [PATCH 1/6] feat: Major security toolkit expansion based on integration feedback Based on real-world WordPress semantic theme integration, this commit addresses gaps identified during testing with sanctify-php: New Modules: - TurtleEscaper: W3C-compliant RDF Turtle escaping (unique differentiator) - Headers: Security headers (CSP, HSTS, X-Frame-Options, CORS, etc.) Enhanced Validator: - IP validation (v4, v6, combined) - UUID, slug, JSON validation - Security validators (noNullBytes, safeFilename, httpsUrl) - Static methods (no instance required) Enhanced Sanitizer: - Context-aware escaping (HTML, JS, CSS, URL, JSON) - Filename sanitization - Static methods (no instance required) Documentation: - HANDOVER_SANCTIFY.md: Integration guidance for sanctify-php team - ROADMAP_PRIORITY.md: Prioritized roadmap based on real-world usage - Updated README with new API reference and examples Compliance: - SPDX license headers on all PHP files - All classes marked final for security --- HANDOVER_SANCTIFY.md | 244 ++++++++++++++++++++++++++++++++++++++++ README.adoc | 195 ++++++++++++++++++++++---------- ROADMAP_PRIORITY.md | 230 ++++++++++++++++++++++++++++++++++++++ src/Headers.php | 251 ++++++++++++++++++++++++++++++++++++++++++ src/Sanitizer.php | 95 +++++++++++++++- src/TurtleEscaper.php | 205 ++++++++++++++++++++++++++++++++++ src/Validator.php | 117 +++++++++++++++++++- 7 files changed, 1268 insertions(+), 69 deletions(-) create mode 100644 HANDOVER_SANCTIFY.md create mode 100644 ROADMAP_PRIORITY.md create mode 100644 src/Headers.php create mode 100644 src/TurtleEscaper.php diff --git a/HANDOVER_SANCTIFY.md b/HANDOVER_SANCTIFY.md new file mode 100644 index 0000000..e840027 --- /dev/null +++ b/HANDOVER_SANCTIFY.md @@ -0,0 +1,244 @@ +# Handover Document: sanctify-php Integration Insights + +## Context + +This document summarizes findings from integrating `php-aegis` and `sanctify-php` into a WordPress semantic theme (wp-sinople-theme). It provides actionable recommendations for the `sanctify-php` team based on real-world usage patterns. + +## Role Clarification + +| Tool | Role | When Used | +|------|------|-----------| +| **php-aegis** | Runtime security library | During request handling (validation, sanitization, headers) | +| **sanctify-php** | Static analysis tool | During development/CI (find vulnerabilities before deploy) | + +These are **complementary**, not competing tools: +- `sanctify-php` finds the bugs +- `php-aegis` provides the fixes + +## Issues Discovered During Integration + +### 1. Haskell Toolchain Dependency + +**Problem**: `sanctify-php` requires GHC/Cabal to build, which is a significant barrier for PHP developers. + +**Impact**: Most PHP teams don't have Haskell expertise or toolchain installed. + +**Recommendations**: +- Provide pre-built binaries for Linux (x86_64, aarch64), macOS (Intel, Apple Silicon), Windows +- Create official Docker image: `ghcr.io/hyperpolymath/sanctify-php:latest` +- Consider GitHub Actions integration that runs analysis without local install +- Add installation via common package managers (Homebrew, apt, nix) + +**Example Docker usage**: +```bash +docker run --rm -v $(pwd):/workspace ghcr.io/hyperpolymath/sanctify-php analyze /workspace +``` + +### 2. PHP 8.x Syntax Support + +**Problem**: Parser may not handle all PHP 8.x syntax (enums, union types, named arguments, attributes, match expressions, constructor property promotion). + +**Test cases needed**: +```php +// Enums (PHP 8.1+) +enum Status: string { + case Draft = 'draft'; + case Published = 'published'; +} + +// Union types (PHP 8.0+) +function process(string|int $input): string|false { ... } + +// Attributes (PHP 8.0+) +#[Route('/api/users')] +class UserController { ... } + +// Constructor property promotion (PHP 8.0+) +class User { + public function __construct( + public readonly string $name, + private int $age = 0, + ) {} +} + +// Named arguments (PHP 8.0+) +htmlspecialchars(string: $input, flags: ENT_QUOTES); + +// Match expressions (PHP 8.0+) +$result = match($status) { + Status::Draft => 'Editing', + Status::Published => 'Live', +}; +``` + +**Recommendation**: Add PHP 8.x grammar rules and comprehensive test suite. + +### 3. RDF/Turtle Output Context Awareness + +**Problem**: Static analyzer doesn't detect RDF/Turtle injection vulnerabilities in semantic web themes. + +**Background**: Semantic WordPress themes output RDF Turtle format for linked data. Standard XSS detection won't catch Turtle-specific injection vectors. + +**Vulnerable pattern** (not currently detected): +```php +// DANGEROUS: addslashes() is insufficient for Turtle +$turtle = '<' . $uri . '> rdfs:label "' . addslashes($label) . '" .'; +``` + +**Attack vectors**: +```turtle +# Turtle escape sequences +\n \r \t \\ \" \uXXXX \UXXXXXXXX + +# IRI injection + owl:sameAs +``` + +**Recommendation**: Add detection rules for: +- `addslashes()` used in RDF/Turtle context +- Unescaped variables in Turtle string literals (`"..."`) +- Unescaped IRIs (`<...>`) +- Missing use of proper escaping functions + +**Suggested rule signatures**: +``` +turtle_string_injection: Detects unescaped user input in Turtle string literals +turtle_iri_injection: Detects unescaped user input in Turtle IRIs +rdf_semantic_injection: Detects potential semantic attacks via RDF +``` + +### 4. WordPress Integration Documentation + +**Problem**: No clear guidance for WordPress-specific vulnerability patterns. + +**WordPress-specific patterns to detect**: + +```php +// DANGEROUS: Direct $_GET/$_POST usage +echo $_GET['query']; // XSS + +// DANGEROUS: Missing nonce verification +if (isset($_POST['action'])) { ... } // CSRF + +// DANGEROUS: Direct SQL interpolation +$wpdb->query("SELECT * FROM users WHERE id = " . $_GET['id']); // SQLi + +// DANGEROUS: Unescaped output +echo $user_input; // Should use esc_html(), esc_attr(), etc. + +// DANGEROUS: Privileged action without capability check +add_action('wp_ajax_delete_user', 'delete_user_handler'); +function delete_user_handler() { + // Missing: current_user_can('delete_users') + wp_delete_user($_POST['user_id']); +} +``` + +**WordPress-specific safe patterns**: +```php +// Safe escaping functions +esc_html($text) +esc_attr($attr) +esc_url($url) +wp_kses($html, $allowed) +wp_kses_post($html) + +// Safe nonce verification +wp_verify_nonce($_POST['_wpnonce'], 'action_name') +check_admin_referer('action_name') + +// Safe capability checks +current_user_can('edit_posts') +``` + +**Recommendation**: Create WordPress-specific ruleset that: +- Detects missing `esc_*` function usage +- Detects missing nonce verification in form handlers +- Detects missing capability checks in AJAX handlers +- Recognizes WordPress sanitization functions as safe sinks + +### 5. IndieWeb/Micropub Pattern Detection + +**Problem**: No awareness of IndieWeb protocols (Micropub, IndieAuth, Webmention). + +**Patterns to detect**: + +```php +// DANGEROUS: Missing IndieAuth token verification +function handle_micropub($request) { + $content = $request['content']; // Unverified! + create_post($content); +} + +// DANGEROUS: Webmention SSRF +function verify_webmention($source) { + $response = wp_remote_get($source); // Can hit internal IPs +} + +// DANGEROUS: Micropub content injection +$mf2 = Mf2\parse($html, $source); +$content = $mf2['items'][0]['properties']['content'][0]; +echo $content; // Unsanitized from external source +``` + +**Recommendation**: Add rules for common IndieWeb vulnerability patterns. + +## Integration Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Development Workflow │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Developer │───▶│ sanctify-php │───▶│ Fix Code │ │ +│ │ Writes Code │ │ (Analysis) │ │ (Guidance) │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────┐ │ +│ │ php-aegis │ │ +│ │ (Runtime) │ │ +│ └──────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Recommended sanctify-php Output Format + +When `sanctify-php` detects a vulnerability, it should suggest the `php-aegis` fix: + +``` +VULNERABILITY: XSS in output context +FILE: theme/template.php:42 +CODE: echo $user_input; + +RECOMMENDATION: + Use php-aegis Sanitizer for proper encoding: + + Before: echo $user_input; + After: echo \PhpAegis\Sanitizer::html($user_input); + + Install: composer require hyperpolymath/php-aegis +``` + +## Priority Recommendations Summary + +| Priority | Issue | Effort | +|----------|-------|--------| +| P0 | Pre-built binaries / Docker image | Medium | +| P0 | PHP 8.x syntax support | High | +| P1 | WordPress-specific rulesets | Medium | +| P1 | RDF/Turtle context detection | Medium | +| P2 | IndieWeb protocol patterns | Low | +| P2 | php-aegis fix suggestions in output | Low | + +## Contact + +For questions about this integration or to coordinate between repos: +- php-aegis: https://github.com/hyperpolymath/php-aegis +- Integration tested in: wp-sinople-theme + +--- + +*Generated from real-world WordPress semantic theme integration experience.* diff --git a/README.adoc b/README.adoc index 501bf71..eee366c 100644 --- a/README.adoc +++ b/README.adoc @@ -16,8 +16,10 @@ php-aegis provides a collection of security-focused utilities for PHP applicatio === Key Features -* **Input Validation** - Strict validation for emails, URLs, and common data formats -* **XSS Prevention** - HTML sanitization with proper encoding +* **Input Validation** - Strict validation for emails, URLs, IPs, UUIDs, and more +* **Context-Aware Sanitization** - HTML, JS, CSS, URL, and JSON output escaping +* **Security Headers** - Easy CSP, HSTS, X-Frame-Options, and more +* **RDF/Turtle Escaping** - Unique W3C-compliant semantic web security (no other PHP lib does this) * **Type Safety** - Full `strict_types` enforcement throughout * **Modern PHP** - Requires PHP 8.1+ for latest security features * **Zero Dependencies** - Core library has no external dependencies @@ -58,90 +60,169 @@ declare(strict_types=1); use PhpAegis\Validator; use PhpAegis\Sanitizer; +use PhpAegis\Headers; -$validator = new Validator(); -$sanitizer = new Sanitizer(); +// Apply security headers (call before any output) +Headers::secure(); // Validate user input $email = $_POST['email'] ?? ''; -if (!$validator->email($email)) { +if (!Validator::email($email)) { throw new InvalidArgumentException('Invalid email address'); } // Sanitize for HTML output $userContent = $_POST['comment'] ?? ''; -$safeHtml = $sanitizer->html($userContent); +$safeHtml = Sanitizer::html($userContent); echo "

{$safeHtml}

"; ---- +=== Semantic Web (RDF/Turtle) + +[source,php] +---- +'; + +// Complete triple +echo TurtleEscaper::triple( + 'https://example.org/resource/1', + 'http://www.w3.org/2000/01/rdf-schema#label', + $userLabel, + 'en' +); +---- + == API Reference === Validator -The `Validator` class provides strict input validation methods. - -==== `email(string $email): bool` +The `Validator` class provides strict input validation methods (all static). -Validates email addresses using PHP's `FILTER_VALIDATE_EMAIL`. +==== Core Validators [source,php] ---- -$validator = new Validator(); - -$validator->email('user@example.com'); // true -$validator->email('invalid'); // false -$validator->email(''); // false +Validator::email('user@example.com'); // true +Validator::url('https://example.com'); // true +Validator::httpsUrl('http://insecure'); // false (requires HTTPS) ---- -==== `url(string $url): bool` - -Validates URLs using PHP's `FILTER_VALIDATE_URL`. +==== Network Validators [source,php] ---- -$validator = new Validator(); +Validator::ip('192.168.1.1'); // true (v4 or v6) +Validator::ipv4('192.168.1.1'); // true +Validator::ipv6('::1'); // true +---- -$validator->url('https://example.com'); // true -$validator->url('ftp://files.example.com'); // true -$validator->url('not-a-url'); // false +==== Format Validators + +[source,php] +---- +Validator::uuid('550e8400-e29b-41d4-a716-446655440000'); // true +Validator::slug('my-post-title'); // true +Validator::json('{"valid": true}'); // true ---- -=== Sanitizer +==== Security Validators -The `Sanitizer` class provides input sanitization for safe output. +[source,php] +---- +Validator::noNullBytes("safe\x00string"); // false (null byte!) +Validator::safeFilename('../../../etc/passwd'); // false +Validator::safeFilename('document.pdf'); // true +---- -==== `html(string $input): string` +=== Sanitizer -Sanitizes strings for safe HTML output, preventing XSS attacks. +The `Sanitizer` class provides context-aware output escaping (all static). -* Encodes `<`, `>`, `&`, `"`, `'` -* Uses UTF-8 encoding -* HTML5 compliant +==== HTML Context [source,php] ---- -$sanitizer = new Sanitizer(); - -$sanitizer->html(''); +Sanitizer::html(''); // Returns: <script>alert("xss")</script> -$sanitizer->html("It's safe & secure"); -// Returns: It's safe & secure +Sanitizer::stripTags('

Hello World

'); +// Returns: Hello World +---- + +==== Other Contexts + +[source,php] +---- +Sanitizer::js("user's input"); // Safe for JS strings +Sanitizer::css("malicious;color:red"); // Safe for CSS +Sanitizer::url("path with spaces"); // URL encoded +Sanitizer::json(['key' => 'value']); // Safe JSON +Sanitizer::filename("../../../etc/passwd"); // Returns: etc_passwd ---- -==== `stripTags(string $input): string` +=== Headers -Removes all HTML and PHP tags from input. +Security headers helper (call before output). [source,php] ---- -$sanitizer = new Sanitizer(); +// Apply all recommended headers at once +Headers::secure(); + +// Or configure individually +Headers::contentSecurityPolicy([ + 'default-src' => ["'self'"], + 'script-src' => ["'self'", 'https://cdn.example.com'], + 'style-src' => ["'self'", "'unsafe-inline'"], +]); +Headers::strictTransportSecurity(maxAge: 31536000, preload: true); +Headers::frameOptions('SAMEORIGIN'); +Headers::referrerPolicy('strict-origin-when-cross-origin'); +Headers::permissionsPolicy([ + 'geolocation' => [], + 'camera' => [], +]); +Headers::removeInsecureHeaders(); // Removes X-Powered-By, Server +---- -$sanitizer->stripTags('

Hello World

'); -// Returns: Hello World +=== TurtleEscaper + +W3C-compliant RDF Turtle escaping for semantic web applications. -$sanitizer->stripTags(''); -// Returns: (empty string) +[source,php] +---- +// Escape string literals +TurtleEscaper::string('Hello "World"'); +// Returns: Hello \"World\" + +// Escape IRIs +TurtleEscaper::iri('https://example.org/resource#1'); + +// Build complete literals with language/datatype +TurtleEscaper::literal('Bonjour', language: 'fr'); +// Returns: "Bonjour"@fr + +TurtleEscaper::literal('42', datatype: 'http://www.w3.org/2001/XMLSchema#integer'); +// Returns: "42"^^xsd:integer + +// Build complete triples +TurtleEscaper::triple( + 'https://example.org/person/1', + 'http://xmlns.com/foaf/0.1/name', + 'Alice', + 'en' +); +// Returns: "Alice"@en . ---- == Security Considerations @@ -206,34 +287,34 @@ vendor/bin/php-cs-fixer fix --dry-run == Roadmap -Planned features for future releases: +See link:ROADMAP_PRIORITY.md[ROADMAP_PRIORITY.md] for the detailed, integration-informed roadmap. -=== v0.2.0 - Extended Validators -* [ ] `Validator::ip()` - IPv4/IPv6 validation -* [ ] `Validator::uuid()` - UUID format validation -* [ ] `Validator::slug()` - URL slug validation -* [ ] `Validator::phone()` - Phone number validation +=== Recently Completed (v0.1.1) -=== v0.3.0 - Security Headers -* [ ] `Headers::csp()` - Content Security Policy helper -* [ ] `Headers::hsts()` - HSTS header helper -* [ ] `Headers::noSniff()` - X-Content-Type-Options -* [ ] `Headers::frameOptions()` - X-Frame-Options +* [x] Extended validators (IP, UUID, slug, JSON, filename safety) +* [x] Context-aware sanitizers (JS, CSS, URL, JSON, filename) +* [x] Security headers module (CSP, HSTS, X-Frame-Options, etc.) +* [x] RDF/Turtle escaping (unique differentiator) +* [x] Static methods (no instance required) +* [x] SPDX license headers -=== v0.4.0 - Rate Limiting -* [ ] `RateLimiter` - Token bucket implementation -* [ ] Redis/APCu backend support +=== Next Up -=== Future +* [ ] IndieWeb security helpers (Micropub, IndieAuth, Webmention) +* [ ] Rate limiting with file/memory backends * [ ] CSRF token generation and validation * [ ] Input filtering chains -* [ ] Audit logging utilities == Related Projects +* https://github.com/hyperpolymath/sanctify-php[sanctify-php] - Static analysis for PHP security (complementary tool) * https://github.com/hyperpolymath/wp-audit-toolkit[wp-audit-toolkit] - WordPress security auditing * https://github.com/hyperpolymath/proof-of-work[proof-of-work] - Proof-of-work spam prevention +=== Integration Notes + +For teams using both `php-aegis` and `sanctify-php`, see link:HANDOVER_SANCTIFY.md[HANDOVER_SANCTIFY.md] for integration guidance and coordinated workflows. + == License MIT License - See link:LICENSE.txt[LICENSE.txt] for details. diff --git a/ROADMAP_PRIORITY.md b/ROADMAP_PRIORITY.md new file mode 100644 index 0000000..f3b790a --- /dev/null +++ b/ROADMAP_PRIORITY.md @@ -0,0 +1,230 @@ +# php-aegis Roadmap (Integration-Informed Priority) + +This roadmap is prioritized based on real-world integration experience with WordPress semantic themes and the feedback received during the wp-sinople-theme security integration. + +## Context: Why This Matters + +During integration testing, the following gaps were identified: + +1. **Feature set too minimal** - WordPress has `esc_html()`, `esc_attr()`, etc. already +2. **No RDF/Turtle support** - Semantic themes need specialized escaping +3. **Missing SPDX headers** - Compliance requirement not met +4. **Not leveraging PHP 8.1+** - Enums, union types, readonly properties unused + +This roadmap addresses these gaps in priority order. + +--- + +## Phase 1: Foundation Fixes (v0.1.1) + +**Goal**: Address compliance and differentiation issues immediately. + +### 1.1 SPDX License Headers ✅ +- Add `SPDX-License-Identifier: MIT OR AGPL-3.0-or-later` to all PHP files +- Add `SPDX-FileCopyrightText` with year and author + +### 1.2 Static Methods +- Convert `Validator` and `Sanitizer` to use static methods +- Rationale: No instance state needed, improves ergonomics +- Before: `(new Sanitizer())->html($input)` +- After: `Sanitizer::html($input)` + +### 1.3 RDF/Turtle Escaping Module ✅ +- `TurtleEscaper::string(string $input): string` - Escape for Turtle string literals +- `TurtleEscaper::iri(string $uri): string` - Escape/validate for Turtle IRIs +- This is a **unique differentiator** - no other PHP library does this properly + +--- + +## Phase 2: Security Headers (v0.2.0) + +**Goal**: Provide value beyond WordPress built-ins. + +### 2.1 Headers Class +```php +Headers::contentSecurityPolicy(array $directives): void +Headers::strictTransportSecurity(int $maxAge, bool $subdomains = true): void +Headers::xFrameOptions(string $value = 'DENY'): void +Headers::xContentTypeOptions(): void // nosniff +Headers::referrerPolicy(string $policy = 'strict-origin-when-cross-origin'): void +Headers::permissionsPolicy(array $permissions): void +``` + +### 2.2 All-in-One Security Headers +```php +Headers::secure(): void // Apply sensible defaults for all headers +``` + +### Why This Matters +- WordPress doesn't provide header helpers +- Frameworks often require manual configuration +- This provides "secure by default" with one function call + +--- + +## Phase 3: Extended Validators (v0.3.0) + +**Goal**: Cover common validation needs with strict, type-safe implementations. + +### 3.1 Network Validators +```php +Validator::ip(string $ip): bool // IPv4 or IPv6 +Validator::ipv4(string $ip): bool +Validator::ipv6(string $ip): bool +Validator::cidr(string $cidr): bool +Validator::hostname(string $host): bool +Validator::domain(string $domain): bool +``` + +### 3.2 Format Validators +```php +Validator::uuid(string $uuid): bool // RFC 4122 +Validator::slug(string $slug): bool // URL-safe slugs +Validator::semver(string $version): bool // Semantic versioning +Validator::iso8601(string $date): bool // ISO 8601 datetime +Validator::json(string $json): bool // Valid JSON +``` + +### 3.3 Security Validators +```php +Validator::noNullBytes(string $input): bool +Validator::printable(string $input): bool +Validator::safeFilename(string $filename): bool // No path traversal +Validator::httpsUrl(string $url): bool // Enforce HTTPS +``` + +--- + +## Phase 4: Context-Aware Sanitization (v0.4.0) + +**Goal**: Provide correct escaping for every output context. + +### 4.1 Context Enum (PHP 8.1+) +```php +enum OutputContext: string { + case Html = 'html'; + case HtmlAttribute = 'attr'; + case JavaScript = 'js'; + case Css = 'css'; + case Url = 'url'; + case Sql = 'sql'; // For display only, not query building + case Json = 'json'; + case Turtle = 'turtle'; // RDF Turtle + case NTriples = 'ntriples'; // RDF N-Triples +} +``` + +### 4.2 Unified Escape Method +```php +Sanitizer::escape(string $input, OutputContext $context): string +``` + +### 4.3 Specialized Methods +```php +Sanitizer::jsString(string $input): string // Safe for JS string literals +Sanitizer::cssString(string $input): string // Safe for CSS values +Sanitizer::urlEncode(string $input): string // Proper URL encoding +Sanitizer::jsonEncode(mixed $input): string // Safe JSON with flags +``` + +--- + +## Phase 5: IndieWeb Security (v0.5.0) + +**Goal**: First-class support for IndieWeb/semantic web patterns. + +### 5.1 Micropub Content Sanitizer +```php +Micropub::sanitizeContent(string $html, array $allowedTags = []): string +Micropub::validateEntry(array $mf2): ValidationResult +``` + +### 5.2 IndieAuth Helpers +```php +IndieAuth::verifyToken(string $token, string $endpoint): TokenResult +IndieAuth::validateMe(string $url): bool // Valid "me" URL +IndieAuth::validateRedirectUri(string $uri, string $clientId): bool +``` + +### 5.3 Webmention Validators +```php +Webmention::validateSource(string $url): bool // Not internal IP +Webmention::validateTarget(string $url, string $domain): bool +``` + +--- + +## Phase 6: Rate Limiting (v0.6.0) + +**Goal**: Protect against abuse without external dependencies. + +### 6.1 Token Bucket Implementation +```php +interface RateLimitStore { + public function get(string $key): ?TokenBucket; + public function set(string $key, TokenBucket $bucket, int $ttl): void; +} + +class MemoryStore implements RateLimitStore { ... } +class FileStore implements RateLimitStore { ... } +class RedisStore implements RateLimitStore { ... } // Optional +class ApcuStore implements RateLimitStore { ... } // Optional +``` + +### 6.2 Rate Limiter +```php +$limiter = new RateLimiter( + store: new FileStore('/tmp/ratelimit'), + capacity: 100, // requests + refillRate: 10, // per second +); + +if (!$limiter->attempt($clientIp)) { + http_response_code(429); + exit; +} +``` + +--- + +## Differentiation Strategy + +### What WordPress Has (don't duplicate) +- `esc_html()`, `esc_attr()`, `esc_url()`, `esc_js()` +- `wp_kses()`, `wp_kses_post()` +- `sanitize_*()` functions +- Nonce verification + +### What php-aegis Provides (unique value) +| Feature | WordPress | Laravel | Symfony | php-aegis | +|---------|-----------|---------|---------|-----------| +| RDF/Turtle escaping | ❌ | ❌ | ❌ | ✅ | +| Security headers helper | ❌ | Partial | Partial | ✅ | +| IndieWeb validation | ❌ | ❌ | ❌ | ✅ | +| Zero dependencies | N/A | ❌ | ❌ | ✅ | +| PHP 8.1+ strict types | ❌ | ❌ | ❌ | ✅ | +| Rate limiting (no Redis) | ❌ | ❌ | ❌ | ✅ | + +--- + +## Success Metrics + +1. **Adoption**: Downloads on Packagist +2. **Integration**: Used in WordPress themes, Laravel packages +3. **Coverage**: CVE fixes attributable to php-aegis usage +4. **Community**: GitHub stars, issues, PRs + +--- + +## Timeline Philosophy + +Per project guidelines, no time estimates are provided. Work proceeds based on: +1. User demand (GitHub issues) +2. Security criticality +3. Contributor availability + +Phases can be reordered based on community feedback. + +--- + +*This roadmap reflects lessons learned from real WordPress integration.* diff --git a/src/Headers.php b/src/Headers.php new file mode 100644 index 0000000..1fc6816 --- /dev/null +++ b/src/Headers.php @@ -0,0 +1,251 @@ + ["'self'"], + ]); + } + + /** + * Set Content-Security-Policy header. + * + * @param array> $directives CSP directives + * @param bool $reportOnly If true, use Content-Security-Policy-Report-Only + */ + public static function contentSecurityPolicy(array $directives, bool $reportOnly = false): void + { + $parts = []; + + foreach ($directives as $directive => $values) { + if (empty($values)) { + $parts[] = $directive; + } else { + $parts[] = $directive . ' ' . implode(' ', $values); + } + } + + $headerName = $reportOnly + ? 'Content-Security-Policy-Report-Only' + : 'Content-Security-Policy'; + + header($headerName . ': ' . implode('; ', $parts)); + } + + /** + * Set Strict-Transport-Security header (HSTS). + * + * @param int $maxAge Max age in seconds (default: 1 year) + * @param bool $includeSubDomains Include subdomains + * @param bool $preload Include in HSTS preload list + */ + public static function strictTransportSecurity( + int $maxAge = 31536000, + bool $includeSubDomains = true, + bool $preload = false + ): void { + $value = 'max-age=' . $maxAge; + + if ($includeSubDomains) { + $value .= '; includeSubDomains'; + } + + if ($preload) { + $value .= '; preload'; + } + + header('Strict-Transport-Security: ' . $value); + } + + /** + * Set X-Frame-Options header to prevent clickjacking. + * + * @param string $value DENY, SAMEORIGIN, or ALLOW-FROM uri + */ + public static function frameOptions(string $value = 'DENY'): void + { + $allowed = ['DENY', 'SAMEORIGIN']; + + if (!in_array($value, $allowed, true) && !str_starts_with($value, 'ALLOW-FROM ')) { + throw new \InvalidArgumentException( + 'X-Frame-Options must be DENY, SAMEORIGIN, or ALLOW-FROM uri' + ); + } + + header('X-Frame-Options: ' . $value); + } + + /** + * Set X-Content-Type-Options to prevent MIME sniffing. + */ + public static function contentTypeOptions(): void + { + header('X-Content-Type-Options: nosniff'); + } + + /** + * Set X-XSS-Protection header. + * + * Note: This header is deprecated in modern browsers but still useful + * for legacy browser support. + * + * @param bool $enable Enable XSS filter + * @param bool $block Block page instead of sanitizing + */ + public static function xssProtection(bool $enable = true, bool $block = true): void + { + if (!$enable) { + header('X-XSS-Protection: 0'); + return; + } + + $value = '1'; + if ($block) { + $value .= '; mode=block'; + } + + header('X-XSS-Protection: ' . $value); + } + + /** + * Set Referrer-Policy header. + * + * @param string $policy Referrer policy value + */ + public static function referrerPolicy(string $policy = 'strict-origin-when-cross-origin'): void + { + $allowed = [ + 'no-referrer', + 'no-referrer-when-downgrade', + 'origin', + 'origin-when-cross-origin', + 'same-origin', + 'strict-origin', + 'strict-origin-when-cross-origin', + 'unsafe-url', + ]; + + if (!in_array($policy, $allowed, true)) { + throw new \InvalidArgumentException( + 'Invalid Referrer-Policy: ' . $policy + ); + } + + header('Referrer-Policy: ' . $policy); + } + + /** + * Set Permissions-Policy header (replaces Feature-Policy). + * + * @param array> $permissions Feature permissions + */ + public static function permissionsPolicy(array $permissions): void + { + $parts = []; + + foreach ($permissions as $feature => $allowlist) { + if (empty($allowlist)) { + $parts[] = $feature . '=()'; + } else { + $quoted = array_map( + static fn(string $v): string => $v === 'self' ? 'self' : '"' . $v . '"', + $allowlist + ); + $parts[] = $feature . '=(' . implode(' ', $quoted) . ')'; + } + } + + header('Permissions-Policy: ' . implode(', ', $parts)); + } + + /** + * Set Cross-Origin-Embedder-Policy header. + * + * @param string $policy require-corp, credentialless, or unsafe-none + */ + public static function crossOriginEmbedderPolicy(string $policy = 'require-corp'): void + { + $allowed = ['require-corp', 'credentialless', 'unsafe-none']; + + if (!in_array($policy, $allowed, true)) { + throw new \InvalidArgumentException( + 'Invalid Cross-Origin-Embedder-Policy: ' . $policy + ); + } + + header('Cross-Origin-Embedder-Policy: ' . $policy); + } + + /** + * Set Cross-Origin-Opener-Policy header. + * + * @param string $policy same-origin, same-origin-allow-popups, or unsafe-none + */ + public static function crossOriginOpenerPolicy(string $policy = 'same-origin'): void + { + $allowed = ['same-origin', 'same-origin-allow-popups', 'unsafe-none']; + + if (!in_array($policy, $allowed, true)) { + throw new \InvalidArgumentException( + 'Invalid Cross-Origin-Opener-Policy: ' . $policy + ); + } + + header('Cross-Origin-Opener-Policy: ' . $policy); + } + + /** + * Set Cross-Origin-Resource-Policy header. + * + * @param string $policy same-origin, same-site, or cross-origin + */ + public static function crossOriginResourcePolicy(string $policy = 'same-origin'): void + { + $allowed = ['same-origin', 'same-site', 'cross-origin']; + + if (!in_array($policy, $allowed, true)) { + throw new \InvalidArgumentException( + 'Invalid Cross-Origin-Resource-Policy: ' . $policy + ); + } + + header('Cross-Origin-Resource-Policy: ' . $policy); + } + + /** + * Remove potentially dangerous headers that leak information. + */ + public static function removeInsecureHeaders(): void + { + header_remove('X-Powered-By'); + header_remove('Server'); + } +} diff --git a/src/Sanitizer.php b/src/Sanitizer.php index 488ce0e..f74fa19 100644 --- a/src/Sanitizer.php +++ b/src/Sanitizer.php @@ -1,27 +1,110 @@ '\\\\', // Backslash must be first + '"' => '\\"', // Double quote + "'" => "\\'", // Single quote + "\t" => '\\t', // Tab + "\b" => '\\b', // Backspace + "\n" => '\\n', // Newline + "\r" => '\\r', // Carriage return + "\f" => '\\f', // Form feed + ]; + + $escaped = str_replace( + array_keys($replacements), + array_values($replacements), + $input + ); + + // Escape any other control characters using \uXXXX + $escaped = preg_replace_callback( + '/[\x00-\x08\x0B\x0E-\x1F\x7F]/', + static fn(array $matches): string => sprintf('\\u%04X', ord($matches[0])), + $escaped + ) ?? $escaped; + + return $escaped; + } + + /** + * Escape and validate an IRI for use in Turtle <...> notation. + * + * @param string $uri The URI/IRI to escape + * @return string Safe for use in Turtle <...> IRI references + * @throws \InvalidArgumentException If the URI is malformed + */ + public static function iri(string $uri): string + { + // Basic validation - must be a valid URL structure + if (!filter_var($uri, FILTER_VALIDATE_URL)) { + // Allow relative IRIs, but validate they don't contain dangerous characters + if (preg_match('/[<>"{}|\\\\^`\x00-\x20]/', $uri)) { + throw new \InvalidArgumentException('Invalid IRI: contains disallowed characters'); + } + } + + // Characters that must be escaped in IRIs per RFC 3987 + // < > " { } | ^ ` \ and control characters + $dangerous = [ + '<' => '%3C', + '>' => '%3E', + '"' => '%22', + '{' => '%7B', + '}' => '%7D', + '|' => '%7C', + '^' => '%5E', + '`' => '%60', + '\\' => '%5C', + ' ' => '%20', + ]; + + $escaped = str_replace( + array_keys($dangerous), + array_values($dangerous), + $uri + ); + + // Escape control characters + $escaped = preg_replace_callback( + '/[\x00-\x1F\x7F]/', + static fn(array $matches): string => sprintf('%%%02X', ord($matches[0])), + $escaped + ) ?? $escaped; + + return $escaped; + } + + /** + * Escape a language tag for Turtle literals. + * + * @param string $tag BCP 47 language tag (e.g., "en", "en-US") + * @return string Validated language tag + * @throws \InvalidArgumentException If tag is invalid + */ + public static function languageTag(string $tag): string + { + // BCP 47 language tag pattern (simplified) + // Full spec: https://tools.ietf.org/html/bcp47 + if (!preg_match('/^[a-zA-Z]{2,3}(?:-[a-zA-Z0-9]{2,8})*$/', $tag)) { + throw new \InvalidArgumentException('Invalid BCP 47 language tag'); + } + + return strtolower($tag); + } + + /** + * Build a complete Turtle literal with optional language tag or datatype. + * + * @param string $value The string value + * @param string|null $language Optional language tag (e.g., "en") + * @param string|null $datatype Optional datatype IRI + * @return string Complete Turtle literal (e.g., "Hello"@en or "42"^^xsd:integer) + */ + public static function literal( + string $value, + ?string $language = null, + ?string $datatype = null + ): string { + $escaped = self::string($value); + $literal = '"' . $escaped . '"'; + + if ($language !== null) { + $literal .= '@' . self::languageTag($language); + } elseif ($datatype !== null) { + // Common XSD prefixes can use shorthand + $xsdPrefix = 'http://www.w3.org/2001/XMLSchema#'; + if (str_starts_with($datatype, $xsdPrefix)) { + $localName = substr($datatype, strlen($xsdPrefix)); + if (preg_match('/^[a-zA-Z][a-zA-Z0-9]*$/', $localName)) { + $literal .= '^^xsd:' . $localName; + } else { + $literal .= '^^<' . self::iri($datatype) . '>'; + } + } else { + $literal .= '^^<' . self::iri($datatype) . '>'; + } + } + + return $literal; + } + + /** + * Create a safe Turtle triple. + * + * @param string $subject Subject IRI + * @param string $predicate Predicate IRI + * @param string $object Object (string value, will be escaped as literal) + * @param string|null $language Optional language tag + * @return string Complete Turtle triple ending with " ." + */ + public static function triple( + string $subject, + string $predicate, + string $object, + ?string $language = null + ): string { + return sprintf( + '<%s> <%s> %s .', + self::iri($subject), + self::iri($predicate), + self::literal($object, $language) + ); + } + + /** + * Create a safe Turtle triple with IRI object. + * + * @param string $subject Subject IRI + * @param string $predicate Predicate IRI + * @param string $objectIri Object IRI + * @return string Complete Turtle triple ending with " ." + */ + public static function tripleIri( + string $subject, + string $predicate, + string $objectIri + ): string { + return sprintf( + '<%s> <%s> <%s> .', + self::iri($subject), + self::iri($predicate), + self::iri($objectIri) + ); + } +} diff --git a/src/Validator.php b/src/Validator.php index 826ee38..32ec37c 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -1,27 +1,132 @@ Date: Sat, 27 Dec 2025 00:50:12 +0000 Subject: [PATCH 2/6] feat: Add compatibility strategy and extended validators Based on second integration learning report: New Validators: - int(): Integer validation with optional min/max range - domain(): RFC 1035 compliant domain validation - hostname(): Domain or IP validation - printable(): ASCII printable characters only - semver(): Semantic versioning validation - iso8601(): ISO 8601 datetime validation - hexColor(): Hex color code validation New Documentation: - COMPATIBILITY.md: PHP 7.4+ strategy via php-aegis-compat package - WordPress/Laravel/Symfony adapter patterns in roadmap HANDOVER_SANCTIFY.md Updates: - GitHub Action requirement (sanctify-php-action) - SARIF output enhancements - Incremental analysis strategy - Composer plugin wrapper recommendation - Standalone vs combined operation guidance - Integration metrics from testing --- COMPATIBILITY.md | 217 +++++++++++++++++++++++++++++++++++++++++++ HANDOVER_SANCTIFY.md | 150 +++++++++++++++++++++++++++++- README.adoc | 8 ++ ROADMAP_PRIORITY.md | 54 +++++++++++ src/Validator.php | 114 +++++++++++++++++++++++ 5 files changed, 542 insertions(+), 1 deletion(-) create mode 100644 COMPATIBILITY.md diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md new file mode 100644 index 0000000..5d0dc17 --- /dev/null +++ b/COMPATIBILITY.md @@ -0,0 +1,217 @@ +# php-aegis Compatibility Strategy + +## The Problem + +php-aegis requires PHP 8.1+, but WordPress officially supports PHP 7.4+. This limits adoption in the WordPress ecosystem where many hosts still run PHP 7.4 or 8.0. + +## Strategy: Dual-Package Approach + +Instead of downgrading the main library, we provide a separate compatibility package. + +``` +hyperpolymath/php-aegis # PHP 8.1+ (main, recommended) +hyperpolymath/php-aegis-compat # PHP 7.4+ (polyfill, limited) +``` + +### Why Not Downgrade? + +1. **Security**: PHP 8.1+ has better security defaults +2. **Type Safety**: Union types, enums, readonly properties +3. **Performance**: PHP 8.x is significantly faster +4. **Maintenance**: Supporting old PHP versions increases complexity + +### The Compatibility Package + +`php-aegis-compat` provides: +- Same API surface as php-aegis +- Works on PHP 7.4, 8.0 +- Gracefully degrades when php-aegis is available + +```php + [ + PhpAegis\Laravel\AegisServiceProvider::class, +], + +// Usage in controllers +public function store(Request $request, Sanitizer $sanitizer) +{ + $safe = $sanitizer->html($request->input('content')); +} + +// Blade directive +@aegis($userContent) // Calls Sanitizer::html() +``` + +--- + +## Migration Path + +### For WordPress Themes/Plugins + +```php +// Before: Using WordPress functions only +echo esc_html($user_input); + +// After: Using php-aegis with WordPress fallback +if (function_exists('aegis_html')) { + echo aegis_html($user_input); +} else { + echo esc_html($user_input); +} + +// Or: Graceful one-liner +echo function_exists('aegis_html') ? aegis_html($user_input) : esc_html($user_input); +``` + +### For New Projects + +```php +// Just use php-aegis directly +use PhpAegis\Sanitizer; + +echo Sanitizer::html($user_input); +``` + +--- + +## Version Support Timeline + +| PHP Version | Support Status | Recommended Package | +|-------------|---------------|---------------------| +| 7.4 | Legacy (EOL Dec 2022) | php-aegis-compat | +| 8.0 | Legacy (EOL Nov 2023) | php-aegis-compat | +| 8.1 | Security fixes only | php-aegis | +| 8.2 | Active | php-aegis | +| 8.3 | Active (current) | php-aegis | +| 8.4+ | Future | php-aegis | + +**Recommendation**: Upgrade to PHP 8.2+ and use php-aegis directly. + +--- + +## Implementation Checklist + +- [ ] Create `hyperpolymath/php-aegis-compat` repository +- [ ] Implement core Sanitizer/Validator classes for PHP 7.4 +- [ ] Add auto-detection for php-aegis (use if available) +- [ ] Create WordPress mu-plugin adapter +- [ ] Create Laravel service provider +- [ ] Publish both packages to Packagist +- [ ] Document migration paths + +--- + +*This strategy maximizes adoption while maintaining security and code quality in the main package.* diff --git a/HANDOVER_SANCTIFY.md b/HANDOVER_SANCTIFY.md index e840027..46f5e85 100644 --- a/HANDOVER_SANCTIFY.md +++ b/HANDOVER_SANCTIFY.md @@ -228,17 +228,165 @@ RECOMMENDATION: |----------|-------|--------| | P0 | Pre-built binaries / Docker image | Medium | | P0 | PHP 8.x syntax support | High | +| P0 | Official GitHub Action (`sanctify-php-action`) | Medium | | P1 | WordPress-specific rulesets | Medium | | P1 | RDF/Turtle context detection | Medium | +| P1 | SARIF output for GitHub Security tab | Low | +| P2 | Incremental analysis (cache, scan changed files only) | High | | P2 | IndieWeb protocol patterns | Low | | P2 | php-aegis fix suggestions in output | Low | +--- + +## Additional Findings (Report 2) + +### 6. GitHub Action Required + +**Problem**: No official GitHub Action for CI integration. + +**Impact**: Teams must write custom workflow configuration or use Docker manually. + +**Recommendation**: Create `hyperpolymath/sanctify-php-action` with: +```yaml +# .github/workflows/security.yml +- uses: hyperpolymath/sanctify-php-action@v1 + with: + path: ./src + config: sanctify.yml + sarif-output: results.sarif +``` + +### 7. SARIF Output for GitHub Integration + +**What Works Well**: SARIF format enables direct GitHub Security tab integration. + +**Enhancement**: Ensure SARIF output includes: +- Rule descriptions with OWASP references +- Severity levels mapped to GitHub's critical/high/medium/low +- Fix suggestions linking to php-aegis methods + +```json +{ + "runs": [{ + "tool": { "driver": { "name": "sanctify-php" } }, + "results": [{ + "ruleId": "xss-output", + "level": "error", + "message": { "text": "Unescaped output" }, + "fixes": [{ + "description": { "text": "Use PhpAegis\\Sanitizer::html()" } + }] + }] + }] +} +``` + +### 8. Incremental Analysis + +**Problem**: Full codebase scans are slow on large projects. + +**Recommendation**: +- Cache AST and taint analysis results +- On subsequent runs, only analyze changed files +- Invalidate cache when dependencies change +- Use file modification timestamps or git diff + +```bash +# First run: full analysis, build cache +sanctify analyze ./src --cache .sanctify-cache + +# Subsequent runs: incremental +sanctify analyze ./src --cache .sanctify-cache --incremental +``` + +### 9. Composer Plugin Wrapper + +**Problem**: PHP developers expect `composer require` installation. + +**Recommendation**: Create a Composer plugin that: +1. Downloads pre-built binary for platform +2. Provides `vendor/bin/sanctify` wrapper +3. Handles updates via Composer + +```bash +composer require --dev hyperpolymath/sanctify-php +vendor/bin/sanctify analyze ./src +``` + +--- + +## Standalone vs Combined Operation + +### Minimal Requirements for Each Tool + +**php-aegis standalone** (runtime protection): +- Zero dependencies (works everywhere PHP runs) +- Static methods for easy drop-in usage +- Works without sanctify-php installed + +**sanctify-php standalone** (static analysis): +- Pre-built binary (no Haskell needed) +- SARIF output for any CI system +- Works without php-aegis (just reports issues) + +### Combined Synergies + +When both tools are used together: + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Combined Workflow │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌────────────┐ ┌─────────────────┐ ┌──────────────────┐ │ +│ │ Write │──▶│ sanctify-php │──▶│ Fix with │ │ +│ │ Code │ │ (finds issues) │ │ php-aegis │ │ +│ └────────────┘ └─────────────────┘ └──────────────────┘ │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌─────────────────────────────────────┐ │ +│ │ sanctify-php recognizes php-aegis │ │ +│ │ methods as "safe sinks" in taint │ │ +│ │ analysis, reducing false positives │ │ +│ └─────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +**Key synergy**: sanctify-php should recognize php-aegis sanitizers as safe: +```haskell +-- sanctify-php taint rules +safeSinks = [ + "PhpAegis\\Sanitizer::html", + "PhpAegis\\Sanitizer::attr", + "PhpAegis\\Sanitizer::js", + "PhpAegis\\Sanitizer::css", + "PhpAegis\\Sanitizer::url", + "PhpAegis\\TurtleEscaper::string", + "PhpAegis\\TurtleEscaper::iri" +] +``` + +--- + +## Integration Metrics + +| Metric | Before Integration | After Integration | +|--------|-------------------|-------------------| +| Files with `strict_types` | 0 | 24 (100%) | +| PHP version | 7.4+ | 8.2+ | +| WordPress version | 5.8+ | 6.4+ | +| CI security checks | 0 | 4 | + +--- + ## Contact For questions about this integration or to coordinate between repos: - php-aegis: https://github.com/hyperpolymath/php-aegis +- sanctify-php: https://github.com/hyperpolymath/sanctify-php - Integration tested in: wp-sinople-theme --- -*Generated from real-world WordPress semantic theme integration experience.* +*Generated from real-world WordPress semantic theme integration experience (Reports 1 & 2).* diff --git a/README.adoc b/README.adoc index eee366c..354fc63 100644 --- a/README.adoc +++ b/README.adoc @@ -133,6 +133,13 @@ Validator::ipv6('::1'); // true Validator::uuid('550e8400-e29b-41d4-a716-446655440000'); // true Validator::slug('my-post-title'); // true Validator::json('{"valid": true}'); // true +Validator::int('42'); // true +Validator::int('5', min: 1, max: 10); // true +Validator::domain('example.com'); // true +Validator::hostname('example.com'); // true (domain or IP) +Validator::semver('1.2.3-beta.1'); // true +Validator::iso8601('2024-01-15T10:30:00Z'); // true +Validator::hexColor('#ff5733'); // true ---- ==== Security Validators @@ -142,6 +149,7 @@ Validator::json('{"valid": true}'); // true Validator::noNullBytes("safe\x00string"); // false (null byte!) Validator::safeFilename('../../../etc/passwd'); // false Validator::safeFilename('document.pdf'); // true +Validator::printable("Hello World!"); // true (ASCII only) ---- === Sanitizer diff --git a/ROADMAP_PRIORITY.md b/ROADMAP_PRIORITY.md index f3b790a..8cf6c43 100644 --- a/ROADMAP_PRIORITY.md +++ b/ROADMAP_PRIORITY.md @@ -187,6 +187,60 @@ if (!$limiter->attempt($clientIp)) { --- +## Phase 7: Ecosystem Expansion (v0.7.0) + +**Goal**: Maximize adoption across PHP ecosystem. + +### 7.1 Compatibility Package + +Create `hyperpolymath/php-aegis-compat` for PHP 7.4+ environments. + +See [COMPATIBILITY.md](COMPATIBILITY.md) for full strategy. + +```bash +# PHP 7.4+ (legacy WordPress hosts) +composer require hyperpolymath/php-aegis-compat + +# PHP 8.1+ (recommended) +composer require hyperpolymath/php-aegis +``` + +### 7.2 WordPress Adapter + +WordPress-style function wrappers: +```php +aegis_html($input) // Maps to Sanitizer::html() +aegis_attr($input) // Maps to Sanitizer::attr() +aegis_js($input) // Maps to Sanitizer::js() +aegis_url($input) // Maps to Sanitizer::url() +aegis_send_security_headers() // Maps to Headers::secure() +``` + +### 7.3 Laravel Service Provider + +```php +// Auto-registered via package discovery +PhpAegis\Laravel\AegisServiceProvider::class + +// Blade directive +@aegis($userContent) + +// DI in controllers +public function store(Sanitizer $sanitizer) { ... } +``` + +### 7.4 Symfony Bundle + +```php +// config/bundles.php +PhpAegis\Symfony\AegisBundle::class => ['all' => true] + +// Twig filter +{{ user_content|aegis_html }} +``` + +--- + ## Differentiation Strategy ### What WordPress Has (don't duplicate) diff --git a/src/Validator.php b/src/Validator.php index 32ec37c..9af2891 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -129,4 +129,118 @@ public static function json(string $json): bool json_decode($json); return json_last_error() === JSON_ERROR_NONE; } + + /** + * Validate integer (string representation). + * + * @param string $value The string to validate + * @param int|null $min Optional minimum value + * @param int|null $max Optional maximum value + */ + public static function int(string $value, ?int $min = null, ?int $max = null): bool + { + $options = []; + + if ($min !== null) { + $options['min_range'] = $min; + } + + if ($max !== null) { + $options['max_range'] = $max; + } + + $result = filter_var( + $value, + FILTER_VALIDATE_INT, + empty($options) ? [] : ['options' => $options] + ); + + return $result !== false; + } + + /** + * Validate domain name. + * + * Validates according to RFC 1035 with modern extensions. + */ + public static function domain(string $domain): bool + { + // Must not be empty and must not exceed 253 characters + if ($domain === '' || strlen($domain) > 253) { + return false; + } + + // Each label must be 1-63 characters, alphanumeric with hyphens (not at start/end) + $pattern = '/^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/'; + + if (!preg_match($pattern, $domain)) { + return false; + } + + // TLD must be at least 2 characters and alphabetic (or valid IDN) + $parts = explode('.', $domain); + $tld = end($parts); + + return strlen($tld) >= 2 && preg_match('/^[a-zA-Z]+$/', $tld) === 1; + } + + /** + * Validate hostname (domain or IP). + */ + public static function hostname(string $host): bool + { + return self::domain($host) || self::ip($host); + } + + /** + * Validate string contains only printable ASCII characters. + */ + public static function printable(string $input): bool + { + // Printable ASCII: 0x20 (space) to 0x7E (~) + return preg_match('/^[\x20-\x7E]*$/', $input) === 1; + } + + /** + * Validate semantic version string (semver). + */ + public static function semver(string $version): bool + { + // SemVer 2.0.0 pattern + $pattern = '/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/'; + return preg_match($pattern, $version) === 1; + } + + /** + * Validate ISO 8601 datetime string. + */ + public static function iso8601(string $datetime): bool + { + // Common ISO 8601 formats + $formats = [ + 'Y-m-d', + 'Y-m-d\TH:i:s', + 'Y-m-d\TH:i:sP', + 'Y-m-d\TH:i:s.uP', + 'Y-m-d\TH:i:s\Z', + 'Y-m-d\TH:i:s.u\Z', + ]; + + foreach ($formats as $format) { + $parsed = \DateTimeImmutable::createFromFormat($format, $datetime); + if ($parsed !== false && $parsed->format($format) === $datetime) { + return true; + } + } + + return false; + } + + /** + * Validate hex color code. + */ + public static function hexColor(string $color): bool + { + return preg_match('/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/', $color) === 1; + } } From 121ba679330454cf623ce708af2fc0aa37098bd6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 27 Dec 2025 00:59:26 +0000 Subject: [PATCH 3/6] docs: Add positioning strategy and Report 3 findings Based on Zotpress integration feedback: New Documentation: - POSITIONING.md: Clear target audience guidance - Best for: APIs, CLI tools, microservices, semantic web - Not needed for: WordPress (use core functions), Laravel/Symfony (use framework helpers) - Unique value: RDF/Turtle escaping, security headers, extended validators HANDOVER_SANCTIFY.md Updates: - Report 3: GHC barrier confirmed (critical) - WordPress security API overlap documented - WordPress-specific detection rules for sanctify-php - Target audience clarification recommendations README/Roadmap Updates: - Added "Best For" / "Not Needed For" sections - Strategic positioning in roadmap intro - Focus on unique capabilities WordPress lacks --- HANDOVER_SANCTIFY.md | 110 ++++++++++++++++++++- POSITIONING.md | 231 +++++++++++++++++++++++++++++++++++++++++++ README.adoc | 16 +++ ROADMAP_PRIORITY.md | 31 ++++-- 4 files changed, 378 insertions(+), 10 deletions(-) create mode 100644 POSITIONING.md diff --git a/HANDOVER_SANCTIFY.md b/HANDOVER_SANCTIFY.md index 46f5e85..70d1574 100644 --- a/HANDOVER_SANCTIFY.md +++ b/HANDOVER_SANCTIFY.md @@ -380,13 +380,119 @@ safeSinks = [ --- +## Additional Findings (Report 3: Zotpress Plugin) + +### 10. GHC Barrier Confirmed (Critical) + +**Problem**: sanctify-php could not run on the Zotpress integration due to missing Haskell toolchain. + +**Impact**: This is now confirmed across multiple integration attempts. The Haskell build requirement is the #1 adoption barrier. + +**Immediate Recommendations**: +1. Provide pre-built binaries for: + - Linux x86_64 (static binary) + - Linux aarch64 (for ARM servers) + - macOS Intel + - macOS Apple Silicon + - Windows x64 +2. Publish Docker image: `ghcr.io/hyperpolymath/sanctify-php:latest` +3. Create GitHub Action that uses the Docker image internally + +**Workaround Used**: Manual analysis using sanctify-php's documented detection patterns. + +### 11. WordPress Security API Overlap + +**Finding**: When analyzing mature WordPress plugins (like Zotpress), they already follow WordPress security best practices using core functions. + +**WordPress provides equivalent security functions**: + +| php-aegis | WordPress Equivalent | Notes | +|-----------|---------------------|-------| +| `Validator::email()` | `is_email()` | WP version is more permissive | +| `Validator::url()` | `wp_http_validate_url()` | WP has SSL enforcement | +| `Sanitizer::html()` | `esc_html()` | Identical functionality | +| `Sanitizer::attr()` | `esc_attr()` | Identical functionality | +| `Sanitizer::js()` | `esc_js()` | WP version is context-aware | +| `Sanitizer::url()` | `esc_url()` | WP handles protocols | +| `Sanitizer::stripTags()` | `wp_strip_all_tags()` | WP handles more edge cases | + +**What sanctify-php should detect**: +- Direct use of raw PHP functions instead of WordPress equivalents +- `echo $var` instead of `echo esc_html($var)` +- `header('Location: ...')` instead of `wp_redirect()` +- Missing `exit;` after redirect + +**sanctify-php rule suggestions**: +```haskell +-- WordPress-specific rules +wpRules = [ + ("use_wp_redirect", "header\\s*\\(\\s*['\"]Location", "Use wp_redirect() instead of header()"), + ("missing_exit_redirect", "wp_redirect\\([^;]+\\);(?!\\s*exit)", "Add exit; after wp_redirect()"), + ("raw_echo", "echo\\s+\\$(?!esc_)", "Escape output with esc_html()/esc_attr()"), + ("direct_superglobal", "\\$_(GET|POST|REQUEST)\\[", "Sanitize superglobals before use") +] +``` + +### 12. Target Audience Clarification Needed + +**Finding**: php-aegis value proposition is unclear for WordPress users. + +**Recommended positioning for sanctify-php**: + +When sanctify-php detects issues in WordPress code, suggest: +1. **First choice**: WordPress native function (if available) +2. **Second choice**: php-aegis function (for gaps WordPress doesn't cover) + +``` +VULNERABILITY: Unescaped output +FILE: plugin.php:42 +CODE: echo $user_input; + +RECOMMENDATION: + WordPress: echo esc_html($user_input); + Or php-aegis: echo \PhpAegis\Sanitizer::html($user_input); +``` + +### 13. WordPress-Unique Security Patterns + +**What sanctify-php should understand about WordPress**: + +```php +// WordPress-specific security patterns + +// 1. ABSPATH protection (must be at top of every PHP file) +if (!defined('ABSPATH')) exit; + +// 2. Nonce verification for forms +check_admin_referer('action_name'); +wp_verify_nonce($_POST['nonce'], 'action_name'); + +// 3. Capability checks for privileged actions +if (!current_user_can('manage_options')) return; + +// 4. Prepared statements for database +$wpdb->prepare("SELECT * FROM table WHERE id = %d", $id); + +// 5. Safe redirect +wp_safe_redirect($url); +exit; +``` + +**Detection rules needed**: +- Missing ABSPATH check at file start +- Form handlers without nonce verification +- AJAX handlers without capability checks +- Missing `exit;` after redirects + +--- + ## Contact For questions about this integration or to coordinate between repos: - php-aegis: https://github.com/hyperpolymath/php-aegis - sanctify-php: https://github.com/hyperpolymath/sanctify-php -- Integration tested in: wp-sinople-theme +- Integration tested in: wp-sinople-theme, Zotpress --- -*Generated from real-world WordPress semantic theme integration experience (Reports 1 & 2).* +*Generated from real-world WordPress integration experience (Reports 1, 2 & 3).* diff --git a/POSITIONING.md b/POSITIONING.md new file mode 100644 index 0000000..4a35c85 --- /dev/null +++ b/POSITIONING.md @@ -0,0 +1,231 @@ +# php-aegis Positioning & Target Audience + +## The Problem We Discovered + +After integrating php-aegis with multiple WordPress projects (themes and plugins), we found: + +> **WordPress already has comprehensive security APIs** (`esc_html()`, `esc_attr()`, `wp_kses()`, etc.) that are deeply integrated with the WordPress ecosystem. + +This means php-aegis **should not compete** with WordPress core functions. Instead, it should: + +1. **Target non-WordPress PHP applications** where no security API exists +2. **Provide unique capabilities** that WordPress (and other frameworks) lack + +--- + +## Target Audience Matrix + +| Audience | php-aegis Value | Recommendation | +|----------|-----------------|----------------| +| **WordPress plugins/themes** | Low | Use WordPress core functions | +| **Laravel applications** | Medium | Use Laravel's helpers, aegis for gaps | +| **Symfony applications** | Medium | Use Twig's escaping, aegis for gaps | +| **Vanilla PHP applications** | **High** | php-aegis is the primary security layer | +| **API-only services** | **High** | No view layer = no framework escaping | +| **CLI tools** | **High** | No framework = aegis fills the gap | +| **Microservices** | **High** | Lightweight, zero-dependency | +| **Semantic Web apps** | **Very High** | TurtleEscaper is unique | + +--- + +## What WordPress Has (Don't Duplicate) + +| WordPress Function | Purpose | php-aegis Equivalent | +|--------------------|---------|---------------------| +| `esc_html()` | HTML content escaping | `Sanitizer::html()` | +| `esc_attr()` | HTML attribute escaping | `Sanitizer::attr()` | +| `esc_url()` | URL escaping with protocol check | `Sanitizer::url()` | +| `esc_js()` | JavaScript escaping | `Sanitizer::js()` | +| `wp_kses()` | HTML filtering with allowlist | ❌ Not implemented | +| `wp_kses_post()` | HTML filtering for posts | ❌ Not implemented | +| `sanitize_text_field()` | Text sanitization | `Sanitizer::stripTags()` | +| `is_email()` | Email validation | `Validator::email()` | +| `wp_http_validate_url()` | URL validation + SSL | `Validator::url()` | +| `absint()` | Positive integer | `Validator::int(..., min: 0)` | + +**For WordPress projects**: Use WordPress functions. They're more battle-tested, ecosystem-integrated, and maintained by Automattic. + +--- + +## What php-aegis Provides (Unique Value) + +These capabilities are **not available** in WordPress, Laravel, or Symfony: + +### 1. RDF/Turtle Escaping (Unique) + +No other PHP library provides W3C-compliant Turtle escaping. + +```php +use PhpAegis\TurtleEscaper; + +// Safe for semantic web applications +TurtleEscaper::string($userLabel); +TurtleEscaper::iri($userProvidedUri); +TurtleEscaper::triple($subject, $predicate, $object, 'en'); +``` + +**Use cases**: +- Linked Data platforms +- Knowledge graphs +- Semantic WordPress themes (like wp-sinople-theme) +- SPARQL endpoint integrations + +### 2. Security Headers Helper + +WordPress doesn't provide header helpers. Frameworks have partial support. + +```php +use PhpAegis\Headers; + +// One-line security hardening +Headers::secure(); + +// Or fine-grained control +Headers::contentSecurityPolicy([...]); +Headers::strictTransportSecurity(maxAge: 31536000, preload: true); +Headers::permissionsPolicy([...]); +``` + +### 3. Extended Validators Not in WordPress + +| php-aegis | WordPress Equivalent | Notes | +|-----------|---------------------|-------| +| `Validator::uuid()` | ❌ None | RFC 4122 UUID validation | +| `Validator::ip()` | ❌ None | IPv4/IPv6 validation | +| `Validator::ipv4()` | ❌ None | IPv4 only | +| `Validator::ipv6()` | ❌ None | IPv6 only | +| `Validator::domain()` | ❌ None | RFC 1035 domain validation | +| `Validator::hostname()` | ❌ None | Domain or IP | +| `Validator::slug()` | `sanitize_title()` | WP sanitizes, aegis validates | +| `Validator::semver()` | ❌ None | Semantic versioning | +| `Validator::iso8601()` | ❌ None | ISO 8601 datetime | +| `Validator::hexColor()` | `sanitize_hex_color()` | WP sanitizes, aegis validates | +| `Validator::safeFilename()` | `sanitize_file_name()` | WP sanitizes, aegis validates | +| `Validator::json()` | ❌ None | JSON structure validation | +| `Validator::int(min, max)` | ❌ None | Integer with range | +| `Validator::printable()` | ❌ None | ASCII printable only | +| `Validator::noNullBytes()` | ❌ None | Path traversal prevention | +| `Validator::httpsUrl()` | `wp_http_validate_url()` | WP has `$ssl` param | + +### 4. Zero Dependencies + +- WordPress functions require WordPress +- Laravel helpers require Laravel +- Symfony components require Symfony + +php-aegis works in any PHP 8.1+ environment with no dependencies. + +--- + +## Recommended Usage Patterns + +### For WordPress Projects + +```php +// DON'T: Use php-aegis for basic escaping +echo \PhpAegis\Sanitizer::html($content); // ❌ Redundant + +// DO: Use WordPress functions +echo esc_html($content); // ✅ Preferred + +// DO: Use php-aegis for unique capabilities +$headers = new \PhpAegis\Headers(); +$headers::secure(); // ✅ WordPress lacks this + +// DO: Use php-aegis for semantic web features +echo \PhpAegis\TurtleEscaper::string($label); // ✅ WordPress lacks this + +// DO: Use php-aegis for validation gaps +if (!\PhpAegis\Validator::uuid($_GET['id'])) { // ✅ WordPress lacks this + wp_die('Invalid ID'); +} +``` + +### For Laravel Projects + +```php +// DON'T: Use php-aegis for Blade escaping +{{ $content }} // Blade auto-escapes, don't use aegis + +// DO: Use php-aegis in non-Blade contexts +$uuid = $request->input('resource_id'); +if (!Validator::uuid($uuid)) { + abort(400, 'Invalid resource ID'); +} + +// DO: Use for security headers (Laravel's is less comprehensive) +Headers::secure(); +``` + +### For Vanilla PHP / APIs + +```php +// DO: Use php-aegis as your primary security layer +use PhpAegis\{Validator, Sanitizer, Headers}; + +// Apply security headers +Headers::secure(); + +// Validate input +if (!Validator::email($_POST['email'])) { + http_response_code(400); + exit(json_encode(['error' => 'Invalid email'])); +} + +// Sanitize output +echo Sanitizer::html($userContent); +``` + +--- + +## Marketing Positioning + +### Tagline Options + +1. **"Security for the rest of PHP"** - Emphasizes non-framework use +2. **"Where frameworks fear to tread"** - Emphasizes unique capabilities +3. **"Semantic web security, done right"** - Emphasizes Turtle escaping niche + +### README Messaging + +``` +php-aegis is a zero-dependency PHP security toolkit for: +- API services without view layers +- CLI tools and microservices +- Semantic web applications (RDF/Turtle) +- Any PHP app without a framework + +For WordPress, use WordPress core functions. +For Laravel/Symfony, use framework helpers + aegis for gaps. +``` + +--- + +## Roadmap Implications + +Based on this positioning, prioritize: + +1. **RDF/Turtle escaping** - Already done, unique differentiator +2. **Security headers** - Already done, fills framework gaps +3. **Extended validators** - Focus on what WordPress lacks (UUID, IP, semver, etc.) +4. **IndieWeb security** - Micropub, IndieAuth, Webmention (unique niche) +5. **Rate limiting** - File-based, no Redis required + +De-prioritize: +- HTML/attribute escaping improvements (frameworks do this well) +- WordPress adapter (WordPress users should use WordPress functions) + +--- + +## Success Metrics + +| Metric | Target Audience Indicator | +|--------|--------------------------| +| Downloads from API/microservice projects | Primary audience | +| Usage in semantic web tools | Niche but high-value | +| Issues asking about WordPress | Signals need for better docs | +| PRs adding framework adapters | Community wants integration | + +--- + +*This positioning reflects insights from WordPress theme (wp-sinople-theme) and plugin (Zotpress) integration attempts.* diff --git a/README.adoc b/README.adoc index 354fc63..4154233 100644 --- a/README.adoc +++ b/README.adoc @@ -14,6 +14,22 @@ image:https://img.shields.io/badge/RSR-Compliant-gold.svg[RSR Compliant] php-aegis provides a collection of security-focused utilities for PHP applications. Named after the mythological shield of Zeus, it aims to protect your applications from common web vulnerabilities. +=== Best For + +* **API services** without view layers +* **CLI tools** and microservices +* **Semantic web applications** (RDF/Turtle escaping - unique to php-aegis) +* **Vanilla PHP** applications without frameworks +* **Framework gaps** - validation/features that WordPress/Laravel/Symfony lack + +=== Not Needed For + +* **WordPress plugins/themes** - Use WordPress core functions (`esc_html()`, `esc_attr()`, etc.) +* **Laravel views** - Use Blade's auto-escaping +* **Symfony/Twig** - Use Twig's escaping + +See link:POSITIONING.md[POSITIONING.md] for detailed guidance. + === Key Features * **Input Validation** - Strict validation for emails, URLs, IPs, UUIDs, and more diff --git a/ROADMAP_PRIORITY.md b/ROADMAP_PRIORITY.md index 8cf6c43..c1838e2 100644 --- a/ROADMAP_PRIORITY.md +++ b/ROADMAP_PRIORITY.md @@ -1,17 +1,32 @@ # php-aegis Roadmap (Integration-Informed Priority) -This roadmap is prioritized based on real-world integration experience with WordPress semantic themes and the feedback received during the wp-sinople-theme security integration. +This roadmap is prioritized based on real-world integration experience with WordPress themes and plugins, reflecting lessons from wp-sinople-theme and Zotpress integrations. -## Context: Why This Matters +## Strategic Positioning -During integration testing, the following gaps were identified: +See [POSITIONING.md](POSITIONING.md) for full positioning strategy. -1. **Feature set too minimal** - WordPress has `esc_html()`, `esc_attr()`, etc. already -2. **No RDF/Turtle support** - Semantic themes need specialized escaping -3. **Missing SPDX headers** - Compliance requirement not met -4. **Not leveraging PHP 8.1+** - Enums, union types, readonly properties unused +**Key insight**: WordPress (and Laravel, Symfony) already have comprehensive security APIs. php-aegis should: -This roadmap addresses these gaps in priority order. +1. **Target non-framework PHP** - APIs, CLI tools, microservices +2. **Provide unique capabilities** - RDF/Turtle, security headers, extended validators +3. **Fill framework gaps** - What WordPress/Laravel/Symfony don't provide + +**Do NOT prioritize**: Duplicating `esc_html()`, `esc_attr()` equivalents that frameworks already do well. + +## Context: Integration Findings + +| Integration | Finding | +|-------------|---------| +| wp-sinople-theme | RDF/Turtle escaping is unique value; basic sanitization duplicates WordPress | +| Zotpress plugin | Mature WP plugins already use core functions; php-aegis not needed for basic security | + +**Prioritize features WordPress lacks**: +- RDF/Turtle escaping ✅ +- Security headers ✅ +- Extended validators (UUID, IP, semver, etc.) ✅ +- IndieWeb security (Micropub, IndieAuth) +- Rate limiting without external dependencies --- From 9e3cd9b0f9a729b6668789529b04c3c2a621c62b Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 27 Dec 2025 01:29:41 +0000 Subject: [PATCH 4/6] fix: Add permissionsPolicy to Headers::secure() and Report 4 findings Bug Fix: - Headers::secure() now includes permissionsPolicy() and removeInsecureHeaders() - Disables geolocation, camera, microphone, payment by default Report 4 (sinople-theme) Integration Findings: For sanctify-php: - UnsafeRedirect false positive when exit; on next line - MissingTextDomain may flag WP core functions - PHP 8.1+ syntax verification needed (nullsafe, match, first-class callable) - Guix package export documentation incomplete For php-aegis: - TurtleEscaper proved unique value (RDF/Turtle feed endpoint) - Clarified php-aegis-compat is planned, not yet implemented - WordPress mu-plugin adapter is Phase 7 roadmap item Integration success: Full WordPress theme integration with graceful fallback --- COMPATIBILITY.md | 8 ++-- HANDOVER_SANCTIFY.md | 111 ++++++++++++++++++++++++++++++++++++++++++- src/Headers.php | 7 +++ 3 files changed, 121 insertions(+), 5 deletions(-) diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 5d0dc17..8ce240a 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -1,16 +1,18 @@ # php-aegis Compatibility Strategy +> **Note**: This document describes the planned compatibility strategy. The `php-aegis-compat` package is not yet implemented. See the [roadmap](ROADMAP_PRIORITY.md) for status. + ## The Problem php-aegis requires PHP 8.1+, but WordPress officially supports PHP 7.4+. This limits adoption in the WordPress ecosystem where many hosts still run PHP 7.4 or 8.0. ## Strategy: Dual-Package Approach -Instead of downgrading the main library, we provide a separate compatibility package. +Instead of downgrading the main library, we will provide a separate compatibility package. ``` -hyperpolymath/php-aegis # PHP 8.1+ (main, recommended) -hyperpolymath/php-aegis-compat # PHP 7.4+ (polyfill, limited) +hyperpolymath/php-aegis # PHP 8.1+ (main, recommended) ✅ Available +hyperpolymath/php-aegis-compat # PHP 7.4+ (polyfill, limited) 📋 Planned ``` ### Why Not Downgrade? diff --git a/HANDOVER_SANCTIFY.md b/HANDOVER_SANCTIFY.md index 70d1574..9effbfc 100644 --- a/HANDOVER_SANCTIFY.md +++ b/HANDOVER_SANCTIFY.md @@ -486,13 +486,120 @@ exit; --- +## Additional Findings (Report 4: sinople-theme Full Integration) + +### 14. Successful Integration Pattern + +**What Worked**: Full integration with WordPress theme including: +- Function wrappers: `sinople_aegis_html()`, `sinople_aegis_attr()`, `sinople_aegis_json()` +- Validation wrappers: `sinople_aegis_validate_*()` functions +- RDF/Turtle feed endpoint using `TurtleEscaper` (unique value!) +- Graceful fallback to WordPress functions when php-aegis unavailable +- Unit tests for the integration + +**Key Success**: TurtleEscaper proved its unique value by enabling a `/feed/turtle/` endpoint. + +### 15. sanctify-php False Positives Identified + +**Issues to address**: + +1. **UnsafeRedirect false positive**: When `exit;` is on the next line +```php +// This triggers false positive: +wp_redirect($url); +exit; + +// sanctify-php expects: +wp_redirect($url); exit; +``` + +2. **MissingTextDomain false positive**: Flags WordPress core functions +```php +// This may be flagged incorrectly: +__('Text', 'theme-domain'); // OK +_e('Text', 'theme-domain'); // OK +esc_html__('Text'); // May flag - but sometimes domain is optional +``` + +**Recommendation**: Add configuration options: +```yaml +# sanctify.yml +rules: + UnsafeRedirect: + allow_next_line_exit: true + MissingTextDomain: + ignore_core_functions: true +``` + +### 16. PHP 8.1+ Syntax Verification Needed + +**Concern**: Parser may not handle modern PHP syntax. + +**Test cases to verify**: +```php +// Nullsafe operator (PHP 8.0+) +$value = $object?->property?->method(); + +// Match expression (PHP 8.0+) +$result = match($type) { + 'html' => Sanitizer::html($input), + 'js' => Sanitizer::js($input), + default => $input, +}; + +// Constructor property promotion (PHP 8.0+) +public function __construct( + private readonly string $name, +) {} + +// First-class callable syntax (PHP 8.1+) +$fn = Sanitizer::html(...); +``` + +### 17. Guix Export Documentation + +**Issue**: Guix package export documentation is incomplete. + +**Recommendation**: Add to sanctify-php docs: +```scheme +;; guix.scm +(use-modules (guix packages) + (guix git-download) + (guix build-system haskell)) + +(package + (name "sanctify-php") + (version "0.1.0") + (source (git-reference + (url "https://github.com/hyperpolymath/sanctify-php") + (commit (string-append "v" version)))) + (build-system haskell-build-system) + (synopsis "PHP security static analyzer") + (license license:agpl3+)) +``` + +--- + +## php-aegis Self-Identified Issues (Report 4) + +These issues were discovered during sinople-theme integration: + +| Issue | Status | Resolution | +|-------|--------|------------| +| `Headers::secure()` missing `permissionsPolicy()` | ✅ Fixed | Added in this PR | +| `php-aegis-compat` package doesn't exist | 📋 Planned | Create separate repo | +| Not published on Packagist | 📋 Planned | Publish after v0.2.0 | +| WordPress mu-plugin adapter not implemented | 📋 Planned | Phase 7 roadmap | + +--- + ## Contact For questions about this integration or to coordinate between repos: - php-aegis: https://github.com/hyperpolymath/php-aegis - sanctify-php: https://github.com/hyperpolymath/sanctify-php -- Integration tested in: wp-sinople-theme, Zotpress +- Integration tested in: wp-sinople-theme, Zotpress, sinople-theme --- -*Generated from real-world WordPress integration experience (Reports 1, 2 & 3).* +*Generated from real-world WordPress integration experience (Reports 1, 2, 3 & 4).* diff --git a/src/Headers.php b/src/Headers.php index 1fc6816..9fa4a6b 100644 --- a/src/Headers.php +++ b/src/Headers.php @@ -33,6 +33,13 @@ public static function secure(): void self::contentSecurityPolicy([ 'default-src' => ["'self'"], ]); + self::permissionsPolicy([ + 'geolocation' => [], + 'camera' => [], + 'microphone' => [], + 'payment' => [], + ]); + self::removeInsecureHeaders(); } /** From 43a917c3f0fb31b9f4b2ed87748f01ecf76995a5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 27 Dec 2025 01:33:39 +0000 Subject: [PATCH 5/6] docs: Add Report 5 findings - TurtleEscaper validated as killer feature Report 5 (Sinople theme) confirmed TurtleEscaper's unique value: - Fixed CRITICAL vulnerability: addslashes() was used for Turtle escaping - This is SQL escaping, not Turtle escaping - real RDF injection risk - TurtleEscaper::literal() provides W3C-compliant escaping New sanctify-php detection rules proposed: - RDF Turtle as distinct output context - WordPress REST API pattern recognition - WordPress hook detection (reduce false positives) Key learning across 5 reports: - TurtleEscaper is the #1 unique value proposition - GHC barrier confirmed in every sanctify-php attempt - php-aegis basic escaping redundant for WordPress - php-aegis shines in: security headers, extended validators, RDF/Turtle --- HANDOVER_SANCTIFY.md | 111 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 2 deletions(-) diff --git a/HANDOVER_SANCTIFY.md b/HANDOVER_SANCTIFY.md index 9effbfc..3795cef 100644 --- a/HANDOVER_SANCTIFY.md +++ b/HANDOVER_SANCTIFY.md @@ -593,13 +593,120 @@ These issues were discovered during sinople-theme integration: --- +## Additional Findings (Report 5: Sinople Theme - Critical Vulnerability Fixed) + +### 18. TurtleEscaper Fixed Real Vulnerability + +**Critical Finding**: The theme was using `addslashes()` for RDF Turtle escaping - this is SQL escaping, NOT Turtle escaping. This was a real RDF injection vulnerability. + +**Before (vulnerable)**: +```php +// DANGEROUS: addslashes() is SQL escaping, not Turtle escaping! +$turtle = '"' . addslashes($label) . '"@en'; +``` + +**After (fixed)**: +```php +use PhpAegis\TurtleEscaper; +$turtle = TurtleEscaper::literal($label, language: 'en'); +``` + +**This validates TurtleEscaper as the #1 unique value proposition of php-aegis.** + +### 19. Security Fixes Applied in Real Integration + +| Severity | Issue | Fix Applied | +|----------|-------|-------------| +| CRITICAL | `addslashes()` for Turtle | `TurtleEscaper::literal()` | +| CRITICAL | IRI interpolation | `Validator::url()` + error handling | +| HIGH | URL validation via `strpos()` | `parse_url()` host comparison | +| HIGH | Unsanitized Micropub input | `sanitize_text_field()` + `wp_kses_post()` | +| MEDIUM | No security headers | `Headers::secure()` equivalent | +| MEDIUM | No rate limiting | 1-min rate limit for Webmentions | +| LOW | Missing `strict_types` | Added to all files | + +### 20. New Detection Rules for sanctify-php + +**RDF Turtle as Distinct Output Context**: + +sanctify-php should recognize Turtle output contexts and flag: +```haskell +-- RDF Turtle detection rules +turtleRules = [ + -- Dangerous: SQL escaping in Turtle context + ("turtle_addslashes", "addslashes\\s*\\([^)]+\\).*['\"]@[a-z]{2}", + "Use TurtleEscaper::literal() instead of addslashes() for Turtle"), + + -- Dangerous: String interpolation in Turtle IRI + ("turtle_iri_interp", "<.*\\$[a-zA-Z_].*>", + "Use TurtleEscaper::iri() for Turtle IRIs"), + + -- Dangerous: Raw variable in Turtle string + ("turtle_string_raw", "\"\\$[a-zA-Z_][^\"]*\"@[a-z]", + "Use TurtleEscaper::string() for Turtle literals") +] +``` + +**WordPress REST API Pattern Recognition**: +```haskell +-- WordPress REST API rules +restRules = [ + ("rest_missing_permission", "register_rest_route.*permission_callback.*__return_true", + "REST routes should verify permissions"), + + ("rest_raw_param", "\\$request\\[.*\\](?!.*sanitize)", + "Sanitize REST API parameters") +] +``` + +**WordPress Hook Detection** (reduce false positives): +```haskell +-- Functions defined via add_action/add_filter are called by WordPress +wpHookFunctions = extractFunctionsFrom [ + "add_action\\s*\\([^,]+,\\s*['\"]([^'\"]+)", + "add_filter\\s*\\([^,]+,\\s*['\"]([^'\"]+)" +] +-- These should not be flagged as "unused functions" +``` + +### 21. php-aegis Enhancement Requests + +From this integration: + +| Request | Priority | Notes | +|---------|----------|-------| +| WordPress nonce validator | Medium | `Validator::wpNonce($nonce, $action)` | +| WordPress capability checker | Medium | `Validator::wpCapability($cap)` | +| TurtleEscaper case sensitivity docs | Low | Language tags should be lowercase | +| SPDX identifier validator | Low | `Validator::spdx($identifier)` | +| Headers + WordPress integration docs | Medium | How to use with `wp_headers` filter | + +--- + +## Final Summary: Integration Value Matrix + +| Tool | WordPress Value | Non-WP Value | Unique Capability | +|------|----------------|--------------|-------------------| +| **php-aegis** | Low (WP has `esc_*`) | **High** | RDF/Turtle escaping | +| **sanctify-php** | **High** (finds WP issues) | **High** | Taint tracking | + +### Key Learnings Across 5 Reports + +1. **TurtleEscaper is the killer feature** - Fixed real vulnerabilities in semantic web themes +2. **GHC barrier is critical** - Confirmed in every sanctify-php integration attempt +3. **WordPress has comprehensive APIs** - php-aegis basic escaping is redundant +4. **php-aegis shines in framework gaps** - Security headers, extended validators, RDF/Turtle +5. **sanctify-php needs WordPress awareness** - Hook detection, REST API patterns + +--- + ## Contact For questions about this integration or to coordinate between repos: - php-aegis: https://github.com/hyperpolymath/php-aegis - sanctify-php: https://github.com/hyperpolymath/sanctify-php -- Integration tested in: wp-sinople-theme, Zotpress, sinople-theme +- Integration tested in: wp-sinople-theme, Zotpress, sinople-theme (×2) --- -*Generated from real-world WordPress integration experience (Reports 1, 2, 3 & 4).* +*Generated from real-world WordPress integration experience (Reports 1-5).* From 3a459bcc15a1a5bf6a66342e75506e80b786fe45 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 27 Dec 2025 01:42:56 +0000 Subject: [PATCH 6/6] docs: Add indieweb2-bastion relationship to IndieWeb security planning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added references to indieweb2-bastion in both HANDOVER and ROADMAP: - Infrastructure layer (bastion, provenance) complements application layer - Provenance graphs for Webmention verification chains - Bastion patterns for rate limiting endpoints - Audit logging as first-class feature Also added SSRF prevention to IndieWeb roadmap (Phase 5.4). Architecture: indieweb2-bastion → Infrastructure (network, DNS, audit) php-aegis → Application (Micropub, IndieAuth, Webmention) sanctify-php → Analysis (static security scanning) --- HANDOVER_SANCTIFY.md | 43 +++++++++++++++++++++++++++++++++++++++++++ ROADMAP_PRIORITY.md | 28 ++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/HANDOVER_SANCTIFY.md b/HANDOVER_SANCTIFY.md index 3795cef..d6b7aae 100644 --- a/HANDOVER_SANCTIFY.md +++ b/HANDOVER_SANCTIFY.md @@ -683,6 +683,49 @@ From this integration: --- +## Related Project: indieweb2-bastion + +The [indieweb2-bastion](https://github.com/hyperpolymath/indieweb2-bastion) repository provides infrastructure-layer security that complements php-aegis and sanctify-php at the application layer. + +### What indieweb2-bastion Does + +| Feature | Purpose | +|---------|---------| +| Hardened bastion ingress | Secure network entry points | +| Oblivious DNS (IPv6) | Privacy-preserving DNS resolution | +| GraphQL DNS APIs | Programmable domain resolution | +| SurrealDB provenance graphs | Audit trails & data lineage | + +### Relationship to IndieWeb Security + +While **not** implementing IndieWeb protocols (Micropub, IndieAuth, Webmention), indieweb2-bastion provides foundational security patterns applicable to IndieWeb infrastructure: + +| indieweb2-bastion | IndieWeb Application | +|-------------------|---------------------| +| Provenance graphs | Track Webmention verification chains | +| Audit capabilities | Log IndieAuth token usage | +| Bastion pattern | Rate limit Webmention endpoints | +| Policy controls (Nickel) | Define allowed Micropub content | + +### Recommended Stack Architecture + +``` +┌─────────────────────────────────────────────────────────┐ +│ Full IndieWeb Stack │ +├─────────────────────────────────────────────────────────┤ +│ indieweb2-bastion │ Infrastructure layer │ +│ (network, DNS, audit)│ (bastion, provenance) │ +├───────────────────────┼─────────────────────────────────┤ +│ php-aegis │ Application layer │ +│ (validation, escaping)│ (Micropub, IndieAuth, Webmention)│ +├───────────────────────┼─────────────────────────────────┤ +│ sanctify-php │ Analysis layer │ +│ (static analysis) │ (find vulnerabilities) │ +└─────────────────────────────────────────────────────────┘ +``` + +--- + ## Final Summary: Integration Value Matrix | Tool | WordPress Value | Non-WP Value | Unique Capability | diff --git a/ROADMAP_PRIORITY.md b/ROADMAP_PRIORITY.md index c1838e2..bcc649b 100644 --- a/ROADMAP_PRIORITY.md +++ b/ROADMAP_PRIORITY.md @@ -148,6 +148,27 @@ Sanitizer::jsonEncode(mixed $input): string // Safe JSON with flags **Goal**: First-class support for IndieWeb/semantic web patterns. +### Related Project: indieweb2-bastion + +The [indieweb2-bastion](https://github.com/hyperpolymath/indieweb2-bastion) repository provides infrastructure-layer security (bastion ingress, oblivious DNS, provenance graphs) that complements php-aegis at the application layer. + +**Architectural relationship**: +``` +┌────────────────────────────────────────────────┐ +│ indieweb2-bastion │ Infrastructure layer │ +│ (network, audit) │ Rate limiting, logging │ +├─────────────────────┼──────────────────────────┤ +│ php-aegis │ Application layer │ +│ (this module) │ Micropub, IndieAuth, │ +│ │ Webmention validation │ +└────────────────────────────────────────────────┘ +``` + +**Lessons from indieweb2-bastion**: +- Use provenance-style tracking for Webmention verification chains +- Apply bastion patterns for rate limiting endpoints +- Consider audit logging as a first-class feature + ### 5.1 Micropub Content Sanitizer ```php Micropub::sanitizeContent(string $html, array $allowedTags = []): string @@ -167,6 +188,13 @@ Webmention::validateSource(string $url): bool // Not internal IP Webmention::validateTarget(string $url, string $domain): bool ``` +### 5.4 SSRF Prevention +```php +// Prevent Webmention SSRF attacks +Webmention::isInternalIp(string $ip): bool +Webmention::resolveAndValidate(string $url): ValidationResult +``` + --- ## Phase 6: Rate Limiting (v0.6.0)