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; + } }