Doc: Resolves #13124: Create a warrant canary page (#14375)

Co-authored-by: Henry Heino <46334387+personalizedrefrigerator@users.noreply.github.com>
This commit is contained in:
Laurent Cozic
2026-02-18 20:45:57 +00:00
committed by GitHub
parent d893680a84
commit b240c7fafc
10 changed files with 273 additions and 1 deletions
+1
View File
@@ -1940,6 +1940,7 @@ packages/tools/update-readme-contributors.js
packages/tools/update-readme-download.test.js
packages/tools/update-readme-download.js
packages/tools/update-readme-sponsors.js
packages/tools/updateCanary.js
packages/tools/updateMarkdownDoc.js
packages/tools/utils/discourse.test.js
packages/tools/utils/discourse.js
+1
View File
@@ -1913,6 +1913,7 @@ packages/tools/update-readme-contributors.js
packages/tools/update-readme-download.test.js
packages/tools/update-readme-download.js
packages/tools/update-readme-sponsors.js
packages/tools/updateCanary.js
packages/tools/updateMarkdownDoc.js
packages/tools/utils/discourse.test.js
packages/tools/utils/discourse.js
+14
View File
@@ -0,0 +1,14 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEaZWFlBYJKwYBBAHaRw8BAQdAIh3xQbjaS0EC+8WuKXNPjVF/ayq0/2GZlheR
qj1G3Qe0RUpvcGxpbiBDYW5hcnkgU2lnbmluZyBLZXkgKFdhcnJhbnQgQ2FuYXJ5
IEtleSkgPGNhbmFyeUBqb3BsaW5hcHAub3JnPoiZBBMWCgBBFiEE+CD4MG3QBaEC
0YzVlGrp+lkV71MFAmmVhZQCGwMFCQPCZwAFCwkIBwICIgIGFQoJCAsCBBYCAwEC
HgcCF4AACgkQlGrp+lkV71MZtwD/Ufd4OAcgkl5T6MSB+WDFg8BXvpaBZfNnZkoo
LrOoqNAA/iqGiiBRoarlus2ATOiWhyXaEpRUQcEeaRhhqHW0BGcCuDgEaZWFlBIK
KwYBBAGXVQEFAQEHQFORKWRLp4hDYzR8Q5IRyF9AIjoziR+sj4icUdvZx4Z6AwEI
B4h+BBgWCgAmFiEE+CD4MG3QBaEC0YzVlGrp+lkV71MFAmmVhZQCGwwFCQPCZwAA
CgkQlGrp+lkV71Nu+AD9Gw4qEmL8WNCNs7idc8CRpGpS2DhasNTV398lbKYzco0B
ANlMrGlMc0w1KhuFxdU4fF3s/ktUUnjJwosxK94l5/MJ
=C9VN
-----END PGP PUBLIC KEY BLOCK-----
+8
View File
@@ -61,6 +61,14 @@ Name | Description
Please see the guide for information on how to contribute to the development of Joplin: https://github.com/laurent22/joplin/blob/dev/readme/dev/index.md
## Warrant Canary Signing Key
Fingerprint:
F820 F830 6DD0 05A1 02D1 8CD5 946A E9FA 5915 EF53
Public key: https://github.com/laurent22/joplin/raw/dev/Assets/keys/joplin-canary-signing-key.asc
# Contributors
Thank you to everyone who've contributed to Joplin's source code!
+1
View File
@@ -63,6 +63,7 @@
"/readme/_i18n",
"/readme/about/changelog/desktop.md",
"/readme/licenses.md",
"/readme/canary.txt",
"/readme/i18n",
"cspell.json",
"node_modules"
+1
View File
@@ -60,6 +60,7 @@
"test": "yarn workspaces foreach --worktree --parallel --verbose --interlaced --jobs 2 run test",
"tsc": "yarn workspaces foreach --worktree --parallel --verbose --interlaced run tsc",
"updateIgnored": "node packages/tools/gulp/tasks/updateIgnoredTypeScriptBuildRun.js",
"updateCanary": "node ./packages/tools/updateCanary",
"updateMarkdownDoc": "node ./packages/tools/updateMarkdownDoc",
"updateNews": "node ./packages/tools/website/updateNews",
"updatePluginTypes": "./packages/generator-joplin/updateTypes.sh",
+3 -1
View File
@@ -250,4 +250,6 @@ mrjo
codegen
analyzed
Perfetto
appmodules
appmodules
armor
clearsign
+115
View File
@@ -0,0 +1,115 @@
import { execFileSync } from 'child_process';
import { writeFile, remove } from 'fs-extra';
import { dirname, join } from 'path';
import { createInterface, Interface as ReadlineInterface } from 'readline';
const rootDir = dirname(dirname(__dirname));
const canaryFile = join(rootDir, 'readme', 'canary.txt');
function formatDate(date: Date): string {
const day = date.getUTCDate();
const month = date.toLocaleString('en-US', { month: 'long', timeZone: 'UTC' });
const year = date.getUTCFullYear();
return `${day} ${month} ${year}`;
}
function addDays(date: Date, days: number): Date {
const result = new Date(date);
result.setUTCDate(result.getUTCDate() + days);
return result;
}
function prompt(rl: ReadlineInterface, question: string): Promise<string> {
return new Promise(resolve => {
rl.question(question, answer => resolve(answer));
});
}
async function promptNonEmpty(rl: ReadlineInterface, question: string): Promise<string> {
while (true) {
const answer = (await prompt(rl, question)).trim();
if (answer) return answer;
console.log('This field cannot be empty. Please try again.');
}
}
function buildCanaryContent(statementDate: Date, headline1: string, headline2: string): string {
const validUntil = addDays(statementDate, 60);
return `Joplin Warrant Canary
Statement date: ${formatDate(statementDate)}
Valid until: ${formatDate(validUntil)}
This warrant canary is updated every 60 days.
If this document has not been updated within 75 days of the
Statement date above, it should be considered expired.
As of the Statement date:
* No National Security Letters have been received by the project or its maintainer.
* No orders under the USA PATRIOT Act or the Foreign Intelligence Surveillance Act have been received by the project or its maintainer.
* No government request accompanied by a gag order has been received by the project or its maintainer.
* No government agency or law enforcement body has required the introduction of backdoors into Joplin software, infrastructure, or services.
* No government agency or law enforcement body has compelled the project or its maintainer to provide access to user data, servers, or infrastructure associated with Joplin.
If any such order is received, and we are legally permitted to do so,
this statement will be updated accordingly. If we are not legally
permitted to disclose the existence of such an order, this statement
will not be updated.
The public key is available at:
https://github.com/laurent22/joplin/raw/dev/Assets/keys/joplin-canary-signing-key.asc
To prevent backdating, this statement includes current public events:
Current international events:
1. ${headline1}
2. ${headline2}
Canary signing key fingerprint:
F820 F830 6DD0 05A1 02D1 8CD5 946A E9FA 5915 EF53
`;
}
async function main() {
const rl = createInterface({ input: process.stdin, output: process.stdout });
try {
console.log('Enter the current international event headlines to include in the canary.\n');
const headline1 = await promptNonEmpty(rl, 'Headline 1: ');
const headline2 = await promptNonEmpty(rl, 'Headline 2: ');
const statementDate = new Date();
const content = buildCanaryContent(statementDate, headline1, headline2);
const tmpFile = `${canaryFile}.tmp`;
await writeFile(tmpFile, content, 'utf8');
execFileSync(
'gpg',
[
'--yes',
'--armor',
'--clearsign',
'--local-user',
'canary@joplinapp.org',
'--output',
canaryFile,
tmpFile,
],
{ stdio: 'inherit' },
);
await remove(tmpFile);
console.log(`Updated: ${canaryFile}`);
} finally {
rl.close();
}
}
main().catch(error => {
console.error(error.message || error);
process.exit(1);
});
+83
View File
@@ -0,0 +1,83 @@
# Warrant Canary
This repository contains the official warrant canary for Joplin.
The purpose of the warrant canary is to provide a regularly updated, cryptographically signed statement indicating that no secret legal orders, gag orders, or similar directives have been received as of the stated date.
If such an order were ever received and disclosure were legally prohibited, the canary would cease to be updated.
## Location of the Canary
The current signed canary is published at:
https://github.com/laurent22/joplin/raw/dev/readme/canary.txt
## Canary Signing Key
The canary is signed using a dedicated OpenPGP key. It is linked from the canary.txt file.
Its fingerprint is present in the canary.txt file itself and duplicated at:
https://github.com/laurent22/joplin/blob/dev/README.md
## Updating the canary file
Run `yarn updateCanary` from the root of the repository and follow the prompt.
## Key Rotation Policy
The canary signing key may be rotated for the following reasons:
* Key expiry
* Suspected compromise
* Maintainer transition
* Operational upgrades (e.g. hardware-backed signing)
Key rotation will never be performed silently.
## Key Rotation Procedure
### 1. Generate a New Key
Create a new dedicated OpenPGP signing key.
Export the new public key in ASCII-armoured format.
### 2. Publish the New Key
Add the new public key to:
https://github.com/laurent22/joplin/raw/dev/Assets/keys/joplin-canary-signing-key.asc
### 3. Update Documentation
#### Update the README
* Mark the new fingerprint as **Active**
* Mark the previous fingerprint as **Retired**
* Document the rotation date
Example:
```
Active Canary Signing Key:
NEW FINGERPRINT
Previous Key (retired 2028-02-18):
OLD FINGERPRINT
```
#### Update updateCanary.ts
Add the new fingerprint to the canary template.
### 4. Transitional Signing
For the first canary issued after rotation:
* Sign with the new key
* Optionally also sign with the old key
This creates a cryptographic bridge between the two identities.
If the old key is compromised, do not dual-sign. Instead, publish a revocation statement.
+46
View File
@@ -0,0 +1,46 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
Joplin Warrant Canary
Statement date: 18 February 2026
Valid until: 19 April 2026
This warrant canary is updated every 60 days.
If this document has not been updated within 75 days of the
Statement date above, it should be considered expired.
As of the Statement date:
* No National Security Letters have been received.
* No orders under the USA PATRIOT Act have been received.
* No orders under the Foreign Intelligence Surveillance Act have been received.
* No government requests have been received that are accompanied by a gag order.
* No law enforcement or government agency has required the introduction of backdoors into Joplin.
If any such order is received, and we are legally permitted to do so,
this statement will be updated accordingly. If we are not legally
permitted to disclose the existence of such an order, this statement
will not be updated.
The public key is available at:
https://github.com/laurent22/joplin/blob/dev/Assets/keys/joplin-canary-signing-key.asc
To prevent backdating, this statement includes current public events:
Current international events:
1. Philippine vice-president Sara Duterte announces 2028 presidential bid
2. African football chief occupying seat illegally and must go, says leading executive
Canary signing key fingerprint:
F820 F830 6DD0 05A1 02D1 8CD5 946A E9FA 5915 EF53
-----BEGIN PGP SIGNATURE-----
iIsEARYKADMWIQT4IPgwbdAFoQLRjNWUaun6WRXvUwUCaZWUBBUcY2FuYXJ5QGpv
cGxpbmFwcC5vcmcACgkQlGrp+lkV71N8lwEAuRNboHOsr7/GbDjpEhkLrYOtzf2Y
3uUBB3YBbPabWvEA/RNCb0sUqFaKUT8Zeq/IsoLydsRsLMYUaUP19WIS/rQM
=UEz2
-----END PGP SIGNATURE-----