| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
This issue proposes a comprehensive ETag strategy for JavaScriptSolidServer aligned with HTTP caching best practices. ETags are critical for efficient caching, bandwidth reduction, and preventing mid-air collisions during concurrent edits.
Difficulty: 35/100
Estimated Effort: 2-3 days
Dependencies: None
File: src/storage/filesystem.js line 32
etag: `"${crypto.createHash('md5').update(stats.mtime.toISOString() + stats.size).digest('hex')}"`Current approach: MD5 hash of mtime + size (metadata-based)
| Aspect | Current | Issue |
|---|---|---|
| Algorithm | MD5 | Cryptographically weak (acceptable for ETags, but not ideal) |
| Input | mtime + size | Not content-based, can miss changes if mtime preserved |
| Type | Strong (no W/ prefix) | Claims byte-identical but isn't truly content-based |
| Caching | Synchronous crypto | Blocks event loop on every stat() call |
File: src/utils/conditional.js (154 lines)
✅ Well implemented:
Current usage:
| Location | Value | Purpose |
|---|---|---|
| resource.js:225 | no-store | Mashlib HTML responses |
| resource.js:303 | no-store | Mashlib HTML responses |
| idp/index.js:202 | public, max-age=3600 | JWKS endpoint |
| idp/index.js:209 | public, max-age=3600 | OpenID configuration |
| idp/credentials.js:147 | no-store | Credentials endpoint |
Missing: No Cache-Control on regular resource responses.
Status: ❌ Not implemented
mtime is available from stat() but not exposed as Last-Modified header.
Severity: MEDIUM
Current ETags are formatted as strong ("abc123") but are generated from metadata, not content. Per RFC 7232:
A strong validator is representation metadata that changes value whenever a change occurs to the representation data that would be observable in the payload body of a 200 (OK) response to GET.
Problem: If a file is modified but mtime is preserved (e.g., touch -m), the ETag won't change even though content changed.
Severity: MEDIUM
Content negotiation transforms stored content:
These transformations produce different byte streams, but may use the same source file ETag.
Severity: LOW
Some clients prefer Last-Modified over ETags. Both should be provided per best practices.
Severity: MEDIUM
Container listings are dynamically generated. Current implementation may use directory mtime, but this doesn't reflect:
Severity: LOW (Performance)
Runs synchronously on every stat() call. For high-traffic servers, this could become a bottleneck.
Severity: MEDIUM
No systematic Cache-Control headers on resource responses. This means:
| Source | Recommendation |
|---|---|
| MDN | Use both ETag and Last-Modified; combine with Cache-Control |
| Cloudflare | Strong ETags for byte-identical; weak for semantic equivalence |
| Fastly | Content hash for strong ETags; metadata for weak |
| Set explicit Cache-Control; don't rely on heuristics |
Tier 1: Strong ETag (content-based)
Tier 2: Weak ETag (metadata-based)
Tier 3: Version ETag (for transformed content)
| Resource Type | ETag Strategy | Rationale |
|---|---|---|
| Small files (<1MB) | Strong (content hash) | Accurate, worth the compute |
| Large files (>1MB) | Weak (metadata) | Too expensive to hash |
| Containers | Weak (mtime + child count) | Dynamic, changes frequently |
| Conneg results | Weak (source + transform) | Derived content |
| Mashlib/UI | Weak (version) | Static but frequently updated |
| Profile | Cache-Control Value | Use Case |
|---|---|---|
| resource | private, no-cache, must-revalidate | User-generated content |
| container | private, no-cache, must-revalidate | Container listings |
| static | public, max-age=3600, stale-while-revalidate=86400 | Mashlib, schemas |
| immutable | public, max-age=31536000, immutable | Versioned assets |
| sensitive | private, no-store | Credentials, tokens |
| discovery | public, max-age=3600 | Well-known endpoints |
Add Last-Modified to all resource responses using stats.mtime.toUTCString().
When content negotiation is enabled, add:
Vary: Accept, Accept-Language
This tells caches that different Accept headers produce different responses.
{
"etag": {
"algorithm": "sha256",
"strongThreshold": 1048576,
"cacheEtags": true,
"cacheMaxSize": 10000
},
"caching": {
"defaultProfile": "resource",
"staticMaxAge": 3600,
"immutableAssets": false
}
}| Aspect | Current | Proposed |
|---|---|---|
| ETag algorithm | MD5 | SHA-256 |
| ETag basis | Metadata only | Content (small) / Metadata (large) |
| ETag type | Always strong | Strong or weak based on accuracy |
| Last-Modified | ❌ Missing | ✅ Always included |
| Cache-Control | ❌ Inconsistent | ✅ Profile-based |
| Vary header | ❌ Missing | ✅ For conneg |
| Container ETags | Basic mtime | Enhanced (children, membership) |
| Conneg ETags | Source ETag | Distinct per transformation |
| Server | ETag Strategy |
|---|---|
| Node Solid Server | Content hash (MD5) |
| Community Solid Server | Content hash + representation metadata |
| ESS (Inrupt) | Proprietary, content-based |
| JSS (current) | Metadata-based MD5 |
| JSS (proposed) | Tiered: content/metadata with proper typing |
ETag as fingerprint: ETags can be used to track users across requests. Mitigated by using private in Cache-Control.
Timing attacks: Content-based ETags reveal if content changed. This is inherent to caching and generally acceptable.
ETag collision: SHA-256 with 162+ bits is collision-resistant. MD5 collisions are feasible but unlikely to be exploited via ETags.
| Back | FazBrowse Home | New Git URL |
Activity