The signing tool keeps no signature on the device (#1, bau-pm BAU-260)

PDF.js's signature dialog saved every signature in localStorage under
pdfjs.signature, "save" ticked on every open, for this origin rather
than for a bau-pm user. Measured on v0.1.0: a typed and an uploaded
signature were offered to a fresh page on the same browser, the way the
next person on a shared tablet would get it, a Redmine logout included.
Another browser profile saw nothing; the server never had anything.

The glue's fifth duty, in every window before PDF.js's modules: delete
the store (a device that saved signatures loses them on its first
visit), refuse writes to that key, hold the save box unticked through a
property on the element and hide it, and hide the dialog's Bild tab and
its file picker, so a scanned signature never enters the dialog.

test/signatures.mjs is the second gate, in CI and the release workflow:
a browser seeded with a real saved entry (captured from v0.1.0's own
store) is offered none, the dialog shows neither the box nor the tab, a
typed signature is placed and not stored, a fresh page is offered
nothing. Fed v0.1.0: 9 findings, exit 1. Against this image: 0, and the
zero-host gate drove 117 tool pages with nothing leaving the origin. A
signed PDF still exports with the signature on the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
magdevandClaude Opus 5.5 committed 2026-11-01 09:48:16 +01:00
1 parent ac6d57562b
commit 4248bc791a
8 files changed
+197 -5

No files matched your search

+51 -1
View File
@@ -2,7 +2,7 @@
*
* Loaded by every page of this image, the PDF.js viewers' pages inside the
* tools included, as a classic script in <head>, so it runs before BentoPDF's
* own modules. It does four things:
* own modules. It does five things:
*
* 1. A link to this image's source on every page, because the AGPL asks the
* source of a modified version served over a network to be offered
@@ -20,6 +20,14 @@
* blob is read inside click(), because the next line revokes its URL.
* 4. For the signing tool, BentoPDF's "flatten" switched on and locked, so a
* signature becomes part of the page and cannot be moved later.
* 5. No signature kept on the device. PDF.js's signature editor saves up to
* five signatures in localStorage, ticked by default, for this origin and
* not for a bau-pm user, so the next person on a shared tablet would find
* the last one's signature one click away, a Redmine logout included
* (bau-pm PLAN.md §2.77, Gitea #1). Every window deletes what is stored,
* refuses the store's writes, and in the viewer the "save" box is held off
* and hidden. The dialog's image tab is hidden too, so a scanned
* signature never enters the tool: signing means drawing or typing it.
*
* Nothing here talks to anything but this origin. The words of the hand-off
* come from bau-pm in the user's language; only the source link has its own.
@@ -31,6 +39,7 @@
var COOKIE = '__Host-bau_pm_handoff';
var API = '/bau-pm/handoff';
var SOURCE_URL = '__BAU_PM_SOURCE_URL__';
var SIGNATURES = 'pdfjs.signature';
var isTop = window.top === window;
// --- shared state --------------------------------------------------------
@@ -72,6 +81,9 @@
return nativeClick.apply(this, arguments);
};
forgetSignatures();
ready(signatureDialog);
if (!isTop) {
signFrame();
return;
@@ -294,6 +306,44 @@
})();
}
// --- 5. no signature kept on the device ---------------------------------
// Runs before PDF.js's modules in every window, so the viewer reads an empty
// store, and a device that saved signatures before this glue loses them on
// its first visit. The write is refused rather than undone afterwards: a
// signature that is never stored cannot be read by the next page either.
function forgetSignatures() {
try {
window.localStorage.removeItem(SIGNATURES);
var nativeSetItem = Storage.prototype.setItem;
Storage.prototype.setItem = function (key) {
if (String(key) === SIGNATURES) { return undefined; }
return nativeSetItem.apply(this, arguments);
};
} catch (e) { /* storage blocked: then nothing can be kept either */ }
}
// The dialog sets the box to "save" each time it opens; a property of the
// element's own shadows the prototype's, so it reads unticked whatever is
// assigned to it. Only PDF.js's viewer has these elements.
function signatureDialog() {
var save = document.getElementById('addSignatureSaveCheckbox');
if (!save) { return; }
Object.defineProperty(save, 'checked', {
configurable: true,
get: function () { return false; },
set: function () {}
});
hide(document.getElementById('addSignatureSaveContainer'));
hide(document.getElementById('addSignatureImageButton'));
var picker = document.getElementById('addSignatureFilePicker');
if (picker) { picker.disabled = true; }
}
function hide(element) {
if (element) { element.style.setProperty('display', 'none', 'important'); }
}
// --- 3. the hand-back's sheet --------------------------------------------
function button(label, kind) {