JavaScript

Untyped and loosely-typed JS: build scripts, config, serverless handlers, legacy app code.

What this is

Untyped and loosely-typed JS — build scripts, config, serverless handlers, legacy app code. Its BLOCKER rules concentrate on injection surfaces (prototype pollution, eval-family dynamic code execution, shell injection) and async correctness (floating promises, unvalidated boundary input); the HIGH tier catches JS-specific footguns TypeScript would otherwise rule out by construction — loose equality coercion, unchecked index access, regexes vulnerable to ReDoS.

How to onboard it

A repository picks up this stack by onboarding on a profile that includes it. redline init detects the profile from what is in the repository, so in most cases this is automatic:

terminal
$ npx redlinegate init  # detects the profile
$ npx redlinegate init --profile fullstack-node  # or name one

11 profiles pull these rules in: fullstack-node, mobile-rn, service-express, service-node, tooling, web-angular, web-next, web-react, web-svelte, web-vanilla, web-vue.

Once onboarded, your files match this stack when they fit any of these globs:

  • **/*.js
  • **/*.jsx
  • **/*.mjs
  • **/*.cjs
  • **/*.vue
  • **/*.svelte

How to use it — 21 rules

Nothing to run. Once your profile includes JavaScript, redline init renders these rules into your repository's AI tooling and the reviewer applies them on every pull request. When a review comment cites one of these ids, this table is where to look up what it catches and why.

Rule idSeverityCatches
javascript/prototype-pollutionBLOCKERPrototype pollution sinks. Recursive merge/`Object.assign` over external input without rejecting `__proto__`, `constructor`, `prototype` keys.
javascript/dynamic-code-executionBLOCKER`eval`, `new Function`, `setTimeout("string")` on anything derived from external input.
javascript/floating-promisesBLOCKERFloating promises. Every promise is `await`ed, returned, or `.catch`-handled. An unhandled rejection terminates the process on modern Node.
javascript/loose-equality-coercionBLOCKER`==` against `null`/`undefined`/`0`/`''` where coercion changes the branch — use `===`, or `== null` only as a deliberate nullish check with a comment.
javascript/shared-module-state-mutationBLOCKERMutating a shared module-scope object/array used across requests or components.
javascript/unvalidated-boundary-inputBLOCKERMissing input validation at the boundary — `JSON.parse` of external payloads without try/catch and a size limit; `req.body` fields consumed without a schema check.
javascript/scattered-env-accessBLOCKERSecrets read ad hoc from `process.env` scattered across modules — one validated config module, fail fast at startup.
javascript/shell-injectionBLOCKER`child_process.exec` with an interpolated string — use `execFile`/`spawn` with argv.
javascript/var-in-new-codeHIGH`var` in new code — `const` by default, `let` when reassigned.
javascript/for-in-over-arraysHIGH`for...in` over arrays, or without `hasOwnProperty` filtering over objects.
javascript/unchecked-index-accessHIGHArray index access assumed defined (`arr[0].x`) without a length or nullish check.
javascript/unsafe-numeric-coercionHIGH`parseInt` without radix; `Number()`/`+` coercion of user input without `Number.isFinite`.
javascript/async-callback-ignored-promiseHIGH`async` callbacks passed to APIs that ignore the returned promise (`array.forEach`, most event emitters) — rejections vanish.
javascript/hand-rolled-deep-cloneHIGHDeep-equality or deep-clone hand-rolled instead of `structuredClone`/a maintained util.
javascript/unsafe-date-arithmeticHIGHDate arithmetic on `Date` objects across DST boundaries or with implicit local timezone.
javascript/try-catch-whole-functionHIGH`try`/`catch` around a whole function body instead of the failing boundary call.
javascript/redosHIGHRegexes built from external input (ReDoS), or catastrophic backtracking patterns (nested quantifiers over user-controlled length).
javascript/prefer-optional-chainingSUGGESTIONOptional chaining and nullish coalescing over `&&`/`||` ladders.
javascript/prefer-named-exportsSUGGESTIONNamed exports over default exports for tree-shaking and refactor safety.
javascript/jsdoc-on-exportsSUGGESTIONJSDoc types on exported functions in files that will not be converted to TS soon.
javascript/array-at-negative-indexSUGGESTION`Array.prototype.at(-1)` over `arr[arr.length - 1]`.

Expected output

A finding from this file, and every finding Redline produces, opens with a machine-readable first line — severity, then the rule id in brackets, then the problem in one line:

a finding from this file
Redline/BLOCKER [javascript/prototype-pollution]: <one-line problem>

Ids are aggregated per rule, which is how the organisation finds out which rules earn their place and which only generate noise — so a finding without a valid id cannot be measured and counts as untagged. Of the 21 rules here, 8 BLOCKER, 9 HIGH and 4 SUGGESTION. Only a BLOCKER must not merge; a SUGGESTION may be dismissed without justification, and is never upgraded to get attention.

If a rule here fires constantly on code your team has deliberately decided to allow, that is the signal to raise with the standards owner — the rule id is what makes that conversation measurable — not to argue it away comment by comment.

How to edit it

Rules are edited in standards/stacks/javascript.md and nowhere else. The rendered copies in AGENTS.md, .github/copilot-instructions.md and .github/instructions/ are generated and are overwritten by the next render. A change here propagates to every onboarded repository as a pull request, so treat it as a production change.

  1. Edit the markdownstandards/ is the only place a human edits a rule. Everything under AGENTS.md, .github/copilot-instructions.md and .github/instructions/ is rendered from it and is overwritten by the next render.
  2. node scripts/assign-rule-ids.mjsAssigns a permanent <stack>/<slug> id to any new rule bullet and rewrites the file in place. Do not invent an id by hand. CI runs the same script with --check and fails if a rule is missing one.
  3. node scripts/render-self.mjsRe-renders this repository's own artifacts from the edited source. CI runs it with --check, so stale checked-in output fails the build.
  4. Bump standards/manifest.json → versionRequired in the same pull request as the rule change. Sync pull requests quote the version, so a repository's rendered artifacts always name where they came from.
  5. Add a CHANGELOG.md entryAlso in the same pull request. A standards change with no measurement is an opinion — record the seed score alongside it.
  6. node scripts/validate.mjsThe bundle self-check CI runs: manifest integrity, well-formed rule ids, the severity output contract surviving your edit, glob portability.

Rule ids are permanent. Rewording a rule is fine and keeps its id; renaming or removing an id orphans every historical telemetry record that cited it.

The full file

standards/stacks/javascript.md · 43 lines · 3.2 KB
# JavaScript Review Rules

Applies to untyped and loosely-typed JS (`.js`, `.jsx`, `.mjs`, `.cjs`), including build
scripts, config files, serverless handlers, and legacy app code. TypeScript rules in the
core standard apply wherever the file is typed.

## BLOCKER — request changes

- `javascript/prototype-pollution` — **Prototype pollution sinks.** Recursive merge/`Object.assign` over external input
  without rejecting `__proto__`, `constructor`, `prototype` keys.
- `javascript/dynamic-code-execution` — **`eval`, `new Function`, `setTimeout("string")`** on anything derived from external input.
- `javascript/floating-promises` — **Floating promises.** Every promise is `await`ed, returned, or `.catch`-handled. An
  unhandled rejection terminates the process on modern Node.
- `javascript/loose-equality-coercion` — **`==` against `null`/`undefined`/`0`/`''` where coercion changes the branch** — use
  `===`, or `== null` only as a deliberate nullish check with a comment.
- `javascript/shared-module-state-mutation` — **Mutating a shared module-scope object/array** used across requests or components.
- `javascript/unvalidated-boundary-input` — **Missing input validation at the boundary** — `JSON.parse` of external payloads without
  try/catch and a size limit; `req.body` fields consumed without a schema check.
- `javascript/scattered-env-access` — **Secrets read ad hoc from `process.env` scattered across modules** — one validated
  config module, fail fast at startup.
- `javascript/shell-injection` — **`child_process.exec` with an interpolated string** — use `execFile`/`spawn` with argv.

## HIGH

- `javascript/var-in-new-code` — `var` in new code — `const` by default, `let` when reassigned.
- `javascript/for-in-over-arrays` — `for...in` over arrays, or without `hasOwnProperty` filtering over objects.
- `javascript/unchecked-index-access` — Array index access assumed defined (`arr[0].x`) without a length or nullish check.
- `javascript/unsafe-numeric-coercion` — `parseInt` without radix; `Number()`/`+` coercion of user input without `Number.isFinite`.
- `javascript/async-callback-ignored-promise` — `async` callbacks passed to APIs that ignore the returned promise (`array.forEach`,
  most event emitters) — rejections vanish.
- `javascript/hand-rolled-deep-clone` — Deep-equality or deep-clone hand-rolled instead of `structuredClone`/a maintained util.
- `javascript/unsafe-date-arithmetic` — Date arithmetic on `Date` objects across DST boundaries or with implicit local timezone.
- `javascript/try-catch-whole-function` — `try`/`catch` around a whole function body instead of the failing boundary call.
- `javascript/redos` — Regexes built from external input (ReDoS), or catastrophic backtracking patterns
  (nested quantifiers over user-controlled length).

## SUGGESTION

- `javascript/prefer-optional-chaining` — Optional chaining and nullish coalescing over `&&`/`||` ladders.
- `javascript/prefer-named-exports` — Named exports over default exports for tree-shaking and refactor safety.
- `javascript/jsdoc-on-exports` — JSDoc types on exported functions in files that will not be converted to TS soon.
- `javascript/array-at-negative-index` — `Array.prototype.at(-1)` over `arr[arr.length - 1]`.