Search by

denzyl / phanalist

denzyl

Performant static analyzer for PHP, which is extremely easy to use. It helps you catch common mistakes in your PHP code.

Package info

github.com/denzyldick/phanalist

Language:Rust

pkg:composer/denzyl/phanalist

Statistics

Installs: 49 846

Dependents: 5

Suggesters: 0

Stars: 162

Open Issues: 2

v1.2.2 2026-11-04 11:02 UTC

README

Phanalist banner

Phanalist

Fast static analyzer for PHP. Finds maintainability and runtime risks with zero configuration required.

Build Latest release License

What it does

Phanalist scans PHP codebases and reports actionable findings — no configuration required. Each finding explains what's wrong, why it matters, and how to fix it.

It covers:

  • Code health — class design, complexity, coupling, method size, naming
  • Runtime risk — division by zero, array/string bounds, null dereferences, unreachable code
  • Resource usage — loops and allocations that grow with input
  • Security — SQL injection, XSS, command injection, weak crypto, unsafe deserialization, header/mail injection, and more

Quickstart

Install:

curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/denzyldick/phanalist/main/bin/init.sh | sh

Scan your project:

cd your-php-project
~/phanalist

On first run a phanalist.yaml is created with sensible defaults. Review findings, then integrate into CI:

name: Phanalist
on: [pull_request]
permissions:
  security-events: write
jobs:
  phanalist:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: denzyldick/phanalist-action@v1

See Installation for Composer, Docker, and source-build options.

Features

  • Fast — built in Rust, analyzes large codebases in seconds
  • 136 built-in rules across code health, runtime verification, resource usage, and security
  • Zero config — works out of the box; tune via phanalist.yaml
  • Multiple output formats — text, JSON, SARIF (GitHub code scanning), Code Climate
  • Editor integration — built-in Language Server (phanalist --lsp)
  • Engineer quality report — attribute violations to authors via git blame (--blame)
  • Baseline — adopt on legacy codebases without fixing everything at once (--use-baseline)

Configuration

Tune rules in phanalist.yaml:

enabled_rules: []      # empty = all rules active
disable_rules: []
exclude_paths:
  - vendor
  - node_modules
rules:
  E0007:
    max_parameters: 8
  E0009:
    max_complexity: 10

All rules are enabled by default; use enabled_rules, disable_rules, and per-rule rules settings in phanalist.yaml to customize them:

  1. AST & Code Quality: Checks syntax structure, formatting conventions, naming rules, method complexity metrics, and class design.
  2. Formal Verification: Analyzes control flow to prove runtime safety (such as preventing division by zero, null errors, and array bounds violations).
  3. Resource Analysis: Measures time complexity, memory allocation growth, and untrusted loop cardinality.
  4. Lifecycle & Runtime Safety: Checks resource cleanup, runtime-error traps, and reliability hazards.
  5. Security: Detects security vulnerabilities and dangerous execution sinks.

Security rules that use source-to-sink taint analysis accept a per-rule taint_mode:

  • annotate (default) keeps the finding and adds the proven source origin.
  • only reports only proven tainted flows.
  • off keeps the legacy heuristic-only behaviour.

The taint pass runs on all sources by default, so library and dependency scans surface dangerous sinks even when the superglobal is in the calling app. Findings are tiered by certainty:

  • Proven — reachable from request input ($_GET, $_POST, …): high confidence, original rule severity, marked [proven] in text output.
  • Lead — a function parameter reaches a sink, but no caller is shown to pass untrusted data: low confidence, minor severity, marked [lead]. Leads are reported but don't fail the run on their own.

Pass --taint-source-request (or set taint_source_all: false in phanalist.yaml) to hide leads and keep only request-rooted flows.

📜 Rules Reference

Every rule links to junior-friendly documentation with bad/good examples, "why it matters," and fix instructions. Rule names match the DESCRIPTION constant in each rule's source file exactly.

Section 1: AST & Code Quality Rules (E0000–E0030)

Type: Syntax, metrics, structure, and design checks.

Code Name Primary Option / Threshold
E0000 Example rule —
E0001 Opening tag position —
E0002 Empty catch —
E0003 Method modifiers —
E0004 Uppercase constants —
E0005 Capitalized class name —
E0006 Property modifiers —
E0007 Method parameters count max_parameters: 8
E0008 Return type signature —
E0009 Cyclomatic complexity max_complexity: 10
E0010 Npath complexity max_paths: 200
E0011 Detect the error suppression symbol: @ —
E0012 Service compatibility with Shared Memory Model include_namespaces, reset_interfaces
E0013 Private method not being called. —
E0014 Law of Demeter violation. Method chaining should be avoided unless returning the same object type. —
E0015 Lack of Cohesion of Methods (LCOM4) threshold: 1
E0016 Cognitive complexity max_complexity: 15
E0017 Coupling Between Objects (CBO) max_coupling: 10
E0018 Weighted Methods per Class (WMC) max_wmc: 50
E0019 Response For a Class (RFC) max_rfc: 50
E0020 Depth of Inheritance Tree (DIT) max_depth: 4
E0021 Number of Children (NOC) max_children: 15
E0022 Afferent and Efferent Coupling (Ca/Ce) max_ca: 20, max_ce: 20
E0023 Instability (I), Abstractness (A), Distance from Main Sequence (D) max_distance: 0.5
E0024 Lines of Code (LOC) per Method max_loc: 50
E0025 Lines of Code (LOC) per File max_loc: 500
E0026 Comment Ratio min_ratio: 0.05, max_ratio: 0.5
E0027 God Class (Brain Class) max_methods: 15, max_fields: 10
E0028 Data Class min_methods: 3
E0029 Class-level Fan-in / Fan-out max_fan_out: 10, max_fan_in: 20
E0030 Cyclomatic Complexity Density max_density: 0.3

Section 2: Formal Verification Rules (E0031–E0043)

Type: Path analysis and runtime safety checks.

Code Name What it Proves / Checks
E0031 Division by Zero Proves that divisors are never zero
E0032 Modulo by Zero Proves that modulo divisors are never zero
E0033 intdiv() by Zero Proves integer division divisors are valid
E0034 intdiv() Overflow Prevents integer overflow during division
E0035 Missing Array Key Proves required array keys exist before access
E0036 Array Index Out of Bounds Proves array index access stays within bounds
E0037 String Offset Out of Bounds Proves string character offsets are valid
E0038 Method Call on Null Proves objects are not null before calling methods
E0039 Property Access on Null Proves objects are not null before accessing properties
E0040 Return Contract Violation Verifies return types match documented contracts
E0041 Unreachable Code Detects dead statements after returns or throws
E0042 Impossible Assertion Detects assertions that can never hold true
E0043 Stream Resource Used After Close Detects reading/writing streams after closing them

Section 3: Resource Analysis Rules (E0044–E0046)

Type: Scalability, memory growth, and time complexity checks.

Code Name What it Analyzes
E0044 Input-Dependent Runtime Growth Flags loops whose time complexity scales poorly with input size
E0045 Input-Dependent Live-Heap Growth Flags memory allocation patterns that grow with input size
E0046 Input-Controlled Cardinality Flags collections sized directly from untrusted user input

Section 4: Security & Lifecycle Rules (E0047–E0135)

Type: Security vulnerabilities and dangerous execution sinks.

E0047–E0052 — Resource lifecycle & runtime safety

Code Name Risk
E0047 Resource lifecycle leak Unclosed file handles or database connections
E0048 Unbounded direct recursion Infinite recursion without base condition changes
E0049 Invalid builtin argument Invalid literal arguments passed to built-in functions
E0050 Position check used as boolean Using strpos() without strict comparison (!== false)
E0051 Count of a non-countable value Calling count() on non-array scalars
E0052 Unchecked json_decode result Missing validation after JSON decoding

E0053–E0059 — Security hazards

Code Name Security Risk
E0053 Unserialize without allowed_classes PHP object injection risk
E0054 Dynamic variable creation Variable variables ($$x) or extract() usage
E0055 Dynamic include or require Unsafe dynamic file inclusion paths
E0056 Weak randomness for security-sensitive value Using rand() or mt_rand() for security tokens
E0057 MD5 or SHA1 used for a password Insecure hashing algorithms for credentials
E0058 Cookie missing Secure or HttpOnly Cookies missing security flags
E0059 Dynamic eval or assert Dangerous code execution sinks (eval())

E0060–E0069 — Injection attacks

Code Name Security Risk
E0060 SQL injection User input concatenated into SQL queries
E0061 Cross-Site Scripting (XSS) Unescaped user input echoed to the browser
E0062 Command injection User input passed to shell execution functions
E0063 LDAP injection User input embedded in LDAP filters
E0064 XPath injection User input embedded in XPath queries
E0065 NoSQL injection User input embedded in NoSQL queries
E0066 Header injection User input written to HTTP headers (CRLF injection)
E0067 Mail injection User input written to mail headers or subject
E0068 Eval injection User input evaluated as PHP code via eval()
E0069 Unsafe deserialization Untrusted data passed to unserialize()

E0070–E0079 — File, session, and crypto risks

Code Name Security Risk
E0070 Remote file inclusion User-controlled file included via include/require
E0071 Unsafe file upload Uploaded file stored to user-controlled destination
E0072 Path traversal User-controlled file path opened for read/write
E0073 Weak cryptographic hash MD5/SHA1 used instead of password_hash()
E0074 Weak random number generator mt_rand()/rand() used for security values
E0075 Session security session_start() without secure cookie settings
E0076 Information disclosure phpinfo() left in production code
E0077 Display errors enabled display_errors enabled via ini_set()
E0078 Variable variables extract() importing user-controlled variables
E0079 Dynamic method call Dynamic method invoked on user-controlled object

E0080–E0089 — Deprecated functions & injection

Code Name Security Risk
E0080 Weak cryptographic function crypt() used for password hashing
E0081 Deprecated regex function ereg/split used instead of preg_*
E0082 Dynamic extension loading dl() loading extensions at runtime
E0083 Open redirect header() redirect to user-controlled URL
E0084 SQL query string concatenation SQL query built by concatenating user input
E0085 Inline PHP execution short tags <?= ?> short echo tag usage
E0086 Deprecated session function session_register()/session_is_registered() usage
E0087 Command execution with backticks Backtick command with interpolated input
E0088 Reflection-based injection Reflection invoked on user-controlled input
E0089 Log injection User input written to log files unsanitized

E0090–E0099 — File, XML, and cookie security

Code Name Security Risk
E0090 File write to user-controlled path file_put_contents() to user-controlled path
E0091 Source code exposure via highlight_file highlight_file()/show_source() in production
E0092 XML External Entity (XXE) XML parsed from user-controlled input
E0093 Template injection User input rendered as a template
E0094 Denial of service via regular expression Regex applied to user-controlled input
E0095 Insecure comparison strcmp()/strcasecmp() not constant-time
E0096 Cookie without secure flag Cookie set without the Secure flag
E0097 Cookie without HttpOnly flag Cookie set without the HttpOnly flag
E0098 Session ID in URL Session ID embedded in redirect URL
E0099 Disable functions bypass ini_set() changing disable_functions

E0100–E0109 — Dynamic code & SSRF

Code Name Security Risk
E0100 Variable function call Variable function invoked on user-controlled name
E0101 Object injection via unserialize Object injection from unserialize of user input
E0102 Command injection via proc_open proc_open() with user-controlled command
E0103 Server-side request forgery Network request to user-controlled URL
E0104 HTTP response splitting header() with user-controlled CRLF sequences
E0105 Local file inclusion Local file included from user-controlled input
E0106 Deserialization of untrusted data unserialize()/wddx_deserialize() of user input
E0107 Weak random for security tokens Weak random used for security-sensitive tokens
E0108 Dynamic instantiation Class instantiated from user-controlled name
E0109 Directory listing exposure opendir() on user-controlled directory

E0110–E0119 — SQL, XML, and session security

Code Name Security Risk
E0110 Raw SQL query execution Raw SQL query executed without parameters
E0111 Deprecated mysql extension Removed ext/mysql extension functions used
E0112 File upload without validation move_uploaded_file() without destination validation
E0113 Session regeneration missing Session ID not regenerated after authentication
E0114 PHP info disclosure phpinfo() exposing server configuration
E0115 XXE via libxml XML parsed by libxml from user-controlled input
E0116 Remote command execution via popen popen() with user-controlled command
E0117 SQL injection via query string SQL query string built from user input
E0118 Log forging via user input User input written to logs without sanitization
E0119 Session cookie without Secure flag Session cookie set without Secure flag

E0120–E0135 — Dynamic execution & header injection

Code Name Security Risk
E0120 Eval with user-controlled string eval() executed on user-controlled string
E0121 Assert with user-controlled string assert() with user-controlled string argument
E0122 File inclusion with user input include/require with user-controlled path
E0123 XSS via echo echo() outputs user-controlled input unescaped
E0124 Weak hash for passwords Weak hash (MD5/SHA1/crypt) used for passwords
E0125 Open redirect via header header() redirects to user-controlled URL
E0126 Path traversal via file_get_contents file_get_contents() reads user-controlled path
E0127 Command execution via system system() with user-controlled command
E0128 Command execution via exec exec() with user-controlled command
E0129 Command execution via shell_exec shell_exec() with user-controlled command
E0130 Command execution via passthru passthru() with user-controlled command
E0131 Command execution via proc_open proc_open() with user-controlled command
E0132 Header injection via header header() writes user input to HTTP headers
E0133 Header injection via setcookie setcookie() writes user input to cookie header
E0134 Mail injection via mail mail() writes user input to mail headers/subject
E0135 PHP info disclosure phpinfo() exposing configuration details

Editor integration

Run Phanalist as a Language Server for real-time diagnostics:

phanalist --lsp

Community

MIT Licensed.