System · InfoFrame
Digital NDA signing
In production
Most cheap signing flows prove that something was signed. This one proves what was signed, and lets the counterparty check it themselves without taking my word for it.
Facts
- Status
- In production since July 2026
- Stack
- Static HTML · Python · PyRunner · ReportLab · Pillow
- Size
- Two files, no framework, no build step
- Agreement
- 16 clauses, Norwegian and English side by side
- Storage
- Fields, canonical text, signature and PDF in the datastore
- Proof
- infoframe.no/nda
The idea
A signature on a document is worth little if the parties later disagree about what the document said. Ordinary e-signing solves identity and timing, but leaves the content as something you have to trust the vendor kept intact.
Here the browser and the server each build the same agreement text independently, character for character, and each compute a checksum. The client sends its checksum with the submission. The server computes its own. Both are printed in the appendix at the back of the PDF, with a line stating whether they match.
The counterparty is then holding a document that contains its own receipt. If anyone later claims the wording was different, the checksum in their own copy settles it.
Deliberate duplication, and a test that guards it
Two implementations of the same text is normally a mistake. Here it is the point: if two parties are to arrive at the same checksum independently, they have to compute it independently. The front end is also a single static file with no build step, so it cannot share code with the server.
The risk is that the two drift apart unnoticed and every PDF after that carries a mismatch. So there is a test that slices the functions out of the HTML file, runs them through node, runs the same input through Python, and compares both the text and the checksum. If they differ, it fails.
The details that came from getting burned
The submission goes as text/plain on purpose. It is one of the content types a browser sends without a preflight request first, and the host does not answer those. The body is still JSON.
An earlier version caught the rejection that came anyway and retried. The result was that every agreement got sent twice. There is now a comment in the code saying no retry belongs here, and the server skips a submission it has already seen.
The signature field is a canvas exported with a transparent background. The PDF library renders that as a black rectangle if you pass it straight through, so the image gets flattened onto white first.
If the organisation number or address is missing, the service refuses to issue the agreement at all. A confidentiality agreement without an identifiable party is worth very little once it is actually disputed.
What this is not
This is a simple electronic signature. Valid and admissible for a confidentiality agreement, on a par with what the large vendors offer at their cheapest tier, but it is not BankID and it does not prove who was holding the mouse. For agreements where identity fraud is a real risk, it is the wrong tool.
The service cannot see the signer's IP address, so two of its three abuse limits are decoration in practice. The overall daily cap is what actually does the work, and the documentation says so rather than presenting three layers of protection.
The Oslo timestamp is fixed to summer time, so from October to March it reads an hour ahead. The UTC timestamp is always correct, and that is the one that counts.