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:
$ 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 id | Severity | Catches |
|---|---|---|
javascript/prototype-pollution | BLOCKER | Prototype pollution sinks. Recursive merge/`Object.assign` over external input without rejecting `__proto__`, `constructor`, `prototype` keys. |
javascript/dynamic-code-execution | BLOCKER | `eval`, `new Function`, `setTimeout("string")` on anything derived from external input. |
javascript/floating-promises | BLOCKER | Floating promises. Every promise is `await`ed, returned, or `.catch`-handled. An unhandled rejection terminates the process on modern Node. |
javascript/loose-equality-coercion | BLOCKER | `==` 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 | BLOCKER | Mutating a shared module-scope object/array used across requests or components. |
javascript/unvalidated-boundary-input | BLOCKER | 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 | BLOCKER | Secrets read ad hoc from `process.env` scattered across modules — one validated config module, fail fast at startup. |
javascript/shell-injection | BLOCKER | `child_process.exec` with an interpolated string — use `execFile`/`spawn` with argv. |
javascript/var-in-new-code | HIGH | `var` in new code — `const` by default, `let` when reassigned. |
javascript/for-in-over-arrays | HIGH | `for...in` over arrays, or without `hasOwnProperty` filtering over objects. |
javascript/unchecked-index-access | HIGH | Array index access assumed defined (`arr[0].x`) without a length or nullish check. |
javascript/unsafe-numeric-coercion | HIGH | `parseInt` without radix; `Number()`/`+` coercion of user input without `Number.isFinite`. |
javascript/async-callback-ignored-promise | HIGH | `async` callbacks passed to APIs that ignore the returned promise (`array.forEach`, most event emitters) — rejections vanish. |
javascript/hand-rolled-deep-clone | HIGH | Deep-equality or deep-clone hand-rolled instead of `structuredClone`/a maintained util. |
javascript/unsafe-date-arithmetic | HIGH | Date arithmetic on `Date` objects across DST boundaries or with implicit local timezone. |
javascript/try-catch-whole-function | HIGH | `try`/`catch` around a whole function body instead of the failing boundary call. |
javascript/redos | HIGH | Regexes built from external input (ReDoS), or catastrophic backtracking patterns (nested quantifiers over user-controlled length). |
javascript/prefer-optional-chaining | SUGGESTION | Optional chaining and nullish coalescing over `&&`/`||` ladders. |
javascript/prefer-named-exports | SUGGESTION | Named exports over default exports for tree-shaking and refactor safety. |
javascript/jsdoc-on-exports | SUGGESTION | JSDoc types on exported functions in files that will not be converted to TS soon. |
javascript/array-at-negative-index | SUGGESTION | `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:
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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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
# 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]`.