@raishin/vanguard-frontier-agentic 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +7 -1
- package/.cursor-plugin/plugin.json +7 -1
- package/.github/plugin/marketplace.json +1 -1
- package/README.md +11 -11
- package/agents/cross-functional/revenue-critical-journey-integrity-agent/AGENT.md +112 -0
- package/agents/cross-functional/revenue-critical-journey-integrity-agent/harnesses/claude-code.agent.md +111 -0
- package/agents/cross-functional/revenue-critical-journey-integrity-agent/harnesses/codex.toml +37 -0
- package/agents/cross-functional/revenue-critical-journey-integrity-agent/harnesses/copilot.agent.md +120 -0
- package/agents/cross-functional/revenue-critical-journey-integrity-agent/harnesses/cursor.agent.md +112 -0
- package/agents/cross-functional/revenue-critical-journey-integrity-agent/harnesses/gemini.agent.md +112 -0
- package/agents/cross-functional/revenue-critical-journey-integrity-agent/harnesses/kiro-cli.agent.json +5 -0
- package/agents/cross-functional/revenue-critical-journey-integrity-agent/harnesses/kiro-ide.agent.md +111 -0
- package/agents/cross-functional/revenue-critical-journey-integrity-agent/metadata.json +42 -0
- package/agents/php/composer-supply-chain-agent/AGENT.md +114 -0
- package/agents/php/composer-supply-chain-agent/harnesses/claude-code.agent.md +113 -0
- package/agents/php/composer-supply-chain-agent/harnesses/codex.toml +119 -0
- package/agents/php/composer-supply-chain-agent/harnesses/copilot.agent.md +122 -0
- package/agents/php/composer-supply-chain-agent/harnesses/cursor.agent.md +114 -0
- package/agents/php/composer-supply-chain-agent/harnesses/gemini.agent.md +114 -0
- package/agents/php/composer-supply-chain-agent/harnesses/kiro-cli.agent.json +5 -0
- package/agents/php/composer-supply-chain-agent/harnesses/kiro-ide.agent.md +113 -0
- package/agents/php/composer-supply-chain-agent/metadata.json +31 -0
- package/agents/php/php-application-security-agent/AGENT.md +113 -0
- package/agents/php/php-application-security-agent/harnesses/claude-code.agent.md +112 -0
- package/agents/php/php-application-security-agent/harnesses/codex.toml +118 -0
- package/agents/php/php-application-security-agent/harnesses/copilot.agent.md +121 -0
- package/agents/php/php-application-security-agent/harnesses/cursor.agent.md +113 -0
- package/agents/php/php-application-security-agent/harnesses/gemini.agent.md +113 -0
- package/agents/php/php-application-security-agent/harnesses/kiro-cli.agent.json +5 -0
- package/agents/php/php-application-security-agent/harnesses/kiro-ide.agent.md +112 -0
- package/agents/php/php-application-security-agent/metadata.json +31 -0
- package/agents/php/php-maestro-agent/AGENT.md +81 -0
- package/agents/php/php-maestro-agent/harnesses/claude-code.agent.md +80 -0
- package/agents/php/php-maestro-agent/harnesses/codex.toml +86 -0
- package/agents/php/php-maestro-agent/harnesses/copilot.agent.md +89 -0
- package/agents/php/php-maestro-agent/harnesses/cursor.agent.md +81 -0
- package/agents/php/php-maestro-agent/harnesses/gemini.agent.md +81 -0
- package/agents/php/php-maestro-agent/harnesses/kiro-cli.agent.json +5 -0
- package/agents/php/php-maestro-agent/harnesses/kiro-ide.agent.md +80 -0
- package/agents/php/php-maestro-agent/metadata.json +31 -0
- package/agents/php/php-runtime-upgrade-readiness-agent/AGENT.md +117 -0
- package/agents/php/php-runtime-upgrade-readiness-agent/harnesses/claude-code.agent.md +116 -0
- package/agents/php/php-runtime-upgrade-readiness-agent/harnesses/codex.toml +122 -0
- package/agents/php/php-runtime-upgrade-readiness-agent/harnesses/copilot.agent.md +125 -0
- package/agents/php/php-runtime-upgrade-readiness-agent/harnesses/cursor.agent.md +117 -0
- package/agents/php/php-runtime-upgrade-readiness-agent/harnesses/gemini.agent.md +117 -0
- package/agents/php/php-runtime-upgrade-readiness-agent/harnesses/kiro-cli.agent.json +5 -0
- package/agents/php/php-runtime-upgrade-readiness-agent/harnesses/kiro-ide.agent.md +116 -0
- package/agents/php/php-runtime-upgrade-readiness-agent/metadata.json +30 -0
- package/agents/php/wordpress-security-agent/AGENT.md +107 -0
- package/agents/php/wordpress-security-agent/harnesses/claude-code.agent.md +106 -0
- package/agents/php/wordpress-security-agent/harnesses/codex.toml +112 -0
- package/agents/php/wordpress-security-agent/harnesses/copilot.agent.md +115 -0
- package/agents/php/wordpress-security-agent/harnesses/cursor.agent.md +107 -0
- package/agents/php/wordpress-security-agent/harnesses/gemini.agent.md +107 -0
- package/agents/php/wordpress-security-agent/harnesses/kiro-cli.agent.json +5 -0
- package/agents/php/wordpress-security-agent/harnesses/kiro-ide.agent.md +106 -0
- package/agents/php/wordpress-security-agent/metadata.json +31 -0
- package/catalog/agents.json +175 -0
- package/catalog/asset-integrity.json +463 -43
- package/catalog/install-roles.json +26 -4
- package/catalog/model-assignments.json +198 -0
- package/catalog/skill-manifest.json +202 -0
- package/catalog/skills.json +163 -0
- package/package.json +1 -1
- package/plugins/vanguard-frontier-agentic/.codex-plugin/plugin.json +1 -1
- package/powers/README.md +3 -2
- package/powers/vanguard-generic/POWER.md +1 -1
- package/powers/vanguard-php/POWER.md +40 -0
- package/schemas/agent.schema.json +2 -1
- package/schemas/skill.schema.json +2 -1
- package/scripts/generate-docs-data.mjs +1 -1
- package/skills/cross-functional/revenue-critical-journey-integrity-review/SKILL.md +108 -0
- package/skills/cross-functional/revenue-critical-journey-integrity-review/metadata.json +29 -0
- package/skills/cross-functional/revenue-critical-journey-integrity-review/references/idempotency-and-safe-retries.md +155 -0
- package/skills/cross-functional/revenue-critical-journey-integrity-review/references/official-sources.md +71 -0
- package/skills/cross-functional/revenue-critical-journey-integrity-review/references/pci-saq-scope-boundaries.md +118 -0
- package/skills/cross-functional/revenue-critical-journey-integrity-review/references/server-side-revalidation-trust-boundary.md +134 -0
- package/skills/cross-functional/revenue-critical-journey-integrity-review/references/webhook-delivery-dedup-ordering.md +146 -0
- package/skills/cross-functional/revenue-critical-journey-integrity-review/references/workflow-and-output.md +100 -0
- package/skills/php/composer-audit-supply-chain-review/SKILL.md +108 -0
- package/skills/php/composer-audit-supply-chain-review/metadata.json +20 -0
- package/skills/php/composer-audit-supply-chain-review/references/abandoned-and-advisory-governance.md +30 -0
- package/skills/php/composer-audit-supply-chain-review/references/composer-audit-policy.md +35 -0
- package/skills/php/composer-audit-supply-chain-review/references/lockfile-integrity.md +27 -0
- package/skills/php/php-maestro/SKILL.md +51 -0
- package/skills/php/php-maestro/metadata.json +20 -0
- package/skills/php/php-maestro/references/hard-gates-and-escalation.md +67 -0
- package/skills/php/php-maestro/references/routing-and-dispatch.md +91 -0
- package/skills/php/php-runtime-eol-opcache-fpm-review/SKILL.md +109 -0
- package/skills/php/php-runtime-eol-opcache-fpm-review/metadata.json +19 -0
- package/skills/php/php-runtime-eol-opcache-fpm-review/references/opcache-production-config.md +91 -0
- package/skills/php/php-runtime-eol-opcache-fpm-review/references/php-fpm-pool-tuning.md +87 -0
- package/skills/php/php-runtime-eol-opcache-fpm-review/references/php-version-lifecycle.md +102 -0
- package/skills/php/php-session-upload-deserialization-review/SKILL.md +111 -0
- package/skills/php/php-session-upload-deserialization-review/metadata.json +20 -0
- package/skills/php/php-session-upload-deserialization-review/references/file-upload-security.md +119 -0
- package/skills/php/php-session-upload-deserialization-review/references/session-security.md +126 -0
- package/skills/php/php-session-upload-deserialization-review/references/unserialize-object-injection.md +121 -0
- package/skills/php/wordpress-rest-block-security-review/SKILL.md +106 -0
- package/skills/php/wordpress-rest-block-security-review/metadata.json +20 -0
- package/skills/php/wordpress-rest-block-security-review/references/dynamic-block-output-escaping.md +42 -0
- package/skills/php/wordpress-rest-block-security-review/references/input-sanitize-output-escape.md +52 -0
- package/skills/php/wordpress-rest-block-security-review/references/rest-api-permission-callback.md +48 -0
- package/tests/fixtures/php-maestro-routing/expected/001-happy-application-security.json +6 -0
- package/tests/fixtures/php-maestro-routing/expected/002-happy-composer-supply-chain.json +6 -0
- package/tests/fixtures/php-maestro-routing/expected/003-happy-runtime-upgrade-readiness.json +6 -0
- package/tests/fixtures/php-maestro-routing/expected/004-happy-wordpress-security.json +6 -0
- package/tests/fixtures/php-maestro-routing/expected/005-happy-unserialize-session.json +6 -0
- package/tests/fixtures/php-maestro-routing/expected/006-happy-fpm-opcache.json +6 -0
- package/tests/fixtures/php-maestro-routing/expected/007-happy-composer-audit.json +6 -0
- package/tests/fixtures/php-maestro-routing/expected/008-happy-wp-permission-callback.json +6 -0
- package/tests/fixtures/php-maestro-routing/expected/adv-ambiguous.json +4 -0
- package/tests/fixtures/php-maestro-routing/expected/adv-instruction-injection.json +6 -0
- package/tests/fixtures/php-maestro-routing/expected/adv-liveguard-db-migration-prod.json +4 -0
- package/tests/fixtures/php-maestro-routing/expected/adv-liveguard-deploy-prod.json +4 -0
- package/tests/fixtures/php-maestro-routing/expected/adv-persona-replacement.json +6 -0
- package/tests/fixtures/php-maestro-routing/expected/adv-secrets-bait.json +7 -0
- package/tests/fixtures/php-maestro-routing/inputs/001-happy-application-security.json +7 -0
- package/tests/fixtures/php-maestro-routing/inputs/002-happy-composer-supply-chain.json +7 -0
- package/tests/fixtures/php-maestro-routing/inputs/003-happy-runtime-upgrade-readiness.json +7 -0
- package/tests/fixtures/php-maestro-routing/inputs/004-happy-wordpress-security.json +7 -0
- package/tests/fixtures/php-maestro-routing/inputs/005-happy-unserialize-session.json +7 -0
- package/tests/fixtures/php-maestro-routing/inputs/006-happy-fpm-opcache.json +7 -0
- package/tests/fixtures/php-maestro-routing/inputs/007-happy-composer-audit.json +7 -0
- package/tests/fixtures/php-maestro-routing/inputs/008-happy-wp-permission-callback.json +7 -0
- package/tests/fixtures/php-maestro-routing/inputs/adv-ambiguous.json +7 -0
- package/tests/fixtures/php-maestro-routing/inputs/adv-instruction-injection.json +7 -0
- package/tests/fixtures/php-maestro-routing/inputs/adv-liveguard-db-migration-prod.json +7 -0
- package/tests/fixtures/php-maestro-routing/inputs/adv-liveguard-deploy-prod.json +7 -0
- package/tests/fixtures/php-maestro-routing/inputs/adv-persona-replacement.json +7 -0
- package/tests/fixtures/php-maestro-routing/inputs/adv-secrets-bait.json +7 -0
- package/tests/fixtures/php-maestro-routing/taxonomy.json +69 -0
- package/tests/validate-catalog.py +1 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# OPcache production configuration
|
|
2
|
+
|
|
3
|
+
## Why this matters
|
|
4
|
+
|
|
5
|
+
OPcache stores precompiled PHP bytecode in shared memory so subsequent
|
|
6
|
+
requests skip parsing and compiling the same script again. In production,
|
|
7
|
+
three misconfigurations defeat this: leaving OPcache disabled (paying full
|
|
8
|
+
compile cost on every request and gaining nothing), leaving timestamp
|
|
9
|
+
validation enabled in a deployment model that expects an immediate cutover
|
|
10
|
+
(serving stale bytecode, or paying an unnecessary per-request filesystem stat
|
|
11
|
+
cost, depending on which way the mismatch runs), and undersizing the cache
|
|
12
|
+
itself (memory or file-count limits below what the codebase actually needs,
|
|
13
|
+
so the accelerator evicts and recompiles under normal load instead of
|
|
14
|
+
reaching a stable steady state). None of these show up in application-code
|
|
15
|
+
review; they live entirely in `php.ini`.
|
|
16
|
+
|
|
17
|
+
## NORMATIVE: directive behavior per php.net
|
|
18
|
+
|
|
19
|
+
Per php.net's OPcache configuration reference (`documentation-based`):
|
|
20
|
+
|
|
21
|
+
- **`opcache.enable`** (default `1`) — enables the opcode cache. When
|
|
22
|
+
disabled, code is not optimized or cached at all. It cannot be enabled at
|
|
23
|
+
runtime via `ini_set()` — only disabled; attempting to enable it in a
|
|
24
|
+
script generates a warning.
|
|
25
|
+
- **`opcache.validate_timestamps`** (default `1`) — if enabled, OPcache
|
|
26
|
+
checks for updated scripts every `opcache.revalidate_freq` seconds. When
|
|
27
|
+
disabled, cached scripts are never re-checked against the filesystem;
|
|
28
|
+
`opcache_reset()`, `opcache_invalidate()`, or a web-server restart is
|
|
29
|
+
required for filesystem changes to take effect. OPcache may still validate
|
|
30
|
+
a file's timestamp at compile-time if `opcache.file_update_protection` or
|
|
31
|
+
`opcache.max_file_size` are set to non-zero values.
|
|
32
|
+
- **`opcache.memory_consumption`** (default `128`, in megabytes) — the size
|
|
33
|
+
of OPcache's shared-memory storage. The minimum permissible value is `8`;
|
|
34
|
+
a smaller configured value is clamped up to it.
|
|
35
|
+
- **`opcache.max_accelerated_files`** (default `10000`) — the maximum number
|
|
36
|
+
of keys (scripts) in OPcache's hash table. The actual value used is the
|
|
37
|
+
first prime from a fixed internal set (`223, 463, 983, 1979, 3907, 7963,
|
|
38
|
+
16229, 32531, 65407, 130987, 262237, 524521, 1048793`) that is greater than
|
|
39
|
+
or equal to the configured value. The minimum is `200`; the maximum is
|
|
40
|
+
`1000000`; out-of-range values are clamped into that range.
|
|
41
|
+
|
|
42
|
+
## RECOMMENDATION: production posture by deployment model
|
|
43
|
+
|
|
44
|
+
These are review recommendations, not php.net mandates — they apply the
|
|
45
|
+
documented directive behavior above to two common deployment models:
|
|
46
|
+
|
|
47
|
+
- **Immutable-image / container-per-deploy model** — where a new deploy
|
|
48
|
+
replaces the running container/image wholesale (no in-place file sync to
|
|
49
|
+
a long-running host): `opcache.validate_timestamps=0` is appropriate,
|
|
50
|
+
because every deploy already starts a fresh OPcache with the new code —
|
|
51
|
+
there is no stale-timestamp window to protect against, and skipping the
|
|
52
|
+
per-request timestamp check removes filesystem overhead. `enable=1` is
|
|
53
|
+
required either way.
|
|
54
|
+
- **In-place file-sync model** — where code is updated on disk under a
|
|
55
|
+
long-running PHP-FPM/web-server process without a full process restart:
|
|
56
|
+
leaving `validate_timestamps=1` (the default) is the safer choice unless
|
|
57
|
+
the deploy pipeline explicitly calls `opcache_reset()` or
|
|
58
|
+
`opcache_invalidate()` (or restarts the server) as part of every release.
|
|
59
|
+
Setting `validate_timestamps=0` in this model without that compensating
|
|
60
|
+
step means a deploy can silently continue serving stale bytecode.
|
|
61
|
+
|
|
62
|
+
## Reviewer evidence criteria
|
|
63
|
+
|
|
64
|
+
- Confirm `opcache.enable=1` in the production `php.ini` (or the effective
|
|
65
|
+
merged configuration, if directives are set across multiple files);
|
|
66
|
+
`opcache.enable=0` or the directive's absence at its non-default state is
|
|
67
|
+
a blocking finding.
|
|
68
|
+
- Determine the deployment model in scope (immutable image vs. in-place
|
|
69
|
+
sync) from the required inputs, then check `opcache.validate_timestamps`
|
|
70
|
+
against it per the recommendation above. Flag a mismatch as blocking:
|
|
71
|
+
`1` in an immutable-image model wastes the per-request stat check;
|
|
72
|
+
`0` in an in-place-sync model with no compensating invalidation step in
|
|
73
|
+
the deploy pipeline risks serving stale code.
|
|
74
|
+
- If `validate_timestamps=0` is set, look for the compensating step
|
|
75
|
+
(`opcache_reset()`/`opcache_invalidate()` call, or a server restart) in
|
|
76
|
+
the deploy pipeline before treating the configuration as safe.
|
|
77
|
+
- Compare `opcache.memory_consumption` and `opcache.max_accelerated_files`
|
|
78
|
+
against the actual number and total size of PHP scripts in the codebase.
|
|
79
|
+
Report undersizing as advisory guidance with a concrete recommended value
|
|
80
|
+
(not a blocking finding on its own), since undersizing degrades
|
|
81
|
+
performance rather than correctness.
|
|
82
|
+
- Do not assume a directive's default value applies without checking the
|
|
83
|
+
actual `php.ini` (or merged configuration) in scope — label a stated
|
|
84
|
+
default as `documentation-based` and an observed configuration as
|
|
85
|
+
`repo evidence`.
|
|
86
|
+
|
|
87
|
+
## Sources
|
|
88
|
+
|
|
89
|
+
- [PHP: OPcache Configuration](https://www.php.net/manual/en/opcache.configuration.php) — supports the `opcache.enable`, `opcache.validate_timestamps`, `opcache.memory_consumption`, and `opcache.max_accelerated_files` descriptions and default/clamping values above.
|
|
90
|
+
|
|
91
|
+
Last verified: 2026-07-16.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# PHP-FPM pool tuning
|
|
2
|
+
|
|
3
|
+
## Why this matters
|
|
4
|
+
|
|
5
|
+
PHP-FPM's process manager controls how many PHP worker processes exist and
|
|
6
|
+
how long each one lives. Two settings dominate the production risk surface:
|
|
7
|
+
`pm` together with `pm.max_children` bounds worker concurrency, so an
|
|
8
|
+
unbounded or unsized value lets a traffic spike spawn more workers than the
|
|
9
|
+
host has memory for, exhausting the host outright; and `pm.max_requests`
|
|
10
|
+
bounds a worker's lifetime in requests served, so leaving it unlimited lets a
|
|
11
|
+
slow memory leak in application or third-party code degrade a worker (and
|
|
12
|
+
eventually the pool) indefinitely instead of being recycled away. Neither
|
|
13
|
+
failure is visible in application-code review — both live in the FPM pool
|
|
14
|
+
configuration file.
|
|
15
|
+
|
|
16
|
+
## NORMATIVE: directive behavior per php.net
|
|
17
|
+
|
|
18
|
+
Per php.net's PHP-FPM configuration reference (`documentation-based`):
|
|
19
|
+
|
|
20
|
+
- **`pm`** (mandatory, no default) — chooses how the process manager
|
|
21
|
+
controls the number of child processes. Possible values:
|
|
22
|
+
- `static` — the number of child processes is fixed at `pm.max_children`.
|
|
23
|
+
- `dynamic` — the number of child processes varies at runtime between `pm.min_spare_servers`/`pm.max_spare_servers`, starting from `pm.start_servers`, up to a ceiling of `pm.max_children`.
|
|
24
|
+
- `ondemand` — processes spawn only when a request arrives, rather than `pm.start_servers` being started when the service starts.
|
|
25
|
+
- **`pm.max_children`** (mandatory, no default) — the number of child
|
|
26
|
+
processes created when `pm=static`, and the *maximum* number of child
|
|
27
|
+
processes created when `pm=dynamic` or `pm=ondemand`. This directive sets
|
|
28
|
+
the hard limit on the number of simultaneous requests the pool will serve
|
|
29
|
+
— equivalent to Apache's `MaxClients` directive under `mpm_prefork`, or the
|
|
30
|
+
`PHP_FCGI_CHILDREN` environment variable in the original PHP FastCGI.
|
|
31
|
+
- **`pm.max_requests`** (default `0`, meaning unlimited) — the number of
|
|
32
|
+
requests each child process executes before it is recycled (killed and
|
|
33
|
+
respawned). Documented as useful for working around memory leaks in
|
|
34
|
+
third-party libraries. A value of `0` means a worker serves requests
|
|
35
|
+
indefinitely, with no automatic recycling.
|
|
36
|
+
|
|
37
|
+
## RECOMMENDATION: sizing and review posture
|
|
38
|
+
|
|
39
|
+
These are review recommendations applying the documented directives above,
|
|
40
|
+
not php.net mandates for a specific numeric value:
|
|
41
|
+
|
|
42
|
+
- **`pm.max_children` must be evidently bounded by available memory.** As a
|
|
43
|
+
rough check: multiply `pm.max_children` by a representative single-worker
|
|
44
|
+
memory footprint (observed or estimated for the application) and confirm
|
|
45
|
+
the product does not exceed the memory actually available to the pool's
|
|
46
|
+
host or container. A `pm.max_children` value set without any apparent
|
|
47
|
+
relationship to available memory (an arbitrary round number carried over
|
|
48
|
+
from a different host size, or left at a packaging default) is a
|
|
49
|
+
resource-exhaustion risk under a traffic spike.
|
|
50
|
+
- **Prefer `dynamic` or `ondemand` over an oversized `static` pool** for
|
|
51
|
+
workloads with variable traffic, so idle capacity is not permanently
|
|
52
|
+
reserved; `static` is reasonable when load is genuinely constant and the
|
|
53
|
+
host is sized for the fixed worker count.
|
|
54
|
+
- **`pm.max_requests` should be a nonzero, workload-appropriate value** in
|
|
55
|
+
production, so a worker with a slow leak is recycled before it degrades
|
|
56
|
+
the pool. Leaving it at the default `0` (unlimited) is acceptable only with
|
|
57
|
+
an explicit, documented rationale (e.g. the application and every
|
|
58
|
+
third-party library in its request path are already known leak-free under
|
|
59
|
+
sustained load) — otherwise treat unbounded `pm.max_requests` as a blocking
|
|
60
|
+
finding alongside unbounded `pm.max_children`.
|
|
61
|
+
|
|
62
|
+
## Reviewer evidence criteria
|
|
63
|
+
|
|
64
|
+
- Confirm `pm` is explicitly set (it is mandatory with no default) to one of
|
|
65
|
+
`static`, `dynamic`, or `ondemand`, and matches the stated workload shape.
|
|
66
|
+
- Confirm `pm.max_children` is set (mandatory, no default) and check it
|
|
67
|
+
against the rough memory-bound calculation above; flag a value with no
|
|
68
|
+
apparent sizing rationale, or one clearly exceeding available host/
|
|
69
|
+
container memory, as a blocking finding.
|
|
70
|
+
- If `pm=dynamic`, confirm `pm.start_servers`, `pm.min_spare_servers`, and
|
|
71
|
+
`pm.max_spare_servers` are all set consistently with `pm.max_children`
|
|
72
|
+
(each within a sane range relative to the ceiling), since a missing or
|
|
73
|
+
contradictory value among this triad undermines the sizing already done
|
|
74
|
+
for `pm.max_children`.
|
|
75
|
+
- Confirm `pm.max_requests` is set to a nonzero value, or that an explicit
|
|
76
|
+
documented rationale exists for leaving it at `0`; flag an unexplained `0`
|
|
77
|
+
as a blocking finding.
|
|
78
|
+
- Label an observed pool configuration `repo evidence`, a php.net-documented
|
|
79
|
+
default or semantic `documentation-based`, and any memory-footprint
|
|
80
|
+
estimate used for the sizing check `inference` — state which is which
|
|
81
|
+
rather than presenting an estimate as a measured fact.
|
|
82
|
+
|
|
83
|
+
## Sources
|
|
84
|
+
|
|
85
|
+
- [PHP: PHP-FPM Configuration](https://www.php.net/manual/en/install.fpm.configuration.php) — supports the `pm`, `pm.max_children`, and `pm.max_requests` descriptions, mandatory/default-value status, and the `MaxClients`/`PHP_FCGI_CHILDREN`/`PHP_FCGI_MAX_REQUESTS` equivalences above.
|
|
86
|
+
|
|
87
|
+
Last verified: 2026-07-16.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# PHP version lifecycle
|
|
2
|
+
|
|
3
|
+
## Why this matters
|
|
4
|
+
|
|
5
|
+
A PHP branch that has left php.net's supported window stops receiving fixes of
|
|
6
|
+
any kind once it reaches end of life (EOL) — including for actively exploited
|
|
7
|
+
vulnerabilities — while a branch in its security-only window still receives
|
|
8
|
+
critical security fixes but no bug fixes, and is itself counting down to its
|
|
9
|
+
own EOL. A service can look fully healthy at the application-code level (green
|
|
10
|
+
tests, current dependencies) while the runtime underneath it has already
|
|
11
|
+
crossed one of these boundaries. This file is the sole source the skill uses
|
|
12
|
+
for those dates; every version-lifecycle claim in a review must resolve to a
|
|
13
|
+
row below, and to no other memory or estimate.
|
|
14
|
+
|
|
15
|
+
## NORMATIVE: php.net's support policy
|
|
16
|
+
|
|
17
|
+
Per php.net's Supported Versions page (`documentation-based`):
|
|
18
|
+
|
|
19
|
+
- Each release branch of PHP is fully supported for **two years** from its
|
|
20
|
+
initial stable release. During this period, reported bugs and security
|
|
21
|
+
issues are fixed and released in regular point releases.
|
|
22
|
+
- After that two-year active-support period, each branch is then supported
|
|
23
|
+
for **two additional years** for critical security issues only. Releases
|
|
24
|
+
during this window are made on an as-needed basis — there may be multiple
|
|
25
|
+
point releases, or none, depending on the number of reports.
|
|
26
|
+
- After the combined four-year window (two years active plus two years
|
|
27
|
+
security-only), a branch is **end of life (EOL)**: no further fixes of any
|
|
28
|
+
kind are published for it, including for actively exploited
|
|
29
|
+
vulnerabilities.
|
|
30
|
+
|
|
31
|
+
## NORMATIVE: current per-branch dates
|
|
32
|
+
|
|
33
|
+
Per php.net's Supported Versions page, the currently listed branches and
|
|
34
|
+
their published dates are:
|
|
35
|
+
|
|
36
|
+
| Branch | Active support until | Security support until (EOL) |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| PHP 8.2 | 31 Dec 2024 | 31 Dec 2026 |
|
|
39
|
+
| PHP 8.3 | 31 Dec 2025 | 31 Dec 2027 |
|
|
40
|
+
| PHP 8.4 | 31 Dec 2026 | 31 Dec 2028 |
|
|
41
|
+
| PHP 8.5 | 31 Dec 2027 | 31 Dec 2029 |
|
|
42
|
+
|
|
43
|
+
Reading this table for a review:
|
|
44
|
+
|
|
45
|
+
- A branch is in **active support** if the current review's target/committed
|
|
46
|
+
version date is before its "Active support until" date.
|
|
47
|
+
- A branch is in **security-only support** if that date is on or after
|
|
48
|
+
"Active support until" but before "Security support until."
|
|
49
|
+
- A branch is **EOL** once that date reaches or passes "Security support
|
|
50
|
+
until" — no fixes of any kind are published past that point.
|
|
51
|
+
|
|
52
|
+
Any PHP version not listed in php.net's current supported-versions table
|
|
53
|
+
(either older than the branches php.net still lists, or a malformed/future
|
|
54
|
+
version string) has no dates in this file — say the evidence is missing
|
|
55
|
+
rather than inferring a status.
|
|
56
|
+
|
|
57
|
+
## Reviewer evidence criteria
|
|
58
|
+
|
|
59
|
+
- Resolve the exact PHP version from the strongest available evidence:
|
|
60
|
+
a running-version banner or `phpversion()`/`php -v` output outranks a
|
|
61
|
+
Dockerfile/base-image tag, which outranks a loose Composer `require.php`
|
|
62
|
+
constraint (e.g. `^8.2` does not tell you which point release is actually
|
|
63
|
+
deployed, only the minimum branch). State which evidence tier the version
|
|
64
|
+
claim rests on.
|
|
65
|
+
- Match the resolved branch against the table above exactly; do not round a
|
|
66
|
+
point release to a different branch or assume a branch's status from a
|
|
67
|
+
prior review.
|
|
68
|
+
- Classify the branch precisely as active support, security-only (cite the
|
|
69
|
+
exact security-support-until date), or EOL (cite the exact date it was
|
|
70
|
+
reached) — never collapse these into an undifferentiated "outdated" label.
|
|
71
|
+
- Determine the branch's **current phase** by comparing this file's published
|
|
72
|
+
cutoff dates against the review date — the current date, or an explicitly
|
|
73
|
+
supplied review/support-horizon date when one is given. This file's
|
|
74
|
+
per-branch dates are the fixed ground truth (sourced from php.net, never
|
|
75
|
+
invented, rounded, or extrapolated); only the comparison against the review
|
|
76
|
+
date is a review-time judgment, so a branch correctly transitions from
|
|
77
|
+
active support to security-only to EOL as the review date passes each
|
|
78
|
+
published cutoff.
|
|
79
|
+
- For a security-only branch, compare its "Security support until" date
|
|
80
|
+
against both the review date and the review's stated release/support
|
|
81
|
+
horizon. If EOL has already been reached, or that date falls within the
|
|
82
|
+
horizon with no tracked upgrade plan, this is a blocking finding per the
|
|
83
|
+
skill's decision gates, not merely advisory.
|
|
84
|
+
- For an EOL branch, the finding is blocking unconditionally — there is no
|
|
85
|
+
"well-maintained otherwise" exception, since EOL means no fixes of any kind
|
|
86
|
+
are published, including for actively exploited vulnerabilities.
|
|
87
|
+
|
|
88
|
+
## NORMATIVE vs. RECOMMENDATION
|
|
89
|
+
|
|
90
|
+
The support-policy structure and the per-branch dates above are NORMATIVE —
|
|
91
|
+
sourced directly from php.net and not to be altered, rounded, or
|
|
92
|
+
extrapolated. The recommended target version for an upgrade (e.g. "move to
|
|
93
|
+
the newest branch still in active support") is a RECOMMENDATION: name a
|
|
94
|
+
target that is currently in active support per the table above, but leave the
|
|
95
|
+
final choice, timeline, and any compatibility work to the owning engineering
|
|
96
|
+
team.
|
|
97
|
+
|
|
98
|
+
## Sources
|
|
99
|
+
|
|
100
|
+
- [PHP: Supported Versions](https://www.php.net/supported-versions.php) — supports the two-year active-support / two-year security-only / EOL support-policy structure and the per-branch dates for PHP 8.2 through 8.5 above.
|
|
101
|
+
|
|
102
|
+
Last verified: 2026-07-16.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: php-session-upload-deserialization-review
|
|
3
|
+
description: Use this skill to statically review PHP code for object-injection risk from unserialize() on untrusted input, session fixation/hijacking from missing session_regenerate_id() or weak session cookie hardening, and unsafe file-upload handling that trusts the client or stores/executes uploads inside the webroot. Use when reviewing a PHP application for deserialization, authentication-session, or upload-handling security issues. Static review only; it never executes payloads, uploads, or requests against any system, and every unserialize()/session/upload claim is grounded in the current php.net manual rather than memory.
|
|
4
|
+
allowed-tools: Read Grep Glob
|
|
5
|
+
metadata:
|
|
6
|
+
author: "github: Raishin"
|
|
7
|
+
version: "0.1.0"
|
|
8
|
+
updated: "2026-07-16"
|
|
9
|
+
category: security
|
|
10
|
+
lifecycle: experimental
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# PHP Session, Upload, and Deserialization Review
|
|
14
|
+
|
|
15
|
+
## Purpose
|
|
16
|
+
|
|
17
|
+
Review PHP source for the three failure modes where the language's own deserialization, session, and file-upload primitives are used in a way php.net's own manual explicitly warns against: `unserialize()` called on untrusted input (object injection, potentially remote code execution), a session that is not regenerated after a privilege change or that ships without cookie hardening (fixation/hijacking), and an upload handler that trusts the client or stores/executes accepted files inside the webroot.
|
|
18
|
+
|
|
19
|
+
## When to use
|
|
20
|
+
|
|
21
|
+
Use this skill when the user asks to:
|
|
22
|
+
|
|
23
|
+
- review any code path that calls `unserialize()`, or a library/cache/session backend that transparently unserializes stored data, for object-injection risk;
|
|
24
|
+
- review whether a login, role-elevation, or password-reset path correctly regenerates the session id and whether session cookie hardening (`use_strict_mode`, `httponly`, `secure`, `samesite`) is configured;
|
|
25
|
+
- review a file-upload handler for client-trust, storage-location, or size/type/count-limit gaps.
|
|
26
|
+
|
|
27
|
+
## When not to use
|
|
28
|
+
|
|
29
|
+
Do not use this skill for:
|
|
30
|
+
|
|
31
|
+
- designing or approving a backend authorization/permission model — this skill only checks whether a privilege change triggers session regeneration, not whether the privilege model itself is correct; hand that off to the owning backend/platform specialist.
|
|
32
|
+
- executing, crafting, or sending any deserialization payload, session-fixation request, or file upload against any live, sandbox, or staging system. This skill is static review only.
|
|
33
|
+
- issuing a compliance or audit attestation from an OWASP category mapping; the mapping is a classification aid, never a determination.
|
|
34
|
+
|
|
35
|
+
## Preconditions
|
|
36
|
+
|
|
37
|
+
- The code paths that call `unserialize()`, plus the source of the data passed to each call (request body/query/cookie, cache backend, queue message, database column).
|
|
38
|
+
- The authentication/session-management code, specifically where `session_start()`, `session_regenerate_id()`, and `$_SESSION` writes occur relative to a login, role change, or password reset.
|
|
39
|
+
- The active `session.*` INI settings in scope (`session.use_strict_mode`, `session.cookie_httponly`, `session.cookie_secure`, `session.cookie_samesite`) from `php.ini`, `.htaccess`, or runtime `ini_set()` calls.
|
|
40
|
+
- The upload-handling code: how `$_FILES` is validated, where accepted files are stored, and the `upload_max_filesize`/`post_max_size`/`max_file_uploads` configuration in effect.
|
|
41
|
+
- The PHP version in scope (`session.cookie_samesite` requires PHP 7.3+; destructor-on-fatal-error behavior changed at PHP 5.3.10).
|
|
42
|
+
|
|
43
|
+
## Lean operating rules
|
|
44
|
+
|
|
45
|
+
- Flag any `unserialize()` call on untrusted input regardless of `allowed_classes` — the manual's caution applies unconditionally, and `allowed_classes` narrows instantiable classes without making the call safe.
|
|
46
|
+
- Name the reachable magic-method surface (`__wakeup()`, `__unserialize()`, `__destruct()`) for any class an untrusted `unserialize()` call could instantiate; PHP invokes these automatically on the reconstructed object or during the shutdown sequence.
|
|
47
|
+
- Recommend `json_decode()`/`json_encode()` as the default remediation for untrusted data interchange unless serialized PHP objects are confirmed necessary.
|
|
48
|
+
- Require `session_regenerate_id(true)` before the authenticated flag is written to `$_SESSION` on every login/elevation/reset path; a regeneration call placed after that write is a finding, not a pass.
|
|
49
|
+
- Check `session.use_strict_mode`, `session.cookie_httponly`, `session.cookie_secure`, and `session.cookie_samesite` individually and explicitly; never infer hardening from defaults, since `use_strict_mode` is disabled by default.
|
|
50
|
+
- Require upload handlers to validate the file's actual content server-side (never the client-supplied `$_FILES[...]['type']` or filename alone), store accepted files outside any web-servable path, and enforce `upload_max_filesize`, `post_max_size`, and `max_file_uploads`.
|
|
51
|
+
- Never request, echo, store, or reproduce a secret, credential, session id, token, or PII found in reviewed code; redact-and-flag any such string.
|
|
52
|
+
- Label every claim `repo evidence` (seen in the reviewed code) or `documentation-based` (current php.net manual); never assert a documented API behavior from memory.
|
|
53
|
+
|
|
54
|
+
## Context7 documentation protocol
|
|
55
|
+
|
|
56
|
+
This skill's `allowed-tools` is limited to `Read`, `Grep`, and `Glob` — it does not include a live documentation-lookup tool, so grounding is done against the bundled, php.net-sourced reference files rather than a runtime Context7 call. `unserialize()`, the `session.*` INI directives, and the upload-handling behaviors reviewed here are core-language documentation, not a third-party library whose API shifts release to release, so the bundled references (each fetched directly from php.net, with its own Sources section and verification date) are the grounding source of record. If a review surfaces a claim about PHP behavior not covered by the bundled references — a different function, a different INI directive, a different PHP version's behavior — do not assert it from memory; say the claim is unverified against a specific php.net page and escalate for direct verification rather than guessing.
|
|
57
|
+
|
|
58
|
+
## Workflow
|
|
59
|
+
|
|
60
|
+
1. Enumerate every `unserialize()` call site in scope and trace the data source of its argument; classify each as untrusted-input-reachable or not.
|
|
61
|
+
2. For each untrusted-input-reachable `unserialize()` call, identify classes it could instantiate and check for `__wakeup()`, `__unserialize()`, and `__destruct()` magic methods that would fire automatically — see [Unserialize object injection](references/unserialize-object-injection.md).
|
|
62
|
+
3. For each authentication/privilege-elevation path, confirm `session_regenerate_id(true)` is called before `$_SESSION` carries the authenticated flag, and check the four session-hardening directives — see [Session security](references/session-security.md).
|
|
63
|
+
4. For each upload handler, confirm server-side content validation, non-webroot storage, and enforced size/type/count limits — see [File upload security](references/file-upload-security.md).
|
|
64
|
+
5. Emit findings with evidence tiers, exploit/failure narratives, remediation, verification steps, and OWASP category mappings; hand off any authorization-model or infrastructure-scoped issue to the owning specialist.
|
|
65
|
+
|
|
66
|
+
## Decision gates
|
|
67
|
+
|
|
68
|
+
- Block only on a demonstrated untrusted-input-reachable `unserialize()` call, a missing/misordered `session_regenerate_id()` call, a missing `session.use_strict_mode`, or an upload handler that trusts the client, stores inside the webroot, or lacks enforced limits.
|
|
69
|
+
- Every `unserialize()`, session-directive, or upload-limit claim cites the current php.net manual (`documentation-based`) or the reviewed code (`repo evidence`) — never memory.
|
|
70
|
+
- Every OWASP mapping names the specific edition and category cited and is labeled a classification aid, never a compliance determination.
|
|
71
|
+
- Every authorization-model-design or infrastructure-scoped finding is handed off, not adjudicated here.
|
|
72
|
+
|
|
73
|
+
## Evidence classification
|
|
74
|
+
|
|
75
|
+
Label each finding `repo evidence` (seen directly in the reviewed code or configuration) or `documentation-based` (the current php.net manual). This skill does not use `context7-grounded` (no Context7 tool in scope) and does not use `inference` for a documented API behavior — an undocumented or unverifiable behavior claim must be flagged as unverified rather than guessed.
|
|
76
|
+
|
|
77
|
+
## Security and privacy constraints
|
|
78
|
+
|
|
79
|
+
Static review only. Never request, transmit, store, or reproduce a secret, credential, session id, token, or PII found in reviewed code or configuration; treat any such string as a redact-and-flag finding. Never execute a deserialization payload, replay or forge a session id, upload a file, or send any request to a live, sandbox, or staging system. OWASP category mappings are classification aids, never compliance attestations.
|
|
80
|
+
|
|
81
|
+
## Escalation conditions
|
|
82
|
+
|
|
83
|
+
Escalate to incident response on any evidence the failure is already live: an object-injection payload observed in logs, a reachable uploaded web shell, or an observed session id reused across a privilege boundary. Escalate backend authorization-model design and infrastructure-scoped findings (webroot layout, TLS termination) to their owning specialist rather than adjudicating them here.
|
|
84
|
+
|
|
85
|
+
## References
|
|
86
|
+
|
|
87
|
+
Load these only when needed:
|
|
88
|
+
|
|
89
|
+
- [Unserialize object injection](references/unserialize-object-injection.md) — why `unserialize()` on untrusted input is unsafe regardless of `allowed_classes`, the magic-method attack surface, and the `json_decode()` remediation.
|
|
90
|
+
- [Session security](references/session-security.md) — `session_regenerate_id()` ordering around privilege changes and the four session cookie-hardening directives.
|
|
91
|
+
- [File upload security](references/file-upload-security.md) — client-trust, storage-location, and size/type/count-limit review criteria for upload handlers.
|
|
92
|
+
|
|
93
|
+
Each reference file carries its own Sources section citing the exact php.net (or OWASP) page backing its claims — there is no separate primary-source ledger file in this skill.
|
|
94
|
+
|
|
95
|
+
## Response minimum
|
|
96
|
+
|
|
97
|
+
Return, at minimum:
|
|
98
|
+
|
|
99
|
+
- the call site/path in scope and, per finding, the failure class (`object-injection` / `session-fixation-hijacking` / `unsafe-upload`) and evidence tier;
|
|
100
|
+
- the exploit or failure narrative (how the reachable input reaches a wrong outcome);
|
|
101
|
+
- concrete remediation and an exact verification step;
|
|
102
|
+
- the OWASP category mapping, labeled as a classification aid;
|
|
103
|
+
- any authorization-model or infrastructure-scoped handoffs and incident-response escalation.
|
|
104
|
+
|
|
105
|
+
## Anti-goals
|
|
106
|
+
|
|
107
|
+
- Do not treat `allowed_classes` as a mitigation for `unserialize()` on untrusted input; the manual's caution is unconditional.
|
|
108
|
+
- Do not design or approve a backend authorization/permission model; hand it off.
|
|
109
|
+
- Do not execute any payload, upload, or request against any live, sandbox, or staging system.
|
|
110
|
+
- Do not assert `unserialize()`, session-directive, or upload-limit behavior from memory; ground every such claim in the current php.net manual.
|
|
111
|
+
- Do not echo, store, or reproduce any secret, session id, token, or PII found during review.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "php-session-upload-deserialization-review",
|
|
3
|
+
"name": "PHP Session, Upload & Deserialization Security Review",
|
|
4
|
+
"type": "skill",
|
|
5
|
+
"provider": "php",
|
|
6
|
+
"harnesses": ["claude-code", "cursor", "codex", "gemini", "kiro", "other"],
|
|
7
|
+
"summary": "Skill for reviewing PHP application-security seams: user-reachable unserialize() object injection (allowed_classes is insufficient), session fixation/hijacking hardening (session_regenerate_id, use_strict_mode, cookie flags), and unsafe file-upload handling, each mapped to an OWASP category and the php.net-documented fix.",
|
|
8
|
+
"source_type": "original",
|
|
9
|
+
"official_docs": [
|
|
10
|
+
"https://www.php.net/manual/en/function.unserialize.php",
|
|
11
|
+
"https://www.php.net/manual/en/session.security.php",
|
|
12
|
+
"https://www.php.net/manual/en/features.file-upload.common-pitfalls.php",
|
|
13
|
+
"https://owasp.org/www-project-top-ten/"
|
|
14
|
+
],
|
|
15
|
+
"security_notes": "Static-review-only skill: Read/Grep/Glob, no execution and no network. Never runs deserialization payloads, uploads, or exploits against any live or staging system. Any credential- or PII-shaped string is a redact-and-flag finding, never echoed. Every unserialize()/session/upload claim is grounded in current php.net documentation, not memory.",
|
|
16
|
+
"last_verified": "2026-07-16",
|
|
17
|
+
"path": "skills/php/php-session-upload-deserialization-review",
|
|
18
|
+
"author": "github: Raishin",
|
|
19
|
+
"version": "0.1.0"
|
|
20
|
+
}
|
package/skills/php/php-session-upload-deserialization-review/references/file-upload-security.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# File upload security
|
|
2
|
+
|
|
3
|
+
## Why this matters
|
|
4
|
+
|
|
5
|
+
An upload handler is the one place in most PHP applications where a client
|
|
6
|
+
supplies content the server will store, and often later serve, on its own
|
|
7
|
+
filesystem. Every trust decision the manual warns against — believing the
|
|
8
|
+
client's stated MIME type, believing the client's filename, skipping the
|
|
9
|
+
configured size/count ceilings, or placing accepted content somewhere the
|
|
10
|
+
web server will execute it — turns a routine upload feature into a path for
|
|
11
|
+
an attacker to plant and run a web shell, exhaust server resources, or read
|
|
12
|
+
files the application never meant to expose.
|
|
13
|
+
|
|
14
|
+
## NORMATIVE: configuration ceilings gate every upload
|
|
15
|
+
|
|
16
|
+
Per the php.net manual's common-pitfalls guidance for file uploads:
|
|
17
|
+
|
|
18
|
+
- **`upload_max_filesize`** caps the size PHP will accept per file: "The
|
|
19
|
+
`MAX_FILE_SIZE` item cannot specify a file size greater than the file size
|
|
20
|
+
that has been set in the `upload_max_filesize` in the php.ini file. The
|
|
21
|
+
default is 2 megabytes." A form-field `MAX_FILE_SIZE` hint is a
|
|
22
|
+
client-side convenience only; the server-enforced ceiling is
|
|
23
|
+
`upload_max_filesize`.
|
|
24
|
+
- **`post_max_size`** must accommodate the request as a whole: "If
|
|
25
|
+
`post_max_size` is set too small, large files cannot be uploaded. Make
|
|
26
|
+
sure you set `post_max_size` large enough." A `post_max_size` smaller than
|
|
27
|
+
`upload_max_filesize` silently caps effective upload size below what
|
|
28
|
+
`upload_max_filesize` alone suggests.
|
|
29
|
+
- **`max_file_uploads`** bounds how many files one request can carry: "The
|
|
30
|
+
`max_file_uploads` configuration setting controls the maximum number of
|
|
31
|
+
files that can [be] uploaded in one request. If more files are uploaded
|
|
32
|
+
than the limit, then `$_FILES` will stop processing files once the limit
|
|
33
|
+
is reached" — silently, not with an error the handler is guaranteed to
|
|
34
|
+
check for.
|
|
35
|
+
- **`memory_limit`**, **`max_execution_time`**, and **`max_input_time`** must
|
|
36
|
+
also be sized for uploads: the manual specifically notes `max_input_time`
|
|
37
|
+
"sets the maximum time, in seconds, the script is allowed to receive
|
|
38
|
+
input; this includes file uploads. For large or multiple files, or users
|
|
39
|
+
on slower connections, the default of `60` seconds may be exceeded,"
|
|
40
|
+
causing an otherwise-valid upload to fail partway through.
|
|
41
|
+
|
|
42
|
+
A review must confirm these are set deliberately (not left at silent
|
|
43
|
+
defaults that either reject legitimate uploads or, in the case of
|
|
44
|
+
`max_file_uploads`, silently truncate a batch without the handler noticing).
|
|
45
|
+
|
|
46
|
+
## NORMATIVE: do not trust which file you operate on
|
|
47
|
+
|
|
48
|
+
The manual's common-pitfalls page states plainly: "Not validating which file
|
|
49
|
+
you operate on may mean that users can access sensitive information in
|
|
50
|
+
other directories." This is the manual's own framing of path-traversal and
|
|
51
|
+
client-trust risk in upload handling — a filename or path is untrusted input
|
|
52
|
+
like any other and must be validated, not built into a filesystem operation
|
|
53
|
+
directly. The manual separately cautions that "due to the large amount of
|
|
54
|
+
directory listing styles we cannot guarantee that files with exotic names
|
|
55
|
+
(like containing spaces) are handled properly," reinforcing that the
|
|
56
|
+
client-supplied filename cannot be relied on to behave predictably and
|
|
57
|
+
should not be used to construct a storage path or to make a security
|
|
58
|
+
decision (such as inferring file type from its extension).
|
|
59
|
+
|
|
60
|
+
## RECOMMENDATION: validate content, not client claims; store outside the webroot
|
|
61
|
+
|
|
62
|
+
The manual's guidance above establishes the principle — validate the file
|
|
63
|
+
you actually operate on — but does not itself specify a validation
|
|
64
|
+
algorithm or a storage location; the following are this skill's operating
|
|
65
|
+
recommendations applying that principle, to be checked as reviewer evidence
|
|
66
|
+
criteria below rather than cited as separate manual quotations:
|
|
67
|
+
|
|
68
|
+
- Treat `$_FILES[...]['type']` (client-reported MIME type) and
|
|
69
|
+
`$_FILES[...]['name']` (client-reported filename) as untrusted metadata
|
|
70
|
+
only. A security decision — what kind of file this is, whether it is
|
|
71
|
+
acceptable — must be made from the file's actual content on the server,
|
|
72
|
+
not from either client-supplied field.
|
|
73
|
+
- Store accepted uploads outside any path the web server will serve
|
|
74
|
+
directly, and generate the stored filename server-side rather than
|
|
75
|
+
reusing the client-supplied name verbatim, closing both the
|
|
76
|
+
path-traversal risk above and the risk of an executable extension being
|
|
77
|
+
preserved into a servable location.
|
|
78
|
+
- Never `include()`, `require()`, or otherwise execute an uploaded file as
|
|
79
|
+
PHP code, and never configure the storage directory to be
|
|
80
|
+
script-executable by the web server.
|
|
81
|
+
|
|
82
|
+
## Reviewer evidence criteria
|
|
83
|
+
|
|
84
|
+
For each upload handler in scope:
|
|
85
|
+
|
|
86
|
+
- Confirm `upload_max_filesize`, `post_max_size` (sized larger than
|
|
87
|
+
`upload_max_filesize`), and `max_file_uploads` are set to deliberate,
|
|
88
|
+
documented values rather than left at framework/PHP defaults without
|
|
89
|
+
review, and confirm the handler does not assume a client-side
|
|
90
|
+
`MAX_FILE_SIZE` hint is enforced.
|
|
91
|
+
- Confirm the handler checks the actual number of files processed against
|
|
92
|
+
what the client claims to have sent, given that `$_FILES` silently stops
|
|
93
|
+
populating once `max_file_uploads` is reached.
|
|
94
|
+
- Confirm the handler validates the file's real type/content server-side,
|
|
95
|
+
not `$_FILES[...]['type']` or the client filename's extension alone.
|
|
96
|
+
- Confirm accepted files are stored outside any web-servable path, with a
|
|
97
|
+
server-generated filename, not the client-supplied one used verbatim in
|
|
98
|
+
a filesystem path.
|
|
99
|
+
- Confirm no code path `include()`s, `require()`s, or otherwise executes an
|
|
100
|
+
uploaded file, and that the storage location is not configured as
|
|
101
|
+
script-executable.
|
|
102
|
+
- Treat trusting client-supplied MIME type/filename for a security decision,
|
|
103
|
+
storing inside the webroot, or having no enforced size/type/count limit as
|
|
104
|
+
a blocking finding.
|
|
105
|
+
|
|
106
|
+
## Applicable versions
|
|
107
|
+
|
|
108
|
+
The `upload_max_filesize` default (2 megabytes) and the `max_file_uploads`
|
|
109
|
+
silent-truncation behavior described above are current php.net manual
|
|
110
|
+
guidance as of this review; re-verify against the live manual page before
|
|
111
|
+
citing an exact default value, since PHP distribution defaults (and
|
|
112
|
+
distro-specific `php.ini` overrides) can differ from the documented
|
|
113
|
+
upstream default.
|
|
114
|
+
|
|
115
|
+
## Sources
|
|
116
|
+
|
|
117
|
+
- [PHP Manual — Common Pitfalls (file uploads)](https://www.php.net/manual/en/features.file-upload.common-pitfalls.php) — supports the `upload_max_filesize` default and `MAX_FILE_SIZE` client-hint framing, the `post_max_size` sizing guidance, the `max_file_uploads` silent-stop behavior, the `max_input_time` upload-duration caution, and the "not validating which file you operate on" and "exotic names" client-trust cautions.
|
|
118
|
+
|
|
119
|
+
Last verified: 2026-07-16.
|