Skip to content

Add storefront unsafe-eval security check - #9059

Open
hwdavidward wants to merge 6 commits into
mainfrom
davidward/storefront-unsafe-eval-security-check
Open

hwdavidward wants to merge 6 commits into
mainfrom
davidward/storefront-unsafe-eval-security-check

Conversation

@hwdavidward

@hwdavidward hwdavidward commented Nov 2, 2026 •

Copy link
Copy Markdown

WHY are these changes introduced?

Storefront JavaScript that compiles strings at runtime can break under a CSP that omits unsafe-eval. Developers need findings that distinguish real calls from comments, strings, and unrelated methods, followed by a review of what actually ships to the storefront.

WHAT is this pull request doing?

Adds STOREFRONT_UNSAFE_EVAL to app security checks. Babel parses theme JavaScript/TypeScript and resolves lexical bindings and immutable aliases; the existing Liquid parser extracts executable script bodies and {% javascript %} tags, while skipping comments, inert script types, and inline bodies of external scripts. Candidates include string eval/timer calls and function constructors, with original line and column locations.

The deterministic check stays unresolved until the accompanying semantic check verifies production reachability, build configuration, dependencies, and lazy-loaded code. Current semantic results supersede candidates, so a verified fallback or unreachable call can be cleared. Missing production artifacts remain unresolved. Deterministic candidates carry the existing confidence: needs_review through stored results and review output; both blocking gates skip these generically. Findings without a confidence value retain their existing blocking behavior. Active agent findings still block under ordinary union or prefer-agent precedence, including stale results that fall back to union. The check itself defines the CSP proof requirements; literal code strings count without attacker-controlled input.

Validation: confidence persistence and public JSON projection, mixed-confidence blocking, union and stale-agent blocking regressions, scanner regression cases, the security engine suite and result/artifact integration tests, app lint and type checking, Knip for app, frozen-lockfile installation, CLI bundle build, and a native ESM scanner smoke test.

How to manually test your changes?

In a theme app extension, add an asset containing const run = window.eval; run("render()") and a Liquid block containing {% javascript %}setTimeout("render()", 1){% endjavascript %}. Run shopify app security check --print-instructions. Both calls should appear as candidates with source locations, and the check should remain unresolved pending semantic review. Run shopify app security review --json --blocking high to see confidence: "needs-review" on the candidates without failing the gate.

Replace the calls with a normal callback or quoted example text and rerun. Those candidates should disappear, while the semantic review requirement remains. Follow the generated instructions to inspect production usage and record the review with shopify app security record.

Release dependency

Publish the matching shopify.dev check page at app-security-checks/storefront-unsafe-eval before release.

Checklist

  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've considered possible documentation changes
  • I've considered analytics changes to measure impact
  • The change is user-facing — I've identified the correct bump type (patch for bug fixes · minor for new features · major for breaking changes) and added a changeset with pnpm changeset add

@hwdavidward
hwdavidward requested a review from a team as a code owner November 2, 2026 15:56
Copilot AI balanced review requested due to automatic review settings November 2, 2026 15:56
@github-actions github-actions Bot added the Area: @shopify/app @shopify/app package issues label Nov 2, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The unsafe-eval rules produce false positives for unrelated calls and non-executable content while missing dynamic constructors, template interpolations, and Liquid JavaScript blocks.

6 open issues
What changed in this PR

Adds STOREFRONT_UNSAFE_EVAL to the deterministic app-security checks, helping developers identify theme extension JavaScript that requires CSP unsafe-eval.

Changes:

  • Adds detection for eval-style execution, dynamic constructors, and string-based timers in theme assets and inline scripts.
  • Registers the check with severity, remediation, and a documentation link.
  • Adds scanner coverage, contract assertions, and a minor-release changeset.
File Description
packages/​app/​src/​cli/​services/​app-security-engine/​tests/​scan-contract.test.ts Tests theme applicability and end-to-end findings.
packages/​app/​src/​cli/​services/​app-security-engine/​tests/​deterministic-rules.test.ts Adds check-version assertions and detection examples.
packages/​app/​src/​cli/​services/​app-security-engine/​tests/​catalog.test.ts Adds the expected documentation slug.
packages/​app/​src/​cli/​services/​app-security-engine/​scanners/​index.ts Registers the theme-scoped deterministic check.
packages/​app/​src/​cli/​services/​app-security-engine/​rules/​js-rules.ts Implements detection, script extraction, and source masking.
packages/​app/​src/​cli/​services/​app-security-engine/​rules/​catalog.ts Defines severity, remediation, and documentation metadata.
.changeset/​storefront-unsafe-eval.md Announces the new check as a minor release.

Review effort: Balanced

@hwdavidward
hwdavidward requested a review from a team as a code owner November 2, 2026 18:41
@hwdavidward

Copy link
Copy Markdown
Author

Binks' finding about JavaScript MIME parameters is a false positive. The HTML standard, §4.12.1.2 explicitly says that scripts whose type attribute is text/javascript; charset=utf-8 are not evaluated.

The distinction is between an HTTP Content-Type header, where MIME parameters are generally ignored for JavaScript, and a <script type="..."> attribute, where they are significant. The scanner correctly skips the reported example. Stripping ; parameters here would incorrectly flag inert script content, so no code change is needed.

Recording this here because the Binks review has not been published as a GitHub thread to reply to or resolve.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: @shopify/app @shopify/app package issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants