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.
| 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-phpfinds the bugsphp-aegisprovides the fixes
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:
docker run --rm -v $(pwd):/workspace ghcr.io/hyperpolymath/sanctify-php analyze /workspaceProblem: Parser may not handle all PHP 8.x syntax (enums, union types, named arguments, attributes, match expressions, constructor property promotion).
Test cases needed:
// 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.
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):
// DANGEROUS: addslashes() is insufficient for Turtle
$turtle = '<' . $uri . '> rdfs:label "' . addslashes($label) . '" .';Attack vectors:
# Turtle escape sequences
\n \r \t \\ \" \uXXXX \UXXXXXXXX
# IRI injection
<http://evil.com> owl:sameAs <http://trusted.com>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
Problem: No clear guidance for WordPress-specific vulnerability patterns.
WordPress-specific patterns to detect:
// 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:
// 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
Problem: No awareness of IndieWeb protocols (Micropub, IndieAuth, Webmention).
Patterns to detect:
// 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 sourceRecommendation: Add rules for common IndieWeb vulnerability patterns.
┌─────────────────────────────────────────────────────────────┐
│ Development Workflow │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Developer │───▶│ sanctify-php │───▶│ Fix Code │ │
│ │ Writes Code │ │ (Analysis) │ │ (Guidance) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ php-aegis │ │
│ │ (Runtime) │ │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
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 | Issue | Effort |
|---|---|---|
| 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 |
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:
# .github/workflows/security.yml
- uses: hyperpolymath/sanctify-php-action@v1
with:
path: ./src
config: sanctify.yml
sarif-output: results.sarifWhat 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
{
"runs": [{
"tool": { "driver": { "name": "sanctify-php" } },
"results": [{
"ruleId": "xss-output",
"level": "error",
"message": { "text": "Unescaped output" },
"fixes": [{
"description": { "text": "Use PhpAegis\\Sanitizer::html()" }
}]
}]
}]
}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
# First run: full analysis, build cache
sanctify analyze ./src --cache .sanctify-cache
# Subsequent runs: incremental
sanctify analyze ./src --cache .sanctify-cache --incrementalProblem: PHP developers expect composer require installation.
Recommendation: Create a Composer plugin that:
- Downloads pre-built binary for platform
- Provides
vendor/bin/sanctifywrapper - Handles updates via Composer
composer require --dev hyperpolymath/sanctify-php
vendor/bin/sanctify analyze ./srcphp-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)
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:
-- 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"
]| 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 |
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:
- Provide pre-built binaries for:
- Linux x86_64 (static binary)
- Linux aarch64 (for ARM servers)
- macOS Intel
- macOS Apple Silicon
- Windows x64
- Publish Docker image:
ghcr.io/hyperpolymath/sanctify-php:latest - Create GitHub Action that uses the Docker image internally
Workaround Used: Manual analysis using sanctify-php's documented detection patterns.
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 $varinstead ofecho esc_html($var)header('Location: ...')instead ofwp_redirect()- Missing
exit;after redirect
sanctify-php rule suggestions:
-- 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")
]Finding: php-aegis value proposition is unclear for WordPress users.
Recommended positioning for sanctify-php:
When sanctify-php detects issues in WordPress code, suggest:
- First choice: WordPress native function (if available)
- 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);
What sanctify-php should understand about WordPress:
// 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
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.
Issues to address:
- UnsafeRedirect false positive: When
exit;is on the next line
// This triggers false positive:
wp_redirect($url);
exit;
// sanctify-php expects:
wp_redirect($url); exit;- MissingTextDomain false positive: Flags WordPress core functions
// This may be flagged incorrectly:
__('Text', 'theme-domain'); // OK
_e('Text', 'theme-domain'); // OK
esc_html__('Text'); // May flag - but sometimes domain is optionalRecommendation: Add configuration options:
# sanctify.yml
rules:
UnsafeRedirect:
allow_next_line_exit: true
MissingTextDomain:
ignore_core_functions: trueConcern: Parser may not handle modern PHP syntax.
Test cases to verify:
// 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(...);Issue: Guix package export documentation is incomplete.
Recommendation: Add to sanctify-php docs:
;; 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+))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 |
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):
// DANGEROUS: addslashes() is SQL escaping, not Turtle escaping!
$turtle = '"' . addslashes($label) . '"@en';After (fixed):
use PhpAegis\TurtleEscaper;
$turtle = TurtleEscaper::literal($label, language: 'en');This validates TurtleEscaper as the #1 unique value proposition of php-aegis.
| 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 |
RDF Turtle as Distinct Output Context:
sanctify-php should recognize Turtle output contexts and flag:
-- 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:
-- 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):
-- 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"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 |
The indieweb2-bastion repository provides infrastructure-layer security that complements php-aegis and sanctify-php at the application layer.
| 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 |
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 |
┌─────────────────────────────────────────────────────────┐
│ 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) │
└─────────────────────────────────────────────────────────┘
| 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 |
- TurtleEscaper is the killer feature - Fixed real vulnerabilities in semantic web themes
- GHC barrier is critical - Confirmed in every sanctify-php integration attempt
- WordPress has comprehensive APIs - php-aegis basic escaping is redundant
- php-aegis shines in framework gaps - Security headers, extended validators, RDF/Turtle
- sanctify-php needs WordPress awareness - Hook detection, REST API patterns
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 (×2)
Generated from real-world WordPress integration experience (Reports 1-5).