Skip to content

Commit b2f3f54

Browse files
Merge pull request #57 from jjohare/gh-pages
Security fixes from audit v0.0.71
2 parents 837f984 + abc8165 commit b2f3f54

8 files changed

Lines changed: 489 additions & 529 deletions

File tree

SECURITY-AUDIT-2026-01-05.md

Lines changed: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,382 @@
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

Comments
 (0)