Enhance security and localization features across the application
- Added rehype-sanitize plugin to the markdown configuration for improved security against XSS attacks. - Updated environment variables in the codebase to include new configurations for SMTP and monitoring. - Implemented secure headers in server and Nginx configurations to bolster security. - Refactored email handling to prevent spoofing by ensuring safe sender addresses. - Improved localization by updating language persistence and button components for better user experience. - Enhanced the uptime API and contact form with better error handling and logging practices. - Updated dependencies in package.json and package-lock.json for better performance and security.
This commit is contained in:
135
SECURITY_AUDIT.md
Normal file
135
SECURITY_AUDIT.md
Normal file
@@ -0,0 +1,135 @@
|
||||
## Executive summary
|
||||
|
||||
Astro SSR is enabled; endpoints handle contact, uptime, and commits. Main risks were HTML injection and missing headers. Minimal edits applied: enable `rehype-sanitize`, drop Mermaid CDN, avoid `innerHTML` where not needed, reduce PII in logs, soften Gemini init, and add headers/CSP snippets for both static hosts and Nginx.
|
||||
|
||||
## Risk table
|
||||
|
||||
| ID | File/Path | Issue | Severity | Why it matters | Fix |
|
||||
|---|---|---|---|---|---|
|
||||
| S1 | `astro.config.ts` | MD/MDX not sanitized | High | XSS via content/slots | Add `rehype-sanitize` |
|
||||
| S2 | `src/pages/[lang]/topdesk.astro` | CDN Mermaid + `innerHTML` | High | CSP bypass/XSS | Use local `mermaid`, keep injection controlled |
|
||||
| S3 | `src/components/widgets/ModernEducation.astro` | Title via `innerHTML` | Med | XSS if title contains HTML | Use `textContent` |
|
||||
| S4 | `src/pages/[lang]/index.astro` | `set:html` for line breaks | Low | Avoidable HTML injection | Render lines with `<br/>` |
|
||||
| S5 | `src/pages/api/contact.ts` | PII in logs | Med | Privacy/legal | Remove PII logs |
|
||||
| S6 | `src/utils/gemini-spam-check.ts` | Throws when key missing | Med | Startup failure | Lazy init, fail open |
|
||||
| S7 | `public/_headers`, `nginx/nginx.conf` | Missing headers/CSP | High | XSS/MIME sniffing | Add strict headers and CSP |
|
||||
| S8 | `src/middleware.ts` | 301 for lang redirect | Low | Sticky cache | Use 302 |
|
||||
| S9 | `src/pages/api/uptime.ts` | Verbose error detail | Low | Info leakage | Return generic details |
|
||||
|
||||
## Detailed findings with references
|
||||
|
||||
- S1: `astro.config.ts` now includes `rehype-sanitize` alongside existing plugins.
|
||||
- S2: `src/pages/[lang]/topdesk.astro` imports `mermaid` locally and renders SVG without remote CDN.
|
||||
- S3/S4: Components/pages adjusted to avoid `innerHTML`/`set:html` for simple cases.
|
||||
- S5/S6/S9: API/util logs are minimal; no PII or stack traces in prod responses.
|
||||
- S7: Headers added for both Nginx and static hosting.
|
||||
- S8: Middleware redirect switched to 302.
|
||||
|
||||
## Applied patches (concise)
|
||||
|
||||
1) Enable sanitize in Astro markdown
|
||||
|
||||
```diff
|
||||
--- a/astro.config.ts
|
||||
+++ b/astro.config.ts
|
||||
@@
|
||||
markdown: {
|
||||
remarkPlugins: [readingTimeRemarkPlugin],
|
||||
- rehypePlugins: [responsiveTablesRehypePlugin, lazyImagesRehypePlugin],
|
||||
+ rehypePlugins: [rehypeSanitize, responsiveTablesRehypePlugin, lazyImagesRehypePlugin],
|
||||
},
|
||||
```
|
||||
|
||||
2) Remove CDN and render Mermaid locally
|
||||
|
||||
```diff
|
||||
--- a/src/pages/[lang]/topdesk.astro
|
||||
+++ b/src/pages/[lang]/topdesk.astro
|
||||
@@
|
||||
- const mermaid = await import('https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs');
|
||||
+ const mermaidModule = await import('mermaid');
|
||||
+ const mermaid = mermaidModule.default || mermaidModule;
|
||||
@@
|
||||
- const { svg } = await mermaid.default.render(id, mermaidCode);
|
||||
+ const { svg } = await mermaid.render(id, mermaidCode);
|
||||
```
|
||||
|
||||
3) Avoid `innerHTML` for titles; use `textContent`
|
||||
|
||||
```diff
|
||||
--- a/src/components/widgets/ModernEducation.astro
|
||||
+++ b/src/components/widgets/ModernEducation.astro
|
||||
@@
|
||||
- const tempDiv = document.createElement('div');
|
||||
- tempDiv.innerHTML = title;
|
||||
- const cleanTitle = tempDiv.textContent || tempDiv.innerText || title;
|
||||
+ const cleanTitle = String(title);
|
||||
@@
|
||||
- document.getElementById('educationModalTitle').innerHTML = title;
|
||||
+ const titleEl = document.getElementById('educationModalTitle');
|
||||
+ if (titleEl) titleEl.textContent = cleanTitle;
|
||||
```
|
||||
|
||||
4) Replace trivial `set:html`
|
||||
|
||||
```diff
|
||||
--- a/src/pages/[lang]/index.astro
|
||||
+++ b/src/pages/[lang]/index.astro
|
||||
@@
|
||||
- <div class="text-lg pt-4 method-highlights" set:html={explosive.approach.highlights.replace(/\n/g, '<br/>')}>
|
||||
- </div>
|
||||
+ <div class="text-lg pt-4 method-highlights">
|
||||
+ {explosive.approach.highlights.split('\n').map((line) => (
|
||||
+ <>
|
||||
+ {line}
|
||||
+ <br />
|
||||
+ </>
|
||||
+ ))}
|
||||
+ </div>
|
||||
```
|
||||
|
||||
5) Reduce PII logging; lazy Gemini init
|
||||
|
||||
```diff
|
||||
--- a/src/pages/api/contact.ts
|
||||
+++ b/src/pages/api/contact.ts
|
||||
@@
|
||||
- console.log('Client IP:', ipAddress);
|
||||
+ // avoid logging IP in production
|
||||
@@
|
||||
- console.log('Rate limit exceeded');
|
||||
+ console.warn('Rate limit exceeded for contact endpoint');
|
||||
```
|
||||
|
||||
```diff
|
||||
--- a/src/utils/gemini-spam-check.ts
|
||||
+++ b/src/utils/gemini-spam-check.ts
|
||||
@@
|
||||
-const GEMINI_API_KEY = process.env.GEMINI_API_KEY;
|
||||
-if (!GEMINI_API_KEY) { throw new Error('...') }
|
||||
-const genAI = new GoogleGenerativeAI(GEMINI_API_KEY);
|
||||
+const GEMINI_API_KEY = process.env.GEMINI_API_KEY;
|
||||
+const genAI = GEMINI_API_KEY ? new GoogleGenerativeAI(GEMINI_API_KEY) : null;
|
||||
```
|
||||
|
||||
6) Headers/CSP
|
||||
|
||||
- Static host `_headers` additions in `public/_headers` (applied).
|
||||
- Nginx secure headers added in `nginx/nginx.conf` (applied).
|
||||
|
||||
Starter CSP (static host file already contains a commented safer baseline):
|
||||
|
||||
```text
|
||||
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://cdn.pixabay.com https://raw.githubusercontent.com; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
|
||||
```
|
||||
|
||||
If you keep inline scripts, use Astro nonces or move to external files to avoid `'unsafe-inline'` for scripts entirely.
|
||||
|
||||
## Follow-up tasks
|
||||
|
||||
- Add CI with: `npm ci`, `npm run check`, `npm run build`, `npm audit --omit=dev`.
|
||||
- Consider switching `output: 'server'` to prerender where feasible for brochure pages; keep APIs SSR.
|
||||
- Review all remaining `set:html` occurrences and ensure content sources are trusted/sanitized.
|
||||
- Document `.env` variables; `.env` example added to README.
|
||||
|
||||
|
Reference in New Issue
Block a user