Skip to content

Security fixes 20260409#1

Merged
buggedcom merged 14 commits into
mainfrom
security-fixes-20260409
Apr 9, 2026
Merged

Security fixes 20260409#1
buggedcom merged 14 commits into
mainfrom
security-fixes-20260409

Conversation

@buggedcom

@buggedcom buggedcom commented Apr 9, 2026

Copy link
Copy Markdown
Owner

Summary

  • Replace all dynamic innerHTML assignments with Lit html/render() throughout chart, card, and component code, eliminating XSS vectors in tooltip, crosshair, legend, axis, and dialog rendering
  • Migrate ha-stubs.ts test helpers from innerHTML + manual HTML escaping to adoptedStyleSheets + Lit render, consistent with the production code approach
  • Fix Cannot read properties of null (reading 'nextSibling') crash when the crosshair moves into a no-data region — caused by innerHTML = "" destroying Lit's internal marker nodes on containers that are also managed by render()
  • Patch lodash prototype-pollution CVE, add least-privilege CI permissions, exclude the generated bundle from CodeQL, and add a dev-sync build ID badge for easier hot-reload verification

Related issues

https://github.com/buggedcom/HASS-Data-Points/security/code-scanning/1
https://github.com/buggedcom/HASS-Data-Points/security/code-scanning/2
https://github.com/buggedcom/HASS-Data-Points/security/code-scanning/3
https://github.com/buggedcom/HASS-Data-Points/security/code-scanning/4
https://github.com/buggedcom/HASS-Data-Points/security/code-scanning/5
https://github.com/buggedcom/HASS-Data-Points/security/code-scanning/6
https://github.com/buggedcom/HASS-Data-Points/security/code-scanning/7
https://github.com/buggedcom/HASS-Data-Points/security/code-scanning/8
https://github.com/buggedcom/HASS-Data-Points/security/code-scanning/9
https://github.com/buggedcom/HASS-Data-Points/security/code-scanning/10

Testing

  • Python unit tests pass (pytest tests/)
  • Frontend unit tests pass (pnpm test)
  • Manually tested in Home Assistant
  • New tests added to cover the change
    =

Checklist

  • Commit messages follow Conventional Commits
  • Bundle rebuilt (pnpm build) and hass-datapoints-cards.js is committed
  • No new TypeScript type errors (pnpm lint:types)
  • No new lint warnings (pnpm lint)

…olicy

- Add .npmpackagejsonlintrc.json enforcing absolute version pins for all
  dependencies and devDependencies
- Add renovate minimumReleaseAge and trustPolicyExclude for semver@5/6
  (old post-release versions that cannot yet be upgraded)
- Wire npm-package-json-lint into pre-commit (staged check) and pre-push
  (full check) hooks
- Update README to mention package.json version linting in hook descriptions
- Add vol.Match / vol.Length / vol.In / vol.Range validators to
  ws_update_event, ws_get_anomalies, and ws_clear_cache schemas, capping
  free-text fields and constraining enum/duration/offset inputs
- Validate entity_id with cv.entity_id on ws_get_anomalies and
  ws_clear_cache (already applied to ws_get_history)
- Gate ws_clear_cache behind _require_admin; only admins may flush cache
- Mirror the same icon/color/message length + format guards into
  SERVICE_RECORD_SCHEMA in __init__.py
- Escape single-quotes in esc() helper (format.ts) via '
- Add voluptuous to requirements_dev.txt and lift the mock stub from
  conftest so validation-constant unit tests run against the real library
- Add 22 new validator unit tests (DescribeValidationConstants) covering
  regex patterns, In/Range/Length validators, and the non-admin cache guard
eslint-config-airbnb-base no longer resolves after the dependency update
to eslint-config-airbnb-extended@3 and ESLint 10. Migrate the flat config:

- Replace compat.extends("airbnb-base") with configs.base.recommended from
  eslint-config-airbnb-extended; register plugins.stylistic and
  plugins.importX which the config uses but does not bundle
- Switch import/ rule overrides to import-x/ to match the new plugin
- Keep tseslint.configs.recommended for non-type-aware TypeScript rules
  (no parserOptions.project required)
- Add includeIgnoreFile(.gitignore) via @eslint/compat
- Remove unused FlatCompat import
- Fix no-useless-assignment in dev-tool.ts: initial values for message,
  icon and color are always overwritten before use; drop them so the rule
  is satisfied
- Auto-fix import-x/no-useless-path-segments and remove stale
  eslint-disable-next-line comments for wc/guard-super-call that were
  no longer needed under the new config
- Bump package.json to exact version pins and add @eslint/compat
- @eslint/eslintrc: only needed for FlatCompat/compat.extends(); both
  removed from eslint.config.mjs in the airbnb-extended migration
…in bundle

After Typescript updates the build broke. Without experimentalDecorators:true, TypeScript emits TC39 standard
decorator syntax including the accessor keyword, which esbuild passes
through untransformed into the bundle. The accessor keyword is only
supported in Chrome 112+ / Firefox 119+ / Safari 16.4+ and causes:

  Uncaught SyntaxError: Invalid or unexpected token

in older or embedded browser environments (e.g. some HA companion builds).

With experimentalDecorators:true TypeScript compiles accessor fields to
getter/setter pairs and decorators to __decorate() calls, producing output
that is fully compatible with the es2021 build target already set in
vite.config.mjs. Lit 3 supports both modes transparently.
- Co-locate Message + Date/time in a two-column row; co-locate Icon +
  Color in a second two-column row; both rows collapse to single column
  below 600px
- Replace the hidden ha-selector behind an "Add target" toggle button
  with an always-visible target selector — no extra click required
- Fix target accumulation: each value-changed event now merges into the
  running _target state via mergeTargetSelections, so selecting a second
  entity/device/area no longer replaces the first
- Remove chip-row's internal label (outer form field owns the label now)
- Widen sidebar overlay to 95vw on screens ≤545px
- Update tests: chip-row label tests reflect new ownership model; dialog
  tests cover layout rows, always-visible selector, and accumulation
@buggedcom buggedcom self-assigned this Apr 9, 2026
@buggedcom buggedcom force-pushed the security-fixes-20260409 branch 3 times, most recently from e3ec872 to 780d1ae Compare April 9, 2026 14:56
…oints

Authenticated non-admin users could previously request history, anomaly
detection, or filtered events for any entity in the system — including
ones hidden from their dashboard.  This closes that hole:

websocket_api.py
- Add _can_read_entity(user, entity_id) helper: admin users always pass;
  non-admin users are checked against their HA permission policy via
  user.permissions.check_entity(entity_id, POLICY_READ)
- ws_get_events: strip forbidden entity_ids from the filter list before
  hitting the store (unfiltered queries remain unchanged)
- ws_get_history: reject with "unauthorized" if caller cannot read the
  requested entity
- ws_get_anomalies: same check for both primary and comparison entity

__init__.py
- handle_record service: for user-initiated calls (user_id present),
  filter entity_ids to only those the caller is permitted to read;
  automation/system calls (no user_id) bypass the check

conftest.py / test_websocket_api.py
- Stub homeassistant.auth.permissions.const with a real POLICY_READ value
- Extend _make_connection to configure entity-level permission mocks
- Add DescribeCanReadEntity, DescribeWsGetEventsPermissions,
  DescribeWsGetHistoryPermissions, DescribeWsGetAnomaliesPermissions
  (15 new tests)

The ha-selector target picker in the frontend already scopes its entity
list to the authenticated user's permissions natively.
Extend bump-version.sh to accept a bump type positional argument
(patch | minor | major, defaulting to patch for backwards compatibility).
Wire up three pnpm scripts so releases can be cut with:

  pnpm version:patch   # 0.4.1 → 0.4.2
  pnpm version:minor   # 0.4.1 → 0.5.0
  pnpm version:major   # 0.4.1 → 1.0.0

All three support --dry-run passthrough.

Additionally bumps the version up to v0.4.2
…HSA)

lodash <4.18.0 is vulnerable to Prototype Pollution via an array-path
bypass in _.unset and _.omit.  The fixed version is 4.18.0.

lodash 4.17.23 was pulled in transitively by @storybook/test 8.6.15.
Adding a pnpm.overrides entry forces all dependents to resolve to
^4.18.0 (currently 4.18.1).

Resolves: buggedcom/HASS-Data-Points Dependabot alert #1 #2
…odeQL)

Addresses actions/missing-workflow-permissions (CodeQL rule) across
ci.yml and commit-lint.yml.

ci.yml
- Add top-level `permissions: contents: read` so every job without its
  own block is locked to read-only rather than inheriting the
  organisation/repository default (which may be read-write on repos
  created before Feb 2023).
- Add `contents: read` to the build job's existing block; a job-level
  block overrides the workflow-level block entirely, so omitting it
  would leave checkout with none.
- The build job retains `id-token: write` and `attestations: write`
  for OIDC-based build provenance attestation.

commit-lint.yml
- Add job-level `permissions: contents: read, pull-requests: read`;
  wagoid/commitlint-github-action requires both to fetch the PR commit
  list via the GitHub API.
hass-datapoints-cards.js is a Vite build artifact.  Scanning it causes
false positives — most notably lit-html's internal /-->/ template-parser
regex being flagged as a bad HTML comment filter (CodeQL rule
js/bad-regexp-for-html-filtering).  The regex is used to locate comment
boundaries in template strings, not to sanitise user input, so the
finding has no actionable remediation path.

Adds .github/codeql/codeql-config.yml with a paths-ignore entry for the
bundle.  The TypeScript sources under src/ are still analysed.
Replace raw innerHTML concatenation in HaFieldStub._render() with
Lit's html tagged template + render(), which auto-escapes all text
interpolations into DOM text nodes rather than raw HTML. Styles are
injected once via adoptedStyleSheets to sidestep Lit's restriction on
interpolating inside <style> elements.

Eliminates the CodeQL "DOM text reinterpreted as HTML" (XSS) finding
for ha-stubs.ts:332 (label and value attributes).
@buggedcom buggedcom force-pushed the security-fixes-20260409 branch 2 times, most recently from 283d0d5 to 248a24f Compare April 9, 2026 15:10
… in stubs

Apply the same adoptedStyleSheets + Lit html/render pattern to all
remaining dynamic innerHTML calls in ha-stubs.ts:

- HaIconStub: extract HA_ICON_STYLES constant, inject once via
  adoptedStyleSheets in constructor, render SVG via Lit html template
  (path attribute set via setAttribute, not string concatenation)
- ha-svg-icon: inherit base class stylesheet, rewrite _render() with
  Lit, eliminating the public path property injection vector
- ha-icon-button: extract HA_ICON_BUTTON_STYLES, use adoptedStyleSheets
  plus DOM createElement for the slot instead of innerHTML
- ha-switch: extract HA_SWITCH_STYLES, inject once via adoptedStyleSheets
  in constructor, rewrite _renderSwitch() with Lit html template so the
  dynamic CSS class uses a safe text binding not string concatenation

Fix annotation-dialog.ts CSS injection: defaultColor was interpolated
directly into a style block where esc() only handles HTML entities
not CSS. Remove the background rule as bindFields() already calls
syncColor() which sets colorPreview.style.background from the safe
input type color element.
Replace all uses of the custom esc() HTML-escaping function with Lit's
html tagged template + render(), which auto-escapes every ${} binding
into a DOM text node or setAttribute() call — no manual escaping needed.

Files changed:
- chart-interaction.ts: buildTooltipRelatedChips now returns
  TemplateResult|null; buildAnnotationTooltip, showTooltip (ttEntities),
  ttSeries multi-row block, and showLineChartCrosshair points all use
  render(html`...`, el) — removes 8 esc() calls
- chart-dom.ts: buildAxisMarkup returns TemplateResult; leftEl/rightEl
  use render(); axisTextStyle drops esc() — removes 3 esc() calls
- history-chart.ts: overlayEl, _renderLegend (+ removes querySelectorAll
  event-binding loop in favour of inline @click), iconEl, and the
  labelsHtml axis loop all migrated — removes 6 esc() calls
- sensor-chart.ts: annotation icon el migrated — removes 1 esc() call
- ha-components.ts: confirmDestructiveAction dialog uses render() with
  inline @click handlers; finish() moved before render() — removes 3
  esc() calls
- dev-tool.ts: _render() uses adoptedStyleSheets + render() — removes
  1 esc() call
- history-targets.ts: removed esc() wrappers from inside Lit html
  templates (were causing double-escaping) — removes 2 esc() calls

Test fix: chart-interaction.spec.ts renders the TemplateResult from
buildTooltipRelatedChips into a container and checks textContent.

esc() remains in format.ts (annotation-dialog.ts still uses it for
HTML attribute values in a non-Lit template) and keeps its own tests.
Generates a random 4-digit ID in dev-sync.sh (VITE_DEV_SYNC_ID) that
Vite bakes into the bundle at sync time. register.ts reads the env var
and, when present, appends an orange badge with the ID to the
console.groupCollapsed output — making it easy to confirm which
dev-sync build is active in the browser console. Regular builds never
set the var, so production bundles carry no sync ID.
@buggedcom buggedcom force-pushed the security-fixes-20260409 branch from 248a24f to 73520ef Compare April 9, 2026 15:12
@buggedcom buggedcom merged commit 03a2f4d into main Apr 9, 2026
19 checks passed
buggedcom added a commit that referenced this pull request Apr 9, 2026
…HSA)

lodash <4.18.0 is vulnerable to Prototype Pollution via an array-path
bypass in _.unset and _.omit.  The fixed version is 4.18.0.

lodash 4.17.23 was pulled in transitively by @storybook/test 8.6.15.
Adding a pnpm.overrides entry forces all dependents to resolve to
^4.18.0 (currently 4.18.1).

Resolves: buggedcom/HASS-Data-Points Dependabot alert #1 #2
@buggedcom buggedcom deleted the security-fixes-20260409 branch April 9, 2026 15:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant