Skip to content
Chapter 81Lesson 9

Quiz - The security baseline

Quiz progress

0 / 0

The protected app shell ships a nonce-based CSP from proxy.ts, but the public marketing site (example.com) ships a nonceless CSP with every third-party origin listed by hand. Why does the marketing site get a different policy instead of the same nonce treatment?

A fresh per-request nonce forces a page to render dynamically, which would forfeit the static prerendering the marketing site wants for SEO and speed. The app shell is already dynamic (session, org, tenant), so the nonce costs it nothing — but on a static page it’s a real loss, so you trade it for an explicit-origin list.

proxy.ts only runs for authenticated routes, so it physically cannot attach a CSP to the public marketing pages — a nonceless policy in next.config.ts is the only mechanism available there.

Marketing pages load third-party scripts and app pages don’t, and 'strict-dynamic' is incompatible with any external origin, so the marketing site must drop nonces to allow them.

You’re walking the endpoint inventory deciding which routes need a dedicated rate limiter. An authenticated, tenant-scoped GET /invoices list read costs nothing per call and can’t be aimed at a victim. What’s the correct call?

Leave it on the wrapper’s coarse per-user/per-org default — no dedicated limiter. It matches none of the three triggers, and “a limiter on every endpoint” is the over-application mistake.

Add a per-IP limiter — every public-facing route should be rate-limited, and per-IP is the safe default for reads.

Add a dedicated per-user limiter in lib/rate-limit.ts, because any endpoint that touches the database is abusable.

A platform operator opens the cross-tenant audit-log view during an incident — gated by the superadmin role. According to the policy, what else must happen?

The read itself writes an admin.audit-log-queried row — reading the most sensitive table is a privileged action, so it gets audited like any other.

Nothing extra — a read never earns an audit row, and the superadmin gate is the entire control.

The operator’s pino session is flagged, but no audit row is written, because writing one would violate the append-only rule.

A user invokes their right to erasure. Their account also has invoices you’re legally required to retain for seven years. A teammate sets deletedAt on the invoice rows so they vanish from the user’s views and calls the erasure satisfied. Why is that wrong?

Soft delete is a visibility tool, not an erasure tool — the email, name, and other PII are still sitting in the row. A legally-retained record must be soft-deleted and anonymized: hidden from view, with the PII scrubbed.

The invoices should have been hard-deleted instead — legal retention doesn’t override a verified erasure request.

Setting deletedAt is fine for erasure; the only mistake is forgetting to also delete the rows from the nightly backup snapshots immediately.

Sort each cookie or tracker by whether it can be set before the user has made any consent choice. (Select every one that is essential — set without consent.) Select all that apply.

The cookie that records the user’s consent choice itself.

The Better Auth session cookie.

PostHog product analytics.

A marketing/ad pixel.

A developer with production access leaves, so you rotate the RESEND_API_KEY they could see. Put the load-bearing ordering rule plainly: when do you revoke the old key at Resend?

Only after the new key is added to Vercel, deployed, and verified healthy. Updating Vercel first leaves a deliberate window where both keys are valid, so there’s never an instant with no working key.

First, before touching Vercel — invalidating the leaked key immediately is the priority, and the brief outage while the new key deploys is acceptable.

At the same moment you add the new key to Vercel, so the two keys are never both valid at once.

A production build fails because a required variable is missing. A teammate “fixes” it by setting SKIP_ENV_VALIDATION=1 in the production runtime environment — the build goes green. What’s the real consequence?

The env gate is now switched off in production for good: a genuinely missing variable no longer fails at deploy on the terminal — it surfaces as a 3am 500 on the first request that needs it, which is exactly the outage the schema existed to prevent.

Nothing harmful — SKIP_ENV_VALIDATION only skips the type generation, so runtime values are still validated on first access.

It’s fine in production but breaks local builds, because the flag is only meant to be read in development.

A critical security patch for one package lands and it’s only three hours old, so pnpm’s 24-hour quarantine (minimumReleaseAge: 1440) is blocking the install. What’s the correct way to get the patch in?

Add just that one package to minimumReleaseAgeExclude in pnpm-workspace.yaml, committed in the same PR so a reviewer sees it and it can be reverted once the version ages past the window.

Set minimumReleaseAge: 0 for this install — it’s the documented way to pull a fresh release when you genuinely need one.

Run the install with a one-off --allow-fresh flag so the bypass stays out of committed config.

A teammate argues that with minimumReleaseAge, blockExoticSubdeps, the install-script approval gate, and a frozen lockfile all on, running pnpm audit is redundant. What’s the flaw in that reasoning?

Those config controls defend against novel attacks — versions nobody has flagged yet, unreviewed install scripts, drifting resolution. pnpm audit catches already-known vulnerabilities by checking installed versions against the advisory database. Different layers; a codebase needs both.

It’s a fair point — the config controls are a strict superset of what pnpm audit checks, so audit only matters before pnpm 11.

The flaw is only that pnpm audit is needed for dev dependencies, which the config controls ignore.

Quiz complete

Score by topic