Date: 2026-06-02 Scope: ControlScripts layer, event routing, cron/background execution, infrastructure (config, logging, DB, CLI), Fury framework services.
File: server/SHServ/Middleware/ControlScripts.php Lines: 12–13
protected static $regular_scripts = []; protected static $actions_scripts = [];
static arrays accumulate scripts across all instantiations. In PHP-FPM, statics persist per worker process. Adding/removing scopes or reloading code does not clear the arrays. Tests (ScriptsModelStateTest) use reflection to reset, but production has no cleanup mechanism. If a scope file is deleted, its scripts remain in static memory until worker restart.
Impact: Stale script entries, phantom actions, memory growth, unpredictable behavior after deploys.
Fix: Flush statics at the start of control_scripts_init(), or use instance-level storage instead of static.
File: server/SHServ/Controllers/ScriptsRESTAPIController.php Method: scope_update file_put_contents($filepath, $file) writes directly over the live PHP class file. If the request is interrupted mid-write, the file is truncated/corrupted. PHP will fail to parse it on the next request, breaking the entire scope and potentially the application.
Impact: Broken scope class → fatal parse error → 500 for all subsequent requests using that scope.
Fix: Write to a temp file, rename() atomically, keep the previous version as .bak.
File: server/SHServ/Controllers/EventsController.php Method: new_event After fastcgi_finish_request(), event handlers are dispatched. Handlers like btn_on_online → sync_btn_channels make synchronous HTTP calls to devices via Button/Relay API. set_time_limit(10) applies to the whole block, but with 5 event dispatches and multiple device calls, 10 seconds may not be enough. If a device is slow or unresponsive, the PHP process is held busy.
Impact: PHP-FPM worker exhaustion, delayed response for other requests, cron/scanning blocked.
Fix: Enforce a strict per-handler timeout (e.g. 2s), run handlers asynchronously via a queue or background job, or use non-blocking HTTP.
scope_update (reinforced)File: server/SHServ/Controllers/ScriptsRESTAPIController.php Any authenticated user can overwrite a scope file with arbitrary PHP. The file is immediately included on the next request via control_scripts_init(). Combined with no RBAC, this is trivial remote code execution.
Impact: Full server compromise.
Fix: Sandbox scope code (e.g. restricted DSL), validate with php -l, disable live editing in production, require admin role.
sync-map.json loaded from disk on every scope init, no cachingFile: server/ControlScripts/Common.php Method: register_global_device_sync_map Each scope that uses Common re-reads sync-map.json via file_get_contents. With 4 scopes, the file is read 4 times per request (~9 KB each). No opcode cache or in-memory cache.
Impact: Unnecessary disk I/O, parse overhead on every request.
Fix: Load once in App::control_scripts_init() and pass to scopes, or cache decoded array in a static/shared variable.
File: server/SHServ/Controllers/CronController.php Method: run_regular_cron_scripts
foreach($regular_scripts as $alias => $script) {
// ...
$script["script"]();
}
No try/catch. If one regular script throws, the exception bubbles up and the remaining scripts in the loop never execute.
Impact: Partial automation — some scheduled tasks silently skipped.
Fix: Wrap each script invocation in try/catch, log the error, continue the loop.
File: server/SHServ/Middleware/ControlScripts.php Method: run_action_script
$result = self::$actions_scripts[$alias]["script"]($params);
No try/catch. An exception in an action script kills the API request with a 500 and no meaningful response to the client.
Fix: Wrap in try/catch, return structured error response.
get_source_code() re-reads the source file on every script registrationFile: server/SHServ/Middleware/ControlScripts.php Method: get_source_code Uses file() + array_slice to extract closure source code. Called once per add_action_script / add_regular_script. For 20 scripts, the file is read 20 times.
Impact: Redundant disk I/O during every request bootstrap.
Fix: Cache file contents per class, or use ReflectionFunction::getClosureUsedVariables + store source once at build time.
control_scripts_init() instantiates all scopes eagerlyFile: server/SHServ/App.php Method: control_scripts_init All .php files in ControlScripts/Scopes/ are scanned, included, and instantiated on every request. There is no lazy loading — even disabled scopes run their constructors and all four register_* methods (though register_actions_scripts / register_regular_scripts are gated by DB state, the constructor still runs).
Impact: Slower bootstrap, unnecessary DB queries, memory overhead.
Fix: Lazy-load scopes when their scripts are actually needed, or cache registration metadata.
File: server/SHServ/Controllers/CronController.php Both run_regular_cron_scripts() and status_update_scanning() return nothing (implicit null). The HTTP response body is empty, HTTP status is 200 by default. There is no way to tell from the response whether scripts ran, how many succeeded, or if any failed.
Impact: Blind monitoring. External cron scheduler (e.g. systemd timer, crontab) cannot detect failures.
Fix: Return structured JSON with execution summary, error count, duration.
File: server/Fury/Kernel/Logging.php Method: dump Appends session logs to Logs/d.m.Y.log.json. File grows indefinitely. No max size, no rotation, no cleanup. On high-traffic days the file can become several GB.
Impact: Disk exhaustion, performance degradation on log write (lock contention, JSON re-encode of huge array).
Fix: Rotate by size (e.g. 10 MB), archive old logs, or switch to structured line-based logging.
File: server/Fury/Kernel/Logging.php logging()->set($place, $title, $message) accepts only three strings. No severity levels, no request ID, no timestamps per entry (only per session), no structured fields.
Impact: Hard to debug production issues, impossible to trace a single request through controllers, models, and device calls.
Fix: Add severity, request/correlation ID, timestamp per log line, optional context array.
debug flag is onFile: server/Fury/Modules/ErrorHandler/ErrorHandler.php Method: view_fatal_error If FCONF["debug"] is true, the handler renders full file paths, source code excerpts, and stack traces as HTML. The debug flag is controlled by .env DEBUG. With no .env file, it defaults to false, but a misconfiguration or accidental .env edit exposes internals.
Impact: Information disclosure — file paths, code snippets, server internals.
Fix: In production mode, never render HTML errors; always log and return generic "Internal Server Error" JSON.
File: project-wide There are no .sql migration files, no schema versioning, no install script. The scripts table definition only exists inside tests/ScriptsModelStateTest.php as an inline CREATE TABLE. Production database setup is manual and undocumented.
Impact: Impossible to recreate production DB, risky deploys, schema drift between environments.
Fix: Create server/migrations/ with numbered SQL files and a CLI runner.
.env absent — fallbacks use insecure defaultsFile: server/SHServ/config.php If .env is missing, DB credentials default to user => "root", password => "", host => "localhost". Device IP scan range defaults to 192.168.2.2–254.
Impact: Accidental connection to wrong/weak DB, scan of wrong subnet.
Fix: Fail hard if .env is missing in production; do not provide insecure fallbacks.
ControlScripts constructor performs DB check per scopeFile: server/SHServ/Middleware/ControlScripts.php Line: 26
if($scope_folder != "ControlScripts" or (new Scripts()) -> script_state("scope", $scope_name)) {
For every non-ControlScripts scope, a new Scripts model is instantiated and a DB query is issued to check if the scope is enabled. With N scopes, N DB round-trips on every request.
Impact: N+1 query problem during every bootstrap.
Fix: Batch-fetch all scope states in a single query in App::control_scripts_init().
sync_map() returns mutable reference to shared static stateFile: server/SHServ/Middleware/ControlScripts.php Method: sync_map Returns self::$sync_map_storage. Callers (scopes) can mutate the shared array, affecting other scopes.
Impact: Cross-scope state pollution, hard-to-debug side effects.
Fix: Return a deep copy, or make sync_map_storage immutable after init.
DeviceScanner silently discards scan failuresFile: server/SHServ/Tools/DeviceScanner.php Failed IPs are skipped without logging. There is no way to know which devices were expected but not found, or whether a device disappeared from the network.
Impact: Silent failures during device discovery, no audit trail for network changes.
Fix: Log scan results — found, lost, unreachable IPs.
text-msgs.php has empty strings for SHServ errorsFile: server/SHServ/text-msgs.php Lines 30–48: device_not_found, unknown_device, error_of_device_auth, device_request_fail, db_error, action_script_not_found, scope_not_found, etc. all have empty string values.
Impact: API returns empty msg fields for many errors, poor UX in clients.
Fix: Fill in meaningful Russian/English messages for all SHServ aliases.
RequiredControlScriptsScope mixes business logic with event handlingFile: server/SHServ/RequiredControlScriptsScope.php The online event handler directly mutates device entity state ($device -> device_ip = ...; $device -> update()). This is business logic that belongs in a service or model method, not in an event handler.
Impact: Business rules scattered across event handlers, hard to unit-test, duplication risk.
Fix: Move device online/offline state transition to Devices model or a dedicated service.
EventsHandlers devmode clutterFile: server/SHServ/EventsHandlers.php All devmode handlers are registered inside an if(FCONF["devmode"]) block at runtime. The code paths are still present and evaluated on every request.
Fix: Use conditional class loading or separate dev-only bootstrap file.
console.php has only one command (get.config)File: server/console.php The CLI entry point only supports dumping config (with credentials stripped). No commands for: run migrations, run cron manually, flush caches, health check, reset rate limiter, re-scan devices.
Fix: Add a command registry with useful ops commands.
sync-map.json has no schema validationFile: server/ControlScripts/sync-map.json Loaded as raw JSON without validation. No checks that aliases correspond to real devices, that channel indices are within device schema, or that the graph is acyclic.
Impact: Invalid sync map causes silent failures (missing device → continue in helper) or wrong channel indices sent to devices.
Fix: Add a JSON Schema and a validation step at bootstrap (or at build time).
ControlScriptsInterface lacks return type declarationsFile: server/SHServ/Implements/ControlScriptsInterface.php Interface methods have no return types, while implementations use : void. This inconsistency weakens type safety.
Fix: Add void return type to interface methods.
Project-wide: no counters for events dispatched, actions executed, cron scripts run/failed, device API call latency, event handler latency.
Fix: Add lightweight metrics (even in-memory counters exposed via a /metrics endpoint) or integrate with Prometheus/OpenTelemetry.
add_event_handler discards handler return valuesFile: server/SHServ/Middleware/ControlScripts.php Method: add_event_handler The wrapper closure ignores the return value of $handler. There is no way for a handler to signal "stop propagation" or return a result.
Fix: Return the handler result, or support explicit stopPropagation() mechanism.
SpotlightsScope::spotlights_by_time is an empty regular scriptFile: server/ControlScripts/Scopes/SpotlightsScope.php Line: 64–70 Registered but does nothing. Pollutes the regular scripts list and cron loop.
Fix: Remove or implement the time-based logic.
TestScriptsScope references hardcoded production deviceFile: server/ControlScripts/Scopes/TestScriptsScope.php Line: 23–26 Alias test_stand_relay is hardcoded. If the device does not exist, the action script will fail at runtime.
Fix: Gate test scopes behind devmode flag, or move to a separate directory excluded from production scan.
EventsModel naming inconsistencyFile: server/SHServ/Models/EventsModel.php Methods: global_any_device_event_call, global_device_event_call, channel_device_event_call, alias_device_event_call, channel_alias_device_event_call. Naming pattern is inconsistent (_any_ vs _alias_ placement).
Fix: Standardize naming to {scope}_{target}_event_call.
ControlScripts constructor uses fragile string parsingFile: server/SHServ/Middleware/ControlScripts.php Line: 24
list($scope_folder, $scope_name) = explode("\\", str_replace("\\Scopes", "", str_replace("SHServ", "", static::class)));
Assumes exactly two namespace segments. If namespace depth changes, parsing breaks.
Fix: Use basename(str_replace('\\', '/', static::class)) for name, and dirname() logic for folder.
Scripts::remove_scope has TOCTOU raceFile: server/SHServ/Models/Scripts.php Method: remove_scope
if(!file_exists($filepath)) { return false; }
if(!@unlink($filepath)) { return false; }
File can be modified between file_exists and unlink.
Fix: Just attempt unlink and check the result; suppress warning with @.
When a device sends an event, it triggers multiple handlers, each making device API calls. There is no tracing ID to correlate the original event with downstream actions in logs.
Fix: Generate a correlation_id in EventsController and pass it through all event handlers and device API calls.
Logging JSON file lacks indexingFile: server/Fury/Kernel/Logging.php Searching logs requires loading and parsing the entire JSON file. No grep-friendly line format.
Fix: Use newline-delimited JSON (NDJSON) for append-only, grep-friendly log files.
ControlScripts constructor bypass for RequiredControlScriptsScopeFile: server/SHServ/App.php RequiredControlScriptsScope is instantiated directly before scanning the Scopes/ directory. It is not listed in control_scripts_instances (it is in $required_control_scripts_instance). But its scripts are still registered in the same static arrays, mixing required and optional scopes.
Impact: Required scope handlers can be accidentally disabled if static arrays are cleared.
Fix: Separate static storage for required vs optional scopes, or ensure required scope is always registered regardless of DB state.
error_handler in config only covers warnings and fatal errorsFile: server/SHServ/config.php
"important_errors" => ["E_WARNING", "E_ERROR", "E_CORE_ERROR", "EXCEPTION"]
Notices, deprecations, and parse errors are silently ignored in production. This can hide bugs.
Fix: Include E_NOTICE, E_DEPRECATED, E_PARSE in dev; log (but don't display) them in production.
| Severity | Count |
|---|---|
| Critical | 6 |
| High | 14 |
| Medium | 15 |
Key themes:
ControlScripts static arrays cause cross-request and cross-test contamination.try/catch, causing cascading failures..env fallbacks are insecure.