diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 4e1a74f0..ded5077c 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -15,6 +15,8 @@ updates:
day: 'friday'
time: '03:00'
timezone: Europe/London
+ cooldown:
+ default-days: 2
commit-message:
prefix: 'chore (deps): '
ignore:
@@ -39,11 +41,10 @@ updates:
versions: [ '>=2.0.0' ]
- dependency-name: 'jimp'
versions: [ '1.6.1' ]
- - dependency-name: 'webpack-dev-server'
- versions: [ '>=5.1.0' ]
groups:
#
- # Grouping so we don't get a seperate PR for every patch version.
+ # Grouping so patch version updates are batched together in a single PR
+ # and similarly with minor version updates
#
patch-updates:
applies-to: version-updates
@@ -51,6 +52,12 @@ updates:
- '*'
update-types:
- 'patch'
+ minor-updates:
+ applies-to: version-updates
+ patterns:
+ - '*'
+ update-types:
+ - 'minor'
# Versioning on Github Actions
- package-ecosystem: "github-actions"
@@ -62,8 +69,14 @@ updates:
day: 'friday'
time: '03:00'
timezone: Europe/London
+ cooldown:
+ default-days: 4
commit-message:
prefix: 'chore (deps): '
+ groups:
+ actions-dependencies:
+ patterns:
+ - "*"
- package-ecosystem: docker
directory: /
@@ -72,3 +85,11 @@ updates:
day: 'friday'
time: '03:00'
timezone: Europe/London
+ cooldown:
+ default-days: 4
+ commit-message:
+ prefix: 'chore (deps): '
+ groups:
+ docker-dependencies:
+ patterns:
+ - "*"
diff --git a/package-lock.json b/package-lock.json
index e4854f73..f1286be0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -155,11 +155,11 @@
"postcss-import": "^16.1.1",
"postcss-loader": "^8.2.1",
"prompt": "^1.3.0",
- "sitemap": "^8.0.3",
- "terser": "^5.46.2",
+ "sitemap": "^9.0.1",
+ "terser": "^5.48.0",
"webpack": "^5.107.1",
"webpack-bundle-analyzer": "^5.3.0",
- "webpack-dev-server": "^5.0.4",
+ "webpack-dev-server": "^5.2.4",
"webpack-node-externals": "^3.0.0",
"worker-loader": "^3.0.8"
},
@@ -16703,29 +16703,39 @@
}
},
"node_modules/sitemap": {
- "version": "8.0.3",
- "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-8.0.3.tgz",
- "integrity": "sha512-9Ew1tR2WYw8RGE2XLy7GjkusvYXy8Rg6y8TYuBuQMfIEdGcWoJpY2Wr5DzsEiL/TKCw56+YKTCCUHglorEYK+A==",
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-9.0.1.tgz",
+ "integrity": "sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/node": "^17.0.5",
+ "@types/node": "^24.9.2",
"@types/sax": "^1.2.1",
"arg": "^5.0.0",
"sax": "^1.4.1"
},
"bin": {
- "sitemap": "dist/cli.js"
+ "sitemap": "dist/esm/cli.js"
},
"engines": {
- "node": ">=14.0.0",
- "npm": ">=6.0.0"
+ "node": ">=20.19.5",
+ "npm": ">=10.8.2"
}
},
"node_modules/sitemap/node_modules/@types/node": {
- "version": "17.0.45",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz",
- "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==",
+ "version": "24.12.4",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz",
+ "integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/sitemap/node_modules/undici-types": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"dev": true,
"license": "MIT"
},
@@ -17336,9 +17346,9 @@
}
},
"node_modules/terser": {
- "version": "5.46.2",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.2.tgz",
- "integrity": "sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw==",
+ "version": "5.48.0",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz",
+ "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@@ -18338,9 +18348,9 @@
}
},
"node_modules/webpack-dev-server": {
- "version": "5.2.3",
- "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.3.tgz",
- "integrity": "sha512-9Gyu2F7+bg4Vv+pjbovuYDhHX+mqdqITykfzdM9UyKqKHlsE5aAjRhR+oOEfXW5vBeu8tarzlJFIZva4ZjAdrQ==",
+ "version": "5.2.4",
+ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.4.tgz",
+ "integrity": "sha512-GqDPGZN9bRqKBTkp4aWkobDDHMsrXKoGSdOH56smIri8qR0JG8gfL8/v/f/OZR3/OKXjG8uwJbFVhKm/FNU/UA==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 53bd1627..aab93268 100644
--- a/package.json
+++ b/package.json
@@ -85,11 +85,11 @@
"postcss-import": "^16.1.1",
"postcss-loader": "^8.2.1",
"prompt": "^1.3.0",
- "sitemap": "^8.0.3",
- "terser": "^5.46.2",
+ "sitemap": "^9.0.1",
+ "terser": "^5.48.0",
"webpack": "^5.107.1",
"webpack-bundle-analyzer": "^5.3.0",
- "webpack-dev-server": "^5.0.4",
+ "webpack-dev-server": "^5.2.4",
"webpack-node-externals": "^3.0.0",
"worker-loader": "^3.0.8"
},
diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index a4f4516a..86d6b504 100644
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -189,6 +189,7 @@
"Generate PGP Key Pair",
"PGP Encrypt",
"PGP Decrypt",
+ "PGP Sign",
"PGP Verify",
"PGP Encrypt and Sign",
"PGP Decrypt and Verify",
diff --git a/src/core/operations/GeneratePGPKeyPair.mjs b/src/core/operations/GeneratePGPKeyPair.mjs
index a26b5cc8..e3fd6742 100644
--- a/src/core/operations/GeneratePGPKeyPair.mjs
+++ b/src/core/operations/GeneratePGPKeyPair.mjs
@@ -12,6 +12,7 @@ import { getSubkeySize, ASP } from "../lib/PGP.mjs";
import { cryptNotice } from "../lib/Crypt.mjs";
import * as es6promisify from "es6-promisify";
const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify;
+const KEY_FLAGS = kbpgp.const.openpgp.key_flags;
/**
@@ -73,11 +74,11 @@ class GeneratePGPKeyPair extends Operation {
if (name) userIdentifier += name;
if (email) userIdentifier += ` <${email}>`;
- let flags = kbpgp.const.openpgp.certify_keys;
- flags |= kbpgp.const.openpgp.sign_data;
- flags |= kbpgp.const.openpgp.auth;
- flags |= kbpgp.const.openpgp.encrypt_comm;
- flags |= kbpgp.const.openpgp.encrypt_storage;
+ let flags = KEY_FLAGS.certify_keys;
+ flags |= KEY_FLAGS.sign_data;
+ flags |= KEY_FLAGS.auth;
+ flags |= KEY_FLAGS.encrypt_comm;
+ flags |= KEY_FLAGS.encrypt_storage;
const keyGenerationOptions = {
userid: userIdentifier,
@@ -89,11 +90,11 @@ class GeneratePGPKeyPair extends Operation {
},
subkeys: [{
"nbits": getSubkeySize(keySize),
- "flags": kbpgp.const.openpgp.sign_data,
+ "flags": KEY_FLAGS.sign_data,
"expire_in": 86400 * 365 * 8
}, {
"nbits": getSubkeySize(keySize),
- "flags": kbpgp.const.openpgp.encrypt_comm | kbpgp.const.openpgp.encrypt_storage,
+ "flags": KEY_FLAGS.encrypt_comm | KEY_FLAGS.encrypt_storage,
"expire_in": 86400 * 365 * 2
}],
asp: ASP
diff --git a/src/core/operations/PGPSign.mjs b/src/core/operations/PGPSign.mjs
new file mode 100644
index 00000000..e3063d89
--- /dev/null
+++ b/src/core/operations/PGPSign.mjs
@@ -0,0 +1,83 @@
+/**
+ * @author GCHQDeveloper581
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import kbpgp from "kbpgp";
+import { ASP, importPrivateKey } from "../lib/PGP.mjs";
+import OperationError from "../errors/OperationError.mjs";
+import * as es6promisify from "es6-promisify";
+const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify;
+
+/**
+ * PGP Sign operation
+ */
+class PGPSign extends Operation {
+
+ /**
+ * PGPSign constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "PGP Sign";
+ this.module = "PGP";
+ this.description = [
+ "Input: the message you want to sign",
+ "
",
+ "Arguments: the ASCII-armoured PGP private key of the sender.",
+ "
",
+ "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.",
+ "
",
+ "This function uses the Keybase implementation of PGP.",
+ ].join("\n");
+ this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy"; // Usually a Wikipedia link. Remember to remove localisation (i.e. https://wikipedia.org/etc rather than https://en.wikipedia.org/etc)
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "Private key of signer",
+ "type": "text",
+ "value": ""
+ },
+ {
+ "name": "Private key passphrase (optional)",
+ "type": "string",
+ "value": ""
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ *
+ * @throws {OperationError} if failed private key import or failed encryption
+ */
+ async run(input, args) {
+ const message = input,
+ [privateKey, passphrase] = args;
+ let signedMessage;
+
+ if (!privateKey) throw new OperationError("Enter the private key of the signer.");
+ const privKey = await importPrivateKey(privateKey, passphrase);
+
+ try {
+ signedMessage = await promisify(kbpgp.box)({
+ "msg": message,
+ "sign_with": privKey,
+ "asp": ASP
+ });
+ } catch (err) {
+ throw new OperationError(`Couldn't sign message: ${err}`);
+ }
+
+ return signedMessage;
+ }
+
+}
+
+export default PGPSign;
diff --git a/src/web/static/sitemap.mjs b/src/web/static/sitemap.mjs
index f373a277..d7d723b1 100644
--- a/src/web/static/sitemap.mjs
+++ b/src/web/static/sitemap.mjs
@@ -1,4 +1,4 @@
-import sm from "sitemap";
+import { SitemapStream, streamToPromise } from "sitemap";
import OperationConfig from "../../core/config/OperationConfig.json" with { type: "json" };
/**
@@ -11,7 +11,7 @@ import OperationConfig from "../../core/config/OperationConfig.json" with { type
const baseUrl = "https://gchq.github.io/CyberChef/";
-const smStream = new sm.SitemapStream({});
+const smStream = new SitemapStream({});
smStream.write({
url: baseUrl,
@@ -28,6 +28,6 @@ for (const op in OperationConfig) {
}
smStream.end();
-sm.streamToPromise(smStream).then(
+streamToPromise(smStream).then(
(buffer) => console.log(buffer.toString()), // eslint-disable-line no-console
);
diff --git a/tests/node/index.mjs b/tests/node/index.mjs
index f872f8f4..52670d48 100644
--- a/tests/node/index.mjs
+++ b/tests/node/index.mjs
@@ -18,6 +18,7 @@ import {
import TestRegister from "../lib/TestRegister.mjs";
import "./tests/nodeApi.mjs";
import "./tests/operations.mjs";
+import "./tests/PGP.mjs";
import "./tests/File.mjs";
import "./tests/Dish.mjs";
import "./tests/NodeDish.mjs";
diff --git a/tests/node/tests/PGP.mjs b/tests/node/tests/PGP.mjs
new file mode 100644
index 00000000..2a695ea3
--- /dev/null
+++ b/tests/node/tests/PGP.mjs
@@ -0,0 +1,69 @@
+/**
+ * PGP node tests.
+ *
+ * @author C85297 [95289555+C85297@users.noreply.github.com]
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import assert from "assert";
+import kbpgp from "kbpgp";
+import * as es6promisify from "es6-promisify";
+
+import TestRegister from "../../lib/TestRegister.mjs";
+import it from "../assertionHandler.mjs";
+import GeneratePGPKeyPair from "../../../src/core/operations/GeneratePGPKeyPair.mjs";
+
+const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify;
+
+const PUBLIC_KEY_BLOCK = /-----BEGIN PGP PUBLIC KEY BLOCK-----[\s\S]*-----END PGP PUBLIC KEY BLOCK-----/;
+
+/**
+ * Generate a PGP key pair and import the generated public key.
+ *
+ * @param {string} keyType
+ * @returns {Promise