|
| 1 | +# JSS Security Audit Report |
| 2 | + |
| 3 | +**Date:** 2026-01-05 |
| 4 | +**Auditor:** Automated QE Fleet Analysis |
| 5 | +**Version Audited:** 0.0.71 |
| 6 | +**Previous Audit:** 2026-01-03 (v0.0.48) |
| 7 | + |
| 8 | +--- |
| 9 | + |
| 10 | +## Executive Summary |
| 11 | + |
| 12 | +A comprehensive multi-agent security audit of JavaScriptSolidServer (v0.0.71) was conducted using 5 specialized review agents examining authentication, path traversal, DoS resistance, WAC authorization, and dependency security. |
| 13 | + |
| 14 | +**Overall Security Posture:** **GOOD** - Major vulnerabilities from previous audit (v0.0.48-0.0.51) have been fixed. Several medium and low severity issues remain. |
| 15 | + |
| 16 | +| Severity | Count | Status | |
| 17 | +|----------|-------|--------| |
| 18 | +| Critical | 0 | - | |
| 19 | +| High | 1 | NEW | |
| 20 | +| Medium | 7 | Open | |
| 21 | +| Low | 8 | Open | |
| 22 | + |
| 23 | +**npm audit:** 0 vulnerabilities found |
| 24 | + |
| 25 | +--- |
| 26 | + |
| 27 | +## Previous Audit Issues - Remediation Status |
| 28 | + |
| 29 | +| Issue | Previous Severity | Status | Fixed In | |
| 30 | +|-------|-------------------|--------|----------| |
| 31 | +| ACL bypass (unauthenticated .acl access) | Critical | FIXED | v0.0.49 | |
| 32 | +| JWT signature not verified | Critical | FIXED | v0.0.49 | |
| 33 | +| SSRF in OIDC discovery | Critical | FIXED | v0.0.50 | |
| 34 | +| SSRF in client document fetch | Critical | FIXED | v0.0.50 | |
| 35 | +| Path traversal via `..` bypass | Critical | FIXED | v0.0.52 | |
| 36 | +| Unauthenticated pod creation | High | FIXED | v0.0.51 | |
| 37 | +| Default token secret | High | FIXED | v0.0.51 | |
| 38 | +| No rate limiting | Medium | FIXED | v0.0.51 | |
| 39 | +| WebSocket subscription spam | High | FIXED | v0.0.52 | |
| 40 | +| JSON.parse DoS | High | PARTIALLY FIXED | v0.0.52 | |
| 41 | + |
| 42 | +--- |
| 43 | + |
| 44 | +## New/Open Vulnerabilities |
| 45 | + |
| 46 | +### HIGH SEVERITY |
| 47 | + |
| 48 | +#### 1. Path Traversal in Git Handler |
| 49 | + |
| 50 | +**Location:** `src/handlers/git.js:82-97` |
| 51 | + |
| 52 | +**Description:** The git handler does NOT use the secure `urlToPath()` function. Instead, it extracts the repository path and passes it directly to `path.resolve()` without sanitization. |
| 53 | + |
| 54 | +```javascript |
| 55 | +const urlPath = decodeURIComponent(request.url.split('?')[0]); |
| 56 | +const repoRelative = extractRepoPath(urlPath); |
| 57 | +const repoAbs = resolve(dataRoot, repoRelative); // No traversal check! |
| 58 | +``` |
| 59 | + |
| 60 | +**Attack Vector:** |
| 61 | +``` |
| 62 | +GET /../../etc/passwd/info/refs?service=git-upload-pack |
| 63 | +``` |
| 64 | + |
| 65 | +**Impact:** Potential directory traversal outside DATA_ROOT when git endpoints are enabled. |
| 66 | + |
| 67 | +**Mitigating Factors:** |
| 68 | +- `findGitDir()` validates git-specific files exist, limiting exploitation |
| 69 | +- Git support is optional |
| 70 | + |
| 71 | +**CVSS Score:** 7.5 (High) |
| 72 | + |
| 73 | +**Recommendation:** Use `urlToPath()` or equivalent sanitization before `path.resolve()`. |
| 74 | + |
| 75 | +--- |
| 76 | + |
| 77 | +### MEDIUM SEVERITY |
| 78 | + |
| 79 | +#### 2. DPoP Replay Attack Vulnerability |
| 80 | + |
| 81 | +**Location:** `src/auth/solid-oidc.js:178-181` |
| 82 | + |
| 83 | +```javascript |
| 84 | +// jti: Unique identifier (we should track these to prevent replay, but skip for now) |
| 85 | +if (!payload.jti) { |
| 86 | + return { thumbprint: null, error: 'DPoP proof missing jti' }; |
| 87 | +} |
| 88 | +``` |
| 89 | + |
| 90 | +**Description:** DPoP proof `jti` (JWT ID) is required but NOT tracked. The same DPoP proof can be replayed within its 5-minute validity window (`DPOP_MAX_AGE`). |
| 91 | + |
| 92 | +**Impact:** Token replay attacks within short window. |
| 93 | + |
| 94 | +**CVSS Score:** 5.3 (Medium) |
| 95 | + |
| 96 | +**Recommendation:** Implement time-bounded jti cache (5-min TTL). |
| 97 | + |
| 98 | +--- |
| 99 | + |
| 100 | +#### 3. Agent Group Authorization Not Implemented |
| 101 | + |
| 102 | +**Location:** `src/wac/checker.js:182` |
| 103 | + |
| 104 | +```javascript |
| 105 | +// TODO: Check agent groups (requires fetching and parsing group documents) |
| 106 | +``` |
| 107 | + |
| 108 | +**Description:** ACL rules using `acl:agentGroup` are parsed (`src/wac/parser.js:152-153`) but never checked during authorization. Group-based access control silently fails. |
| 109 | + |
| 110 | +**Impact:** Resources protected by agent groups may be incorrectly denied or allowed. |
| 111 | + |
| 112 | +**CVSS Score:** 6.1 (Medium) |
| 113 | + |
| 114 | +**Recommendation:** Implement group document fetching and membership checking. |
| 115 | + |
| 116 | +--- |
| 117 | + |
| 118 | +#### 4. JSON.parse Without Size Limits (Partial) |
| 119 | + |
| 120 | +**Locations:** |
| 121 | +- `src/wac/parser.js:40` - ACL content parsing |
| 122 | +- `src/auth/did-nostr.js:129,141` - DID document parsing |
| 123 | +- `src/nostr/relay.js:242` - WebSocket message parsing |
| 124 | +- `src/auth/token.js:105` - Token payload parsing |
| 125 | +- `src/ap/routes/inbox.js:126` - ActivityPub inbox |
| 126 | + |
| 127 | +**Description:** `safeJsonParse()` exists (`src/utils/url.js:242-246`) but is not used consistently. |
| 128 | + |
| 129 | +**Impact:** DoS via large JSON payloads in unprotected parsers. |
| 130 | + |
| 131 | +**CVSS Score:** 5.3 (Medium) |
| 132 | + |
| 133 | +**Recommendation:** Use `safeJsonParse()` consistently across all JSON parsing. |
| 134 | + |
| 135 | +--- |
| 136 | + |
| 137 | +#### 5. Missing Global Security Headers |
| 138 | + |
| 139 | +**Description:** Security headers are only applied for mashlib responses, not globally: |
| 140 | + |
| 141 | +**Present (mashlib only):** |
| 142 | +- `X-Frame-Options: DENY` |
| 143 | +- `Content-Security-Policy: frame-ancestors 'none'` |
| 144 | + |
| 145 | +**Missing globally:** |
| 146 | +- `X-Content-Type-Options: nosniff` |
| 147 | +- `Strict-Transport-Security` (for HTTPS deployments) |
| 148 | +- `Referrer-Policy` |
| 149 | + |
| 150 | +**CVSS Score:** 4.3 (Medium) |
| 151 | + |
| 152 | +**Recommendation:** Add security headers via Fastify plugin for all responses. |
| 153 | + |
| 154 | +--- |
| 155 | + |
| 156 | +#### 6. DNS Resolution Failure Allows SSRF |
| 157 | + |
| 158 | +**Location:** `src/utils/ssrf.js:125-130` |
| 159 | + |
| 160 | +```javascript |
| 161 | +} catch (err) { |
| 162 | + // DNS resolution failed - could be a legitimate issue or attacker trying to bypass |
| 163 | + // For security, we'll allow it through but log a warning |
| 164 | + console.warn(`DNS resolution failed for ${hostname}: ${err.message}`); |
| 165 | +} |
| 166 | +``` |
| 167 | + |
| 168 | +**Description:** When DNS resolution fails, the request is allowed to proceed. An attacker could exploit DNS timing or failure to bypass SSRF protection. |
| 169 | + |
| 170 | +**Impact:** Potential SSRF bypass via DNS manipulation. |
| 171 | + |
| 172 | +**CVSS Score:** 4.9 (Medium) |
| 173 | + |
| 174 | +**Recommendation:** Block requests on DNS resolution failure. |
| 175 | + |
| 176 | +--- |
| 177 | + |
| 178 | +#### 7. RDF Parsing Without Triple Limits |
| 179 | + |
| 180 | +**Locations:** `src/rdf/turtle.js:34`, `src/patch/sparql-update.js:106` |
| 181 | + |
| 182 | +**Description:** N3.js parser is used without limits on triple count or recursion depth. Combined with 10MB body limit, a crafted Turtle file could cause CPU exhaustion. |
| 183 | + |
| 184 | +**CVSS Score:** 5.3 (Medium) |
| 185 | + |
| 186 | +**Recommendation:** Add triple count limit (e.g., 100,000 max). |
| 187 | + |
| 188 | +--- |
| 189 | + |
| 190 | +#### 8. Verbose Error Messages to Clients |
| 191 | + |
| 192 | +**Locations:** |
| 193 | +- `src/handlers/resource.js:501-505` - Turtle parsing errors |
| 194 | +- `src/auth/solid-oidc.js:198` - DPoP proof errors |
| 195 | + |
| 196 | +**Description:** Internal error messages from parsing libraries are exposed to clients. |
| 197 | + |
| 198 | +**Impact:** Information disclosure about implementation details. |
| 199 | + |
| 200 | +**CVSS Score:** 4.3 (Medium) |
| 201 | + |
| 202 | +--- |
| 203 | + |
| 204 | +### LOW SEVERITY |
| 205 | + |
| 206 | +#### 9. PodName Single-Pass Sanitization |
| 207 | + |
| 208 | +**Location:** `src/utils/url.js:69` |
| 209 | + |
| 210 | +```javascript |
| 211 | +let safePodName = podName.replace(/\.\./g, ''); |
| 212 | +``` |
| 213 | + |
| 214 | +Uses single-pass `..` removal unlike the iterative approach for urlPath. Mitigated by subsequent boundary check. |
| 215 | + |
| 216 | +--- |
| 217 | + |
| 218 | +#### 10. No Global WebSocket Connection Limit |
| 219 | + |
| 220 | +**Location:** `src/notifications/websocket.js` |
| 221 | + |
| 222 | +Per-connection subscription limit (100) exists, but no global limit across all connections. |
| 223 | + |
| 224 | +--- |
| 225 | + |
| 226 | +#### 11. Minimum Username Length |
| 227 | + |
| 228 | +**Location:** `src/idp/interactions.js:370-371` |
| 229 | + |
| 230 | +Minimum username length is 3 characters, allowing very short usernames. |
| 231 | + |
| 232 | +--- |
| 233 | + |
| 234 | +#### 12. No Password Strength Validation |
| 235 | + |
| 236 | +**Location:** `src/idp/interactions.js` |
| 237 | + |
| 238 | +No minimum password length or complexity requirements enforced. |
| 239 | + |
| 240 | +--- |
| 241 | + |
| 242 | +#### 13. TOKEN_SECRET Entropy Not Validated |
| 243 | + |
| 244 | +**Location:** `src/auth/token.js:16-33` |
| 245 | + |
| 246 | +Production requires TOKEN_SECRET but doesn't validate minimum entropy/length. |
| 247 | + |
| 248 | +--- |
| 249 | + |
| 250 | +#### 14. bcrypt Rounds |
| 251 | + |
| 252 | +**Location:** `src/idp/accounts.js:41` |
| 253 | + |
| 254 | +Uses 10 rounds (acceptable but 12 recommended for future-proofing). |
| 255 | + |
| 256 | +--- |
| 257 | + |
| 258 | +#### 15. CORS Reflected Origin Pattern |
| 259 | + |
| 260 | +**Location:** `src/ldp/headers.js:93-102` |
| 261 | + |
| 262 | +Reflects any origin with credentials. Intentional for Solid interoperability but should be documented. |
| 263 | + |
| 264 | +--- |
| 265 | + |
| 266 | +#### 16. Console.error Information Logging |
| 267 | + |
| 268 | +**Locations:** `src/handlers/resource.js:129,203,290` |
| 269 | + |
| 270 | +Error messages logged to console may reveal internal details if logs are exposed. |
| 271 | + |
| 272 | +--- |
| 273 | + |
| 274 | +## Positive Security Findings |
| 275 | + |
| 276 | +| Control | Location | Assessment | |
| 277 | +|---------|----------|------------| |
| 278 | +| Path traversal protection | `src/utils/url.js:24-46` | Multi-pass `..` removal + path.resolve boundary check | |
| 279 | +| JWT signature verification | `src/auth/token.js:125-160` | Proper jose.jwtVerify() against JWKS | |
| 280 | +| HMAC timing-safe comparison | `src/auth/token.js:94-101` | crypto.timingSafeEqual() used | |
| 281 | +| Production token secret enforcement | `src/auth/token.js:21-26` | process.exit(1) if not set | |
| 282 | +| ACL Control permission for .acl files | `src/auth/middleware.js:378-389` | Stricter than spec (more secure) | |
| 283 | +| Default deny on missing ACL | `src/wac/checker.js:30-33` | Restrictive default | |
| 284 | +| Rate limiting | `src/server.js`, `src/idp/index.js` | Comprehensive coverage | |
| 285 | +| SSRF protection | `src/utils/ssrf.js` | Private IP blocking, DNS rebinding protection | |
| 286 | +| WebSocket limits | `src/notifications/websocket.js` | 100 subs/connection, 2048 char URL limit | |
| 287 | +| Dotfile blocking | `src/server.js:235-251` | Allowlist approach | |
| 288 | +| Body size limits | `src/server.js:84` | 10MB global, 1MB for IdP | |
| 289 | +| bcrypt password hashing | `src/idp/accounts.js` | Proper implementation | |
| 290 | +| DPoP proof validation | `src/auth/solid-oidc.js` | Method, URI, timestamp, ath, cnf.jkt checks | |
| 291 | +| Nostr NIP-98 validation | `src/auth/nostr.js` | Event kind, timestamp, URL, method, signature | |
| 292 | +| Slug validation | `src/handlers/container.js:56-69` | Strict alphanumeric + length limit | |
| 293 | + |
| 294 | +--- |
| 295 | + |
| 296 | +## Dependency Security |
| 297 | + |
| 298 | +**npm audit:** 0 vulnerabilities |
| 299 | + |
| 300 | +| Package | Version | Latest | Notes | |
| 301 | +|---------|---------|--------|-------| |
| 302 | +| jose | 6.1.3 | 6.1.3 | Current - JWT/JWKS handling | |
| 303 | +| bcrypt | 6.0.0 | 6.0.0 | Current - Password hashing | |
| 304 | +| oidc-provider | 9.6.0 | 9.6.0 | Current - OIDC IdP | |
| 305 | +| fastify | 4.29.1 | 5.6.2 | Major version behind (v5 available) | |
| 306 | +| better-sqlite3 | 12.5.0 | 12.5.0 | Current | |
| 307 | +| nostr-tools | 2.19.4 | 2.19.4 | Current | |
| 308 | + |
| 309 | +**Outdated (non-security):** |
| 310 | +- @fastify/middie: 8.3.3 -> 9.1.0 |
| 311 | +- @fastify/rate-limit: 9.1.0 -> 10.3.0 |
| 312 | +- @fastify/websocket: 8.3.1 -> 11.2.0 |
| 313 | +- fastify: 4.29.1 -> 5.6.2 |
| 314 | + |
| 315 | +--- |
| 316 | + |
| 317 | +## Recommendations |
| 318 | + |
| 319 | +### Priority 1 (High) |
| 320 | + |
| 321 | +1. **Fix git handler path traversal** - Use `urlToPath()` in `src/handlers/git.js` |
| 322 | + |
| 323 | +### Priority 2 (Medium) |
| 324 | + |
| 325 | +2. **Implement DPoP jti tracking** - Add time-bounded cache for replay prevention |
| 326 | +3. **Implement agent group checking** - Complete TODO in `src/wac/checker.js` |
| 327 | +4. **Use safeJsonParse consistently** - Replace raw `JSON.parse()` calls |
| 328 | +5. **Add global security headers** - X-Content-Type-Options, HSTS |
| 329 | +6. **Block on DNS failure** - Change SSRF to deny on DNS resolution failure |
| 330 | + |
| 331 | +### Priority 3 (Low) |
| 332 | + |
| 333 | +7. Add RDF triple count limits |
| 334 | +8. Add password strength requirements |
| 335 | +9. Validate TOKEN_SECRET entropy |
| 336 | +10. Add global WebSocket connection limit |
| 337 | +11. Consider Fastify v5 upgrade path |
| 338 | + |
| 339 | +--- |
| 340 | + |
| 341 | +## Changes Since Last Audit (v0.0.51 -> v0.0.71) |
| 342 | + |
| 343 | +20 releases since last audit, including: |
| 344 | + |
| 345 | +- **v0.0.52** - Security hardening (path traversal fix) |
| 346 | +- **v0.0.53** - Stricter pod creation rate limit |
| 347 | +- **v0.0.54** - SSRF false positive fix |
| 348 | +- **v0.0.55** - Secure cookie flag for HTTPS |
| 349 | +- **v0.0.56-0.0.58** - Invite-only registration, quotas, did:nostr |
| 350 | +- **v0.0.59-0.0.61** - Nostr relay, ActivityPub federation |
| 351 | +- **v0.0.62-0.0.66** - ActivityPub fixes, Android support |
| 352 | +- **v0.0.67-0.0.71** - UI improvements, error pages, mashlib integration |
| 353 | + |
| 354 | +--- |
| 355 | + |
| 356 | +## Legacy Audit Files |
| 357 | + |
| 358 | +| File | Status | Action | |
| 359 | +|------|--------|--------| |
| 360 | +| SECURITY-AUDIT-2026-01-03.md | Outdated (v0.0.48) | Retained for history | |
| 361 | +| SECURITY-AUDIT-2026-01-15.md | Invalid (future date) | **REMOVED** | |
| 362 | + |
| 363 | +--- |
| 364 | + |
| 365 | +## Conclusion |
| 366 | + |
| 367 | +JavaScriptSolidServer v0.0.71 demonstrates strong security fundamentals with all critical vulnerabilities from previous audits remediated. The primary concern is the **HIGH severity path traversal in the git handler** which should be addressed promptly. |
| 368 | + |
| 369 | +The codebase shows security-conscious design patterns including: |
| 370 | +- Defense-in-depth path traversal protection |
| 371 | +- Proper cryptographic verification |
| 372 | +- Comprehensive rate limiting |
| 373 | +- SSRF protection with DNS rebinding mitigation |
| 374 | +- Secure defaults (deny on missing ACL) |
| 375 | + |
| 376 | +**Overall Assessment:** Suitable for production use after addressing the git handler path traversal issue. |
| 377 | + |
| 378 | +--- |
| 379 | + |
| 380 | +_Report generated: 2026-01-05_ |
| 381 | +_Audit methodology: Multi-agent QE fleet (5 specialized reviewers)_ |
| 382 | +_Next audit recommended: 2026-04-05_ |
0 commit comments