---
title: "22 — Web Security — Defending the Browser's 'Run Anyone's Code' Model"
uid: web-security
tags: ["security", "https", "cors", "owasp", "roadmap:frontend", "csp", "xss"]
excerpt: "The browser trusts the origin — and every defense either enforces that boundary, encrypts the pipe, declares trusted sources, or names the known attacks."
date: 2026-08-12T18:35:08+0000
source: https://www.aveshina.my.id/en/blog/web-security
---

CORS, CSP, TLS, OWASP, XSS, CSRF — a wall of acronyms that all somehow meant "bad thing, fix it." Writing them down collapsed the wall into one idea I could actually hold.

The model that clicked: **the browser's foundational decision is to run anyone's code and fetch anyone's resources, and it decides what to trust by *origin*.** Every web security defense I've met is a response to that one decision. Each one either **enforces the origin boundary** (CORS), **encrypts the pipe** between origins (HTTPS/TLS), **declares which sources are trusted to execute** (CSP), or **enumerates the known attacks** so you know what you're defending against (OWASP Top 10). Four jobs, one model. Once I saw it that way, the acronyms stopped being a list and started being a layered defense.

```figure
<svg viewBox="0 0 740 320" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="A layered-defense diagram for web security. A browser window sits at the center labelled runs anyone's code, trusts the origin. Four shields ring it, each labeled with a defense and its job: HTTPS encrypts the pipe, CORS enforces the origin boundary, CSP declares trusted sources, OWASP Top 10 enumerates known attacks. Each shield glows toward the browser showing it protects a different facet.">
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">
    <!-- browser core -->
    <rect x="270" y="120" width="200" height="80" rx="10" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.8"/>
    <text x="370" y="150" font-size="14" font-weight="700" fill="#1e1b4b" text-anchor="middle">Browser</text>
    <text x="370" y="170" font-size="10.5" fill="#475569" text-anchor="middle">runs anyone's code,</text>
    <text x="370" y="184" font-size="10.5" fill="#475569" text-anchor="middle">trusts the origin</text>

    <!-- HTTPS — top -->
    <rect x="290" y="24" width="160" height="56" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="370" y="48" font-size="13" font-weight="700" fill="#052e16" text-anchor="middle">HTTPS / TLS</text>
    <text x="370" y="66" font-size="10" fill="#052e16" text-anchor="middle">encrypts the pipe</text>
    <line x1="370" y1="80" x2="370" y2="120" stroke="#16a34a" stroke-width="1.5" stroke-dasharray="3 3"/>

    <!-- CORS — left -->
    <rect x="40" y="132" width="160" height="56" rx="8" fill="#fef9c3" stroke="#ca8a04" stroke-width="1.5"/>
    <text x="120" y="156" font-size="13" font-weight="700" fill="#422006" text-anchor="middle">CORS</text>
    <text x="120" y="174" font-size="10" fill="#422006" text-anchor="middle">enforces the origin boundary</text>
    <line x1="200" y1="160" x2="270" y2="160" stroke="#ca8a04" stroke-width="1.5" stroke-dasharray="3 3"/>

    <!-- CSP — right -->
    <rect x="540" y="132" width="160" height="56" rx="8" fill="#fce7f3" stroke="#db2777" stroke-width="1.5"/>
    <text x="620" y="156" font-size="13" font-weight="700" fill="#500724" text-anchor="middle">CSP</text>
    <text x="620" y="174" font-size="10" fill="#500724" text-anchor="middle">declares trusted sources</text>
    <line x1="540" y1="160" x2="470" y2="160" stroke="#db2777" stroke-width="1.5" stroke-dasharray="3 3"/>

    <!-- OWASP — bottom -->
    <rect x="290" y="240" width="160" height="56" rx="8" fill="#cffafe" stroke="#0891b2" stroke-width="1.5"/>
    <text x="370" y="264" font-size="13" font-weight="700" fill="#083344" text-anchor="middle">OWASP Top 10</text>
    <text x="370" y="282" font-size="10" fill="#083344" text-anchor="middle">enumerates known attacks</text>
    <line x1="370" y1="200" x2="370" y2="240" stroke="#0891b2" stroke-width="1.5" stroke-dasharray="3 3"/>
  </g>
</svg>
```

## The premise worth stating plainly

A web page is unusual software. A native app is a binary I chose to install and the OS largely trusts; a web page is HTML, CSS, and JavaScript that **the browser fetches and executes on every visit, from servers it didn't vet** [1]. The browser can't refuse to run it — that's the whole point of the web. So instead it asks one question on which everything hangs: *where did this come from?* That "where" is the **origin** — the combination of scheme, host, and port (https://aveshina.my.id:443). Same origin is implicitly trusted; different origin is not. Every defense below is a refinement of that single rule.

## CORS — enforcing the origin boundary

The browser's default rule is the **same-origin policy**: a script running on aveshina.my.id may not read responses from requests it makes to api.twitter.com [2]. That sounds restrictive, and it is —deliberately. Without it, any site I visited could silently call my bank's API using my cookies and read the response.

CORS (Cross-Origin Resource Sharing) is the **relaxation mechanism**, not the restriction. It's how a server at api.example.com can say to a browser, "responses from me are allowed to be read by scripts at app.example.com" [2][3]. It works entirely through HTTP headers. For simple requests the server returns:

```
Access-Control-Allow-Origin: https://app.example.com
```

For requests that could change data (anything with a custom header, or methods like PUT/DELETE, or non-simple content types), the browser sends a **preflight** — an OPTIONS request asking "is this allowed?" — before the real request [3]. The thing I had to straighten out: **CORS is enforced by the browser, not the server.** The server is just declaring policy via headers; the browser is the one that blocks the response from reaching the script if the headers don't permit it. The same API called from curl has no CORS gate at all — because curl doesn't implement the same-origin policy.

CORS also mitigates **CSRF** (Cross-Site Request Forgery) in practice: because cross-origin requests are gated, a malicious site can't trivially fire authenticated state-changing requests at my API and have them succeed — though CSRF ultimately needs its own defenses too (anti-CSRF tokens — one-time values a form must echo back — and SameSite cookies, which tell the browser not to send the cookie on cross-site requests), since simple form POSTs predate CORS and are allowed by default.

## HTTPS — encrypting the pipe

Same-origin policy assumes I can actually tell who I'm talking to and that the bytes aren't being tampered with in flight. On plain HTTP that assumption is false — anyone between the browser and the server (the coffee-shop Wi-Fi, an ISP, a compromised router) can read and alter the traffic.

HTTPS is the same HTTP conversation from my earlier notes, wrapped in **TLS** — an encryption layer that gives two guarantees [4][5]:

- **Confidentiality** — the bytes on the wire are encrypted; the man in the middle sees scrambled data (ciphertext).
- **Integrity** — TLS authenticates the server (via its certificate) and detects any tampering with the data.

The browser's padlock icon means exactly: *this conversation is encrypted and the server proved who it is.* It does **not** mean "this site is safe" or "this site is honest" — a phishing site can have a valid certificate and a padlock too. The certificate proves identity, not intent [4]. The single practical move this left me with: never send anything I'd mind an attacker reading over plain HTTP, and treat a site that loads mixed HTTP subresources on an HTTPS page as broken — the browser will block or warn.

## CSP — declaring trusted sources

CORS gates *cross-origin reads*. CSP (Content Security Policy) gates *what's allowed to execute at all*, even from the same origin. It's a defense against **XSS** (Cross-Site Scripting) — the attack where untrusted input ends up executed as JavaScript on my page because I didn't escape it [6][7].

CSP is a declarative allowlist, sent as a header (or <meta> tag), that names the trustworthy sources for each resource type. A strict policy looks like:

```
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:;
```

default-src 'self' is the spine: unless a more specific directive says otherwise, every resource — scripts, styles, images, fonts, frames — may only come from the page's own origin [7]. No inline scripts, no eval (a function that runs a string as code), no third-party CDNs (content delivery networks) I didn't explicitly allow. The payoff is that even if an attacker does slip <script>alert(1)</script> into my HTML, the browser refuses to execute it, because inline scripts aren't in the allowlist. CSP turns XSS from "one missed escape and you're owned" into "one missed escape that the browser quietly refuses to run."

It also mitigates **clickjacking** (an attacker embedding my site in a hidden <iframe> and tricking the user into clicking it) via frame-ancestors 'none' — the modern replacement for X-Frame-Options [7]. And it can report violations to a URL with report-uri, so I see in production the moment something tries to load from an untrusted source.

## OWASP Top 10 — naming the known attacks

CORS, HTTPS, and CSP are *defenses*. OWASP is the *map of what's attacking*. The **OWASP Top 10** is a periodically-updated list of the ten most critical web application security risks, compiled from real incident data [8][9]. It's not exhaustive, and the exact ordering shifts between editions, but it's the shared vocabulary — when someone says "that's an A03," they mean injection, and now I know what they mean.

The entries I keep close, because they're the ones the frontend actually touches:

- **Injection** (SQL, command, etc.) — untrusted input reaches an interpreter (a program that runs text as code, like a database or shell). The frontend's job here is mostly to get out of the way: parameterized queries (queries with placeholders, so input can't be run as code) on the backend do the real work; client-side validation is UX, not security.
- **XSS** — covered above; it's injection where the interpreter is the browser. CSP is the browser-side backstop, escaping/output-encoding is the fix.
- **Broken Access Control / CSRF** — the server doing something on behalf of a user who didn't intend it. CORS helps, but the real fix is server-side authorization checks, anti-CSRF tokens, and SameSite cookies.
- **Security Misconfiguration / Vulnerable Dependencies** — default credentials, verbose errors, an npm install that pulled in a package with a known CVE (a published vulnerability ID). The last one is where I actually spend time: npm audit, keeping the lockfile current, and not trusting a package just because it has a lot of stars.

The value of OWASP for me isn't memorizing the list. It's that when I'm building a feature, I can run the Top 10 as a *checklist of failure modes* — "did I consider injection here? XSS? access control?" — instead of waiting for a bug report to tell me what I forgot.

## How these compose

The four defenses don't compete; they sit at different points in a single request's life:

```figure
<svg viewBox="0 0 740 220" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="A single request passing through four defenses in order. Left to right: Browser with a label OWASP guides what to defend. Step 1, an arrow enters a TLS tunnel labeled HTTPS encrypts the pipe. Step 2, the request hits the server which checks Access-Control headers labeled CORS enforces the origin boundary. Step 3, the response HTML carries a CSP header labeled CSP declares trusted sources for execution. The browser at the right only executes what CSP allows.">
  <defs>
    <marker id="sarrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
      <path d="M0,0 L10,5 L0,10 z" fill="#64748b"/>
    </marker>
  </defs>
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">
    <!-- browser -->
    <rect x="20" y="80" width="110" height="60" rx="8" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.5"/>
    <text x="75" y="108" font-size="12" font-weight="700" fill="#1e1b4b" text-anchor="middle">Browser</text>
    <text x="75" y="124" font-size="9.5" fill="#475569" text-anchor="middle">OWASP guides</text>
    <text x="75" y="136" font-size="9.5" fill="#475569" text-anchor="middle">what to defend</text>

    <!-- TLS tunnel -->
    <rect x="160" y="70" width="120" height="80" rx="40" fill="none" stroke="#16a34a" stroke-width="1.8" stroke-dasharray="4 3"/>
    <text x="220" y="105" font-size="11" font-weight="700" fill="#052e16" text-anchor="middle">HTTPS</text>
    <text x="220" y="121" font-size="9.5" fill="#052e16" text-anchor="middle">encrypts the pipe</text>

    <!-- server -->
    <rect x="310" y="80" width="120" height="60" rx="8" fill="#fef9c3" stroke="#ca8a04" stroke-width="1.5"/>
    <text x="370" y="108" font-size="12" font-weight="700" fill="#422006" text-anchor="middle">Server</text>
    <text x="370" y="124" font-size="9.5" fill="#422006" text-anchor="middle">CORS headers:</text>
    <text x="370" y="136" font-size="9.5" fill="#422006" text-anchor="middle">origin boundary</text>

    <!-- response with CSP -->
    <rect x="460" y="80" width="120" height="60" rx="8" fill="#fce7f3" stroke="#db2777" stroke-width="1.5"/>
    <text x="520" y="108" font-size="12" font-weight="700" fill="#500724" text-anchor="middle">Response + CSP</text>
    <text x="520" y="124" font-size="9.5" fill="#500724" text-anchor="middle">trusted sources</text>
    <text x="520" y="136" font-size="9.5" fill="#500724" text-anchor="middle">declared</text>

    <!-- executed -->
    <rect x="610" y="80" width="110" height="60" rx="8" fill="#cffafe" stroke="#0891b2" stroke-width="1.5"/>
    <text x="665" y="108" font-size="12" font-weight="700" fill="#083344" text-anchor="middle">Execute</text>
    <text x="665" y="124" font-size="9.5" fill="#083344" text-anchor="middle">only what CSP</text>
    <text x="665" y="136" font-size="9.5" fill="#083344" text-anchor="middle">allows</text>

    <!-- flow arrows -->
    <line x1="130" y1="110" x2="158" y2="110" stroke="#64748b" stroke-width="1.5" marker-end="url(#sarrow)"/>
    <line x1="280" y1="110" x2="308" y2="110" stroke="#64748b" stroke-width="1.5" marker-end="url(#sarrow)"/>
    <line x1="430" y1="110" x2="458" y2="110" stroke="#64748b" stroke-width="1.5" marker-end="url(#sarrow)"/>
    <line x1="580" y1="110" x2="608" y2="110" stroke="#64748b" stroke-width="1.5" marker-end="url(#sarrow)"/>

    <text x="370" y="190" font-size="10.5" fill="#64748b" text-anchor="middle" font-style="italic">one request — four gates, each at a different point</text>
  </g>
</svg>
```

OWASP isn't a gate in that flow — it's the map I use while *building* it, telling me which gates to put where. HTTPS protects the wire. CORS protects the server from unauthorized cross-origin callers. CSP protects the browser from executing the wrong thing. Each closes a hole the others don't.

## How I use this

The habit these notes left me with is a single question when I ship a feature: *which of the four am I relying on here, and is it actually configured?* A fetch to a new API — is CORS configured on the server, or am I about to discover it at deploy time? Anything handling user input — is it escaped on output, and is there a CSP header catching what I miss? Any page handling auth — is it HTTPS-only, and are the cookies SameSite and Secure? Running the OWASP Top 10 as a mental checklist turns "is this secure?" from a vague dread into a short, finite list of things to verify. The model — origin trust, with four layered defenses — is what made that list finite.

## References

[1] OWASP, "OWASP Top Ten," 2023. [Online]. Available: [https://owasp.org/www-project-top-ten/](https://owasp.org/www-project-top-ten/)

[2] Mozilla, "Cross-Origin Resource Sharing (CORS)," MDN Web Docs, 2024. [Online]. Available: [https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)

[3] R. Bika, "Understanding CORS," rbika.com, 2020. [Online]. Available: [https://rbika.com/blog/understanding-cors](https://rbika.com/blog/understanding-cors)

[4] Cloudflare, "What is HTTPS?," 2024. [Online]. Available: [https://www.cloudflare.com/en-gb/learning/ssl/what-is-https/](https://www.cloudflare.com/en-gb/learning/ssl/what-is-https/)

[5] Google, "Why HTTPS Matters," web fundamentals, 2022. [Online]. Available: [https://developers.google.com/web/fundamentals/security/encrypt-in-transit/why-https](https://developers.google.com/web/fundamentals/security/encrypt-in-transit/why-https)

[6] OWASP, "OWASP Cheat Sheet Series," 2024. [Online]. Available: [https://cheatsheetseries.owasp.org/](https://cheatsheetseries.owasp.org/)

[7] Mozilla, "Content Security Policy (CSP)," MDN Web Docs, 2024. [Online]. Available: [https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)

[8] Google, "Content Security Policy (CSP)," web.dev, 2023. [Online]. Available: [https://web.dev/articles/csp](https://web.dev/articles/csp)

[9] The New Stack, "OWASP Top 10: A Guide to the Worst Software Vulnerabilities," 2023. [Online]. Available: [https://thenewstack.io/owasp-top-10-a-guide-to-the-worst-software-vulnerabilities/](https://thenewstack.io/owasp-top-10-a-guide-to-the-worst-software-vulnerabilities/)

```quiz
Q: A script on aveshina.my.id fetches data from api.twitter.com and the browser blocks reading the response. Who actually enforced that block?
- The Twitter API server, by refusing the request
- The browser, because the response lacked the right CORS headers
correct: 1
explain: CORS is enforced by the browser, not the server. The server only declares policy via Access-Control headers; the browser is what stops the script from reading the response. The same API called from curl has no CORS gate.

Q: The padlock icon in the address bar means the site is…
- encrypted in transit and the server proved its identity — not that it's honest or safe
- a trustworthy site that won't try to scam me
correct: 0
explain: The padlock means TLS encryption and certificate-based identity. A phishing site can also have a valid certificate; the padlock proves identity, not intent.

Q: An attacker slips raw <script> tags into your HTML. Which defense is the backstop that makes the browser refuse to run it?
- CORS
- CSP with default-src 'self' and no 'unsafe-inline'
correct: 1
explain: CSP is an allowlist of trusted sources for execution. A strict policy excludes inline scripts, so even injected <script> is silently refused. CORS is about cross-origin reads, not execution.

Q: What is the OWASP Top 10?
- A periodically-updated list of the ten most critical web application security risks
- A set of ten tools you must install to secure a web app
correct: 0
explain: The OWASP Top 10 is a ranked list of the most critical risks, compiled from real incident data. It's a shared vocabulary and a checklist of failure modes, not software.

Q: Which defense sits at which point in a request's life, in order?
- OWASP encrypts the pipe, HTTPS enforces the origin, CORS maps attacks, CSP declares sources
- HTTPS encrypts the pipe, CORS enforces the origin boundary, CSP declares trusted sources, OWASP guides what to defend
correct: 1
explain: HTTPS protects the wire, CORS gates cross-origin reads at the server, CSP gates execution in the browser. OWASP is the map used while building, not a runtime gate.
```
