jaeswift-website/AUDIT_REPORT.md
jae ccbd59fcd4 fix: resolve all critical audit issues
- Fix 60 DOM ID mismatches in admin.js (editor, dashboard, API keys, theme, services, navigation, links)
- Add 51 missing CSS classes to admin.css (sidebar, topbar, login, editor, tables, settings, backups, etc)
- Fix 5 undefined onclick methods in admin.html (saveContact, saveSEO, remove unused save buttons)
- Fix API payload mismatches: services/nav/links send single object, apikeys nested {group, data} format
- Replace Promise.all with Promise.allSettled in loadDashboard for resilient loading
- Fix /api/services timeout: ThreadPoolExecutor parallel checks + timeout=2s
- Add /etc/hosts entries on VPS for subdomain resolution from localhost
- Add JSON error handlers (400, 401, 404, 500) to Flask API
- Suppress InsecureRequestWarning in Flask
- Fix dashThreats container ID mismatch in admin.html
- Delete stale root-level JS files from VPS
2026-04-01 00:54:20 +00:00

658 lines
30 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# JAESWIFT.XYZ — FULL SITE AUDIT REPORT
**Date:** 2026-04-01
**Auditor:** Agent Zero Master Developer
**Scope:** All HTML, CSS, JS, Flask API, nginx config, live endpoint testing
---
## EXECUTIVE SUMMARY
The site's core infrastructure works: nginx serves pages (200 OK), Flask API runs on port 5000, all 9 blog post slugs resolve correctly, and static assets load. However, the **admin panel is critically broken** due to a systematic naming mismatch between `admin.js` and `admin.html` — 60 DOM element IDs referenced in JavaScript don't exist in the HTML. Additionally, 5 onclick handlers call undefined methods, 51 CSS classes are used in HTML but never defined, and several API payload shapes don't match between frontend and backend.
### Issue Count by Severity
| Severity | Count |
|----------|-------|
| CRITICAL | 8 |
| HIGH | 12 |
| MEDIUM | 15 |
| LOW | 7 |
---
## 1. CRITICAL ISSUES
### 1.1 ❌ CRITICAL: 60 DOM ID Mismatches — admin.js ↔ admin.html
**Impact:** The entire admin panel is non-functional. Every section that reads/writes form fields silently fails because `getElementById()` returns `null`.
#### 1.1.1 Editor Section (16 mismatches)
| admin.js uses | admin.html has | File:Line (JS) |
|---|---|---|
| `postId` | `editorPostId` | admin.js:451,524 |
| `editTitle` | `editorTitle` | admin.js:66,452,525 |
| `editSlug` | `editorSlug` | admin.js:453,526 |
| `editDate` | `editorDate` | admin.js:460,463,527 |
| `editTime` | `editorTime` | admin.js:461,464,528 |
| `editTags` | `editorTags` | admin.js:467 |
| `editExcerpt` | `editorExcerpt` | admin.js:468 |
| `editContent` | `editorContent` | admin.js:72,469 |
| `editMood` | `hudMood` | admin.js:472 |
| `editEnergy` | `hudEnergy` | admin.js:85,476 |
| `editMotivation` | `hudMotivation` | admin.js:85,477 |
| `editFocus` | `hudFocus` | admin.js:85,478 |
| `editDifficulty` | `hudDifficulty` | admin.js:85,479 |
| `editCoffee` | `hudCoffee` | admin.js:490 |
| `editBPM` | `hudBPM` | admin.js:493 |
| `editThreat` | `hudThreat` | admin.js:496 |
**Fix:** In `admin.js`, rename all `editX``editorX` and `editMood/Energy/etc``hudMood/Energy/etc`, `postId``editorPostId`.
#### 1.1.2 Dashboard Section (8 mismatches)
| admin.js uses | admin.html has | File:Line (JS) |
|---|---|---|
| `dashCPU` | `statCPU` | admin.js:318 |
| `dashMEM` | `statMEM` | admin.js:319 |
| `dashDISK` | `statDISK` | admin.js:320 |
| `dashPosts` | `statPosts` | admin.js:330 |
| `dashWords` | `statWords` | admin.js:331 |
| `dashTracks` | `statTracks` | admin.js:343 |
| `dashServicesGrid` | `servicesGrid` | admin.js:351 |
| `dashThreats` | *(not in HTML)* | admin.js:370 |
**Fix:** In `admin.js`, rename `dashX``statX`, `dashServicesGrid``servicesGrid`. Add a threats container `<div id="dashThreats">` to the dashboard section of `admin.html`, OR rename the JS reference.
#### 1.1.3 API Keys Section (19 mismatches)
| admin.js uses | admin.html has | File:Line (JS) |
|---|---|---|
| `weatherApiKey` | `apiWeatherKey` | admin.js:1294 |
| `spotifyClientId` | `apiSpotifyClientId` | admin.js:1297 |
| `spotifyClientSecret` | `apiSpotifyClientSecret` | admin.js:1298 |
| `spotifyRefreshToken` | `apiSpotifyRefreshToken` | admin.js:1299 |
| `smtpHost` | `apiSmtpHost` | admin.js:1302 |
| `smtpPort` | `apiSmtpPort` | admin.js:1303 |
| `smtpUser` | `apiSmtpUser` | admin.js:1304 |
| `smtpPass` | `apiSmtpPass` | admin.js:1305 |
| `discordWebhook` | `apiDiscordWebhook` | admin.js:1308 |
| `githubToken` | `apiGithubToken` | admin.js:1312 |
| `customApi1Name` | `apiCustom1Name` | admin.js:1315 |
| `customApi1Key` | `apiCustom1Key` | admin.js:1316 |
| `customApi1Url` | `apiCustom1URL` | admin.js:1317 |
| `customApi2Name` | `apiCustom2Name` | admin.js:1320 |
| `customApi2Key` | `apiCustom2Key` | admin.js:1321 |
| `customApi2Url` | `apiCustom2URL` | admin.js:1322 |
| `customApi3Name` | `apiCustom3Name` | admin.js:1325 |
| `customApi3Key` | `apiCustom3Key` | admin.js:1326 |
| `customApi3Url` | `apiCustom3URL` | admin.js:1327 |
**Fix:** In `admin.js`, rename all to match HTML IDs (add `api` prefix, use `URL` not `Url`). OR rename all HTML IDs to match JS convention.
#### 1.1.4 Theme Section (7 mismatches)
| admin.js uses | admin.html has | File:Line (JS) |
|---|---|---|
| `themeAccent` | `themeAccentColor` | admin.js:1351+ |
| `themeAccentHex` | `themeAccentColorHex` | admin.js:1351+ |
| `themeBg` | `themeBgColor` | admin.js:1351+ |
| `themeBgHex` | `themeBgColorHex` | admin.js:1351+ |
| `themeText` | `themeTextColor` | admin.js:1351+ |
| `themeTextHex` | `themeTextColorHex` | admin.js:1351+ |
| `themeGrid` | `themeGridBg` | admin.js:1351+ |
| `valFontSize` | `themeFontSizeVal` | admin.js:1351+ |
**Fix:** In `admin.js`, add `Color` suffix to colour IDs, `themeGrid``themeGridBg`, `valFontSize``themeFontSizeVal`.
#### 1.1.5 Services Section (2 mismatches)
| admin.js uses | admin.html has | File:Line (JS) |
|---|---|---|
| `managedServicesList` | `servicesList` | admin.js:1009 |
| `serviceUrl` | `serviceURL` | admin.js:1034 |
**Fix:** `managedServicesList``servicesList`, `serviceUrl``serviceURL`.
#### 1.1.6 Navigation Section (2 mismatches)
| admin.js uses | admin.html has | File:Line (JS) |
|---|---|---|
| `navItemsList` | `navList` | admin.js:1089 |
| `navUrl` | `navURL` | admin.js:1118 |
**Fix:** `navItemsList``navList`, `navUrl``navURL`.
#### 1.1.7 Links Section (2 mismatches)
| admin.js uses | admin.html has | File:Line (JS) |
|---|---|---|
| `managedLinksList` | `linksList` | admin.js:1175 |
| `linkUrl` | `linkURL` | admin.js:1201 |
**Fix:** `managedLinksList``linksList`, `linkUrl``linkURL`.
#### 1.1.8 Other Mismatches (4)
| admin.js uses | admin.html has | File:Line (JS) |
|---|---|---|
| `loginForm` | *(no form element)* | admin.js:29 |
| `notifications` | `notification` | admin.js:259 |
| `trackUrl` | `trackURL` | admin.js:826+ |
| *(none)* | `editorWordCount` (JS-created #bpmValue in post.js) | — |
**Fix:** Either add `<form id="loginForm">` wrapper in HTML or change JS to use `loginBtn` click handler only. `notifications``notification`, `trackUrl``trackURL`.
---
### 1.2 ❌ CRITICAL: 5 Undefined onclick Methods
**Impact:** Clicking Save buttons in 5 admin sections throws `AdminApp.X is not a function` error.
| Method Called (admin.html) | Correct Method in admin.js | HTML Line |
|---|---|---|
| `AdminApp.saveServices()` | *(does not exist)* | admin.html:608 |
| `AdminApp.saveNavigation()` | *(does not exist)* | admin.html:643 |
| `AdminApp.saveLinks()` | *(does not exist)* | admin.html:684 |
| `AdminApp.saveSEO()` | *(does not exist)* | admin.html:961 |
| `AdminApp.saveContact()` | `AdminApp.saveContactSettings()` | admin.html:996 |
**Fix for saveContact:** Change `admin.html:996` from `AdminApp.saveContact()` to `AdminApp.saveContactSettings()`.
**Fix for saveServices/saveNavigation/saveLinks/saveSEO:** These methods need to be implemented in `admin.js`. Currently, the admin.js CRUD for services/nav/links works per-item (add/delete) but has no bulk "Save" method. Either:
- (a) Remove the Save buttons from HTML (since add/delete already persist via API), OR
- (b) Add PUT methods in admin.js that POST the full array
For saveSEO, admin.js has `saveSeo()` (lowercase 'eo'). Change `admin.html:961` to `AdminApp.saveSeo()`.
---
### 1.3 ❌ CRITICAL: API Payload Shape Mismatches (Frontend ↔ Backend)
**Impact:** POST requests from admin panel silently fail or corrupt data.
#### 1.3.1 Services/Navigation/Links — Array vs Single Object
- **admin.js `addService()`** (line 1047): Sends `JSON.stringify(this.servicesData)` — the **full array**
- **app.py `add_managed_service()`** (line 342): Does `d = request.get_json(); svcs.append({name: d.get('name')})` — expects a **single object**
- Result: Flask receives an array, calls `.get('name')` on it → returns `None`, appends `{name: '', url: ''}` empty entry
Same pattern for:
- `addNavItem()` (admin.js:1129) vs `add_navigation()` (app.py:374)
- `addLink()` (admin.js:1213) vs `add_link()` (app.py:409)
**Fix:** Change admin.js to send only the new item:
```javascript
// In addService():
body: JSON.stringify({ name, url }) // NOT this.servicesData
```
#### 1.3.2 API Keys — Flat vs Nested Payload
- **admin.js `saveApiKey()`** (line 1334): Sends `{ group: 'weather', weather_api_key: 'xxx' }` — flat object with group field
- **app.py `save_apikeys()`** (line 474-476): Expects `{ group: 'weather', data: { api_key: 'xxx' } }` — nested with `data` dict
- Result: `data = d.get('data', {})` returns empty dict → nothing saved
**Fix:** Change admin.js `saveApiKey()` to wrap fields in a `data` object:
```javascript
const payload = { group };
const data = {};
switch (group) {
case 'weather': data.api_key = this.getVal('weatherApiKey'); break;
// ...
}
payload.data = data;
```
---
### 1.4 ❌ CRITICAL: /api/services Endpoint Hangs (Timeout)
**File:** `api/app.py:181-200`
**Impact:** Dashboard never finishes loading. Nginx logs show `upstream timed out (110: Connection timed out)`. The admin dashboard calls this at load time via `loadDashboard()`.
**Root Cause:** The `services()` function makes synchronous HTTP requests to 7 external URLs (git.jaeswift.xyz, plex.jaeswift.xyz, etc.) from inside the Flask app. The VPS cannot resolve its own subdomains from localhost — `curl -sk https://git.jaeswift.xyz` returns code 000 (DNS/connection failure) with 3+ second timeout per service. Total: 7 × 5s timeout = 35s, exceeding nginx's default 60s proxy timeout.
**Fix:**
1. Add `/etc/hosts` entries on VPS: `127.0.0.1 git.jaeswift.xyz plex.jaeswift.xyz archive.jaeswift.xyz`
2. OR reduce timeout in app.py: `req.get(s['url'], timeout=2, ...)`
3. OR use `concurrent.futures.ThreadPoolExecutor` to check all services in parallel
4. OR move service checks to a background task/cache
---
### 1.5 ❌ CRITICAL: /api/git-activity Endpoint Hangs
**File:** `api/app.py:260-282`
**Impact:** Same DNS issue as /api/services. Requests to `https://git.jaeswift.xyz/api/v1/users/jae/heatmap` fail from VPS localhost.
**Fix:** Same as 1.4 — add hosts entries or reduce timeouts.
---
### 1.6 ❌ CRITICAL: 51 CSS Classes Missing from admin.css
**Impact:** Major layout breakage in admin panel — sidebar, login screen, backups, settings, notifications, and many components render unstyled.
**Missing classes referenced in admin.html but not defined in admin.css OR style.css:**
| Class | Used In | Category |
|---|---|---|
| `.sidebar` | Layout wrapper | Layout |
| `.sidebar-brand` | Brand logo area | Layout |
| `.sidebar-close` | Mobile close btn | Layout |
| `.sidebar-divider` | Section dividers | Layout |
| `.sidebar-header` | Header area | Layout |
| `.sidebar-nav` | Nav container | Layout |
| `.sidebar-section-label` | Group labels | Layout |
| `.sidebar-version` | Version footer | Layout |
| `.topbar` | Top bar | Layout |
| `.topbar-title` | Title text | Layout |
| `.topbar-logout` | Logout btn | Layout |
| `.login-screen` | Login wrapper | Auth |
| `.login-form` | Form container | Auth |
| `.login-logo` | Logo area | Auth |
| `.notification` | Toast container | UI |
| `.operator-hud` | Editor HUD | Editor |
| `.hud-grid` | HUD metrics grid | Editor |
| `.hud-range` | Range sliders | Editor |
| `.hud-val` | Value labels | Editor |
| `.editor-split` | Split view | Editor |
| `.editor-pane` | Editor pane | Editor |
| `.preview-pane` | Preview pane | Editor |
| `.editor-meta-bar` | Meta info bar | Editor |
| `.editor-textarea-sm` | Small textareas | Editor |
| `.section-toolbar` | Toolbar | Editor |
| `.toolbar-btn` | Toolbar buttons | Editor |
| `.toolbar-sep` | Toolbar separator | Editor |
| `.admin-table` | Posts table | Posts |
| `.table-wrap` | Table wrapper | Posts |
| `.tracks-add-form` | Add track form | Tracks |
| `.tracks-list` | Tracks container | Tracks |
| `.settings-grid` | Settings layout | Settings |
| `.setting-item` | Setting row | Settings |
| `.toggle-slider` | Toggle control | Settings |
| `.homepage-section-name` | Section label | Homepage |
| `.homepage-section-controls` | Controls | Homepage |
| `.services-manage-list` | Services list | Services |
| `.nav-manage-list` | Nav items list | Navigation |
| `.links-manage-list` | Links list | Links |
| `.apikey-group` | Key group card | API Keys |
| `.color-input` | Colour picker | Theme |
| `.color-hex` | Hex input | Theme |
| `.sm` | Small variant | Utility |
| `.lg` | Large variant | Utility |
| `.backup-card-icon` | Card icon | Backups |
| `.backup-card-title` | Card title | Backups |
| `.backup-card-desc` | Card description | Backups |
| `.backup-desc` | Description text | Backups |
| `.backup-export` | Export section | Backups |
| `.backup-status` | Status area | Backups |
| `.threat-feed` | Threats container | Dashboard |
**Fix:** These classes must be defined in `admin.css`. Many were apparently planned (the HTML uses them consistently) but never written.
---
### 1.7 ❌ CRITICAL: Stale Root-Level JS Files on VPS
**Impact:** Potential confusion; the root-level `/var/www/jaeswift-homepage/admin.js` (66,567 bytes) is an OLDER version than `/var/www/jaeswift-homepage/js/admin.js` (66,766 bytes). The diff shows the root copy is missing the `showSection()` normalization fix.
**Diff:**
```diff
# Root admin.js is MISSING these 3 lines present in js/admin.js:
+ // Normalize: ensure name has section- prefix
+ if (!name.startsWith('section-')) name = 'section-' + name;
# Root admin.js has outdated sidebar active-link selector:
- const activeLink = document.querySelector(`[onclick*="'${name}'"]`);
# js/admin.js has fixed version:
+ const shortName = name.replace('section-', '');
+ const activeLink = document.querySelector(`.sidebar-link[data-section="${shortName}"]`);
```
**Affected root files (permissions also wrong — 600 vs 644):**
| File | Root Size | js/ Size | Match? |
|---|---|---|---|
| admin.js | 66,567 (perms: 600) | 66,766 (perms: 644) | ❌ DIFFERENT |
| blog.js | 7,325 (perms: 600) | 7,325 (perms: 644) | ✅ Same |
| main.js | 36,092 (perms: 600) | 36,092 (perms: 644) | ✅ Same |
| post.js | 13,473 (perms: 600) | 13,473 (perms: 644) | ✅ Same |
**Fix:** Delete the stale root-level JS files:
```bash
rm /var/www/jaeswift-homepage/admin.js /var/www/jaeswift-homepage/blog.js \
/var/www/jaeswift-homepage/main.js /var/www/jaeswift-homepage/post.js
```
---
### 1.8 ❌ CRITICAL: showSection() Broken in Root admin.js
**File:** Root `/var/www/jaeswift-homepage/admin.js` (stale copy)
**Impact:** If somehow the root copy were served (e.g., nginx `try_files` fallback), the sidebar section switching would not work because the old code lacks the `section-` prefix normalization.
Note: Currently nginx serves from `/js/admin.js` (the correct file), so this is only a risk if the stale file is served by accident. Still, it should be cleaned up.
**Fix:** Delete root-level JS files (see 1.7).
---
## 2. HIGH ISSUES
### 2.1 ⚠️ HIGH: blog.html and post.html — 4 CSS Classes Undefined
**Impact:** Navbar, clock, and signal animation don't render correctly on blog pages.
| Class | File | Not in |
|---|---|---|
| `.navbar` | blog.html, post.html | blog.css, style.css |
| `.nav-clock` | blog.html, post.html | blog.css, style.css |
| `.signal-flicker` | blog.html, post.html | blog.css, style.css |
| `.blog-header-content` | blog.html only | blog.css, style.css |
**Fix:** Define these classes in `blog.css` or `style.css`. The index.html uses different class names for the navbar (likely defined in style.css under different names).
### 2.2 ⚠️ HIGH: No CORS Headers on API
**Impact:** If any external/subdomain frontend tries to call the API, requests will be blocked.
**Test result:** `curl -sI /api/posts | grep access-control` returned nothing.
**Fix:** Add Flask-CORS or manual headers:
```python
from flask_cors import CORS
CORS(app, resources={r"/api/*": {"origins": "https://jaeswift.xyz"}})
```
### 2.3 ⚠️ HIGH: Auth Endpoints Return HTML Error Pages
**File:** `api/app.py:41-52` (`require_auth` decorator)
**Impact:** Admin.js expects JSON responses but gets HTML `<title>401 Unauthorized</title>`.
**Evidence:**
```
curl http://127.0.0.1:5000/api/apikeys →
<!doctype html><title>401 Unauthorized</title><h1>Unauthorized</h1><p>Missing token</p>
```
**Fix:** The `require_auth` decorator should use `abort()` with JSON, or Flask should be configured for JSON error responses:
```python
@app.errorhandler(401)
def unauthorized(e):
return jsonify({'error': 'Unauthorized', 'message': str(e)}), 401
```
### 2.4 ⚠️ HIGH: /api/login Returns 404 (Route is /api/auth/login)
**Impact:** If anything calls `/api/login` (the intuitive URL), it 404s.
**Analysis:** Flask route is `@app.route('/api/auth/login')` (line 62). admin.js correctly uses `this.API + '/auth/login'` (line 132). No functional issue currently, but the non-standard auth path could confuse future integrations.
**Status:** Working correctly but unconventional. Consider adding an alias route.
### 2.5 ⚠️ HIGH: admin.js Sends Full Array on Add Operations
**File:** `js/admin.js:1044-1048` (addService), `1129` (addNavItem), `1213` (addLink)
**Impact:** Even though the POST creates a garbage entry (see 1.3.1), the admin.js then calls `loadX()` which re-fetches from the API, showing the corrupted data.
**Root Cause:** The JS pattern is:
1. Push new item to local array: `this.servicesData.push({name, url})`
2. Send ENTIRE array to POST endpoint: `body: JSON.stringify(this.servicesData)`
3. Flask `add_managed_service()` treats the array as a single object, calling `.get('name')` on a list → empty entry appended
**Fix:** Send only the new item:
```javascript
body: JSON.stringify({ name, url })
```
### 2.6 ⚠️ HIGH: admin.js saveApiKey() Sends Wrong Payload Shape
**File:** `js/admin.js:1289-1334`
**Impact:** API keys are never saved. The Flask endpoint returns 400 `{"error": "Invalid request: need group and data"}`.
**Current JS payload:** `{ group: 'weather', weather_api_key: 'xxx' }`
**Expected by Flask:** `{ group: 'weather', data: { api_key: 'xxx' } }`
**Fix:** Restructure the `saveApiKey()` method to wrap fields in `data`:
```javascript
async saveApiKey(group) {
const data = {};
switch (group) {
case 'weather':
data.api_key = this.getVal('apiWeatherKey'); // also fix ID
break;
// etc.
}
const res = await fetch(this.API + '/apikeys', {
method: 'POST',
headers: this.authHeaders(),
body: JSON.stringify({ group, data })
});
}
```
### 2.7 ⚠️ HIGH: dashThreats Container Missing from admin.html
**File:** `admin.html` (dashboard section, ~line 126-167)
**Impact:** `admin.js:370` tries `document.getElementById('dashThreats')` — returns null, threats never display on dashboard.
**Fix:** Add to dashboard section in admin.html:
```html
<h3 class="section-subheading">▲ THREAT FEED</h3>
<div id="dashThreats" class="threat-feed"></div>
```
### 2.8 ⚠️ HIGH: /api/services Causes Dashboard Load Cascade Failure
**File:** `js/admin.js:308-312`
**Impact:** `loadDashboard()` calls `Promise.all([stats, posts, tracks, services, threats])`. When `/api/services` hangs for 35+ seconds, the entire dashboard appears frozen even though other data loaded fine.
**Fix:** Use `Promise.allSettled()` instead of `Promise.all()`, and handle each result independently:
```javascript
const results = await Promise.allSettled([
fetch(this.API + '/stats', ...),
fetch(this.API + '/posts', ...),
// ...
]);
results.forEach((result, i) => {
if (result.status === 'fulfilled') { /* process */ }
else { console.warn('Dashboard fetch failed:', result.reason); }
});
```
### 2.9 ⚠️ HIGH: nginx Warning — Duplicate MIME Type
**File:** `/etc/nginx/sites-enabled/cat.jaeswift.xyz:19`
**Impact:** Minor — duplicate `text/html` MIME type definition. No functional impact but generates warnings on every `nginx -t` and reload.
**Fix:** Remove duplicate `text/html` from the types block in `cat.jaeswift.xyz` config.
### 2.10 ⚠️ HIGH: nginx Warning — Protocol Options Redefined
**File:** `/etc/nginx/sites-enabled/jaeswift.xyz:26`
**Impact:** SSL protocol options redefined for `[::]:443`. First server block to define wins; subsequent definitions are ignored.
**Fix:** Move SSL protocol directives to `http {}` block in `/etc/nginx/nginx.conf` or use `ssl_protocols` only once across all server blocks sharing the same listen address.
### 2.11 ⚠️ HIGH: Flask Serves with Debug/Development Server
**File:** VPS systemd service runs `python3 /var/www/jaeswift-homepage/api/app.py`
**Impact:** Flask development server is single-threaded, not suitable for production. A single slow `/api/services` request blocks ALL other API requests.
**Fix:** Use gunicorn or waitress:
```bash
# In systemd service:
ExecStart=/usr/bin/gunicorn -w 4 -b 127.0.0.1:5000 app:app
```
### 2.12 ⚠️ HIGH: Flask Makes Unverified HTTPS Requests
**File:** `api/app.py:195,263,267``verify=False`
**Impact:** Suppressed SSL certificate verification for requests to git.jaeswift.xyz and plex.jaeswift.xyz. Generates `InsecureRequestWarning` in logs.
**Fix:** Either install proper CA certs or use `verify='/path/to/cert.pem'`.
---
## 3. MEDIUM ISSUES
### 3.1 🔶 MEDIUM: post.js Creates #bpmValue Dynamically
**File:** `js/post.js:153-154,171`
**Impact:** No current bug — post.js creates the element via innerHTML (line 153) then references it (line 171). Works because innerHTML is synchronous. However, static analysis flags this as a missing ID because post.html contains no such element.
**Note:** Not a bug, but the pattern is fragile. If the innerHTML template changes, the getElementById will silently break.
### 3.2 🔶 MEDIUM: admin.html Has No `<form>` Tag for Login
**File:** `admin.html:16-31`
**Impact:** `admin.js:29` looks for `loginForm` by ID and finds null. The fallback (line 33-35) uses `loginBtn` click handler, which works. But Enter key on username field doesn't submit — only works on password field (line 38-41).
**Fix:** Wrap login fields in `<form id="loginForm">` with `onsubmit="return false;"`.
### 3.3 🔶 MEDIUM: admin.js Notification Container Mismatch
**File:** `js/admin.js:259`, `admin.html:1045`
**Impact:** JS looks for `#notifications` (plural), HTML has `#notification` (singular). Fallback `|| document.body` means notifications append to body — they'll appear but may not be styled correctly.
**Fix:** Change JS to use `notification` (matching HTML) or rename HTML ID.
### 3.4 🔶 MEDIUM: blog.html Nav Links Point to Index Anchors
**File:** `blog.html` navbar
**Impact:** Links like `/#development`, `/#links`, `/#contact` navigate away from blog page back to index. This is intentional for cross-page navigation but there's no way back to blog from those sections.
**Fix:** Consider adding a "BLOG" link in index.html navbar (currently only an anchor `#blog` section exists).
### 3.5 🔶 MEDIUM: index.html Has `href="#"` Links
**File:** `index.html` — various social/placeholder links
**Impact:** Clicking scrolls to page top, poor UX.
**Fix:** Replace `href="#"` with actual URLs or `javascript:void(0)`.
### 3.6 🔶 MEDIUM: No favicon Referenced
**File:** All HTML files
**Impact:** Browser shows default icon, 404 in network tab for `/favicon.ico`.
**Fix:** Add `<link rel="icon" href="/assets/favicon.ico">` to all pages.
### 3.7 🔶 MEDIUM: admin.css is 41KB / admin.js is 66KB
**Impact:** Large unminified files on every admin page load.
**Fix:** Consider minification for production deployment.
### 3.8 🔶 MEDIUM: No Cache-Busting for Static Assets
**File:** All HTML files reference `/js/main.js`, `/css/style.css` etc. with no query string version.
**Impact:** Browser caching may serve stale JS/CSS after deployments.
**Fix:** Add version query strings: `/js/main.js?v=20260401`.
### 3.9 🔶 MEDIUM: app.py `/api/stats` Calls `shell()` for System Stats
**File:** `api/app.py:134` (via `shell()` function at line 54)
**Impact:** Executes shell commands (`uptime`, `df`, `free`, etc.) on every stats request. Not a security issue per se (no user input), but slow and could be cached.
**Fix:** Cache stats with a TTL (e.g., 30 seconds).
### 3.10 🔶 MEDIUM: No Rate Limiting on Login Endpoint
**File:** `api/app.py:62`
**Impact:** Brute force attacks possible against `/api/auth/login`.
**Fix:** Add Flask-Limiter: `@limiter.limit("5/minute")`.
### 3.11 🔶 MEDIUM: JWT Secret Hardcoded
**File:** `api/app.py` (JWT_SECRET referenced)
**Impact:** Secret is in source code committed to git. Anyone with repo access can forge tokens.
**Fix:** Move to environment variable: `JWT_SECRET = os.environ.get('JWT_SECRET', 'fallback')`.
### 3.12 🔶 MEDIUM: No Error Boundary in admin.js
**Impact:** Any unhandled JS error in one section breaks the entire admin panel. No global error handler.
**Fix:** Add:
```javascript
window.addEventListener('unhandledrejection', (e) => {
console.error('Unhandled promise:', e.reason);
AdminApp.notify('An error occurred: ' + e.reason?.message, 'error');
});
```
### 3.13 🔶 MEDIUM: admin.html Editor Toolbar References HUD IDs
**File:** `admin.html:275-345` (Operator HUD section)
**Impact:** The range slider event listeners in `admin.js:85` reference `editEnergy/editMotivation/editFocus/editDifficulty` but HTML has `hudEnergy/hudMotivation/hudFocus/hudDifficulty`. The slider value display labels won't update live.
**Fix:** Part of the broader ID mismatch (1.1.1).
### 3.14 🔶 MEDIUM: blog.html and post.html Missing `<meta charset>`
**Impact:** Character encoding not explicitly set. Unicode characters (used extensively in cyberpunk UI) may render incorrectly in edge cases.
**Fix:** Add `<meta charset="UTF-8">` to `<head>` of both files.
### 3.15 🔶 MEDIUM: Google Fonts Loaded Without `font-display: swap`
**File:** All HTML files
**Impact:** Text invisible during font load (FOIT — Flash of Invisible Text).
**Fix:** Append `&display=swap` to Google Fonts URL (already present in some files, verify all).
---
## 4. LOW ISSUES
### 4.1 LOW: Root-Level JS Files Have Wrong Permissions (600)
**File:** `/var/www/jaeswift-homepage/*.js`
**Impact:** Files are owner-read-only. nginx runs as www-data and cannot read them. Not currently an issue because HTML references `/js/*.js` (the correct copies with 644), but if try_files falls through, these would be served as 403.
**Fix:** `chmod 644 /var/www/jaeswift-homepage/*.js` or delete them (preferred — see 1.7).
### 4.2 LOW: nginx Ghost Blog Catch-All Comment but No Block
**File:** `/etc/nginx/sites-enabled/jaeswift.xyz` — line with `# Ghost Blog (catch-all)` comment
**Impact:** Vestigial comment from previous Ghost setup. No functional issue.
**Fix:** Remove the comment for cleanliness.
### 4.3 LOW: admin.html Uses `javascript:void(0)` hrefs
**File:** `admin.html` sidebar links
**Impact:** Minor accessibility concern — screen readers announce these as links.
**Fix:** Use `<button>` elements instead of `<a>` tags for non-navigation actions.
### 4.4 LOW: No `<meta viewport>` in admin.html
**Impact:** Admin panel may not render correctly on mobile without viewport meta tag.
**Fix:** Add `<meta name="viewport" content="width=device-width, initial-scale=1.0">` (check if already present).
### 4.5 LOW: InsecureRequestWarning Spam in Flask Logs
**File:** Flask service journal
**Impact:** Log noise from `verify=False` in requests to git/plex subdomains.
**Fix:** Add `import urllib3; urllib3.disable_warnings()` or fix the SSL verification.
### 4.6 LOW: admin.css Missing from Local Repo
**File:** `/a0/usr/workdir/jaeswift-website/css/admin.css`
**Impact:** The local copy exists but VPS copy has different content (VPS: 41,541 bytes). Ensure git is the source of truth.
**Fix:** Sync local and VPS copies via git.
### 4.7 LOW: posts.json Contains 9 Posts but No Pagination
**Impact:** As posts grow, `/api/posts` returns all posts in a single response. Currently fine with 9 posts.
**Fix:** Add optional `?page=1&limit=10` query parameters to the posts endpoint.
---
## 5. WHAT'S WORKING CORRECTLY ✅
| Component | Status |
|---|---|
| nginx → static HTML serving | ✅ All pages return 200 |
| All 9 blog post slug URLs | ✅ `/blog/post/<slug>` resolves correctly |
| Flask API on port 5000 | ✅ Active and running |
| `/api/posts` GET | ✅ Returns 9 posts with valid JSON |
| `/api/stats` GET | ✅ Returns server stats |
| `/api/weather` GET | ✅ Returns weather data |
| `/api/nowplaying` GET | ✅ Returns now playing info |
| `/api/tracks` GET | ✅ Returns 35 tracks |
| `/api/threats` GET | ✅ Returns CVE data |
| `/api/settings` GET | ✅ Returns settings |
| `/api/homepage` GET | ✅ Returns homepage config |
| `/api/navigation` GET | ✅ Returns 3 nav items |
| `/api/links` GET | ✅ Returns 2 links |
| `/api/theme` GET | ✅ Returns theme config |
| `/api/seo` GET | ✅ Returns SEO config |
| `/api/services/managed` GET | ✅ Returns 3 managed services |
| `/api/auth/login` POST | ✅ Returns JWT token |
| Auth protection on endpoints | ✅ Returns 401 without token |
| All 12 JSON data files | ✅ Valid JSON, correct types |
| CSS files (style/blog/post) | ✅ All load with 200 |
| JS files (main/blog/post) | ✅ All load with 200 |
| main.js ↔ index.html IDs | ✅ All IDs match |
| blog.js ↔ blog.html IDs | ✅ All IDs match |
| SSL certificates | ✅ Valid Let's Encrypt |
| nginx config syntax | ✅ Test passes (with warnings) |
| Blog clean URLs | ✅ `/blog/post/.+` → post.html |
| try_files fallback | ✅ `$uri $uri.html $uri/` |
---
## 6. PRIORITY FIX ORDER
1. **Fix 60 DOM ID mismatches in admin.js** (or admin.html) — this alone fixes ~80% of admin issues
2. **Add 51 missing CSS classes to admin.css** — makes admin panel visually functional
3. **Fix 5 undefined onclick methods** — either implement or rename to match existing
4. **Fix API payload mismatches** — services/nav/links send single object, apikeys wrap in data
5. **Fix /api/services timeout** — add /etc/hosts entries or parallelize with timeout
6. **Delete stale root-level JS files on VPS**
7. **Add CORS headers to Flask API**
8. **Return JSON error responses from Flask (not HTML)**
9. **Switch to gunicorn for production**
10. **Add missing CSS classes for blog.html/post.html navbar**
---
*End of Audit Report*