Audited cloud file upload, AIUI iframe, context broker, FileBrowser proxy, and RPC endpoints. Key findings: - XSS: safe (Vue template escaping) - Context broker: properly validates origins - FileBrowser: medium risk path traversal (client-side), token in URLs - CSRF: high risk (no tokens, but mitigated by JSON content type) - Nginx: missing security headers Full report: docs/security-audit-2026-03-05.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9.2 KiB
Archipelago Security Audit Report
Date: 2026-03-05 Scope: Cloud file upload, AIUI iframe, context broker, FileBrowser proxy, RPC endpoints Auditor: Automated code audit (Claude)
Executive Summary
The Archipelago frontend is well-protected against XSS thanks to Vue's default template escaping. The context broker has correct origin validation. However, there are path traversal risks in the FileBrowser client, CSRF gaps in the RPC layer, and token exposure in download URLs. None are remotely exploitable without LAN access, but they should be addressed before public-facing deployment.
| Area | Risk | Severity |
|---|---|---|
| XSS in file names | Protected by Vue escaping | None |
| Context broker origin | Correctly validated | None |
| AIUI iframe sandbox | Properly configured | None |
| FileBrowser path traversal | Client-side paths not sanitized | Medium |
| FileBrowser token in URLs | Token exposed in query strings | Medium |
| CORS policy | Access-Control-Allow-Origin: * on some endpoints |
High |
| CSRF tokens | No CSRF mechanism exists | High |
| Nginx security headers | Missing X-Frame-Options, CSP, nosniff | Medium |
| X-Frame-Options stripping | All app proxies strip framing protection | Medium |
1. XSS in File Names — NO ISSUES FOUND
All file name rendering uses Vue's {{ }} text interpolation, which auto-escapes HTML:
CloudFolder.vue— section names via{{ section?.name }}FileCard.vue:34—{{ item.name }}(text interpolation)FileCardGrid.vue:65—{{ item.name }}(text interpolation)CloudToolbar.vue— breadcrumbs via{{ crumb.name }}Home.vue— only numeric metrics displayed (storage bytes, folder counts)
No use of v-html, innerHTML, or other unsafe rendering anywhere in the cloud feature. A file named <script>alert(1)</script>.txt renders as literal escaped text.
Upload handling in CloudFolder.vue:302-308 passes raw File objects (not strings), and filebrowser-client.ts:74 properly URL-encodes file names with encodeURIComponent().
Verdict: Safe. Vue's default escaping provides robust XSS protection.
2. AIUI Iframe & Context Broker — NO ISSUES FOUND
Iframe Sandbox
Chat.vue:34 uses sandbox="allow-scripts allow-same-origin allow-forms" — the minimum permissions needed for AIUI to function. allow-same-origin is required for postMessage origin validation to work.
Origin Validation
contextBroker.ts:27-34 correctly derives the allowed origin:
const url = new URL(aiuiUrl, window.location.origin)
this.allowedOrigin = url.origin
contextBroker.ts:65 validates every incoming message:
if (event.origin !== this.allowedOrigin) return
contextBroker.ts:475 sends responses with explicit target origin:
this.iframe.value.contentWindow.postMessage(msg, this.allowedOrigin)
For same-origin AIUI (production: /aiui/), this.allowedOrigin equals window.location.origin, which is correct.
Chat.vue:98-108 also validates origin for the ready message independently.
Verdict: Properly secured. Double origin validation, explicit target origins on postMessage.
3. FileBrowser Path Traversal — MEDIUM RISK
Finding: Paths not URL-encoded in API calls
filebrowser-client.ts constructs API URLs with raw path strings:
- Line 55:
fetch(\${this.baseUrl}/api/resources${safePath}`)` - Line 69:
return \${this.baseUrl}/api/raw${safePath}?auth=${this.token}`` - Line 100:
fetch(\${this.baseUrl}/api/resources${safePath}`)` - Line 127:
fetch(\${this.baseUrl}/api/resources${safePath}`)`
The safePath helper only prepends / if missing — it does NOT reject .. sequences or canonicalize paths.
Mitigating Factors
- FileBrowser runs in a container with volume mount
/var/lib/archipelago/filebrowser:/srv— the daemon itself enforces path boundaries - Nginx proxies to
127.0.0.1:8083— not externally accessible - Paths come from FileBrowser API responses (server-generated), not direct user input in most cases
- LAN-only access — attacker needs network access
Recommendations
- Add path validation in
filebrowser-client.ts:function sanitizePath(path: string): string { const normalized = path.split('/').filter(p => p !== '..' && p !== '.').join('/') return normalized.startsWith('/') ? normalized : `/${normalized}` } - URL-encode path components in download URLs
- Verify FileBrowser container uses
--read-onlyfilesystem
4. FileBrowser Token Exposure — MEDIUM RISK
Finding: JWT in query parameters
filebrowser-client.ts:69 exposes the auth token in download URLs:
return `${this.baseUrl}/api/raw${safePath}?auth=${this.token}`
This token appears in:
- Browser history
- Nginx access logs
- HTTP Referer headers
- DOM (in
<a href="...">elements)
Recommendation
Use the X-Auth header (already used for other requests at line 49) instead of query parameters. For downloads, use a short-lived download token or proxy through a backend endpoint.
5. CORS Policy — HIGH RISK (LAN-scoped)
Finding: Wildcard CORS on multiple endpoints
core/archipelago/src/api/handler.rs:15 defines const CORS_ANY: &str = "*" and applies it to:
/api/container/logs(lines 108, 118)/archipelago/node-message(line 142)/electrs-status(line 153)/proxy/lnd/(lines 173, 183)
The main /rpc/v1 endpoint does NOT set CORS headers (more restrictive by default).
Mitigating Factors
- Server is LAN-only (no public internet exposure)
- Main RPC endpoint is not affected
credentials: 'include'withAccess-Control-Allow-Origin: *is actually blocked by browsers (CORS spec requires specific origin when credentials are used)
Recommendations
- Replace
*with the specific Archipelago origin - Add
Access-Control-Allow-Credentials: trueonly where needed - Handle OPTIONS preflight requests properly
6. CSRF Protection — HIGH RISK (LAN-scoped)
Finding: No CSRF mechanism
- No CSRF token generation or validation
- No
X-Requested-Withcustom header requirement - No
SameSitecookie attribute - No
Originheader validation in the RPC handler
Mitigating Factors
- JSON-RPC requires
Content-Type: application/json— this is NOT a "simple" CORS content type, so browsers send preflight OPTIONS requests for cross-origin POSTs. Since the backend returns 404 for OPTIONS, cross-origin JSON-RPC calls are effectively blocked. - LAN-only access — attacker needs to be on the same network
- Session cookies — authentication appears to use session cookies from
/rpc/v1, but an attacker on the LAN could craft a same-origin request
Recommendations
- Add
X-Requested-With: XMLHttpRequestheader inrpc-client.tsand validate it server-side - Implement synchronizer token pattern for state-changing operations
- Validate
Originheader in the Rust handler
7. Nginx Security Headers — MEDIUM RISK
Finding: Missing standard security headers
The nginx config lacks:
X-Content-Type-Options: nosniffReferrer-Policy: strict-origin-when-cross-originContent-Security-Policyfor the main UI
Finding: X-Frame-Options stripped from all app proxies
Every app proxy block includes:
proxy_hide_header X-Frame-Options;
proxy_hide_header Content-Security-Policy;
This is intentional (apps are embedded in iframes), but increases clickjacking surface.
Recommendations
- Add security headers to the main location blocks:
add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; - Add
Content-Security-Policywithframe-ancestors 'self'for the main UI - For app proxies, replace stripped headers with
X-Frame-Options: SAMEORIGINto allow Archipelago iframing but block external sites
Priority Action Items
| Priority | Action | Effort |
|---|---|---|
| 1 | Add X-Requested-With header to RPC client + validate server-side |
Low |
| 2 | Add nginx security headers (nosniff, referrer-policy) | Low |
| 3 | Replace X-Frame-Options stripping with SAMEORIGIN override |
Low |
| 4 | Sanitize FileBrowser paths client-side | Low |
| 5 | Move FileBrowser download auth from URL to header | Medium |
| 6 | Replace wildcard CORS with specific origins | Medium |
| 7 | Implement CSRF synchronizer tokens | High |
| 8 | Add Content-Security-Policy header | High |
Files Audited
neode-ui/src/views/Chat.vueneode-ui/src/views/CloudFolder.vueneode-ui/src/views/Home.vueneode-ui/src/services/contextBroker.tsneode-ui/src/api/filebrowser-client.tsneode-ui/src/api/rpc-client.tsneode-ui/src/api/container-client.tsneode-ui/src/stores/cloud.tsneode-ui/src/stores/aiPermissions.tsneode-ui/src/types/aiui-protocol.tsneode-ui/src/components/cloud/FileCard.vueneode-ui/src/components/cloud/FileCardGrid.vueneode-ui/src/components/cloud/CloudToolbar.vuecore/archipelago/src/api/handler.rscore/archipelago/src/api/rpc/mod.rscore/archipelago/src/api/rpc/auth.rscore/archipelago/src/api/rpc/package.rsimage-recipe/configs/nginx-archipelago.conf