ReviewOS

stacks/stx

strict: failOnViolation replaces the page with an error string at HTTP 200 and logs nothing — #1854's symptom is back, minus the warning

#2097
Closed glennmichael123 opened this 8 days ago ·
8 days ago

Reproduced on @stacksjs/stx 0.2.605. #1854 described this and is closed COMPLETED; the failure is present again, and one detail is now worse: with failOnViolation: true there is no log line at all, so nothing points at the cause.

The repro

A single prohibited call on a public marketing page, strict: { enabled: true, failOnViolation: true }:

@extends('layouts/marketing')
@section('content')
  <script>
    const probe = document.getElementById('nope')
  </script>
  ...
baselinewith the violation
HTTP200200
bytes383299396
<h1>presentgone
</footer>presentgone
dev server log—nothing

The <body> the visitor receives:

[Error processing layout: [STX] DOM API violation in resources/views/features/scheduling.stx
(composed into resources/layouts/marketing.stx): ✗ document.getElementById() is prohibited
(script line 2) → Use useRef("name") instead ...]

So the whole page is replaced by an error string that leaks source paths and the project's directory layout, at HTTP 200, with no server-side signal.

Why nothing is logged — the part that is new

dist/script-validation.js ends:

if (strictConfig.failOnViolation)
  throw Error(baseMessage);
else
  console.warn(baseMessage);

It is either/or. Turning the flag on does not add a throw to the warning, it replaces the warning. A/B on two cold-started dev servers, same planted violation, nothing else changed:

failOnViolationserver logHTTP
false[STX] DOM API violation in resources/views/blog.stx …200
true(silent)200

true is therefore strictly less observable than false — the opposite of what the flag is for.

Why it is swallowed

dist/process.js:416-425:

} catch (error) {
  if (error instanceof StxValidationError)
    throw error;                                    // 417
  const enhancedError = new StxRuntimeError(`Template processing failed: ${msg}`, …);
  errorLogger.log(enhancedError, { filePath, context });
  devHelpers.logDetailedError(enhancedError, { … });
  if (options.debug)
    throw enhancedError;
  return errorRecovery.createFallbackContent("Template Processing", enhancedError);
}

Line 417 is the intended escape hatch and it never fires, because validateClientScript throws a plain Error. The violation falls through to createFallbackContent, which is what the visitor gets.

Confirmed by calling the renderer directly, outside any server:

const out = await processDirectives(src, {}, 'scratch/probe.stx',
  { strict: { enabled: true, failOnViolation: true } }, new Set())
// NO THROW — processDirectives returned 332 chars

The class for this already exists and is never used

StxValidationError carries isFatal = true and defaults its code to DOM_API_VIOLATION — it was written for exactly this path:

class StxValidationError extends StxError {
  isFatal = true
  constructor(…) { super(e, "stx_VALIDATION_ERROR", …, a || t.DOM_API_VIOLATION); … }
}

Every occurrence of it in the shipped package:

dist/errors/types.js        class StxValidationError extends StxError {
dist/errors/types.d.ts      declaration
dist/errors/index.js        re-export
dist/errors/index.d.ts      re-export
dist/process.js:24          import { … StxValidationError } from "./error-handling.js"
dist/process.js:417         if (error instanceof StxValidationError)

grep -rhoE "new (StxValidationError|hr)\(" dist/*.js → no matches. It is defined, exported, imported and type-checked against, and never constructed anywhere. The rethrow branch at process.js:417 is dead code.

Ask

  1. validateClientScript should throw StxValidationError, not Error, so process.js:417 rethrows it and failOnViolation is actually fatal. This looks like the one-line fix the branch was written for.
  2. Warn and throw, rather than either/or. A fatal error whose message only reaches the response body is not a diagnostic — log it server-side on both settings.
  3. A render failure must not return 200. #1854 asked for this and it still does; createFallbackContent output should carry a 500 so health gates and crawlers see it.
  4. Never put createFallbackContent's text in a production response — it contains absolute-ish source paths and the project's directory structure.
  5. A regression test: a page with one prohibited call, failOnViolation: true, asserting the render throws rather than resolving.

Note for anyone hitting this now

allowPatterns is rule-global (#2049), but the inline form is per-line and per-rule and does work in 0.2.605:

// stx-strict-ignore-next-line location.assign
location.assign('/login')

The directive targets the next line only — a comment between it and the statement silently shifts the target and the violation still fires.

Related: #1854 (same swallow, closed), #2049 (allowPatterns is rule-global), #1911 (comment false positives blocking failOnViolation).

8 days ago

Fixed on main in d452c62 and ecbc95f.

Fatal strict validation now warns and throws StxValidationError through page, layout, component and include rendering. Both HTTP hosts return a generic 500 rather than leaking source paths; repeated and router-fragment requests are covered.

Regression tests: packages/stx/test/strict-fatal-render.test.ts and packages/bun-plugin/test/serve-strict-failure.test.ts. Local verification: 1,312 component/native/include/strict regressions passed, plus 16 HTTP/status checks. The new HTTP regression also passed in CI run 39454919060.

Version: these fixes are included in the v0.2.609 source tag (5504a6b). At verification npm latest was still 0.2.607, so the tag does not yet establish published availability.

CI caveat: the full run had two separate failures, a stale MapPolygon inventory count (corrected in db758d2) and a router scroll-restoration test (16/16 tests pass in its local rerun). Full CI rerun: https://github.com/stacksjs/stx/actions/runs/39456525701 — still pending; this comment does not claim all CI is green.

Sign in to comment on this issue.