Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## 1.2.3 — 2026-10-05

- Preserve existing outbound WordPress HTTP integrations in 1.x: production and staging no longer enable `WP_HTTP_BLOCK_EXTERNAL` automatically. Explicit environment/constant opt-in retains operator host overrides and the Composer-managed core-host fallback.
- Validate a non-empty outbound host list without a global wildcard when blocking is enabled. Doctor reports compatibility-preserving disabled blocking separately from host restrictions.
- Report the WordPress hardening configuration switch separately from unverified consumer hook activation. Runtime preflight does not require or install the private Security package.
- Align discovery paths and read-only cache selection with Kernel 1.1.5's `discovery-packages.json` and `meta.json`; legacy PHP metadata is never executed or used to select an unwritable cache.
- Retire safe stale default JSON and legacy PHP discovery files after a configured cache override, while preserving unsafe generations that selected the private fallback and validating all targets before deletion.
- Require Kernel 1.1.5 or newer only when the optional kernel is installed. Update the Runtime/Kernel pair together and warm the selected JSON cache before read-only deployment.
- Match Kernel's private temporary fallback for classic document roots and cold read-only projects, including process ownership without ext-posix. Doctor selects it without creating cache directories; maintenance validates its owner and protected ancestors and preserves legacy default generations.
- Upgrade note: regenerate the managed configuration section and inspect preserved customized sections; an unchanged 1.2.0–1.2.2 generated file still contains its earlier HTTP default.

## 1.2.2 — 2026-10-03

- Align read-only Doctor and explicit cache maintenance with Kernel 1.1.4's selected private fallback, configured cache/build roots and discovery metadata.
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ install or update. The same setup is available as a standalone command.

Runtime does not require the SymPress kernel. If your application uses it, the
[kernel integration](docs/kernel-integration.md) adds diagnostics, environment dumps
and explicit cache maintenance to `wp console`.
and explicit cache maintenance to `wp console`. Runtime 1.2.3 requires Kernel
1.1.5 or newer when that optional package is installed; update both packages
together as described in the [1.2.3 upgrade notes](docs/releases/1.2.3.md).

## Requirements

Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@
},
"require-dev": {
"phpunit/phpunit": "^13.0",
"sympress/kernel": "^1.1.4",
"sympress/kernel": "^1.1.5",
"sympress/qa": "^0.1.2"
},
"conflict": {
"sympress/kernel": "<1.1.4",
"sympress/kernel": "<1.1.5",
"symfony/event-dispatcher-contracts": "<3.4.2"
},
"autoload": {
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0005-compatibility-and-differences.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ The feature union stays available. Differences below are scoped behaviors with s
| D27 | Explicit health configuration scans tables on every setup; file-update constants are not managed. | Native health scans use explicit doctor commands. Native staging/production receives editable Composer-managed defaults, with existing constants preserved. Legacy defaults and health execution remain unchanged. ADR 0025. |
| D28 | Remote downloads have no persistent per-project content lock. | Native downloads pin SHA-256 content identities; explicit update remains possible, and configured hashes still win. Legacy locking is opt-in. ADR 0025. |
| D29 | Request-derived URL defaults and opt-in forwarded scheme can trust an untrusted ingress. | Every profile fails closed without explicit canonical WP_HOME in staging/production. Local/development keeps a validated host fallback. Forwarded HTTPS requires configured IP/CIDR trust and a proxy that replaces forwarded headers. |
| D30 | Unfiltered HTML/uploads and outbound HTTP depend on WordPress defaults or project-specific code. | Native Composer-managed staging/production applies explicit secure defaults and a narrow core-host HTTP allowlist. Existing constants/environment overrides and legacy opt-in remain supported. ADR 0028. |
| D30 | Unfiltered HTML/uploads and outbound HTTP depend on WordPress defaults or project-specific code. | Native Composer-managed staging/production applies unfiltered-content/upload defaults. Outbound HTTP blocking remains explicit opt-in throughout 1.x, with a narrow core-host fallback when enabled. Existing constants/environment overrides remain supported. ADR 0028. |
| D30 | Runtime caching can serialize real environment overrides and auto can behave as an unconditional enable flag. | Native payloads retain safe file defaults and non-secret interpolation expressions, excluding process-derived and command-derived result bytes from persisted value maps. Unrelated keys remain cached/dumped; dynamic source-chain selectors decline persistence. Auto checks writable parents before shutdown; explicit native true requires writable new-cache storage. Legacy boolean behavior is retained. |

Compatibility is enabled by default for legacy detection and independently switchable off. It is not a dependency on wecodemore. Use native names internally. Migration reads without executing PHP command providers; it writes a new config atomically, retains the original, refuses conflicting targets unless forced and lists exact source locations needing PHP changes. A second migration is a no-op.
Expand Down
8 changes: 5 additions & 3 deletions docs/adr/0015-kernel-operations-and-diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ Status: implementation decision, 2026-09-29. Implements ADR 0003 and native acce

`kernel-cache` is an explicitly selected maintenance step. It resolves the canonical environment through the same WordPress environment mapping used by generated configuration. Default paths are `var/cache/{environment}/kernel`; APP_CACHE_DIR and APP_BUILD_DIR overrides resolve relative to the project or retain their absolute directory. The actual SiteKernel path API is compared in integration tests. If an active App kernel was explicitly constructed for a different environment, cache operations fail rather than selecting another environment silently.

Runtime 1.2.2 uses a pure counterpart of Kernel 1.1.4's location policy, because calling Kernel's resolver can create and log a private fallback. Its implicit selection is `var/cache-private-{effective-user}/{environment}/kernel` when the normal location is public, group/world writable or unavailable. Tests compare selected paths and real compiled warmup with the installed Kernel. Doctor reports fallback selection without creating it and fails unsafe explicit roots.
Runtime 1.2.3 uses a read-only counterpart of Kernel 1.1.5's location policy, because calling Kernel's resolver can create and log a private fallback. Its first implicit choice is `var/cache-private-{effective-user}/{environment}/kernel` when the normal location is public, group/world writable or unavailable. If that fallback is public or unwritable, the selected root is `{canonical-system-temp}/sympress-kernel-{effective-user}-{24-character-canonical-project-hash}`. Process ownership without ext-posix is verified with the UID of a temporary-file probe, matching Kernel; no cache directory is created by inspection. Existing read-only generations are selected by the presence of `meta.json`; legacy `meta.php` cannot select an unwritable location. Tests compare selected paths, JSON discovery writes and real compiled warmup with the installed Kernel, including classic document roots, cold read-only projects and ext-posix absence. Doctor reports fallback selection without creating it and fails unsafe explicit roots.

All targets are checked before deletion. Cache/build directories are cleared without following child symlinks. In-project targets retain the generic project boundary. Explicit external cache/build roots have a separate boundary: the root must already exist with mode 0700, its owner must match the current PHP identity, and root/ancestor symlinks, parent traversal, foreign descendant owners and group/world writable directories fail. A safe default `discovery-packages.php` is removed when overrides leave legacy metadata there; an unsafe implicit generation that caused fallback is never adopted or deleted. Other environments and sibling asset caches remain intact. This does not change the twelve upstream default steps or run a kernel inside Composer.
All targets are checked before deletion. Cache/build directories are cleared without following child symlinks. In-project targets retain the generic project boundary. Explicit external cache/build roots have a separate boundary: the root must already exist with mode 0700, its owner must match the current PHP identity, and root/ancestor symlinks, parent traversal, foreign descendant owners and group/world writable directories fail. Discovery follows the selected cache as `discovery-packages.json`. Safe default `discovery-packages.json` and legacy `discovery-packages.php` files are removed when overrides leave stale metadata there; an unsafe implicit generation that caused fallback is never adopted or deleted. No legacy PHP metadata is executed. Other environments and sibling asset caches remain intact. This does not change the twelve upstream default steps or run a kernel inside Composer.

Implicit temporary roots may be absent during diagnostics. Existing roots and selected path components must be owned by the calling identity and must not be symlinked or group/world writable. External ancestors must be protected and owned by the caller or system root; shared writable ancestors require the sticky bit. Maintenance uses that selected temporary root, never the project-local default as a deletion boundary. Explicit external overrides retain their existing mode-0700 requirement.

Build IDs remain unchanged unless `kernel-build-id` is configured or `--generate-build-id` is used with explicit `kernel-cache` selection. IDs allow 1-128 letters, digits, dots, underscores and hyphens. They are stored atomically in a private `var/runtime/{environment}/kernel-build-id.json`. Generated configuration exposes the ID to the kernel's constant-based reader before boot. Precedence is an existing constant, environment value, configured fallback, persisted ID. Invalid persisted content fails without echoing its value.

Expand All @@ -20,7 +22,7 @@ An existing boot provider suppresses generation and removes a previous runtime-o

## Console registration

RuntimeBundle is optional. Kernel is a development dependency for real integration tests and a suggested production package, not a runtime requirement for plain WordPress. The bundle registers doctor, check, validate and dump-env in the kernel console without sharing the setup container. Definitions and execution are delegated to the native RuntimeCommand.
RuntimeBundle is optional. Kernel is a development dependency for real integration tests and a suggested production package, not a runtime requirement for plain WordPress. Runtime 1.2.3 conflicts with Kernel versions below 1.1.5 so Composer resolves the pair using the JSON metadata contract. The bundle registers doctor, check, validate and dump-env in the kernel console without sharing the setup container. Definitions and execution are delegated to the native RuntimeCommand.

The pinned kernel discovers library metadata but does not automatically activate composer-plugin bundles. Therefore generated configuration registers RuntimeBundle through the kernel's existing public `symfony_register_bundles` filter, after project autoloading is available. The callback checks both optional classes before registration. A project with a custom configuration template can register the bundle in `config/bundles.php` instead. Tests exercise actual WordPress hook functions, actual kernel discovery, a compiled SiteKernel container and native command execution. No kernel source modification is required.

Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0025-production-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Native configuration uses automatic writable-only environment caching and
Composer-managed staging/production defaults. Legacy profiles retain boolean
cache defaults, explicit invalidation and automatic health scans when configured;
their file-update defaults are unchanged. ADR 0023 covers environment payloads.
The additional unfiltered-content/upload and outbound HTTP defaults are defined
The additional unfiltered-content/upload defaults and explicit outbound HTTP policy are defined
in [ADR 0028](0028-production-wordpress-hardening.md).

Table scanning moves to explicit native `doctor --database-health`, with optional
Expand Down
42 changes: 31 additions & 11 deletions docs/adr/0028-production-wordpress-hardening.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# ADR 0028: Composer-managed WordPress security defaults

Status: implemented; candidate verification is recorded in the change review.
Status: implemented; outbound HTTP decision corrected for 1.x compatibility on
2026-10-05. Candidate verification is recorded in the change review.

## Decision

The generated `COMPOSER_MANAGED` section enables
`SYMPRESS_ENABLE_WORDPRESS_HARDENING`, `DISALLOW_UNFILTERED_HTML` and
`WP_HTTP_BLOCK_EXTERNAL`, and disables `ALLOW_UNFILTERED_UPLOADS`. It uses the
`SYMPRESS_ENABLE_WORDPRESS_HARDENING` and `DISALLOW_UNFILTERED_HTML`, and disables
`ALLOW_UNFILTERED_UPLOADS`. It uses the
same native staging/production `auto` selection as the existing file update
defaults. Explicit `composer-managed=true` also applies these defaults locally;
`false`, local/development `auto`, and legacy profile defaults preserve their
Expand All @@ -19,13 +20,23 @@ that implements it; Runtime does not import or install a Security package or
register its WordPress hooks. Bootstrap acceptance must verify the consumer's
actual behavior separately.

`WP_ACCESSIBLE_HOSTS` defaults to `api.wordpress.org,downloads.wordpress.org`.
Outbound WordPress HTTP blocking remains explicit opt-in throughout Runtime 1.x.
The automatic block introduced in 1.2.0–1.2.2 breaks existing payment, licensing
and API integrations on an otherwise ordinary production/staging update.
`composer-managed` and the hardening switch alone therefore do not enable it.
Existing explicit `WP_HTTP_BLOCK_EXTERNAL` environment values and constants
remain authoritative. Stock managed configuration must be regenerated when
upgrading; preserved customized sections require operator review.

When blocking is explicitly enabled, `WP_ACCESSIBLE_HOSTS` in a generated
Composer-managed section defaults to `api.wordpress.org,downloads.wordpress.org`.
The API and download hosts are exact approved core hosts, without a wildcard.
An operator-provided constant or environment value replaces the complete list.
Operators must retain required core hosts and append approved Wordfence,
payment, licensing, SMTP API and other integration hosts explicitly. For example:

```dotenv
WP_HTTP_BLOCK_EXTERNAL=true
WP_ACCESSIBLE_HOSTS=api.wordpress.org,downloads.wordpress.org,api.payments.example
```

Expand All @@ -34,16 +45,25 @@ is an application HTTP API boundary, not a firewall for raw cURL, SMTP or other
network clients. See the official
[WordPress HTTP request policy](https://developer.wordpress.org/reference/classes/wp_http/block_request/).

Strict `doctor --production` checks the effective four boolean values. Explicit
unsafe overrides remain executable for compatibility but fail production
preflight. Modified/opaque generated PHP still returns unknown instead of
assuming the default is active. Operator-specific external host approval and
actual hardening hook registration require application acceptance evidence.
Strict `doctor --production` checks the effective hardening switch and the two
unfiltered-content/upload boolean values. Its `wordpress-hardening-switch` result
establishes only configuration. A separate `wordpress-hardening-activation`
result is `unverified`: Runtime neither installs the private Security package
nor evaluates consumer hook activation. This advisory result does not fail a
Runtime-owned preflight, and exit zero is not application security acceptance.

The outbound policy check accepts the compatibility-preserving disabled state.
When explicitly enabled, `external-http-hosts` rejects empty host lists, empty
entries and a global `*` entry. Disabled blocking makes the host restriction
`not-applicable`. Opaque generated PHP returns `unknown`. Operator-specific host
approval and actual hook registration require application acceptance evidence.

## Verification

`ProductionWpConfigTest` covers native auto/on/off and environment/predefined
constant overrides, an operator HTTP allowlist, and real WordPress cold/warm
production boot. `ProductionDoctorTest` covers generated defaults and explicit
unsafe exceptions. The private `sympress/security` package remains independent;
production boot, and the real WordPress HTTP policy for production and staging.
`ProductionDoctorTest` covers generated defaults, explicit unsafe exceptions,
empty/global host lists and the unverified hook activation result.
The private `sympress/security` package remains independent;
no Starter or Demo integration follows from this decision.
2 changes: 1 addition & 1 deletion docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ The standalone runner uses the current project directory and its Composer vendor
| `flush-env-cache` | Remove only the runtime cache in the configured environment directory; WordPress installation is not required |
| `dump-env <environment>` | Resolve an explicit raw environment into a private `.env.dump.php`; does not execute steps or run-only autoload |
| `doctor`, `check` | Read-only project, environment, permission, symlink, database and kernel diagnostics |
| `doctor --json`, `check --json` | Structured redacted results; exit 0 pass, 1 fail, 2 unknown without failures |
| `doctor --json`, `check --json` | Structured redacted results; exit 0 for passed Runtime checks, 1 fail, 2 unknown without failures. Advisory `unverified` consumer activation and `not-applicable` restrictions do not alter this exit status. |
| `kernel-cache` | Explicitly clear only the selected environment's kernel cache/build and discovery metadata |
| `kernel-cache --generate-build-id` | Explicitly generate and persist a new kernel deployment ID |
| `kernel-boot` | Reconcile optional boot ownership; generation requires kernel-boot configuration |
Expand Down
7 changes: 7 additions & 0 deletions docs/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ The command reads inline `extra.wpstarter`, referenced JSON and root `wpstarter.

## Choose a behavior profile

Throughout Runtime 1.x, outbound WordPress HTTP blocking is explicit opt-in:
set `WP_HTTP_BLOCK_EXTERNAL=true` and review `WP_ACCESSIBLE_HOSTS` only when the
project requires that restriction. Production/staging and `composer-managed`
alone do not block payment, API or licensing integrations. When upgrading from
1.2.0–1.2.2, regenerate stock managed sections; customized sections are preserved
and must be inspected for the earlier automatic blocking constant.

Keep `release-3.0.1` or `upstream-dev` while preserving the corresponding WP Starter behavior. Selecting `native` is a separate behavior change: explicit step selections override configured skips, `WP_ENVIRONMENT_TYPE` has first selector priority, local dotenv overrides are enabled, and VCS/step ordering differs. Renaming configuration keys alone does not require switching profiles. Review [ADR 0005](adr/0005-compatibility-and-differences.md) before making that decision.

Legacy profiles default `wp-config-autoload` to true; migration records that inherited value explicitly. It loads the actual configured vendor autoloader after WordPress's hook API and before environment PHP and `early-hook-file`, preserving Composer `autoload.files` callbacks and vendor classes at that stage. Retain it if any early code/plugin relies on this behavior. Native defaults false; the environment parser itself remains independent of Composer either way. Existing edited `AUTOLOAD` sections are preserved, so inspect regeneration diagnostics and custom templates rather than assuming an option change rewrote hand-maintained PHP.
Expand Down
Loading
Loading