diff -pruN 2.4.11+~2.4.0-2/debian/changelog 2.4.11+~2.4.0-2ubuntu1/debian/changelog
--- 2.4.11+~2.4.0-2/debian/changelog	2022-11-21 11:16:07.000000000 +0000
+++ 2.4.11+~2.4.0-2ubuntu1/debian/changelog	2025-09-23 14:34:31.000000000 +0000
@@ -1,3 +1,17 @@
+node-sha.js (2.4.11+~2.4.0-2ubuntu1) questing; urgency=medium
+
+  * SECURITY UPDATE: improper input validation
+    - debian/patches/CVE-2025-9288-1.patch: Validate input types in hash.js.
+    - debian/patches/CVE-2025-9288-2.patch: Embed to-buffer.js and its
+      dependencies in node_modules.
+    - debian/control: Add node-get-intrinsic, node-isarray, and
+      node-is-typed-array to Depends.
+    - debian/copyright: Add copyright details for embedded Node modules.
+    - debian/install: Install embedded Node modules.
+    - CVE-2025-9288
+
+ -- Edwin Jiang <edwin.jiang@canonical.com>  Tue, 23 Sep 2025 14:34:31 +0000
+
 node-sha.js (2.4.11+~2.4.0-2) unstable; urgency=medium
 
   [ Debian Janitor ]
diff -pruN 2.4.11+~2.4.0-2/debian/control 2.4.11+~2.4.0-2ubuntu1/debian/control
--- 2.4.11+~2.4.0-2/debian/control	2022-11-21 11:16:07.000000000 +0000
+++ 2.4.11+~2.4.0-2ubuntu1/debian/control	2025-09-23 14:34:31.000000000 +0000
@@ -1,7 +1,8 @@
 Source: node-sha.js
 Section: javascript
 Priority: optional
-Maintainer: Debian Javascript Maintainers <pkg-javascript-devel@lists.alioth.debian.org>
+Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
+XSBC-Original-Maintainer: Debian Javascript Maintainers <pkg-javascript-devel@lists.alioth.debian.org>
 Uploaders: Bastien Roucariès <rouca@debian.org>
 Build-Depends: debhelper-compat (= 13)
  , dh-sequence-nodejs
@@ -19,7 +20,10 @@ Rules-Requires-Root: no
 Package: node-sha.js
 Architecture: all
 Depends: ${misc:Depends}
+ , node-get-intrinsic
  , node-inherits
+ , node-isarray
+ , node-is-typed-array
  , node-safe-buffer (>= 5.0.1)
  , nodejs:any
 Provides: ${nodejs:Provides}
diff -pruN 2.4.11+~2.4.0-2/debian/copyright 2.4.11+~2.4.0-2ubuntu1/debian/copyright
--- 2.4.11+~2.4.0-2/debian/copyright	2022-11-21 11:16:07.000000000 +0000
+++ 2.4.11+~2.4.0-2ubuntu1/debian/copyright	2025-09-23 14:34:31.000000000 +0000
@@ -18,6 +18,14 @@ Files: debian/*
 Copyright: 2017 Bastien Roucariès <rouca@debian.org>
 License: Expat
 
+Files: debian/patches/CVE-2025-9288-2.patch
+Copyright: 2016, Mathias Buus <https://github.com/mafintosh>
+ 2023-2024, Jordan Harband
+License: Expat
+Comment: installed into /usr/share/nodejs/sha.js/node_modules/
+ to-buffer: (c) 2016, Mathias Buus <https://github.com/mafintosh>
+ others: 2023-2024, Jordan Harband
+
 Files: types-*/*
 Copyright: Microsoft Corporation
 License: Expat
diff -pruN 2.4.11+~2.4.0-2/debian/install 2.4.11+~2.4.0-2ubuntu1/debian/install
--- 2.4.11+~2.4.0-2/debian/install	1970-01-01 00:00:00.000000000 +0000
+++ 2.4.11+~2.4.0-2ubuntu1/debian/install	2025-09-23 14:34:31.000000000 +0000
@@ -0,0 +1 @@
+node_modules/* usr/share/nodejs/sha.js/node_modules/
\ No newline at end of file
diff -pruN 2.4.11+~2.4.0-2/debian/patches/CVE-2025-9288-1.patch 2.4.11+~2.4.0-2ubuntu1/debian/patches/CVE-2025-9288-1.patch
--- 2.4.11+~2.4.0-2/debian/patches/CVE-2025-9288-1.patch	1970-01-01 00:00:00.000000000 +0000
+++ 2.4.11+~2.4.0-2ubuntu1/debian/patches/CVE-2025-9288-1.patch	2025-09-23 14:34:31.000000000 +0000
@@ -0,0 +1,106 @@
+Description: Support multi-byte wide typed arrays
+Author: Nikita Skovoroda <chalkerx@gmail.com>
+Origin: upstream, https://github.com/browserify/sha.js/commit/f2a258e9
+Bug: https://github.com/browserify/sha.js/pull/78
+Bug-Debian: https://bugs.debian.org/1111769
+Forwarded: not-needed
+Applied-Upstream: 2.4.12, https://github.com/browserify/sha.js/pull/78
+Reviewed-By: Xavier Guimard <yadd@debian.org>
+Last-Update: 2025-09-14
+
+--- a/hash.js
++++ b/hash.js
+@@ -1,4 +1,5 @@
+ var Buffer = require('safe-buffer').Buffer
++var toBuffer = require('to-buffer');
+ 
+ // prototype class for hash functions
+ function Hash (blockSize, finalSize) {
+@@ -9,10 +10,7 @@
+ }
+ 
+ Hash.prototype.update = function (data, enc) {
+-  if (typeof data === 'string') {
+-    enc = enc || 'utf8'
+-    data = Buffer.from(data, enc)
+-  }
++  data = toBuffer(data, enc || 'utf8')
+ 
+   var block = this._block
+   var blockSize = this._blockSize
+--- a/test/test.js
++++ b/test/test.js
+@@ -2,6 +2,12 @@
+ var tape = require('tape')
+ var Sha1 = require('../').sha1
+ 
++var nodeSupportsUint16 = false;
++try {
++	crypto.createHash('sha1').update(new Uint16Array());
++	nodeSupportsUint16 = true;
++} catch (err) {}
++
+ var inputs = [
+   ['', 'ascii'],
+   ['abc', 'ascii'],
+@@ -11,8 +17,10 @@
+   ['123456789abcdef123456789abcdef123456789abcdef123456789ab', 'ascii'],
+   ['0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde', 'ascii'],
+   ['0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', 'ascii'],
+-  ['foobarbaz', 'ascii']
+-]
++  ['foobarbaz', 'ascii'],
++  [Buffer.from('buffer')],
++  nodeSupportsUint16 ? [new Uint16Array([1, 2, 3])] : null
++].filter(Boolean);
+ 
+ tape("hash is the same as node's crypto", function (t) {
+   inputs.forEach(function (v) {
+@@ -31,7 +39,7 @@
+     var _hash = crypto.createHash('sha1')
+ 
+     for (var i = 0; i < v[0].length; i = (i + 1) * 2) {
+-      var s = v[0].substring(i, (i + 1) * 2)
++      var s = v[0].slice(i, (i + 1) * 2);
+       hash.update(s, v[1])
+       _hash.update(s, v[1])
+     }
+@@ -70,7 +78,7 @@
+     var _hash = crypto.createHash('sha1')
+ 
+     for (var i = 0; i < v[0].length; i = (i + 1) * 2) {
+-      var s = v[0].substring(i, (i + 1) * 2)
++      var s = v[0].slice(i, (i + 1) * 2);
+       hash.update(Buffer.from(s, 'ascii').toString('hex'), 'hex')
+       _hash.update(Buffer.from(s, 'ascii').toString('hex'), 'hex')
+     }
+@@ -84,6 +92,29 @@
+   t.end()
+ })
+ 
++tape('throws on invalid input', function (t) {
++	var invalid = [
++		{}, // non-arrayish
++		{ length: 20 }, // undefined values
++		[NaN], // non-numbers
++		[[]], // non-numbers
++		[1, 1.5], // non-integers
++		[1, 256], // out of bounds
++		[-1, 0] // out of bounds
++	];
++
++	invalid.forEach(function (input) {
++		var hash = new Sha1();
++
++		t['throws'](function () {
++			hash.update(input);
++			hash.digest('hex');
++		});
++	});
++
++	t.end();
++});
++
+ tape('call digest for more than MAX_UINT32 bits of data', function (t) {
+   var _hash = crypto.createHash('sha1')
+   var hash = new Sha1()
diff -pruN 2.4.11+~2.4.0-2/debian/patches/CVE-2025-9288-2.patch 2.4.11+~2.4.0-2ubuntu1/debian/patches/CVE-2025-9288-2.patch
--- 2.4.11+~2.4.0-2/debian/patches/CVE-2025-9288-2.patch	1970-01-01 00:00:00.000000000 +0000
+++ 2.4.11+~2.4.0-2ubuntu1/debian/patches/CVE-2025-9288-2.patch	2025-09-23 14:34:31.000000000 +0000
@@ -0,0 +1,233 @@
+Description: import to-buffer, needed to fix CVE-2025-9288
+Author: Yadd <yadd@debian.org>
+Forwarded: not-needed
+Last-Update: 2025-09-14
+
+--- /dev/null
++++ b/node_modules/call-bind-apply-helpers/actualApply.js
+@@ -0,0 +1,10 @@
++'use strict';
++
++var bind = require('function-bind');
++
++var $apply = require('./functionApply');
++var $call = require('./functionCall');
++var $reflectApply = require('./reflectApply');
++
++/** @type {import('./actualApply')} */
++module.exports = $reflectApply || bind.call($call, $apply);
+--- /dev/null
++++ b/node_modules/call-bind-apply-helpers/applyBind.js
+@@ -0,0 +1,10 @@
++'use strict';
++
++var bind = require('function-bind');
++var $apply = require('./functionApply');
++var actualApply = require('./actualApply');
++
++/** @type {import('./applyBind')} */
++module.exports = function applyBind() {
++	return actualApply(bind, $apply, arguments);
++};
+--- /dev/null
++++ b/node_modules/call-bind-apply-helpers/functionApply.js
+@@ -0,0 +1,4 @@
++'use strict';
++
++/** @type {import('./functionApply')} */
++module.exports = Function.prototype.apply;
+--- /dev/null
++++ b/node_modules/call-bind-apply-helpers/functionCall.js
+@@ -0,0 +1,4 @@
++'use strict';
++
++/** @type {import('./functionCall')} */
++module.exports = Function.prototype.call;
+--- /dev/null
++++ b/node_modules/call-bind-apply-helpers/index.js
+@@ -0,0 +1,15 @@
++'use strict';
++
++var bind = require('function-bind');
++var $TypeError = require('es-errors/type');
++
++var $call = require('./functionCall');
++var $actualApply = require('./actualApply');
++
++/** @type {(args: [Function, thisArg?: unknown, ...args: unknown[]]) => Function} TODO FIXME, find a way to use import('.') */
++module.exports = function callBindBasic(args) {
++	if (args.length < 1 || typeof args[0] !== 'function') {
++		throw new $TypeError('a function is required');
++	}
++	return $actualApply(bind, $call, args);
++};
+--- /dev/null
++++ b/node_modules/call-bind-apply-helpers/reflectApply.js
+@@ -0,0 +1,4 @@
++'use strict';
++
++/** @type {import('./reflectApply')} */
++module.exports = typeof Reflect !== 'undefined' && Reflect && Reflect.apply;
+--- /dev/null
++++ b/node_modules/call-bound.js
+@@ -0,0 +1,19 @@
++'use strict';
++
++var GetIntrinsic = require('get-intrinsic');
++
++var callBindBasic = require('call-bind-apply-helpers');
++
++/** @type {(thisArg: string, searchString: string, position?: number) => number} */
++var $indexOf = callBindBasic([GetIntrinsic('%String.prototype.indexOf%')]);
++
++/** @type {import('.')} */
++module.exports = function callBoundIntrinsic(name, allowMissing) {
++	/* eslint no-extra-parens: 0 */
++
++	var intrinsic = /** @type {(this: unknown, ...args: unknown[]) => unknown} */ (GetIntrinsic(name, !!allowMissing));
++	if (typeof intrinsic === 'function' && $indexOf(name, '.prototype.') > -1) {
++		return callBindBasic(/** @type {const} */ ([intrinsic]));
++	}
++	return intrinsic;
++};
+--- /dev/null
++++ b/node_modules/es-errors/type.js
+@@ -0,0 +1,4 @@
++'use strict';
++
++/** @type {import('./type')} */
++module.exports = TypeError;
+--- /dev/null
++++ b/node_modules/to-buffer.js
+@@ -0,0 +1,109 @@
++'use strict';
++
++var Buffer = require('safe-buffer').Buffer;
++var isArray = require('isarray');
++var typedArrayBuffer = require('typed-array-buffer');
++
++var isView = ArrayBuffer.isView || function isView(obj) {
++	try {
++		typedArrayBuffer(obj);
++		return true;
++	} catch (e) {
++		return false;
++	}
++};
++
++var useUint8Array = typeof Uint8Array !== 'undefined';
++var useArrayBuffer = typeof ArrayBuffer !== 'undefined'
++	&& typeof Uint8Array !== 'undefined';
++var useFromArrayBuffer = useArrayBuffer && (Buffer.prototype instanceof Uint8Array || Buffer.TYPED_ARRAY_SUPPORT);
++
++module.exports = function toBuffer(data, encoding) {
++	/*
++	 * No need to do anything for exact instance
++	 * This is only valid when safe-buffer.Buffer === buffer.Buffer, i.e. when Buffer.from/Buffer.alloc existed
++	 */
++	if (data instanceof Buffer) {
++		return data;
++	}
++
++	if (typeof data === 'string') {
++		return Buffer.from(data, encoding);
++	}
++
++	/*
++	 * Wrap any TypedArray instances and DataViews
++	 * Makes sense only on engines with full TypedArray support -- let Buffer detect that
++	 */
++	if (useArrayBuffer && isView(data)) {
++		// Bug in Node.js <6.3.1, which treats this as out-of-bounds
++		if (data.byteLength === 0) {
++			return Buffer.alloc(0);
++		}
++
++		// When Buffer is based on Uint8Array, we can just construct it from ArrayBuffer
++		if (useFromArrayBuffer) {
++			var res = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
++			/*
++			 * Recheck result size, as offset/length doesn't work on Node.js <5.10
++			 * We just go to Uint8Array case if this fails
++			 */
++			if (res.byteLength === data.byteLength) {
++				return res;
++			}
++		}
++
++		// Convert to Uint8Array bytes and then to Buffer
++		var uint8 = data instanceof Uint8Array ? data : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
++		var result = Buffer.from(uint8);
++
++		/*
++		 * Let's recheck that conversion succeeded
++		 * We have .length but not .byteLength when useFromArrayBuffer is false
++		 */
++		if (result.length === data.byteLength) {
++			return result;
++		}
++	}
++
++	/*
++	 * Uint8Array in engines where Buffer.from might not work with ArrayBuffer, just copy over
++	 * Doesn't make sense with other TypedArray instances
++	 */
++	if (useUint8Array && data instanceof Uint8Array) {
++		return Buffer.from(data);
++	}
++
++	var isArr = isArray(data);
++	if (isArr) {
++		for (var i = 0; i < data.length; i += 1) {
++			var x = data[i];
++			if (
++				typeof x !== 'number'
++				|| x < 0
++				|| x > 255
++				|| ~~x !== x // NaN and integer check
++			) {
++				throw new RangeError('Array items must be numbers in the range 0-255.');
++			}
++		}
++	}
++
++	/*
++	 * Old Buffer polyfill on an engine that doesn't have TypedArray support
++	 * Also, this is from a different Buffer polyfill implementation then we have, as instanceof check failed
++	 * Convert to our current Buffer implementation
++	 */
++	if (
++		isArr || (
++			Buffer.isBuffer(data)
++				&& data.constructor
++				&& typeof data.constructor.isBuffer === 'function'
++				&& data.constructor.isBuffer(data)
++		)
++	) {
++		return Buffer.from(data);
++	}
++
++	throw new TypeError('The "data" argument must be a string, an Array, a Buffer, a Uint8Array, or a DataView.');
++};
+--- /dev/null
++++ b/node_modules/typed-array-buffer.js
+@@ -0,0 +1,19 @@
++'use strict';
++
++var $TypeError = require('es-errors/type');
++
++var callBound = require('call-bound');
++
++/** @type {undefined | ((thisArg: import('.').TypedArray) => Buffer<ArrayBufferLike>)} */
++var $typedArrayBuffer = callBound('TypedArray.prototype.buffer', true);
++
++var isTypedArray = require('is-typed-array');
++
++/** @type {import('.')} */
++// node <= 0.10, < 0.11.4 has a nonconfigurable own property instead of a prototype getter
++module.exports = $typedArrayBuffer || function typedArrayBuffer(x) {
++	if (!isTypedArray(x)) {
++		throw new $TypeError('Not a Typed Array');
++	}
++	return x.buffer;
++};
diff -pruN 2.4.11+~2.4.0-2/debian/patches/series 2.4.11+~2.4.0-2ubuntu1/debian/patches/series
--- 2.4.11+~2.4.0-2/debian/patches/series	2022-11-21 11:16:07.000000000 +0000
+++ 2.4.11+~2.4.0-2ubuntu1/debian/patches/series	2025-09-23 14:34:31.000000000 +0000
@@ -1,2 +1,4 @@
 0001-Fix-testsuite.patch
 0002-Fix-FTBFS-in-32-bits-arch.patch
+CVE-2025-9288-1.patch
+CVE-2025-9288-2.patch
