Skip to content

Remove raw JavaScript support and tighten CSP defaults - #337

Merged
oblomov-dev merged 5 commits into
mainfrom
claude/gallant-maxwell-6db9bx
Sep 25, 2026
Merged

oblomov-dev merged 5 commits into
mainfrom
claude/gallant-maxwell-6db9bx

Conversation

@oblomov-dev

Copy link
Copy Markdown
Member

This change removes the deprecated raw JavaScript feature from follow_up_action() and significantly hardens the default Content Security Policy.

Summary

The raw JavaScript injection pattern — where backends could send arbitrary <script> tags to execute in the browser — has been removed entirely. This was a self-inflicted XSS vector that bypassed UI5's output encoding and broke strict Content Security Policies. All use cases now have safer alternatives through built-in frontend events or custom controls.

The default CSP has been tightened to remove 'unsafe-eval' and 'unsafe-inline' for scripts, with the page's single inline script now allowed only by its SHA-256 hash. This closes a major attack surface while maintaining compatibility with modern UI5 releases.

Key Changes

  • Removed raw JavaScript support: follow_up_action() no longer accepts arbitrary JavaScript expressions. Unknown event names are silently ignored rather than executed.
  • Updated documentation: Removed extensive sections explaining the raw JS feature, its security risks, and workarounds. Replaced with guidance to use control_global, control_by_id, hash_back, or custom controls instead.
  • Hardened default CSP:
    • Removed 'unsafe-eval' and 'unsafe-inline' from script-src
    • Split script-src and style-src into explicit directives (no longer relying on default-src fallback)
    • Removed jsDelivr and cdnjs from default sources
    • Page's inline script is now allowed by SHA-256 hash only
    • style-src retains 'unsafe-inline' because UI5 renders style attributes
  • Updated deprecation table: Changed references from "raw expression" to appropriate alternatives (cs_event-hash_back, custom controls in z2ui5_ccc)
  • Fixed example code: Corrected wizard step example to use correct control ID (STEP1 instead of wiz)
  • Added ICON_POOL to global objects: Extended the whitelist of callable global objects in control_global
  • Improved search indexing: Fixed heading anchor generation to match VitePress's actual slugification algorithm, including handling of leading digits and special characters
  • Enhanced build robustness: Added error handling for missing release version, improved outline generation to avoid double-escaping HTML entities, and fixed print stylesheet to hide all interactive elements

Implementation Details

  • The CSP changes maintain backward compatibility for UI5 1.84+ (which loads modules without eval()). Only UI5 1.71–1.82 popups may need 'unsafe-eval' re-enabled via the user exit.
  • Custom JavaScript now ships as proper custom controls in the customer frontend BSP (z2ui5_ccc), with defined interfaces and reviewable code.
  • The search engine's heading anchor generation now correctly implements VitePress's slugify function, fixing 93 previously broken heading links in the documentation index.

https://claude.ai/code/session_01RBw7XcLuTa94iCUYZEtuh5

…d the fences the gates skipped

Bugs found by reading the site's scripts and theme against the built pages:

- build-site: the "On this page" outline escaped the heading text a second
  time, so "Build & Test" read "Build &amp; Test" and a heading with quotes
  or angle brackets spelt out its entities. The text out of the rendered
  heading is HTML already and is written back as it is.
- pages.mjs: heading anchors in the search index were a plain slug, not
  VitePress's - a heading starting with a digit, one carrying an arrow or a
  long dash, and the second heading of one name on a page all opened the
  page at the top. 93 of 737 heading links were dead. slugify is now the
  copy of @mdit-vue/shared's, with markdown-it-anchor's -1 suffix, and
  test/search.test.mjs pins the three shapes.
- build-site: the 404 page suggested /docs/<section>.html for a section's
  index page, which the build never writes; the suggestion keeps its
  `index` now.
- list-runnable, check-examples, check-api-names: a fence with an info
  string (```abap edit on the front door, ```abap [ABAP] in the quickstart,
  ```abap [lcl_help]) was invisible to all three, so the most-copied example
  on the site was never compiled and the runnable count read 63 against
  check:playground's 64. The regexes accept the info string; check:examples
  now compiles 70 classes, runnable lists 64.
- SiteNav.vue: the Playground item bound an href that was declared nowhere,
  and leave( ) sent every non-docs item to the catalogue - a press on
  Playground opened Samples in the VitePress build. It has its ref, is
  lifted like the other two, and leave( ) branches on the site.
- playground.js: a failed load of the embed loader was cached for the life
  of the page; the promise is forgotten on error so the next press retries.
- playground.mjs: the Run-button rules did not see CLASS-METHODS as a
  declaration (a class with one unimplemented got a button) and did not
  know popover_display as output.
- build-site: a Shiki line with a second class (highlighted, diff) fell out
  of the rebuilt listing; and a build with disagreeing release numbers now
  fails instead of printing "Version null" in every bar.
- README: the API gates judge against main, not the release the site
  names, as AGENTS.md has said since the pin was cut; the Run-button count
  is the measured one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RBw7XcLuTa94iCUYZEtuh5
…e name is -1

`## EML` under `# EML` is `eml-1` in the built page, because markdown-it-anchor
counts every heading. headings( ) now counts the title too and still indexes
only the second and third level; the test says so with the real shape.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RBw7XcLuTa94iCUYZEtuh5
…and the repo's own words brought up to date

- docs.css: the print block set a white page and kept the dark palette - the
  site switches schemes by attribute, which a browser's print emulation does
  not touch - so a reader who chose dark printed light grey on white. The
  tokens go back to light under @media print. The same block hid a
  `.run-panel` nothing writes; it hides the Run button, the running frame,
  its bar and the fold button now. The outline's two media queries both
  matched at exactly 1100px.
- style.css (the VitePress second opinion): the focus-ring selector list
  ended in a comma and ran into the line-counter rule, so nothing focused
  had a ring and everything focused had `counter-reset: line`.
- config.mjs: apple-touch-icon pointed at the .ico; the PNG the static
  build writes exists.
- build-site: the drawer's checkbox carries an accessible name once its
  label's text is hidden at desk width.
- index.md: rel="noopener" on the project's own links, as build-site says.
- README, SECURITY, CONTRIBUTING, package.json: fifteen gates, not eight;
  the README's list now names all fifteen; the first two sections of the
  bar are this site, not the middle two; the site is written by
  scripts/build-site.mjs, not VitePress; and SECURITY says what the build
  fetches and runs from the sibling deployment, which a security reader
  should know.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RBw7XcLuTa94iCUYZEtuh5
…), and a dozen sentences match the framework again

Read against z2ui5_if_client on main and the framework's source:

- Frontend, Deprecations, WebSocket: `follow_up_action( )` no longer runs a
  raw JavaScript expression - the client class says so in as many words
  ("there is no code form: a raw JavaScript val is not run") - yet the
  Frontend page carried a hundred lines on how to, the deprecations table
  named "a raw expression" as the replacement for three removed things, and
  the WebSocket page opened its socket with one. The section says what
  replaced the form (control_global, control_by_id, hash_back, a custom
  control in z2ui5_ccc), the table rows point there, and the WebSocket page
  names the frontend's own cc/Websocket, which its sample uses.
- Frontend: `ICON_POOL` is on the control_global whitelist; the method is
  `addCustomCurrencies` and it merges rather than adding one code;
  `setNextStep` is a WizardStep's method, so the example addresses the step.
- Troubleshooting: the type-coercion section said `abap_bool` arrives as
  "X" and a CheckBox needs a formatter - the Formatter and Binding pages,
  and the framework, say the boolean types travel as JSON true/false and the
  trap is a flag typed `c LENGTH 1`. The event-name section pointed at
  `cs_event-*` for the reader's own event names, which are frontend actions.
- Popup: `NEW z2ui5_cl_sample_confirm( mv_question = … )` on a class with no
  constructor; the attribute is set after NEW, as the other pages do.
- Walkthrough step 3 and the cheat sheet said the check_* methods are
  exclusive; `check_on_init( )` implies `check_on_navigated( )`, which is why
  init goes first. Step 7 said one step was left with three to go.
- Value Help: an `i_multiselect` / `ls_res-table` sentence left over from a
  removed built-in popup, and old typed-builder names for the suggestion
  aggregation. Demo Output named `_cc_plain_xml`, a method of the frozen
  builder, for what the code does with core:HTML.
- Themes: the Belize rows and the user exit example - removed in UI5 1.136,
  as the View page already says. Insight 22: the framework's CSRF gate is
  a check, not a token. Insight 12: `custom_js` exists only in the frozen
  package. Renaming: the exit lookup's literal is `Z2UI5_IF_UI5_EXIT`, with
  a fallback beside it - not the two the page named.
- i18n: a text symbol goes on a single-quoted literal, as the sample has it.
  Barcode: SAPUI5, not "the UI5 framework". About: ten runnable steps.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RBw7XcLuTa94iCUYZEtuh5
…setup pages match the code again

- Security: the default policy carried no jsDelivr and no cdnjs, no
  'unsafe-eval' and no 'unsafe-inline' for scripts for a while - the page
  showed the old tag, called 'unsafe-eval' part of the default, and told the
  reader to remove it by pasting a whole tag that re-added the CDN hosts and
  'unsafe-inline' to default-src: a reader who followed the hardening
  section ended up weaker than the default. The page now shows the shipped
  tag, explains the explicit script-src/style-src split and the script hash,
  edits a directive with REPLACE instead of replacing the tag, and keeps one
  section for the 1.71 to 1.82 popup case that switches 'unsafe-eval' on.
  The header table no longer lists cache-control among the exit's defaults
  (the handler decides caching per verb), and the CSRF sentence names the
  X-Forwarded-Host precedence and its switch. UI5 bootstrapping and the
  troubleshooting page linked the old section; they say the new thing.
- Bootstrap attributes: a row in t_add_config cannot override an attribute
  the framework writes first - a browser keeps the first of two - so the
  page stops promising that and drops frameOptions from its example.
- Developer Tools (debugging, hello world): the six tabs and the footer as
  they are. Hello world quotes the start page's actual error text.
- Renaming: src/02 holds five objects; the workflow is build-rename;
  custom-controls-customer lives in abap2UI5-addons. abaplint page: the
  rename config is rename.jsonc. Frontend extensibility: two ui5 yaml files.
- setup / user exits: set_config_http_get runs once more per response for
  its security headers. Performance: public means part of the model, not
  "not serialized". VS Code: the distribution setting has no default.
  S/4 Public Cloud: `cloud` is the classic runtime, `cloud_v2` the
  legacy-free preview. Walkthrough step 11: the mobile_start page is about
  SAP Mobile Start, not the home screen.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RBw7XcLuTa94iCUYZEtuh5
@oblomov-dev
oblomov-dev merged commit 1ac98cf into main Sep 25, 2026
1 check passed
@oblomov-dev
oblomov-dev deleted the claude/gallant-maxwell-6db9bx branch September 25, 2026 22:19
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.

2 participants