diff --git a/experiments/module-10-security-demo/README.md b/experiments/module-10-security-demo/README.md
index 1035c20..9f9fcaf 100644
--- a/experiments/module-10-security-demo/README.md
+++ b/experiments/module-10-security-demo/README.md
@@ -16,6 +16,25 @@ Open:
http://127.0.0.1:8090/
```
+## Run With Security Headers
+
+For Module 10-E remediation validation, run the local security-header server:
+
+```powershell
+python .\server.py --port 8090
+```
+
+Expected response headers:
+
+```text
+Content-Security-Policy
+X-Frame-Options
+X-Content-Type-Options
+Referrer-Policy
+Permissions-Policy
+Strict-Transport-Security
+```
+
## DevTools Panels
Use Chrome DevTools:
diff --git a/experiments/module-10-security-demo/index.html b/experiments/module-10-security-demo/index.html
index 34c5ea2..9e667bf 100644
--- a/experiments/module-10-security-demo/index.html
+++ b/experiments/module-10-security-demo/index.html
@@ -5,15 +5,7 @@
Module 10 Security Inspection Demo
-
+
diff --git a/experiments/module-10-security-demo/server.py b/experiments/module-10-security-demo/server.py
new file mode 100644
index 0000000..bcc88cf
--- /dev/null
+++ b/experiments/module-10-security-demo/server.py
@@ -0,0 +1,49 @@
+from __future__ import annotations
+
+import argparse
+import os
+from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
+from pathlib import Path
+
+
+SECURITY_HEADERS = {
+ "Content-Security-Policy": (
+ "default-src 'self'; "
+ "base-uri 'self'; "
+ "connect-src 'self'; "
+ "form-action 'self'; "
+ "frame-ancestors 'none'; "
+ "img-src 'self' data:; "
+ "object-src 'none'; "
+ "script-src 'self'; "
+ "style-src 'self'"
+ ),
+ "X-Frame-Options": "DENY",
+ "X-Content-Type-Options": "nosniff",
+ "Referrer-Policy": "no-referrer-when-downgrade",
+ "Permissions-Policy": "geolocation=(), microphone=(), camera=()",
+ "Strict-Transport-Security": "max-age=31536000; includeSubDomains",
+}
+
+
+class SecurityHeadersHandler(SimpleHTTPRequestHandler):
+ def end_headers(self) -> None:
+ for header, value in SECURITY_HEADERS.items():
+ self.send_header(header, value)
+ super().end_headers()
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(description="Serve Module 10 demo locally.")
+ parser.add_argument("--host", default="127.0.0.1")
+ parser.add_argument("--port", type=int, default=8090)
+ args = parser.parse_args()
+
+ os.chdir(Path(__file__).resolve().parent)
+ server = ThreadingHTTPServer((args.host, args.port), SecurityHeadersHandler)
+ print(f"Serving Module 10 demo at http://{args.host}:{args.port}/")
+ server.serve_forever()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/experiments/module-10-security-demo/styles.css b/experiments/module-10-security-demo/styles.css
new file mode 100644
index 0000000..3a8bbe4
--- /dev/null
+++ b/experiments/module-10-security-demo/styles.css
@@ -0,0 +1,41 @@
+body {
+ font-family: system-ui, sans-serif;
+ margin: 2rem;
+ line-height: 1.5;
+ max-width: 960px;
+}
+
+header,
+section {
+ margin-bottom: 1.5rem;
+}
+
+.card {
+ border: 1px solid #ccc;
+ border-radius: 12px;
+ padding: 1rem;
+ margin: 0.75rem 0;
+}
+
+code {
+ background: #f4f4f4;
+ padding: 0.1rem 0.3rem;
+ border-radius: 4px;
+}
+
+button {
+ padding: 0.6rem 1rem;
+ border-radius: 8px;
+ border: 1px solid #555;
+ background: white;
+ cursor: pointer;
+}
+
+button:focus-visible {
+ outline: 3px solid #000;
+ outline-offset: 3px;
+}
+
+.status {
+ font-weight: 700;
+}