diff --git a/README.md b/README.md index c9827c8..1e33e67 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Display HA dashboards in kiosk mode directly on your HAOS server. -## Author: Jeff Kosowsky (version: 1.3.0, February 2026) +## Author: Jeff Kosowsky (version: 1.3.1, April 2026) ## Description @@ -42,7 +42,7 @@ please file an \*\*include full details of your setup (including computer hardware and display type details)and what you did along with a complete log. -You can also use the `screenshot` REST API or keybinding (`Ctl+Alt+k`) or +Note you can use the `screenshot` REST API or keybinding (`Ctl+Alt+k`) or touch gesture (Quadruple 3 finger tap) to save a screenshot to `/media/screenshots` @@ -74,19 +74,11 @@ ______________________________________________________________________ 4. Press **Start** to run the Add-on. **If you are having trouble installing the add-on or getting displays and -touchscreens working, please see the github issues page -(https://github.com/puterboy/HAOS-kiosk/issues)as many common issues have +touchscreens working, please see the **TROUBLESHOOTING** section below as +well as the github issues page +(https://github.com/puterboy/HAOS-kiosk/issues) as many common issues have already been addressed and resolved** -### Notes - -- If screen is not working on an RPi3, try adding the following lines to - the `[pi3]` section of your `config.txt` on the boot partition: - ``` - dtoverlay=vc4-fkms-v3d - max_framebuffers=2 - ``` - ______________________________________________________________________ ## Configuration Options @@ -263,11 +255,16 @@ examples, and default gestures. ### Command Whitelist Regex -Regex (Python) of shell command that can be used in creating gesture action -commands or when running the `run_command` and `run_commands` REST APIs. +Regex (Python) of shell commands that can be used in creating gesture +action commands or when running the `run_command` and `run_commands` REST +APIs. -If left blank, then all commands are allowed except for those blacklisted -as dangerous (otherwise, whitelist overrides internal blacklist). +If only base name given, then path is assumed to be: +`PATH=/bin:/usr/bin/:/usr/local/bin` + +If left blank, then all commands in `$PATH` are allowed except for those +blacklisted as dangerous (otherwise, whitelist overrides path restrictions +and internal blacklist). The pre-defined command blacklist includes commands like: @@ -279,16 +276,13 @@ The pre-defined command blacklist includes commands like: cp, chmod, chown, dd, ln, mv, rm, tar mount, umount curl, nc, wget - find, xargs" + find, xargs ``` Note that if you want to truly allow *all* commands, then use the wildcard `.*` but beware that it is DANGEROUS. If you want to disallow all commands set the regex to `^$`. -Note that regardless of setting only commands found in `/bin`, `/usr/bin`, -and `/usr/local/bin` of the HAOSKiosk Add-on container are allowed. - ### VNC SERVER Launch VNC Server on port 5900 if password non-blank. If password set to @@ -983,3 +977,22 @@ Luakit modes and commands are similar to vi See [luakit documentation](https://wiki.archlinux.org/title/Luakit) for further usage information and available commands. + +______________________________________________________________________ + +## TROUBLESHOOTING + +- If the display is not working on an RPi3, try adding the following lines + to the `[pi3]` section of your `config.txt` on the boot partition: + + ``` + dtoverlay=vc4-fkms-v3d + max_framebuffers=2 + ``` + +- If you see black borders (underscan) around the display on a Raspberry Pi + you can disable overscan in `config.txt` on the boot partition: + + ``` + disable_overscan=1 + ``` diff --git a/haoskiosk/CHANGELOG.md b/haoskiosk/CHANGELOG.md index a2a7cc7..e292fc6 100644 --- a/haoskiosk/CHANGELOG.md +++ b/haoskiosk/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## v1.3.1 - April 2026 + +- Updated auto-login JS injection in 'userconf.lua' for 2026.4+ +- Fixed whitelist logic to allow commands outside of default path + ## v1.3.0 - February 2026 - Added more key bindings for opening/closing/rotating tabs and windows diff --git a/haoskiosk/Dockerfile b/haoskiosk/Dockerfile index eab7495..d401705 100644 --- a/haoskiosk/Dockerfile +++ b/haoskiosk/Dockerfile @@ -1,9 +1,9 @@ ################################################################################ # Add-on: HAOS Kiosk Display (haoskiosk) # File: Dockerfile -# Version: 1.3.0 +# Version: 1.3.1 # Copyright Jeff Kosowsky -# Date: February 2026 +# Date: April 2026 ################################################################################ ARG BUILD_FROM diff --git a/haoskiosk/README.md b/haoskiosk/README.md index c9827c8..1e33e67 100644 --- a/haoskiosk/README.md +++ b/haoskiosk/README.md @@ -2,7 +2,7 @@ Display HA dashboards in kiosk mode directly on your HAOS server. -## Author: Jeff Kosowsky (version: 1.3.0, February 2026) +## Author: Jeff Kosowsky (version: 1.3.1, April 2026) ## Description @@ -42,7 +42,7 @@ please file an \*\*include full details of your setup (including computer hardware and display type details)and what you did along with a complete log. -You can also use the `screenshot` REST API or keybinding (`Ctl+Alt+k`) or +Note you can use the `screenshot` REST API or keybinding (`Ctl+Alt+k`) or touch gesture (Quadruple 3 finger tap) to save a screenshot to `/media/screenshots` @@ -74,19 +74,11 @@ ______________________________________________________________________ 4. Press **Start** to run the Add-on. **If you are having trouble installing the add-on or getting displays and -touchscreens working, please see the github issues page -(https://github.com/puterboy/HAOS-kiosk/issues)as many common issues have +touchscreens working, please see the **TROUBLESHOOTING** section below as +well as the github issues page +(https://github.com/puterboy/HAOS-kiosk/issues) as many common issues have already been addressed and resolved** -### Notes - -- If screen is not working on an RPi3, try adding the following lines to - the `[pi3]` section of your `config.txt` on the boot partition: - ``` - dtoverlay=vc4-fkms-v3d - max_framebuffers=2 - ``` - ______________________________________________________________________ ## Configuration Options @@ -263,11 +255,16 @@ examples, and default gestures. ### Command Whitelist Regex -Regex (Python) of shell command that can be used in creating gesture action -commands or when running the `run_command` and `run_commands` REST APIs. +Regex (Python) of shell commands that can be used in creating gesture +action commands or when running the `run_command` and `run_commands` REST +APIs. -If left blank, then all commands are allowed except for those blacklisted -as dangerous (otherwise, whitelist overrides internal blacklist). +If only base name given, then path is assumed to be: +`PATH=/bin:/usr/bin/:/usr/local/bin` + +If left blank, then all commands in `$PATH` are allowed except for those +blacklisted as dangerous (otherwise, whitelist overrides path restrictions +and internal blacklist). The pre-defined command blacklist includes commands like: @@ -279,16 +276,13 @@ The pre-defined command blacklist includes commands like: cp, chmod, chown, dd, ln, mv, rm, tar mount, umount curl, nc, wget - find, xargs" + find, xargs ``` Note that if you want to truly allow *all* commands, then use the wildcard `.*` but beware that it is DANGEROUS. If you want to disallow all commands set the regex to `^$`. -Note that regardless of setting only commands found in `/bin`, `/usr/bin`, -and `/usr/local/bin` of the HAOSKiosk Add-on container are allowed. - ### VNC SERVER Launch VNC Server on port 5900 if password non-blank. If password set to @@ -983,3 +977,22 @@ Luakit modes and commands are similar to vi See [luakit documentation](https://wiki.archlinux.org/title/Luakit) for further usage information and available commands. + +______________________________________________________________________ + +## TROUBLESHOOTING + +- If the display is not working on an RPi3, try adding the following lines + to the `[pi3]` section of your `config.txt` on the boot partition: + + ``` + dtoverlay=vc4-fkms-v3d + max_framebuffers=2 + ``` + +- If you see black borders (underscan) around the display on a Raspberry Pi + you can disable overscan in `config.txt` on the boot partition: + + ``` + disable_overscan=1 + ``` diff --git a/haoskiosk/config.yaml b/haoskiosk/config.yaml index 34deb34..257daab 100644 --- a/haoskiosk/config.yaml +++ b/haoskiosk/config.yaml @@ -3,8 +3,9 @@ name: "HAOS Kiosk Display" description: | Start X server and browser on local HAOS server and display dashboards in kiosk mode (Jeff Kosowsky) -version: "1.3.0" +version: "1.3.1" slug: "haoskiosk" +url: https://github.com/puterboy/HAOS-kiosk/tree/main/haoskiosk arch: - aarch64 diff --git a/haoskiosk/gesture_commands.json b/haoskiosk/gesture_commands.json index e1c59b8..9944aa4 100644 --- a/haoskiosk/gesture_commands.json +++ b/haoskiosk/gesture_commands.json @@ -1,9 +1,9 @@ # ============================================================================== # HAOS Kiosk Display — Mouse & Touch Input Engine # File: gesture_commands.json -# Version: 1.3.0 +# Version: 1.3.1 # Copyright Jeff Kosowsky -# Date: February 2026 +# Date: April 2026 # ------------------------------------------------------------------------------ # USER CUSTOMIZABLE GESTURES — loaded BEFORE built-in # defaults in CMD_DICTand thus have higher precedence since diff --git a/haoskiosk/mouse_touch_inputs.py b/haoskiosk/mouse_touch_inputs.py index 03b0e9e..b68688d 100644 --- a/haoskiosk/mouse_touch_inputs.py +++ b/haoskiosk/mouse_touch_inputs.py @@ -12,9 +12,9 @@ """------------------------------------------------------------------------------- # HAOS Kiosk Display — Mouse & Touch Input Engine # File: MouseTouchInputs -# Version: 1.3.0 +# Version: 1.3.1 # Copyright Jeff Kosowsky -# Date: February 2026 +# Date: April 2026 # #### DESCRIPTION: Full-featured X11 parser and command launcher for multi-button press and @@ -250,8 +250,8 @@ Each command string/list is validated to make sure that all programs mentioned are allowed (using function is_command_allowed). Specifically, unless ALLOW_ALL_USER_COMMANDS is True, the programs are tested to ensure: - - Program exists within ALLOWED_PATH - Program is white-listed (if COMMAND_WHITELIST_REGEX is not None) + - Program exists within ALLOWED_PATH - Program is not black-listed (note whitelist when set overrides blacklist) When a gesture sequence is generated, the GESTURE_CMDS_LIST is used to find the @@ -342,7 +342,6 @@ #------------------------------------------------------------------------------- ### MYTODOS: - Add arbitrary positions - - Test #------------------------------------------------------------------------------- """ @@ -370,9 +369,9 @@ from Xlib import display #type: ignore[import-untyped] #pylint: disable=import-error from Xlib.xobject.drawable import Window #type: ignore[import-untyped] #pylint: disable=import-error #------------------------------------------------------------------------------- -__version__ = "1.3.0" +__version__ = "1.3.1" __author__ = "Jeff Kosowsky" -__copyright__ = "Copyright 2025 Jeff Kosowsky" +__copyright__ = "Copyright 2025-2026 Jeff Kosowsky" #------------------------------------------------------------------------------- #### User Configuration @@ -465,6 +464,7 @@ def debug(level: int, msg: str) -> None: ## Restrict paths to specific, non-system bins ALLOWED_PATHS = {"/bin", "/usr/bin", "/usr/local/bin"} # Executables must be in these directoriesp +ALLOWED_PATHS_STR = ":".join(ALLOWED_PATHS) ## Commands that are white-listed -- all others are blocked (Note: set to ".*" to allow all or "" to block all) DEFAULT_COMMAND_WHITELIST_REGEX = r"cat|date|dbus-send|echo|false|grep|head|ls|luakit|notify-send|ping|ping6|ps|pstree|sleep|tail|test|top|tree|xdotool|xset" @@ -1761,14 +1761,15 @@ def _parse_command_value(value: Any) -> CommandsDict: Returns the corresponding CommandsDict object with "cmds" and "execs" plus the optional "msg" and "timeout" keys. Raise an exception if: - Not a valid raw command object (i.e. not a CommandsType or CommandsDict) - - Command is blacklisted or not whitelisted (and ALLOW_ALL_USER_COMMANDS is False) + - Command is not whitelisted (and ALLOW_ALL_USER_COMMANDS is False) or path disallowed or not whitelisted """ def is_path_allowed(prog_path: str) -> bool: """Return True if binary is in an allowed directory.""" try: real_path = os.path.realpath(prog_path) - return any(real_path.startswith(allowed + "/") for allowed in ALLOWED_PATHS) + parent_dir = os.path.dirname(real_path) + return parent_dir in ALLOWED_PATHS except Exception: return False @@ -1800,21 +1801,21 @@ def is_command_allowed(command_str: str) -> tuple[bool, str]: #pylint: disable= for prog in programs: # 1. Program not found - prog_path = shutil.which(prog) or "" + prog_path = shutil.which(prog, path=ALLOWED_PATHS_STR) or "" if not prog_path: return False, f"Program not found: {prog}" - # 2. PATH restriction - if not is_path_allowed(prog_path): - return False, f"Program not in allowed paths: {prog_path}" - - # 3. Whitelist — Allow if whitelisted; deny if not - # Note whitelist overrides blacklist if set + # 2. Whitelist — Allow if whitelisted; deny if not + # Note whitelist overrides blacklist and PATH restriction if set if COMPILED_WHITELIST_REGEX is not None: if not COMPILED_WHITELIST_REGEX.fullmatch(prog): return False, f"Program not in Whitelist: {prog}" continue + # 2. PATH restriction + if not is_path_allowed(prog_path): + return False, f"Program not in allowed paths: {prog_path}" + # 4. Blacklist — Deny if blacklisted if COMPILED_BLACKLIST_REGEX.fullmatch(prog): return False, f"Blacklisted program: {prog}" diff --git a/haoskiosk/rest_server.py b/haoskiosk/rest_server.py index d5803d9..5cfdfeb 100644 --- a/haoskiosk/rest_server.py +++ b/haoskiosk/rest_server.py @@ -1,9 +1,9 @@ """------------------------------------------------------------------------------- # Add-on: HAOS Kiosk Display (haoskiosk) # File: services.py -# Version: 1.3.0 +# Version: 1.3.1 # Copyright Jeff Kosowsky -# Date: February 2026 +# Date: April 2026 Launch REST API server with following commands: POST /launch_url {"url": ""} @@ -29,15 +29,14 @@ - Requires REST_BEARER_TOKEN for protected commands if caller is not localhost - Commands must: - Satisfy whitelist regex + - Satisfy path restriction - Not be on blacklist - Not contain destructive tokens This can be over-ridden by setting ALLOW_ALL_USER_COMMANDS = True, BUT not allowed now #------------------------------------------------------------------------------- ### MYTODOS: - - Add broader whitelist - - Add ability to import whitelist (maybe add special key to allow all?) - - Test +# #----------------------------------------------------------------------------""" # pylint: disable=line-too-long # pylint: disable=invalid-name @@ -71,9 +70,9 @@ from aiohttp import web #type: ignore[import-not-found] #pylint: disable=import-error #------------------------------------------------------------------------------- -__version__ = "1.3.0" +__version__ = "1.3.1" __author__ = "Jeff Kosowsky" -__copyright__ = "Copyright 2025 Jeff Kosowsky" +__copyright__ = "Copyright 2025-2026 Jeff Kosowsky" # ----------------------------------------------------------------------------- # # Global variables @@ -101,6 +100,7 @@ ## Restrict paths to specific, non-system bins ALLOWED_PATHS = {"/bin", "/usr/bin", "/usr/local/bin"} # Executables must be in these directories +ALLOWED_PATHS_STR = ":".join(ALLOWED_PATHS) ## Commands that are white-listed -- all others are blocked (Note: set to ".*" to allow all or "" to block all) DEFAULT_COMMAND_WHITELIST_REGEX = r"cat|date|dbus-send|echo|false|grep|head|ls|luakit|notify-send|ping|ping6|ps|pstree|sleep|tail|test|top|tree|xdotool|xset" @@ -200,7 +200,8 @@ def is_path_allowed(prog_path: str) -> bool: """Return True if binary is in an allowed directory.""" try: real_path = os.path.realpath(prog_path) - return any(real_path.startswith(allowed + "/") for allowed in ALLOWED_PATHS) + parent_dir = os.path.dirname(real_path) + return parent_dir in ALLOWED_PATHS except Exception: return False @@ -232,26 +233,25 @@ def is_command_allowed(command_str: str) -> tuple[bool, str]: #pylint: disable= for prog in programs: # 1. Program not found - prog_path = shutil.which(prog) or "" + prog_path = shutil.which(prog, path=ALLOWED_PATHS_STR) or "" if not prog_path: return False, f"Program not found: {prog}" - # 2. PATH restriction - if not is_path_allowed(prog_path): - return False, f"Program not in allowed paths: {prog_path}" - - # 3. Whitelist — Allow if whitelisted; deny if not - # Note whitelist overrides blacklist if set + # 2. Whitelist — Allow if whitelisted; deny if not + # Note whitelist overrides blacklist and PATH restriction if set if COMPILED_WHITELIST_REGEX is not None: if not COMPILED_WHITELIST_REGEX.fullmatch(prog): return False, f"Program not in Whitelist: {prog}" continue - # 4. Blacklist — Deny if blacklisted + # 3. PATH restriction + if not is_path_allowed(prog_path): + return False, f"Program not in allowed paths: {prog_path}" + + # 4. Blacklist - Deny if blacklisted if COMPILED_BLACKLIST_REGEX.fullmatch(prog): return False, f"Blacklisted program: {prog}" - return True, "Safe - Whitelisted" # --------------------------------------------------------------------------- # diff --git a/haoskiosk/run.sh b/haoskiosk/run.sh index d750f8c..25d2813 100755 --- a/haoskiosk/run.sh +++ b/haoskiosk/run.sh @@ -3,9 +3,9 @@ ################################################################################ # Add-on: HAOS Kiosk Display (haoskiosk) # File: run.sh -# Version: 1.3.0 +# Version: 1.3.1 # Copyright Jeff Kosowsky -# Date: February 2026 +# Date: April 2026 # # Code does the following: # - Import and sanity-check the following variables from HA/config.yaml diff --git a/haoskiosk/userconf.lua b/haoskiosk/userconf.lua index 7b06197..e0a4c61 100644 --- a/haoskiosk/userconf.lua +++ b/haoskiosk/userconf.lua @@ -1,9 +1,9 @@ --[=[ Add-on: HAOS Kiosk Display (haoskiosk) File: userconf.lua for HA minimal browser run on server -Version: 1.3.0 +Version: 1.3.1 Copyright Jeff Kosowsky -Date: February 2026 +Date: April 2026 Code does the following: - Sets browser window to fullscreen @@ -198,28 +198,28 @@ webview.add_signal("init", function(view) if consecutive_load_failures[v] < MAX_LOAD_FAILURES then msg.warn("Page load failed (%d/%d): %s", consecutive_load_failures[v], MAX_LOAD_FAILURES, v.uri or "unknown") else - local ffi = require("ffi") - ffi.cdef("int getpid(void);") - local luakit_pid = ffi.C.getpid() + local ffi = require("ffi") + ffi.cdef("int getpid(void);") + local luakit_pid = ffi.C.getpid() - local url = v.uri or ha_url + local url = v.uri or ha_url msg.error("RESTARTING Luakit (PID=%d) after %d page load failures: %s", luakit_pid, MAX_LOAD_FAILURES, url) - -- Send kill signal to current luakit pid, wait to complete kill, wait for dbus to fully disconnect, remove /tmp ipc file, launch new luakit, echo PID - local cmd = string.format([[ - (kill %d; + -- Send kill signal to current luakit pid, wait to complete kill, wait for dbus to fully disconnect, remove /tmp ipc file, launch new luakit, echo PID + local cmd = string.format([[ + (kill %d; while kill -0 %d 2>/dev/null; do sleep 0.1; done; sleep 2; - rm -f /tmp/luakit-%d-* 2>/dev/null; + rm -f /tmp/luakit-%d-* 2>/dev/null; luakit '%s' & echo "New Luakit PID=$!") & - ]], luakit_pid, luakit_pid, luakit_pid, url) - os.execute(cmd) + ]], luakit_pid, luakit_pid, luakit_pid, url) + os.execute(cmd) end elseif status ~= "finished" then return end -- Only proceed when the page is fully loaded - consecutive_load_failures[v] = 0 -- Reset consecutive load failures counter + consecutive_load_failures[v] = 0 -- Reset consecutive load failures counter - -- Print RSS memory consumption + -- Print RSS memory consumption local mem_file = io.open("/proc/self/statm", "r") local rss_mb = "NA" if mem_file then @@ -249,16 +249,25 @@ webview.add_signal("init", function(view) local js_auto_login = string.format([[ setTimeout(function() { try { - const usernameField = document.querySelector('input[autocomplete="username"]'); - const passwordField = document.querySelector('input[autocomplete="current-password"]'); + // 2026.4+ working version uses shadowRoot; preserve backward-compatibility + const haInputs = document.querySelectorAll('ha-input'); + const usernameField = haInputs[0]?.shadowRoot?.querySelector('wa-input')?.shadowRoot?.querySelector('input[autocomplete="username"]') + || document.querySelector('input[autocomplete="username"]'); + const passwordField = haInputs[1]?.shadowRoot?.querySelector('wa-input')?.shadowRoot?.querySelector('input[autocomplete="current-password"]') + || document.querySelector('input[autocomplete="current-password"]'); const haCheckbox = document.querySelector('ha-checkbox'); - const submitButton = document.querySelector('ha-button, mwc-button'); + const submitButton = document.querySelector('ha-button'); - if (usernameField && passwordField && submitButton) { + if (usernameField && passwordField) { // Note post 2026.4 requires 'change' event oo usernameField.value = '%s'; usernameField.dispatchEvent(new Event('input', { bubbles: true })); + usernameField.dispatchEvent(new Event('change', { bubbles: true })); + passwordField.value = '%s'; passwordField.dispatchEvent(new Event('input', { bubbles: true })); + passwordField.dispatchEvent(new Event('change', { bubbles: true })); + + console.log('Auto-login: fields filled + events dispatched'); } else { console.log('Auto-login failed: missing elements', { username: !!usernameField, @@ -272,7 +281,7 @@ webview.add_signal("init", function(view) haCheckbox.dispatchEvent(new Event('change', { bubbles: true })); } - submitButton.click(); + if (submitButton) submitButton.click(); } catch(e) { console.warn('Auto-login JS error:', e); } }, %d); ]], single_quote_escape(username), single_quote_escape(password), login_delay * 1000) diff --git a/haoskiosk/xorg.conf.default b/haoskiosk/xorg.conf.default index 24a962a..102345f 100644 --- a/haoskiosk/xorg.conf.default +++ b/haoskiosk/xorg.conf.default @@ -1,9 +1,9 @@ ################################################################################ # Add-on: HAOS Kiosk Display (haoskiosk) # File: xorg.conf -# Version: 1.3.0 +# Version: 1.3.1 # Copyright Jeff Kosowsky -# Date: February 2026 +# Date: April 2026 # # Minimal xorg.conf to work with OpenGL/DRI video and libinput mice & keyboards #