[grade=A urn:ump:qszwspay7vhx7wcpqekkkidni3ehhgidvze5qk2y22fvg2wqz3ja] fix(DC-170): reliability batch — hangs, atomicity, fail-closed, SW (audit batch 6)
8 reliability fixes across 8 files (AUDIT-2026-10-27.md findings
9/11/12/13/14/16/20 + calendar credential item):
- dns.js check-update/update HANGS: bare returns + empty else/catch
branches left requests hanging until client timeout; every path now
answers (502 with message); upstream JSON.parse wrapped (non-JSON ->
502)
- calendar.js ICS fetch timeout actually applies: fetchT ignores
opts.timeout (undici) — was running at 5s default, not the intended
15s; now timeoutMs third arg
- calendar.js PUT /sources: updating pass no longer nulls the stored
token (and vice versa) — existing credential record loaded, only the
provided field overwritten
- state-manager.js ATOMIC WRITES: _atomicWrite = tmp file + fs.rename
(POSIX-atomic); write()/update() no longer truncate-then-write the
live file (crash mid-write corrupted services.json/config.json with
no recovery path). Test mock extended to the tmp+rename contract
- dashboard-ws.js auth fallback FAIL-CLOSED: missing authVerifier used
to accept any non-empty session cookie (DC-061 footgun on unwired
boot paths); now rejects + loud CRITICAL log
- middleware.js tailscale gate: new tailscale.failClosed config (default
off = unchanged); when set, outages/errors deny (503) instead of
silently disabling the gate; fail-open path now logs the exact key
- sw.js: offline fallback actually works ((await match) || (await
match) — old Promise||Promise was always truthy); only 2xx cached
(5xx pages were stored and served as the app shell); install is
per-URL tolerant (one bad asset kept the stale SW alive forever)
- config/settings.js GET /config (public): corrupt config.json degrades
to {setupComplete:false, corrupt:true} instead of 500 on every load
SCOPE CORRECTION: audit finding #10 (7 reversed-arg errorResponse
sites) was a FALSE POSITIVE — those files alias message-first error();
see lesson #14. Full suite 173/173 suites, 3740/3740 tests.
This commit is contained in:
8 files changed
+115
-31
No files matched your search
+14
-5
@@ -53,14 +53,18 @@ async function networkFirst(request, preloadResponsePromise) {
|
||||
try {
|
||||
const preloadResponse = preloadResponsePromise ? await preloadResponsePromise : null;
|
||||
if (preloadResponse) {
|
||||
cache.put(request, preloadResponse.clone()).catch(() => {});
|
||||
if (preloadResponse.ok) cache.put(request, preloadResponse.clone()).catch(() => {});
|
||||
return preloadResponse;
|
||||
}
|
||||
const response = await fetch(request);
|
||||
cache.put(request, response.clone()).catch(() => {});
|
||||
// DC-170: only cache GOOD responses — a 5xx was being stored and later
|
||||
// served as the app shell by both strategies.
|
||||
if (response.ok) cache.put(request, response.clone()).catch(() => {});
|
||||
return response;
|
||||
} catch (_) {
|
||||
return caches.match(request) || caches.match('/index.html');
|
||||
// DC-170: `caches.match(x) || caches.match(y)` returned a Promise (always
|
||||
// truthy) so the offline fallback never ran — await both sides.
|
||||
return (await caches.match(request)) || (await caches.match('/index.html'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +74,7 @@ async function staleWhileRevalidate(request) {
|
||||
|
||||
const networkPromise = fetch(request)
|
||||
.then((response) => {
|
||||
cache.put(request, response.clone()).catch(() => {});
|
||||
if (response && response.ok) cache.put(request, response.clone()).catch(() => {});
|
||||
return response;
|
||||
})
|
||||
.catch(() => null);
|
||||
@@ -82,8 +86,13 @@ async function staleWhileRevalidate(request) {
|
||||
self.addEventListener('install', (event) => {
|
||||
self.skipWaiting();
|
||||
event.waitUntil(
|
||||
// DC-170: per-URL caching instead of one all-or-nothing addAll — a single
|
||||
// renamed/missing asset previously rejected the whole install, leaving the
|
||||
// OLD service worker (and its stale cache) in control indefinitely.
|
||||
caches.open(CACHE).then((cache) =>
|
||||
cache.addAll(PRECACHE.map((url) => new Request(url, { cache: 'reload' })))
|
||||
Promise.all(PRECACHE.map((url) =>
|
||||
cache.add(new Request(url, { cache: 'reload' })).catch(() => {})
|
||||
))
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user