fix(security): apply os.path.basename sanitizer + fix Unicode escapes + remaining review items

## CodeQL path-injection (pages_v3.py)
Switch from Path.name to os.path.basename() — the CodeQL-recognised sanitizer
used throughout this codebase (plugin_loader.py lines 74, 157).  All path
operations now use safe_id/safe_fn derived from os.path.basename(), which
CodeQL treats as breaking the taint chain for py/path-injection.

## XSS Unicode escaping (pages_v3.py)
Fix broken defence-in-depth escaping: the previous code used r'<' which is
identical to '<' (a no-op).  Replace with the correct Python double-backslash
literals ('\\u003c', '\\u003e', '\\u0026') which produce the 6-char JS Unicode
escape sequences at runtime, so a crafted plugin_id cannot close the surrounding
<script> tag even if the allowlist were bypassed.

## Nullable type normalization (plugin_config.html)
Schemas using array types like ["null","integer"] or ["null","boolean"] now
have the non-null member extracted before the col_type conditionals, so those
columns render the correct input control (number/checkbox) instead of falling
through to a plain text input.

## file-upload-single.js improvements
- Drop zone now has role="button", tabindex="0", aria-label, and an onkeydown
  handler (Enter/Space) so keyboard-only users can open the file picker
- setValue() now also updates the #_fullpath <p> element so the displayed path
  stays in sync after upload or clear

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-05-30 21:22:39 -04:00
parent e873632f95
commit 4be334c678
3 changed files with 53 additions and 23 deletions

View File

@@ -504,9 +504,14 @@
{% for col_name in display_columns %}
{% set col_def = item_properties.get(col_name, {}) %}
{% set col_title = col_def.get('title', col_name|replace('_', ' ')|title) %}
{% set col_xwidget = col_def.get('x-widget', '') %}
{% set col_xwidget = col_def.get('x-widget') or col_def.get('x_widget', '') %}
{% set col_enum = col_def.get('enum', []) %}
{% set col_ctype = col_def.get('type', 'string') %}
{% set _raw_ctype = col_def.get('type', 'string') %}
{% if _raw_ctype is iterable and _raw_ctype is not string %}
{% set col_ctype = (_raw_ctype | reject('equalto','null') | list | first) or 'string' %}
{% else %}
{% set col_ctype = _raw_ctype or 'string' %}
{% endif %}
{% if col_xwidget == 'date-picker' %}{% set col_min_w = '140px' %}
{% elif col_xwidget == 'time-picker' %}{% set col_min_w = '115px' %}
{% elif col_xwidget == 'file-upload-single' %}{% set col_min_w = '200px' %}
@@ -525,7 +530,13 @@
<tr class="array-table-row" data-index="{{ item_index }}">
{% for col_name in display_columns %}
{% set col_def = item_properties.get(col_name, {}) %}
{% set col_type = col_def.get('type', 'string') %}
{# Normalize nullable types e.g. ["null","integer"] → "integer" #}
{% set _raw_type = col_def.get('type', 'string') %}
{% if _raw_type is iterable and _raw_type is not string %}
{% set col_type = (_raw_type | reject('equalto','null') | list | first) or 'string' %}
{% else %}
{% set col_type = _raw_type or 'string' %}
{% endif %}
{% set col_xwidget = col_def.get('x-widget') or col_def.get('x_widget', '') %}
{% set col_enum = col_def.get('enum', []) %}
{% set col_value = item.get(col_name, col_def.get('default', '')) %}