ZendHQ 3.2.0
New features
-
Account lockout and login throttling for local RBAC accounts.
Local ZendHQ accounts are now protected against brute-force password guessing. After a configurable number of consecutive failed logins an account is permanently locked (
zendhqd.session.max_failed_login_attempts, default 5, range 3..10, cannot be disabled), and each failed login imposes an exponentially growing cool-down during which further attempts are refused (initial delayzendhqd.session.initial_throttle_delay, default 500ms, range 100..3000 ms). A successful login resets the counter.Unlock a locked account with
zendhqctl access user unlock, or reset its password withzendhqctl access user password(which also unlocks it and resets the counter). The lock state, failed-attempt count, and last failed-login time are shown byzendhqctl access user print. Because host-localzendhqctlwrites the RBAC database directly, it is the recovery path even for a locked defaultadminuser.Lockout and throttling are enforced per account, not per client address. LDAP and OpenID Connect accounts are unaffected.
-
Forensic logging of failed client logins.
Every failed client authentication now produces a single warning recording the attempted user name, the authentication backend, the client connection (transport and peer address/port, or a marker for local-socket clients), and a short failure reason (invalid-credentials, locked, throttled, no-password, ...). For WebSocket clients behind a reverse proxy, the client-supplied
X-Forwarded-For,X-Real-IP, andForwardedheaders are also included and clearly labelled as untrusted. This information is for logging only and is never used in a lockout or throttling decision. -
Timezone for recurring Job Queue jobs.
Recurring (cron) jobs can now be given an IANA timezone, for example
America/New_York, so that their schedules follow that zone's daylight saving time rules instead of always being evaluated in UTC. The cron expression is matched against wall-clock time in the selected zone, and the resulting execution time is stored and reported in UTC.Across a daylight saving transition, local times that do not exist because the clock jumps forward are shifted to the next valid instant, and local times that occur twice because the clock jumps back run on the first occurrence only.
Every queue and every recurring job carries a timezone, defaulting to
UTC. The value is resolved when a queue or job is created, consistent with every other Job Queue default: the newzendhqd.jobqueue.timezonedirective (defaultUTC) supplies the default for new queues, a queue's default is copied onto the recurring jobs created in it, and a job can carry its own value. Changing a configuration directive or a queue default afterwards does not affect queues or jobs that already exist. One-shot jobs have no schedule to evaluate and never carry a timezone.Behavior is unchanged for existing installations. Schedules were already evaluated in UTC, and the database upgrade sets
UTCon the queues and recurring jobs it finds.Set the timezone through the JSON-RPC
jqnamespace:schedule.timezoneonjq.add_jobandjq.update_job, andjob_defaults.timezoneonjq.add_queueandjq.update_queue. An omitted member leaves the current value unchanged, while an explicitnullre-inherits the value from the level above. Invalid IANA identifiers and the empty string are rejected with an error. The attribute applies to recurring jobs only.The PHP API carries the same capability.
ZendHQ\JobQueue\RecurringScheduletakes an optional timezone as its second constructor argument and reports it through the newgetTimezone(), andZendHQ\JobQueue\QueueDefinitiontakes a queue-level default as its fourth constructor argument with the newgetDefaultTimezone()andsetDefaultTimezone(). Both accept the IANA identifier as astringor as a\DateTimeZone, and both report it back as a?string. Existing callers are unaffected: the new arguments are optional.Only a region identifier is accepted from a
\DateTimeZone. PHP also allows UTC offsets (+02:00) and abbreviations (CEST); those are rejected, because they carry no daylight saving rules and so cannot do what the timezone exists for. Passingnullto the constructors means inherit, and leaves a queue's existing default alone;setDefaultTimezone(null)clears a queue's default so that it re-inherits the configured one. Because the daemon resolves the precedence when a job or queue is written, the getters report the timezone that actually applies once the object has been read back. Scheduling a job or creating a queue with a timezone against a ZendHQ daemon older than 3.2 raises anInvalidArgumentexception rather than silently dropping it.The attribute is carried over zend-rpc and is only sent to PHP extensions that have negotiated the ZendHQ 3.2 API version or newer. The legacy ZeroMQ protocol does not carry it at all, so older PHP extensions keep working unchanged.
Changes
-
Local RBAC passwords are now stored more securely.
Passwords for local ZendHQ RBAC accounts are now hashed with a per-user random salt using PBKDF2-HMAC-SHA256. Existing databases are upgraded automatically: on first run, stored passwords are migrated to the new format with no loss of access — users keep their current passwords and no administrator action is required.
-
The hard-coded default
adminpassword has been removed.A freshly installed
adminuser now has no password and cannot log in until one is set. Set the initial password withzendhqctl access user password admin, or provide theZENDHQ_DEFAULT_PASSWORDenvironment variable to the daemon (the value becomes theadminpassword on the next start where the user has no password yet — useful for unattended and container deployments).Upgrade impact: installations that relied on the implicit
zendphpdefault are locked out after upgrading and must set theadminpassword using one of the methods above. The daemon logs a warning on start when the defaultadminuser has no password. The deprecatedsession.auth_token_hashdirective is still honoured as an explicit override when theadminuser has no password in the RBAC database.
Bug fixes
-
Fixed a memory leak in the ZendHQ daemon where the data of a PHP request that never completed was kept forever. The daemon collects request metadata, server variables, Z-Ray messages, POST data, response bodies and code-trace state for every request, and released it only when the PHP extension reported the end of the request. That report never arrives when the PHP worker dies mid-request or the connection to the node is lost, so on a busy or unstable node memory grew without bound; a request being code traced also left an open dump file handle and an orphaned partial dump on disk.
The daemon now drops a request that has been in progress for longer than
zendhqd.php_request_timeout(default 10 minutes), and anything that arrives for a request after that is rejected. The timeout must be longer than the longest request a node can produce; a request that outlives it is treated as dead and its data, including an in-progress code trace, is discarded.
New API and database schema versions
- zend-rpc API version is now
20260811. - JSON-RPC
sessionnamespace API version is now1.15. - JSON-RPC
jqnamespace API version is now1.6. - User database schema version is now
2.1. On first run, existing bare SHA-256 password hashes are migrated in place to salted PBKDF2 hashes, and theuser_userstable gains theis_locked,last_failed_login_sec, andfailed_countcolumns used for account lockout and login throttling. - Job Queue database schema version is now
2.5. Thejobqueue_queuestable gains ajob_timezonecolumn and thejobqueue_jobstable gains atimezonecolumn. Both are set toUTCon the existing queues and recurring jobs, which is the zone their schedules were already evaluated in. One-shot jobs keep no timezone.