Sign and Configure DocMDP Restriction with Invalid Changes
This is the counterpart to the valid changes guide: the same certified document, but the later revision adds a page instead of an annotation. Page changes are rejected at every DocMDP level, so validation reports the signature as invalid and lists what went wrong.
The revisions, numbered from zero as the diff report numbers them:
| Revision | What happens |
|---|---|
| 0 | The original document. |
| 1 | addSignature certifies it at mdp.FillFormsAndAnnots. |
| 2 | addSomeInvalidChanges appends a blank page. |
The rejected change
appender, err := model.NewPdfAppender(reader)
if err != nil {
return err
}
appender.AddPages(model.NewPdfPage())
if err := appender.WriteToFile(outputPath); err != nil {
return err
}Nothing here fails or warns. The appender writes the page happily; the change is only rejected later, when a validator compares the revisions. Certifying a document does not stop anyone from editing it, it only makes the edit detectable.
Adding a page produces two errors, because the default diff policy notices both
the new page and the change to the page tree that had to accompany it. That is
also why the level makes no difference: the page tree checks in the default policy
are unconditional, so a page added, removed or replaced is an error even at
mdp.NoRestrictions.
What counts as a rejected change
The default policy compares pages, the page tree, annotations and form fields between revisions. Within that scope, and leaving aside the page tree, the level decides:
mdp.NoChangesrejects everything tracked.mdp.FillFormsrejects removing a form field, adding or changing an annotation that is not a widget, and removing any annotation at all, widgets included.mdp.FillFormsAndAnnotsrejects removing a form field, and nothing else.mdp.NoRestrictionsrejects nothing outside the page tree.
Full details are in Sign and configure DocMDP restriction.
Limitations
The scope cuts both ways. The default policy documents itself as comparing only
pages, the page tree, annotations and form fields, so a later revision that
rewrites a page’s content stream, changing the text a reader sees, is not reported
at any level. If detecting that matters, implement
mdp.DiffPolicy and pass it through ValidateWithOpts; the default policy will
not do it, and reader.ValidateSignatures cannot be given a custom policy.
Errors carry a revision number and a description, and nothing else. There is no
object number or diff to inspect programmatically, so a report saying Pages were changed is as specific as the SDK gets.
Run the example
addSignature certifies the document, addSomeInvalidChanges adds the page, and
ValidateFile reopens the file and prints the outcome.
git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/signatures
go run pdf_sign_docmdp_invalid_changes.go <INPUT_PDF_PATH> <OUTPUT_PDF_PATH>If this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.
View the full source
Sample output
Run against a one-page input:
== Signature 1
Name: Test Signature Appearance Name
Date: 2026-08-04 00:12:32 +0700 UTC+0700
Reason: TestSignatureAppearance Reason
Location not specified
Contact info not specified
Fields: 1
Signed: Document is signed
Signature validation: Is invalid
Trusted: Untrusted certificate
diff is permitted: false
MDP errors:
Pages were changed in revisions #2
page #2 was added in revisions #2
Revocation data: CRL not found
Revocation data: OCSP not found
DoneThe page number in the second error is the position of the added page, so it tracks the input’s page count.
