Appendix B. Self-check answers and explanations
Chapter 1
1-1. Environment variables without the NEXT_PUBLIC_ prefix are isolated strictly as server-only variables: they can only be accessed inside server runtime execution contexts (such as Route Handlers, Server Components, and Server Actions) and are completely stripped out of the JavaScript files shipped to the browser. By contrast, any variable prefixed with NEXT_PUBLIC_ has its literal string value inlined directly into the compiled client JavaScript bundle files at build time (next build), making that exact value visible in plaintext to anyone inspecting browser developer tools or page sources. Because NEXT_PUBLIC_ values are baked into immutable static assets during the build phase rather than evaluated dynamically per request, after changing NEXT_PUBLIC_API_BASE in the Vercel dashboard you must trigger a fresh redeploy (a new build) for the updated value to reach users' browsers.
1-2. Within a single HTTP request, the network round trip between the user's browser and the serverless function happens only once, whereas the serverless function performs multiple sequential round trips (Multiple RTTs) to the database—first to establish the connection (TCP 3-way handshake, TLS 1.2/1.3 negotiation, and SCRAM-SHA-256 authentication) and then to run sequential waterfall queries (verifying the session, loading user profile rows, checking permissions, and writing audit records). When the serverless function executes in US East (iad1) while the primary database sits in Seoul, each cross-Pacific round trip adds roughly 180 ms of physical propagation delay, causing a handful of sequential queries and connection setup to accumulate over a full second of unnecessary latency. Pinning the function region to Seoul (icn1) colocates compute with the primary database and drops each internal query RTT to just 2–3 milliseconds.
1-3. Supavisor Transaction Mode (port 6543) borrows a backend PostgreSQL connection from the pool only for the few milliseconds required to execute a single transaction (BEGIN to COMMIT) or individual query and immediately returns that connection to the pool for another function to reuse. Therefore, serverless application runtime traffic must use Transaction Mode (port 6543) so hundreds of concurrent ephemeral function instances can multiplex queries over a small pool of real database connections without exhausting max_connections. Conversely, Session Mode (port 5432) or a Direct Connection dedicates a backend connection 1:1 to a single client until disconnect and preserves session-level state (prepared statements, session-wide SET variables, and advisory locks), so database schema migrations (migrate) must be isolated onto port 5432 (DIRECT_URL).
1-4. In PostgreSQL Row Level Security (RLS), the USING clause is evaluated against existing rows already stored in the table to filter which rows a requester is permitted to read (SELECT), target for modification (UPDATE), or remove (DELETE). By contrast, the WITH CHECK clause is evaluated against the new or resulting row state produced by an INSERT or UPDATE statement to verify whether the proposed data satisfies policy rules before committing to disk. If an UPDATE policy specifies a proper ownership check in USING (such as owner_id = (SELECT auth.uid())) but leaves WITH CHECK (true) overly permissive, an authenticated user can target their own existing row and update its owner_id, org_id, or approval status to another user's or tenant's identifier—injecting unauthorized records into another tenant's workspace or escalating privileges.
1-5. The first required header is x-vercel-protection-bypass: <SECRET>, which presents the project's configured Protection Bypass for Automation secret token to the Vercel edge. The second header is x-vercel-set-bypass-cookie: true (or samesitenone when testing cross-site flows), which instructs Vercel to set a signed bypass cookie in the automated browser on the initial page load so all subsequent client-side navigations, script loads, and API fetches across the Preview deployment pass through Deployment Protection seamlessly without failing with 401/403 responses.
1-6. (1) Bucket visibility setting: Because personal contracts contain confidential legal and personal data, the bucket must be created as a Private Bucket (public = false) so objects can never be downloaded via unauthenticated public CDN URLs even if a file path is guessed or leaked in browser history. (2) Upload flow: To avoid hitting Vercel's serverless function request body payload limit (4.5 MB), the browser first calls a server API route that verifies the user's authenticated session and tenant permissions and mints a short-lived signed upload URL (createSignedUploadUrl); the browser then uploads the 25 MB PDF binary directly to Supabase Storage using that one-time signed URL. (3) Viewing flow: Whenever the user opens the contract, the server (or storage.objects RLS policy) verifies ownership and issues a short-lived signed download URL (createSignedUrl, for example valid for 60 seconds) so only the authorized owner can view the document within a tight expiration window.
Chapter 2
2-1. If two teammates run terraform apply simultaneously from their laptops without state locking, both processes read the same baseline state file concurrently, make conflicting cloud API calls, and overwrite each other's updates (a classic race condition)—causing newly provisioned cloud resources to be orphaned outside the state file, corrupting the JSON state structure, or triggering duplicate resource creation errors. On an AWS S3 backend, you must enable state locking (use_lockfile = true for native S3 conditional lockfiles, or dynamodb_table) to serialize runs and prevent concurrent execution, and enable S3 Bucket Versioning (alongside server-side encryption encrypt = true) so operators can immediately restore the previous S3 object version if the state file is ever damaged or overwritten.
2-2. In terraform plan output, the ~ symbol (Update in-place) means Terraform will modify the resource's configuration attributes directly via cloud provider APIs while keeping the existing physical resource intact, whereas the -/+ symbol (Replace) means an immutable attribute was changed so Terraform will permanently destroy (-) the existing cloud resource and provision (+) a brand-new replacement resource from scratch. Because executing -/+ against a live production database deletes the database instance and wipes all stored customer data, you must add lifecycle { prevent_destroy = true } inside the resource block so any execution plan that attempts to destroy or replace the database aborts immediately with a hard error during terraform plan.
2-3. If you simply edit a resource's identifier in your .tf code from aws_instance.app to aws_instance.api_server and immediately run terraform apply, Terraform has no way of knowing you intended a rename; instead, it treats the old address as deleted and the new address as brand new—destroying (-) the live aws_instance.app server and creating (+) a new aws_instance.api_server instance, causing an immediate outage and losing local instance state. To update the logical resource address inside terraform.tfstate without touching the underlying cloud server, you must add a declarative moved { from = aws_instance.app, to = aws_instance.api_server } block to your configuration and verify that terraform plan shows zero destroy or create actions.
2-4. Marking a variable or output with sensitive = true only redacts the value as (sensitive value) in terminal stdout logs when running terraform plan and terraform apply; Terraform still writes the literal secret value in unencrypted plaintext inside the terraform.tfstate JSON file so it can compare attribute diffs on future runs. To prevent secret exposure through state files, you must (1) block *.tfstate* and .terraform/ from ever being committed to Git via .gitignore, (2) store state exclusively in a KMS-encrypted remote backend (such as S3 with encrypt = true) restricted to least-privilege IAM roles, and (3) use ephemeral variables/resources (ephemeral = true), write-only attributes (password_wo), or external Secrets Manager references so plaintext secrets are never persisted into state.
2-5. Because the manual console change is configuration drift that exists in the cloud but not in your .tf codebase, running the next terraform apply without reconciling it will cause Terraform to revert the manual edit and delete the hotfix port rule. To preserve the emergency rule permanently and safely: (1) update the .tf security group resource definition in your repository to declare the exact port rule added during the outage (and use an import block if a standalone resource was created), and (2) run terraform plan to verify that Terraform reports No changes. Your infrastructure matches the configuration. before committing and merging the change into Git.
2-6. When a pull request is opened from a feature branch, the proposed code has not yet passed peer code review or security approval; granting a PR workflow production write (apply) permissions would allow an unreviewed or malicious pull request to modify live production infrastructure or exfiltrate state secrets simply by opening a PR. Therefore, PR validation workflows must assume a least-privilege read-only plan IAM role, while the privileged apply role is restricted strictly to workflows running after code review approval and merge into main. Inside the Condition block of the production apply role's OIDC trust policy, you must verify both the aud (Audience, sts.amazonaws.com) claim and the sub (Subject) claim locked to the exact organization, repository, and production branch or environment (repo:org/repo:ref:refs/heads/main or repo:org/repo:environment:production).
Chapter 3
3-1. Under the DNS specification (RFC 1034 and RFC 2181), if a node in the DNS tree holds a CNAME record, no other resource record of any type is allowed to coexist at that same node name. Because the root domain (Zone Apex, example.com) is required by the protocol to hold SOA (Start of Authority) and NS (Name Server) records that define the authoritative zone itself, placing a standard CNAME record at the Zone Apex is illegal and breaks zone resolution. In Amazon Route 53, you solve this constraint by creating an Alias record at the Zone Apex pointing to the AWS Application Load Balancer (ALB); Route 53 resolves the ALB's current IP addresses internally and returns standard A or AAAA records to querying DNS resolvers (in Cloudflare, the equivalent mechanism is CNAME Flattening).
3-2. Recursive DNS resolvers operated by ISPs and public DNS services around the world cache your domain's resolved IP address for the full duration of the TTL that was attached to the record at the time they last queried it (86,400 seconds = 24 hours), and they do not contact your authoritative nameserver again until that cached timer expires locally. If you wait until migration day to lower the TTL to 60 seconds and swap the IP address at the same time, any recursive resolver that queried your domain shortly before the cutover will ignore the new settings and continue routing users to the old server IP for up to 24 hours. Therefore, you must lower the TTL to 60–300 seconds at least one full old-TTL window (24+ hours) before the migration and wait for all old 24-hour cached entries across the internet to expire before switching the IP address.
3-3. Users' web browsers will fail to open the site with an ERR_TOO_MANY_REDIRECTS (infinite redirect loop) error. Even though the user connects to Cloudflare over encrypted https://, Cloudflare's Flexible mode decrypts the request and forwards it from the Cloudflare edge to the origin server over unencrypted plaintext http:// on port 80 (with X-Forwarded-Proto: http). Seeing a plaintext HTTP request arrive, the origin web server's redirect rule responds with a 301 or 308 redirect instructing the browser to load https://—which sends the browser back to Cloudflare over https://, which Cloudflare forwards to the origin over http:// again in an endless loop. In addition, Flexible leaves traffic between Cloudflare and the origin exposed in plaintext. You must install a valid TLS certificate (from a public CA or Cloudflare Origin CA) on the origin server and set the SSL/TLS mode to Full (Strict).
3-4. Enabling the orange cloud (Proxied) only changes DNS resolution so public queries return Cloudflare edge IP addresses instead of the origin IP. If your origin server's network firewall (Security Group) still allows inbound traffic on port 80 or 443 from 0.0.0.0/0, an attacker who discovers your origin server's real IP address (through historical DNS logs, a misconfigured subdomain, or IPv4 scanning) can send HTTP requests with a Host: yourdomain.com header directly to the origin IP—completely bypassing Cloudflare's edge and WAF rules. To block direct origin access, you must implement Origin Lockdown on the origin side by (1) restricting inbound firewall rules on port 443 exclusively to Cloudflare's published IPv4/IPv6 CIDR lists, (2) enabling Authenticated Origin Pulls (mTLS client certificate verification) so the origin server rejects any TLS handshake that does not present your Cloudflare zone's client certificate, or (3) closing all inbound ports and connecting via Cloudflare Tunnel (cloudflared).
3-5. If a CDN caches a personalized API endpoint (/api/profile) in a shared edge cache, the JSON response body and any Set-Cookie session header generated for the first signed-in user are stored under that URL's cache key and served verbatim to subsequent visitors—triggering a catastrophic cache poisoning data breach where users receive strangers' private account profiles and active session cookies. If a CDN caches or enables response buffering on a real-time LLM streaming endpoint (/api/chat/stream, text/event-stream), token chunks are held back at the edge until the entire completion finishes instead of streaming incrementally, breaking the real-time typing experience and causing gateway timeouts. Both routes must be configured with a CDN Cache Rule set to Bypass Cache, with /api/profile returning Cache-Control: private, no-store and /api/chat/stream returning Cache-Control: no-cache, no-transform (with proxy buffering disabled via X-Accel-Buffering: no).
3-6. First, configure Automated Retries and Multi-Provider Fallbacks (Universal Endpoint / Fallbacks) in Cloudflare AI Gateway so that whenever the primary LLM provider (OpenAI) returns a 503 service outage error or times out, the gateway automatically retries or fails over to a secondary model on a backup provider (such as Anthropic Claude or Google Gemini) without requiring an application code deployment. Second, enable Gateway Rate Limiting to cap the maximum number of requests allowed per minute or hour so runaway agent loops are throttled at the edge before consuming expensive external LLM tokens. Third, enable Unified Observability (Analytics & Logging) and deterministic Prompt Caching to track latency, error rates, and token cost across providers in a single dashboard while serving repeated identical deterministic requests directly from the edge cache.
Chapter 4
4-1. A traditional perimeter VPN places an authenticated user's device directly onto the internal corporate network subnet (L3/L4), granting broad implicit trust to communicate with multiple hosts and ports across that network segment. By contrast, a NIST SP 800-207 Zero Trust Network Access (ZTNA) architecture grants zero implicit trust based on network location; instead, it continuously verifies user identity and Device Posture on every session and opens access strictly to the specific individual application, host, and port authorized for that user's role. Consequently, when a single connected laptop is infected with malware on a traditional perimeter VPN, the attacker can pivot sideways across the open internal subnet to scan and compromise neighboring servers and databases—a propagation risk known as Lateral Movement.
4-2. A conventional web server waits passively for external clients on the internet to initiate inbound TCP connections into the corporate network, which requires opening inbound ports (such as 80 or 443) on the perimeter firewall or Security Group. By contrast, with cloudflared (Cloudflare Tunnel), the lightweight connector daemon running inside your private VPC subnet initiates an outbound encrypted connection (over port 443/7844) to the Cloudflare edge first. When an external employee passes Cloudflare Access SSO and posture authentication at the edge, their HTTP request is proxied back down that already established outbound tunnel to the internal server—requiring zero open inbound firewall ports on your network perimeter.
4-3. Both 192.168.1.0/24 and 192.168.0.0/24 are the default RFC 1918 private LAN subnets preconfigured on the vast majority of consumer home Wi-Fi routers, coffee-shop networks, and mobile hotspots. Numbering a corporate cloud VPC as 192.168.1.0/24 creates an Overlapping Subnet Collision on remote employees' laptops: the operating system's routing table cannot distinguish whether packets addressed to 192.168.1.x should stay on the local home Wi-Fi LAN (where the wireless printer and home router live) or enter the corporate VPN tunnel (where the internal server lives). To fix this fundamentally, assign the corporate cloud VPC a non-overlapping RFC 1918 CIDR block from a less common range (such as 10.240.0.0/16 or 172.28.0.0/16) and configure Include-mode Split Tunneling to route only the exact internal subnets, CGNAT addresses (100.64.0.0/10), or internal domains required.
4-4. Even though external users on the internet cannot route packets directly to a private RFC 1918 IP address, publishing internal server hostnames (db.dev, payroll, gpu-cluster) and their private IP addresses inside a public authoritative DNS zone leaks your internal asset inventory and network subnet layout to attackers conducting DNS reconnaissance. Split-Horizon DNS resolves this by registering internal hostnames (*.internal.corp) exclusively inside a private internal DNS zone (so public DNS queries return NXDOMAIN) and configuring the ZTNA/VPN client with conditional DNS forwarding so only DNS queries matching *.internal.corp are forwarded through the encrypted tunnel to the internal corporate DNS resolver.
4-5. Setting NODE_TLS_REJECT_UNAUTHORIZED=0 does not selectively trust your corporate Root CA—it disables TLS certificate verification completely for all outbound connections, leaving the developer's workstation defenseless against man-in-the-middle (MITM) attacks presenting forged certificates on any public network. Instead, you must keep strict TLS verification enabled and register the corporate Root CA certificate properly with each runtime: for Node.js (and Node-based AI coding agents), export NODE_EXTRA_CA_CERTS="/path/to/company-root-ca.pem", which appends the corporate Root CA onto Node's built-in public CA list; for Python (requests, pip) and OpenSSL tools, create a combined PEM file concatenating the standard public root CA bundle with your corporate Root CA and point SSL_CERT_FILE and REQUESTS_CA_BUNDLE to that combined bundle (because those environment variables replace the default CA bundle entirely).
4-6. (1) RDS connectivity component: Because you cannot install a VPN or ZTNA agent binary directly onto a managed AWS RDS instance, deploy a redundant pair (2+ instances across AZs for high availability) of Subnet Routers (or cloudflared private network connectors) inside the same private VPC subnet to bridge traffic to the RDS instance. (2) Host and port restriction: Configure a least-privilege ACL (with Default Deny) that permits src: ["group:contractors"] to connect strictly to the staging RDS IP address on port 5432 only, blocking port 22 (SSH) and all other internal hosts to prevent lateral movement. (3) Automated offboarding: Integrate your central Identity Provider (IdP) with the ZTNA/VPN platform using the SCIM protocol so that deactivating a contractor in HR/IdP automatically disables their ZTNA account and revokes all active tunnel sessions and tokens within seconds.
Chapter 5
5-1. The four sequential steps of the Dual-Key zero-downtime secret rotation pattern are: (1) generate the new key (V2) and deploy verification code that accepts an ordered key ring of [V2, V1] so tokens or webhooks signed by either key pass verification; (2) switch token issuance (or the external webhook sender) to sign exclusively with V2; (3) wait through the maximum session TTL (or webhook retry window) while monitoring structured logs to confirm that verification matches against the legacy key V1 have dropped to zero; and (4) remove V1 from the verification list and permanently revoke it. By contrast, when an external API key is accidentally leaked to a public GitHub repository, attackers can immediately exploit that key during any grace window to exfiltrate customer data or rack up massive API charges—so you must deploy the replacement key and revoke the leaked key immediately with zero grace period.
5-2. Recovery Point Objective (RPO) is the maximum age of historical data loss an organization can tolerate (measured backward in time from the moment of an incident), whereas Recovery Time Objective (RTO) is the maximum elapsed time allowed to restore normal service operation after an outage begins. If a database relies solely on a daily snapshot taken at 04:00 a.m. and an accidental DELETE query runs at 2:00 p.m., none of the legitimate writes committed during the 10-hour window between 04:00 a.m. and 2:00 p.m. exist in the morning snapshot—meaning a snapshot restore wipes out 10 hours of live customer data. Solving this requires Point-in-Time Recovery (PITR), which combines physical base backups with continuous archiving of PostgreSQL's Write-Ahead Log (WAL) so the database can be replayed and restored to the exact second right before the 2:00 p.m. mistake occurred.
5-3. During a zero-downtime rolling deployment, old application instances (v1) and new application instances (v2) run simultaneously against the same database. Executing ALTER TABLE RENAME COLUMN (or DROP COLUMN) in a single step immediately breaks every running v1 instance that still queries the old column name, and makes rolling v2 back to v1 impossible if a defect is discovered. The four phases of the Expand-Contract (Parallel Change) pattern are: (1) Expand: add the new column as nullable alongside the existing column; (2) Dual-Write & Backfill: deploy application code that writes to both old and new columns simultaneously while backfilling historical rows in small batches; (3) Cutover: deploy application code that reads and writes only the new column and validate NOT NULL constraints (NOT VALID → VALIDATE); and (4) Contract: drop the old column only after zero running instances reference it.
5-4. When fast static or cached responses (such as 10 ms edge hits) represent the vast majority of traffic, the arithmetic mean (Average Latency) dilutes and hides severe multi-second stalls experienced by users on database-heavy or LLM streaming routes; tracking p95 and p99 tail latency percentiles reveals the true latency experienced by the slowest 5% and 1% of requests. When collecting structured JSON logs and distributed traces (trace_id), you must scrub (redact): (1) authentication credentials such as Authorization headers and session cookies (Cookie / Set-Cookie), (2) infrastructure secrets such as LLM API keys and database connection passwords, and (3) sensitive personal data (PII) such as user passwords, payment card numbers, national ID numbers, and confidential prompt contents.
5-5. When every application server instance immediately retries failed calls against an overloaded database or external LLM API in a tight loop without waiting, the synchronized flood of retry requests multiplies the load on top of normal traffic—triggering a Retry Storm (Thundering Herd) that overwhelms the struggling dependency and causes a total cascading outage. To prevent this, space out retries using Exponential Backoff with Jitter (doubling wait intervals between retries while adding randomized time offsets so retries desynchronize), and wrap the dependency in a Circuit Breaker that trips Open once consecutive failures cross a threshold—immediately returning a fallback response without calling the overloaded downstream service during its cooldown window.
5-6. Debugging code and writing a new patch while a P1 payment outage is actively burning violates the first law of incident response ("Mitigate first, investigate later"), leaving paying customers broken for tens of minutes or hours while the engineer reads logs. Within the first 10 minutes, the Incident Commander must direct an instant rollback to the previous known-good deployment (or disable the faulty change via a Feature Flag / Kill Switch) to bring the error rate back to 0% immediately. Once service is restored, the team conducts a Blameless Postmortem that avoids punishing the individual engineer and instead identifies systemic gaps (such as why automated tests or canary checks missed the regression) and assigns concrete, deadline-tracked engineering action items to prevent recurrence.