eresus-php-audit — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited eresus-php-audit (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Perform a comprehensive, depth-first security audit of PHP codebases. This skill provides the complete knowledge of Kunlun-M's CVI rule set, organized by vulnerability class, plus framework-specific patterns for Laravel, WordPress, Symfony, and modern PHP 8.x applications.
Use view_file and grep_search exclusively. No terminal commands.
composer.json / composer.lock for known vulnerable dependenciesphp constraint in composer.json)Inspired by Kunlun-M's EntranceFinder plugin — systematically find all user-facing entry points:
.php files that can be accessed directly (not included/required)wp_ajax_*, Laravel API routes, custom handlersSearch patterns for entry points:
$_GET, $_POST, $_REQUEST, $_FILES, $_COOKIE, $_SERVERfile_get_contents('php://input')Route::get, Route::post, Route::any, Route::resourceadd_action('wp_ajax_, add_action('rest_api_init#[Route(, @Route(, routing.yaml definitionsSearch for HTTP request functions with user-controlled URLs:
curl_setopt($ch, CURLOPT_URL, $userInput)
file_get_contents($userUrl)
fopen($userUrl, 'r')
$client->get($userInput) // Guzzle
$client->request('GET', $userInput)Severity: HIGH — can lead to internal service access, cloud metadata theft Trace: Check if URL comes from $_GET, $_POST, database with user data
Search for raw SQL construction:
$db->query("SELECT * FROM users WHERE id = " . $_GET['id'])
$wpdb->query("SELECT * FROM $table WHERE id = $id")
$pdo->query("SELECT ... $var ...")
mysqli_query($conn, "... $var ...")Safe patterns: $pdo->prepare(), $wpdb->prepare(), Eloquent query builder Severity: CRITICAL when user input reaches query without parameterization
Search for shell execution functions:
system($userInput)
exec($userInput)
passthru($userInput)
shell_exec($userInput)
`$userInput` (backticks)
popen($userInput, 'r')
proc_open($userInput, ...)
pcntl_exec($userInput)Severity: CRITICAL — always results in RCE if input is user-controlled
Search for dynamic code execution:
eval($userInput)
assert($userInput) // PHP < 8.0
preg_replace('/.*/e', $replacement, $subject) // deprecated /e modifier
create_function($args, $userInput)
call_user_func($userInput, $args)
call_user_func_array($userInput, $args)
array_map($userInput, $data)
usort($data, $userInput)Severity: CRITICAL if any argument is user-controlled
Search for unescaped output:
echo $_GET['input']
echo $userInput // without htmlspecialchars()
<?= $userInput ?>
print($userInput)
printf("%s", $userInput) // in HTML contextSafe patterns: htmlspecialchars($var, ENT_QUOTES, 'UTF-8'), esc_html() (WP), {{ $var }} (Blade) Dangerous: {!! $var !!} (Laravel Blade raw), | raw (Twig)
include($userInput)
include_once($userInput)
require($userInput)
require_once($userInput)Severity: CRITICAL if path is user-controlled Check: Is allow_url_include enabled? (RFI)
file_get_contents($userInput) // path traversal read
file_put_contents($userInput, $data) // arbitrary file write
unlink($userInput) // arbitrary file delete
copy($src, $userInput) // arbitrary file placement
rename($old, $userInput) // arbitrary file move
readfile($userInput) // information disclosureSeverity: HIGH to CRITICAL depending on operation
$doc = new DOMDocument()
$doc->loadXML($userInput) // XXE if no protection
simplexml_load_string($userInput) // XXE
$reader = new XMLReader()
$reader->xml($userInput) // XXESafe: libxml_disable_entity_loader(true) (deprecated PHP 8.0+, secure by default)
unserialize($userInput)
unserialize($_COOKIE['data'])
unserialize(base64_decode($_GET['data']))Severity: CRITICAL — Property-Oriented Programming (POP) chain exploitation Gadget hunting: Search for classes with:
__wakeup() — called on deserialization__destruct() — called on object destruction__toString() — called on string cast__call() — called on undefined method__get() / __set() — called on property accessKunlun-M's phpunserializechain plugin methodology — trace POP chains:
unserialize() with user input__destruct() → calls method → file write / command execPendingBroadcast → __destruct() → dispatch()Process → __destruct() → stop() → command executionBufferHandler → __destruct() → close() → arbitrary write// Weak comparison
if ($_POST['password'] == $storedPassword) // type juggling: "0" == 0
if (md5($_POST['password']) == $storedHash) // magic hash: "0e..." == 0
// Missing auth check
// Check if sensitive functions lack is_admin(), current_user_can(), auth checkKey pattern: == vs === for authentication — PHP type juggling attack
session_set_cookie_params(['secure' => false])
setcookie($name, $value) // missing secure, httponly, samesite flags
$_SESSION['admin'] = $_POST['is_admin'] // user-controlled session data
session_id($_GET['sessid']) // session fixation// Missing CSRF token verification
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// No token check before processing
}
// WordPress: Missing nonce verification
// check_ajax_referer() missing
// wp_verify_nonce() missingphpinfo()
error_reporting(E_ALL)
display_errors = On
var_dump($sensitiveData)
print_r($debug)$password = "hardcoded"
$apiKey = "sk_live_..."
$dbPassword = "root"
define('DB_PASSWORD', 'actual_password')allow_url_fopen = On // enables RFI via include()
allow_url_include = On // enables remote file inclusion
expose_php = On // version disclosure
register_globals = On // variable injection (legacy)
magic_quotes_gpc = Off // no auto-escaping (legacy)
open_basedir // check if properly set
disable_functions // check if dangerous funcs are disabled.env file exposure (web-accessible .env)APP_DEBUG=true in productionAPP_KEY rotation{!! !!} raw Blade output with user data$fillable vs $guardedRoute::any() over-permissive routingdefined('ABSPATH') check$wpdb->prepare() usage (must use %s, %d placeholders)update_option() / add_option() with user inputwp_remote_get() / wp_remote_post() for SSRFis_admin() (checks admin page, NOT admin privilege — use current_user_can())esc_html(), esc_attr(), esc_url(), wp_kses() usagepermission_callback must not be __return_true for sensitive data)sanitize_text_field(), absint(), wp_unslash() input sanitization@Route with missing security annotations| raw filter with user datakernel.debug in productioncomposer.lock for known CVEs (compare against advisories)eval(), system(), exec() in install scriptscomposer.jsonunserialize() with external dataeval(), assert(), create_function()== comparison for authentication/authorization$_GET/$_POST directly in SQL queries$_GET/$_POST in include()/require()echo/print of user input without encodingAPP_DEBUG=true or display_errors=On in productionallow_url_include enabledis_admin() used for privilege checks (wrong function){!! $userInput !!} in Blade templatesFor each finding, report:
### CVI-[ID]: [Vulnerability Class]
**Severity**: [LOW/MEDIUM/HIGH/CRITICAL]
**Confidence**: [LOW/MEDIUM/HIGH]
**File**: [path]:[line]
**Vulnerable Code**:
[show the code]
**Data Flow**:
[source] → [intermediaries] → [sink]
**Impact**: [what an attacker achieves]
**Remediation**: [specific fix with code example]
**CVI Reference**: CVI-[xxxx]Use ONLY:
view_file — read source codegrep_search — find patterns across the codebaseDo NOT use any terminal commands.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.