Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,17 +129,19 @@ jobs:
# the scan is lenient — planting one `new Function(` in dist makes it
# exit 1.
#
# SCOPE, STATED SO A GREEN RUN IS NOT READ AS MORE THAN IT IS. This
# THE TARGET IS THE PUBLISHED SURFACE, NOT `dist` (DS §2, v2.71.0). This
# package's `files` list ships BOTH `src` and `dist`, and its default
# export is `"." : "./src/index.js"` — so a consumer doing
# `import … from 'markdownwizard-tools'` gets the SOURCE, and only the
# `./iife` subpath gets what this scan covers. src/ scans clean today
# (all 8 files, exit 0), so nothing is hiding there; whether §2's target
# should widen for a package whose default export is source is a
# standards question, raised in tgwab-standards#173 rather than decided
# here. Widening this to `dist src` is a one-word change and is green.
# export is `"." : "./src/index.js"` — so a consumer writing
# `import … from 'markdownwizard-tools'` gets the SOURCE, and `dist`
# alone covered only the `./iife` subpath. That was a control aimed at
# the path most consumers do not use.
#
# `scripts/no-eval.sh` is re-vendored in the same change because it HAD
# TO BE: the previous copy read only "$1", so `dist src` would have
# scanned dist and dropped src silently — the widened gate would have
# looked widened and covered exactly what it did before.
- name: No runtime code generation in built output (§2)
run: ./scripts/no-eval.sh dist
run: ./scripts/no-eval.sh dist src

# Full tree, deliberately not --omit=dev (DS §15): the dev half is the
# build toolchain, and what it writes into dist/ is what ships. Excluding it
Expand Down
34 changes: 30 additions & 4 deletions scripts/no-eval.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,50 @@
# The CSP MUST NOT be loosened instead.
set -euo pipefail

TARGET="${1:-dist}"
# TARGETS: every path this package actually ships (DS §2). Defaults to `dist`,
# which is what every caller passed before this took more than one.
#
# TWO DEFECTS THIS REPLACED, BOTH OF WHICH REPORTED GREEN OVER NOTHING:
#
# 1. It read only "$1". `no-eval.sh dist src` set TARGET=dist and DROPPED src
# silently — measured 2026-09-07 by planting `new Function(` in src/ and
# running all three forms: `src` alone exits 1, `dist` alone exits 0, and
# `dist src` printed "eval-free: OK (dist)" and exited 0. A gate widened
# that way looks widened, reports green, and scans exactly what it did
# before.
#
# 2. A target that did not exist reported "eval-free: OK" and exited 0,
# because the `|| true` below swallows grep's exit-2. So a build that never
# ran read as a clean scan. Latent rather than live — all 11 adopters run
# this after a build step — but it is the same failure class as (1), and a
# missing target is now a hard error.
if [ "$#" -eq 0 ]; then set -- dist; fi

missing=()
for t in "$@"; do [ -e "$t" ] || missing+=("$t"); done
if [ "${#missing[@]}" -gt 0 ]; then
printf '::error::no-eval target not found: %s\n' "${missing[*]}"
printf 'A missing target is a failure, not a pass: it used to print "eval-free: OK" and exit 0, so a build that never ran read as a clean scan.\n'
exit 1
fi

ALLOW=".eval-allowlist"

PATTERN='(^|[^A-Za-z0-9_$])eval[[:space:]]*\(|new[[:space:]]+Function[[:space:]]*\(|(^|[^A-Za-z0-9_$.])Function[[:space:]]*\([[:space:]]*["'"'"'`]|set(Timeout|Interval|Immediate)[[:space:]]*\([[:space:]]*["'"'"'`]'

hits="$(grep -nEr "$PATTERN" \
--include='*.js' --include='*.mjs' --include='*.cjs' --include='*.html' \
"$TARGET" || true)"
"$@" || true)"

if [ -s "$ALLOW" ]; then
hits="$(printf '%s\n' "$hits" \
| grep -vFf <(grep -v '^[[:space:]]*#' "$ALLOW" | grep -v '^[[:space:]]*$') || true)"
fi

if [ -n "$hits" ]; then
printf '::error::runtime code-generation found in %s (CSP has no %s)\n' "$TARGET" "'unsafe-eval'"
printf '::error::runtime code-generation found in %s (CSP has no %s)\n' "$*" "'unsafe-eval'"
printf '%s\n' "$hits"
exit 1
fi

printf 'eval-free: OK (%s)\n' "$TARGET"
printf 'eval-free: OK (%s)\n' "$*"
Loading