This roadmap is prioritized based on real-world integration experience with WordPress themes and plugins, reflecting lessons from wp-sinople-theme and Zotpress integrations.
See POSITIONING.md for full positioning strategy.
Key insight: WordPress (and Laravel, Symfony) already have comprehensive security APIs. php-aegis should:
- Target non-framework PHP - APIs, CLI tools, microservices
- Provide unique capabilities - RDF/Turtle, security headers, extended validators
- Fill framework gaps - What WordPress/Laravel/Symfony don't provide
Do NOT prioritize: Duplicating esc_html(), esc_attr() equivalents that frameworks already do well.
| 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
Goal: Address compliance and differentiation issues immediately.
- Add
SPDX-License-Identifier: MIT OR PMPL-1.0-or-laterto all PHP files - Add
SPDX-FileCopyrightTextwith year and author
- Convert
ValidatorandSanitizerto use static methods - Rationale: No instance state needed, improves ergonomics
- Before:
(new Sanitizer())->html($input) - After:
Sanitizer::html($input)
TurtleEscaper::string(string $input): string- Escape for Turtle string literalsTurtleEscaper::iri(string $uri): string- Escape/validate for Turtle IRIs- This is a unique differentiator - no other PHP library does this properly
Goal: Provide value beyond WordPress built-ins.
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): voidHeaders::secure(): void // Apply sensible defaults for all headers- WordPress doesn't provide header helpers
- Frameworks often require manual configuration
- This provides "secure by default" with one function call
Goal: Cover common validation needs with strict, type-safe implementations.
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): boolValidator::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 JSONValidator::noNullBytes(string $input): bool
Validator::printable(string $input): bool
Validator::safeFilename(string $filename): bool // No path traversal
Validator::httpsUrl(string $url): bool // Enforce HTTPSGoal: Provide correct escaping for every output context.
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
}Sanitizer::escape(string $input, OutputContext $context): stringSanitizer::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 flagsGoal: First-class support for IndieWeb/semantic web patterns.
The 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
Micropub::sanitizeContent(string $html, array $allowedTags = []): string
Micropub::validateEntry(array $mf2): ValidationResultIndieAuth::verifyToken(string $token, string $endpoint): TokenResult
IndieAuth::validateMe(string $url): bool // Valid "me" URL
IndieAuth::validateRedirectUri(string $uri, string $clientId): boolWebmention::validateSource(string $url): bool // Not internal IP
Webmention::validateTarget(string $url, string $domain): bool// Prevent Webmention SSRF attacks
Webmention::isInternalIp(string $ip): bool
Webmention::resolveAndValidate(string $url): ValidationResultGoal: Protect against abuse without external dependencies.
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$limiter = new RateLimiter(
store: new FileStore('/tmp/ratelimit'),
capacity: 100, // requests
refillRate: 10, // per second
);
if (!$limiter->attempt($clientIp)) {
http_response_code(429);
exit;
}Goal: Maximize adoption across PHP ecosystem.
Create hyperpolymath/php-aegis-compat for PHP 7.4+ environments.
See COMPATIBILITY.md for full strategy.
# PHP 7.4+ (legacy WordPress hosts)
composer require hyperpolymath/php-aegis-compat
# PHP 8.1+ (recommended)
composer require hyperpolymath/php-aegisWordPress-style function wrappers:
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()// Auto-registered via package discovery
PhpAegis\Laravel\AegisServiceProvider::class
// Blade directive
@aegis($userContent)
// DI in controllers
public function store(Sanitizer $sanitizer) { ... }// config/bundles.php
PhpAegis\Symfony\AegisBundle::class => ['all' => true]
// Twig filter
{{ user_content|aegis_html }}esc_html(),esc_attr(),esc_url(),esc_js()wp_kses(),wp_kses_post()sanitize_*()functions- Nonce verification
| 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) | ❌ | ❌ | ❌ | ✅ |
- Adoption: Downloads on Packagist
- Integration: Used in WordPress themes, Laravel packages
- Coverage: CVE fixes attributable to php-aegis usage
- Community: GitHub stars, issues, PRs
Per project guidelines, no time estimates are provided. Work proceeds based on:
- User demand (GitHub issues)
- Security criticality
- Contributor availability
Phases can be reordered based on community feedback.
This roadmap reflects lessons learned from real WordPress integration.