Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
All notable changes to this project are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/).

## [0.4.1] — 2026-09-06

### Added
- Web-demo security headers (CSP, X-Frame-Options DENY, nosniff, referrer, permissions) in `web-demo/next.config.mjs`.
- SECURITY.md updated for header controls; BYOK OpenAI key remains browser-only (no first-party logging).

## [0.4.0] — 2026-08-21

### Added
Expand Down
31 changes: 23 additions & 8 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Security Assessment — AAROP

**Date:** 2026-08-21
**Date:** 2026-09-06
**Scope:** Auth, XSS, injection, secrets, CORS, supply chain, Python tool sandbox
**Context:** Public deploy is a **100% client-side Next.js demo** ([aarop.vercel.app](https://aarop.vercel.app/)). The Python `core/` engine is an **offline reference implementation** (no network, no API keys).

Expand All @@ -19,8 +19,9 @@
| CORS | **N/A** | No first-party API routes |
| Supply chain | **Accepted (Next 14)** | Stay on Next **14.x**. Do not `--force` onto 15/16 |
| Build config | **OK** | No `ignoreBuildErrors`. `tsc --noEmit` in CI |
| HTTP headers | **Added** | CSP, frame deny, nosniff, referrer, permissions (2026-09-06) |

**Overall (public Vercel demo):** Low residual risk — browser-only simulation, deterministic mock provider, no backend secrets, no auth boundary to break.
**Overall (public Vercel demo):** Low residual risk — browser-only simulation, deterministic mock provider, no backend secrets, no auth boundary to break. Not a claim of being unhackable; there is simply little server surface. Headers reduce casual XSS framing / MIME sniff risks.

**Overall (if someone pointed the Python engine at untrusted tools or a public network):** Medium — the demo `eval` calculator is charset-gated, not a real sandbox (no seccomp / containers).

Expand Down Expand Up @@ -83,7 +84,21 @@ The live demo can send **one** narration request to `https://api.openai.com/v1/c

---

## 5. HTTP surface
## 5. HTTP hardening (2026-09-06)

Headers in `web-demo/next.config.mjs`:

- `X-Content-Type-Options: nosniff`
- `X-Frame-Options: DENY`
- `Referrer-Policy: strict-origin-when-cross-origin`
- `Permissions-Policy: camera=(), microphone=(), geolocation=()`
- CSP: `default-src 'self'`; script/style `'unsafe-inline'` (Next); `connect-src 'self' https://api.openai.com` (BYOK only); `frame-ancestors 'none'`; `object-src 'none'`

No first-party API routes to rate-limit. OpenAI key stays in React state (not localStorage) and is sent only to `api.openai.com`.

---

## 6. HTTP surface

| Path | Auth | Notes |
|------|------|--------|
Expand All @@ -96,15 +111,15 @@ No `app/api/` routes. No Socket.io. No hello-world placeholder APIs.

---

## 6. Secrets & config hygiene
## 7. Secrets & config hygiene

- Root and `web-demo/` `.gitignore` exclude `.env`, `.env.*`.
- No `.env.example` is required — the demo has no server secrets.
- CI uses no repository secrets.

---

## 7. Dependency / supply chain
## 8. Dependency / supply chain

**This pass**
- Web demo stays on **Next 14** (patched 14.2.x). Advisories that only clear on Next 15/16 are **accepted residual risk**.
Expand All @@ -121,15 +136,15 @@ Do **not** run `npm audit fix --force` onto Next 15/16.

---

## 8. CORS & network
## 9. CORS & network

- Live demo is same-origin static/client. No CORS policy to get wrong.
- Python core makes **no** outbound calls (mock model provider).
- The optional browser→OpenAI call is a third-party fetch, not an AAROP API.

---

## 9. Residual risk & acceptance
## 10. Residual risk & acceptance

**Accepted for portfolio demo**
- No authentication on the public site.
Expand All @@ -144,7 +159,7 @@ Do **not** run `npm audit fix --force` onto Next 15/16.

---

## 10. How to re-test
## 11. How to re-test

```bash
# Python core
Expand Down
2 changes: 1 addition & 1 deletion web-demo/lib/aarop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ function mockComplete(prompt: string): { text: string; cost: number } {
return { text: `[mock-llm:${h.toString(16).slice(0, 8)}] ${prompt.slice(0, 48)}`, cost: +cost.toFixed(6) };
}

// optional real LLM (bring-your-own-key); used only for a single generation step
// optional real LLM (bring-your-own-key); browser→OpenAI only — never logged to a first-party API / never localStorage
export async function realComplete(
prompt: string, apiKey: string
): Promise<{ text: string; cost: number }> {
Expand Down
26 changes: 26 additions & 0 deletions web-demo/next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
/** @type {import('next').NextConfig} */
const securityHeaders = [
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "X-Frame-Options", value: "DENY" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
{
key: "Content-Security-Policy",
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: blob:",
"font-src 'self' data:",
// BYOK narration calls OpenAI from the browser only — never a first-party API
"connect-src 'self' https://api.openai.com",
"frame-ancestors 'none'",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
].join("; "),
},
];

const nextConfig = {
reactStrictMode: true,
async headers() {
return [{ source: "/:path*", headers: securityHeaders }];
},
};
export default nextConfig;
Loading