Files
archy/docs/security-audit-2026-03-05.md
Dorian d7c9f4917a docs: add security audit report for new features (Task 22)
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>
2026-03-05 08:49:22 +00:00

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

  1. FileBrowser runs in a container with volume mount /var/lib/archipelago/filebrowser:/srv — the daemon itself enforces path boundaries
  2. Nginx proxies to 127.0.0.1:8083 — not externally accessible
  3. Paths come from FileBrowser API responses (server-generated), not direct user input in most cases
  4. LAN-only access — attacker needs network access

Recommendations

  1. 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}`
    }
    
  2. URL-encode path components in download URLs
  3. Verify FileBrowser container uses --read-only filesystem

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

  1. Server is LAN-only (no public internet exposure)
  2. Main RPC endpoint is not affected
  3. credentials: 'include' with Access-Control-Allow-Origin: * is actually blocked by browsers (CORS spec requires specific origin when credentials are used)

Recommendations

  1. Replace * with the specific Archipelago origin
  2. Add Access-Control-Allow-Credentials: true only where needed
  3. 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-With custom header requirement
  • No SameSite cookie attribute
  • No Origin header validation in the RPC handler

Mitigating Factors

  1. 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.
  2. LAN-only access — attacker needs to be on the same network
  3. Session cookies — authentication appears to use session cookies from /rpc/v1, but an attacker on the LAN could craft a same-origin request

Recommendations

  1. Add X-Requested-With: XMLHttpRequest header in rpc-client.ts and validate it server-side
  2. Implement synchronizer token pattern for state-changing operations
  3. Validate Origin header in the Rust handler

7. Nginx Security Headers — MEDIUM RISK

Finding: Missing standard security headers

The nginx config lacks:

  • X-Content-Type-Options: nosniff
  • Referrer-Policy: strict-origin-when-cross-origin
  • Content-Security-Policy for 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

  1. 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;
    
  2. Add Content-Security-Policy with frame-ancestors 'self' for the main UI
  3. For app proxies, replace stripped headers with X-Frame-Options: SAMEORIGIN to 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.vue
  • neode-ui/src/views/CloudFolder.vue
  • neode-ui/src/views/Home.vue
  • neode-ui/src/services/contextBroker.ts
  • neode-ui/src/api/filebrowser-client.ts
  • neode-ui/src/api/rpc-client.ts
  • neode-ui/src/api/container-client.ts
  • neode-ui/src/stores/cloud.ts
  • neode-ui/src/stores/aiPermissions.ts
  • neode-ui/src/types/aiui-protocol.ts
  • neode-ui/src/components/cloud/FileCard.vue
  • neode-ui/src/components/cloud/FileCardGrid.vue
  • neode-ui/src/components/cloud/CloudToolbar.vue
  • core/archipelago/src/api/handler.rs
  • core/archipelago/src/api/rpc/mod.rs
  • core/archipelago/src/api/rpc/auth.rs
  • core/archipelago/src/api/rpc/package.rs
  • image-recipe/configs/nginx-archipelago.conf