Skip to content

Commit 12e3acf

Browse files
Address copilot pass 7 on #4
Two findings: 1. Privkey memory residue (line 364). Setting memPrivKey = null drops our reference but leaves the 32 bytes in the heap until GC. JS gives no real memory clearing, but for a Uint8Array we own, fill(0) overwrites the bytes in place — meaningful for a multi-step UI where the key sits between PATCH and Test. Added clearMemPrivKey() and routed the three call sites through it. 2. Profile GET assumed JSON Content-Type (line 456). If the pod returned Turtle / an HTML error page despite our Accept, getRes.json() would throw a generic "Unexpected token in JSON" error. Now reads as text first, validates content-type contains "json", surfaces the actual content-type and a body prefix on mismatch, and wraps JSON.parse with a clearer error message.
1 parent 9c2e001 commit 12e3acf

1 file changed

Lines changed: 35 additions & 4 deletions

File tree

doctor.js

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,21 @@ let lastIssuer = null;
3838
let lastVmKid = null;
3939
let memPrivKey = null; // 32-byte secp256k1 privkey, in-memory only
4040

41+
/**
42+
* Zero out + drop the in-memory privkey. JS gives us no real memory
43+
* clearing — once we release a Uint8Array reference, the previous
44+
* bytes hang around in the heap until GC. But we own this buffer, so
45+
* overwriting in place at least removes the secret from anything we
46+
* still hold, which is meaningful for a multi-step UI flow where the
47+
* key sits around between PATCH and Test.
48+
*/
49+
function clearMemPrivKey() {
50+
if (memPrivKey) {
51+
try { memPrivKey.fill(0); } catch { /* not a Uint8Array — fine */ }
52+
}
53+
memPrivKey = null;
54+
}
55+
4156
// Allow ?webid=… in the URL to pre-fill (handy for sharing / bookmarks).
4257
const params = new URLSearchParams(window.location.search);
4358
if (params.has('webid')) {
@@ -358,7 +373,7 @@ const session = new Session({
358373
// ends. The UI promises sign-out clears state, and a privkey
359374
// sitting in a tab that's no longer authenticated is just
360375
// exposure with no purpose.
361-
memPrivKey = null;
376+
clearMemPrivKey();
362377
lastVmKid = null;
363378
privkeyInput.value = '';
364379
testSection.hidden = true;
@@ -403,7 +418,7 @@ function hideLwsAuthSection() {
403418
testResult.textContent = '';
404419
testResult.className = 'test-result';
405420
testSection.hidden = true;
406-
memPrivKey = null;
421+
clearMemPrivKey();
407422
lastVmKid = null;
408423
// Clear the input element too — the in-memory privkey is gone but a
409424
// pasted value would otherwise persist in the DOM across diagnostic
@@ -453,7 +468,23 @@ patchButton.addEventListener('click', async () => {
453468
});
454469
if (!getRes.ok) throw new Error(`GET profile: HTTP ${getRes.status}`);
455470
const etag = getRes.headers.get('etag');
456-
const current = await getRes.json();
471+
// Validate Content-Type before JSON.parse so a Turtle (or HTML
472+
// error page) response gives a clear actionable message rather
473+
// than a generic "Unexpected token in JSON" SyntaxError.
474+
const ct = (getRes.headers.get('content-type') || '').toLowerCase();
475+
const body = await getRes.text();
476+
if (!ct.includes('json')) {
477+
throw new Error(
478+
`profile GET returned Content-Type "${ct || '(none)'}" — expected JSON-LD. ` +
479+
`First bytes: ${body.slice(0, 80).replace(/\s+/g, ' ')}`,
480+
);
481+
}
482+
let current;
483+
try {
484+
current = JSON.parse(body);
485+
} catch (err) {
486+
throw new Error(`profile body is not valid JSON: ${err.message}`);
487+
}
457488

458489
// Pick a fragment that's either unused or already holds the same
459490
// key (idempotent re-run). Re-running with a different key won't
@@ -501,7 +532,7 @@ patchButton.addEventListener('click', async () => {
501532
// Otherwise a UI that already showed the test section from a
502533
// prior successful run would still claim "ready to test" with a
503534
// stale kid against a now-uncertain server state.
504-
memPrivKey = null;
535+
clearMemPrivKey();
505536
lastVmKid = null;
506537
testSection.hidden = true;
507538
testResult.textContent = '';

0 commit comments

Comments
 (0)