Module: JavaScript
JavaScript·087·5 MIN READ

087: JavaScript Security, Reliability, and Production Readiness

TOPICS COVERED: JavaScript Security, Reliability, and Production Readiness

Outcomes

By the end of this lesson, you can:

  • identify common browser JavaScript security boundaries;
  • render untrusted text safely;
  • understand XSS risks around HTML injection;
  • avoid eval-style code execution;
  • protect secrets by understanding client-side visibility;
  • validate external data;
  • design request cancellation and stale-response handling;
  • apply a production-readiness checklist before shipping.

Security Principle: The Browser Is an Untrusted Client

Anything shipped to browser JavaScript can be inspected and modified by the user.

Do not place real secrets in frontend code:

js
const DATABASE_PASSWORD = "secret"; // never

Public API identifiers may be okay if designed to be public. Private credentials belong on trusted servers.

XSS and Unsafe HTML

This is dangerous with untrusted input:

js
results.innerHTML = userComment;

Prefer text:

js
results.textContent = userComment;

When rich HTML is a real product requirement, use a well-reviewed sanitization strategy and a strict content model. Do not invent a regex sanitizer.

Attribute and URL Boundaries

Setting text is not the only security concern.

js
link.href = userProvidedUrl;

Validate allowed schemes/origins where the link crosses a trust boundary.

Example:

js
function safeExternalUrl(raw) {
  const url = new URL(raw);

  if (!["https:", "http:"].includes(url.protocol)) {
    throw new Error("Unsupported URL protocol");
  }

  return url.href;
}

The correct allowlist depends on the product.

Avoid Dynamic Code Execution

Avoid:

js
eval(userInput);
new Function(userInput)();

Dynamic code execution creates severe security and maintainability risks and interacts badly with strong Content Security Policy.

Use data-driven dispatch instead:

js
const actions = {
  open: openOrder,
  cancel: cancelOrder,
};

const action = actions[userInput];

if (!action) {
  throw new Error("Unsupported action");
}

action();

External Data Is Untrusted

Even your own API can return unexpected data due to bugs, version drift, or compromised dependencies.

js
function isProduct(value) {
  return (
    value !== null &&
    typeof value === "object" &&
    typeof value.id === "string" &&
    typeof value.name === "string" &&
    typeof value.price === "number"
  );
}

For larger applications, schema validation libraries can provide more robust contracts.

Prototype Pollution Awareness

Avoid blindly assigning untrusted keys.

js
Object.assign(target, untrusted);

The exact risk depends on runtime/library behavior and downstream usage. Safer architecture validates permitted fields explicitly:

js
function normalizeSettings(input) {
  return {
    theme:
      input.theme === "dark" ? "dark" : "light",
    pageSize:
      Number.isInteger(input.pageSize)
        ? input.pageSize
        : 20,
  };
}

Allowlist business fields rather than accepting arbitrary object structure.

CSRF and Authentication Boundaries

Browser requests can automatically include cookies depending on cookie configuration and request context. Applications using cookie-based authentication must understand CSRF defenses.

Frontend JavaScript alone is not the security authority. Server-side protections, SameSite cookie settings, CSRF tokens where necessary, CORS policy, and origin checks work together.

CORS Is Not Access Control

CORS controls whether browser JavaScript can read certain cross-origin responses. It does not make a public HTTP endpoint private.

Authentication and authorization still belong on the server.

Race Conditions and Stale Responses

Reliability issue:

  1. user searches tea;
  2. request A starts;
  3. user searches coffee;
  4. request B starts;
  5. B returns first;
  6. A returns later and overwrites coffee results.

Use cancellation or request identity.

js
let currentController;

async function search(query) {
  currentController?.abort();

  currentController = new AbortController();

  const response = await fetch(
    `/api/search?q=${encodeURIComponent(query)}`,
    {
      signal: currentController.signal,
    }
  );

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }

  return response.json();
}

Idempotency Awareness

Retries are safe only when the operation semantics allow them.

Repeated GETs are usually safe. Repeating a payment/creation POST may not be safe unless the API supports idempotency keys or another deduplication strategy.

Do not implement automatic retries blindly.

Unhandled Promise Rejections

Always define who owns an async error.

js
button.addEventListener("click", async () => {
  try {
    await save();
  } catch (error) {
    showError(error);
  }
});

Background tasks still need observability and error handling.

Defensive DOM Lifecycle

Clean up:

  • event listeners;
  • timers;
  • observers;
  • pending requests;
  • subscriptions.

This is both a reliability and memory concern.

Dependency Risk

Production JavaScript often includes third-party packages.

Practices:

  • minimize dependencies;
  • prefer maintained libraries;
  • inspect dependency purpose and update history;
  • use lockfiles;
  • run security/audit tooling appropriately;
  • review major updates;
  • do not import a large package for a trivial utility without justification.

Production Checklist

Before shipping a feature, ask:

Correctness

  • Are input types normalized?
  • Are empty/error/loading states defined?
  • Are failures surfaced to users appropriately?
  • Are async races handled?

Security

  • Is untrusted content rendered safely?
  • Are URLs validated?
  • Are secrets absent from browser code?
  • Does the server enforce authorization?
  • Are dangerous dynamic-code APIs avoided?

Accessibility

  • Does keyboard interaction work?
  • Is focus managed appropriately?
  • Are status/error messages perceivable?
  • Are native controls used where possible?

Performance

  • Are expensive handlers debounced/throttled only when needed?
  • Are large DOM updates minimized?
  • Are requests cancelled when obsolete?
  • Are listeners/observers cleaned up?

Maintainability

  • Are modules cohesive?
  • Are side effects isolated?
  • Are names domain-oriented?
  • Are tests present for critical rules and regressions?

Final Integration Exercise

Build a small searchable product browser with:

  • ESM modules;
  • validated API data;
  • debounced input;
  • AbortController cancellation;
  • URL query synchronization;
  • safe DOM rendering;
  • loading/error/empty/success states;
  • keyboard-accessible controls;
  • unit tests for normalization and filtering;
  • one integration test for search behavior;
  • cleanup on unmount.

Architecture suggestion:

text
src/
├── api/
│   └── products-api.js
├── domain/
│   ├── normalize-product.js
│   └── filter-products.js
├── ui/
│   ├── render-products.js
│   └── search-controller.js
└── app.js

Do not begin by writing everything in app.js. Make boundaries explicit.

Advanced Production Readiness: State Machines and Failure Policy

Complex UI states become easier to reason about when you model them explicitly.

Instead of several unrelated booleans:

js
let isLoading = false;
let hasError = false;
let hasData = false;

use one state:

js
let state = {
  status: "idle",
  data: null,
  error: null,
};

Transitions:

text
idle → loading
loading → success
loading → error
loading → idle (cancelled)

This prevents impossible combinations such as loading + success + error at the same time.

Failure policy belongs to the product

For each async operation, define:

  • who sees the failure;
  • whether it is retryable;
  • whether retries are automatic;
  • how many times;
  • whether the operation is idempotent;
  • what is logged/observed;
  • what state the UI returns to.

A blanket catch (error) { console.log(error) } is not a recovery strategy.

CSP-friendly JavaScript

A strong Content Security Policy is easier to deploy when application code avoids inline scripts, inline event-handler attributes, and dynamic code execution.

Prefer:

html
<script type="module" src="/assets/app.js"></script>

and:

js
button.addEventListener("click", handleClick);

over:

html
<button onclick="handleClick()">Save</button>

Security architecture begins before the security review.

Recap

Production JavaScript is not only syntax. It is safe boundaries, predictable state, controlled side effects, cleanup, observability, accessibility, and evidence-driven performance.