Skip to content

Feat/with async crypto#14

Open
Santosl2 wants to merge 9 commits intoWhiskeySockets:masterfrom
Santosl2:feat/with-async-crypto
Open

Feat/with async crypto#14
Santosl2 wants to merge 9 commits intoWhiskeySockets:masterfrom
Santosl2:feat/with-async-crypto

Conversation

@Santosl2
Copy link
Copy Markdown

@Santosl2 Santosl2 commented Apr 14, 2026

Summary by CodeRabbit

  • New Features

    • Added automatic timeout protection for long-running operations to prevent indefinite hangs.
  • Improvements

    • Enhanced error messages for better diagnostics during cryptographic validation failures.
    • Migrated cryptographic operations to industry-standard WebCrypto APIs.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 14, 2026

📝 Walkthrough

Walkthrough

This pull request converts the codebase from synchronous Node crypto primitives to async WebCrypto APIs. It replaces synchronous functions (crypto.randomBytes, generateKeyPairSync) with WebCrypto equivalents, converts six core crypto functions and multiple session/key management functions to async, adds a timeout wrapper for job execution, and adds the async-mutex dependency.

Changes

Cohort / File(s) Summary
Dependency
package.json
Added async-mutex@^0.5.0 as a runtime dependency.
Core Cryptography
src/crypto.js
Converted encrypt, decrypt, calculateMAC, hash, deriveSecrets, and verifyMAC to async functions using WebCrypto (subtle.encrypt, subtle.decrypt, subtle.sign, subtle.digest) instead of Node's synchronous crypto; enhanced error messages in verifyMAC with length and hex value details.
Elliptic Curve Operations
src/curve.js
Converted getPublicFromPrivateKey, generateKeyPair, calculateAgreement, calculateSignature, and verifySignature to async functions; replaced Node's generateKeyPairSync/diffieHellman/randomBytes with WebCrypto subtle.generateKey, subtle.exportKey, and subtle.deriveBits for X25519; changed buffer slicing from .slice(1) to .subarray(1).
Key Generation
src/keyhelper.js
Converted generateSignedPreKey and generatePreKey to async functions awaiting curve.generateKeyPair() and curve.calculateSignature(...); switched random byte generation from nodeCrypto.randomBytes(2) to webcrypto.getRandomValues(new Uint16Array(1)).
Session Management
src/session_builder.js, src/session_cipher.js
Converted session initialization and encryption/decryption methods to async, awaiting crypto operations (curve.verifySignature, curve.generateKeyPair, curve.calculateAgreement, crypto.deriveSecrets, crypto.encrypt, crypto.decrypt, crypto.verifyMAC); converted calculateSendingRatchet and calculateRatchet to async; replaced buffer .slice() with .subarray() in encryption/decryption paths; reworked fillMessageKeys from recursive to iterative loop.
Logging & Job Management
src/session_record.js, src/queue_job.js, src/numeric_fingerprint.js
Commented out console.info() statements in session record methods; added withTimeout(fn, ms=15000) helper in queue executor to reject long-running jobs with timeout errors; updated iterateHash to await async crypto.hash().

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 Hops with glee through async chains,
WebCrypto flows through crypto veins,
No more sync blocks us in place—
Await the future, embrace the grace!
With timeouts ticking, sessions clean, ⏱️✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat/with async crypto' is directly related to the main objective of the changeset, which converts cryptographic operations throughout the codebase from synchronous to asynchronous using WebCrypto APIs.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feat/with-async-crypto

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/curve.js (1)

122-134: ⚠️ Potential issue | 🟡 Minor

Add documentation explaining the isInit parameter's security implications.

The isInit ? true : curveJs.verify(...) pattern at line 133 intentionally bypasses signature verification during session initialization (confirmed by git commit "remove unnecessary verify in session creation"). However, there is no comment explaining when this bypass is appropriate or why it's safe in the Signal protocol context. Add an inline comment documenting this behavior to prevent future maintainers from misinterpreting it as a security bug.

Additionally, note that this function is declared async but returns a synchronous value—consider whether this aligns with the module's stated goal of maintaining an async interface for consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/curve.js` around lines 122 - 134, The verifySignature function currently
bypasses actual signature checking when isInit is true (returning isInit ? true
: curveJs.verify(...)), which needs an inline comment documenting why bypassing
verification is safe during session initialization and under what
Signal-protocol conditions this is allowed; add an explicit comment next to the
isInit branch referencing verifySignature, isInit, and curveJs.verify that
states the intended behavior, security rationale, and any assumptions (e.g.,
this path is only used during initial session setup where verification is
performed elsewhere). Also clarify the async signature: either remove the async
keyword from verifySignature if it is always synchronous, or add a short inline
comment explaining that the function is intentionally async to preserve an async
interface contract across the module. Ensure the changes are local to the
verifySignature function and its surrounding comments so maintainers can easily
find and understand the rationale.
🧹 Nitpick comments (2)
src/session_cipher.js (1)

227-233: Use Object.prototype.hasOwnProperty.call here.

Calling hasOwnProperty directly on a mutable plain object is what ESLint is flagging, and it can be shadowed by an own property. Use the prototype form instead.

Small fix
-        if (!chain.messageKeys.hasOwnProperty(message.counter)) {
+        if (!Object.prototype.hasOwnProperty.call(chain.messageKeys, message.counter)) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/session_cipher.js` around lines 227 - 233, Replace the direct
hasOwnProperty call on the mutable plain object to avoid shadowing: in the
decrypt path where you call maybeStepRatchet, getChain, and fillMessageKeys
(look for methods maybeStepRatchet, getChain, fillMessageKeys and the variable
chain.messageKeys in session_cipher.js), change the check
"chain.messageKeys.hasOwnProperty(message.counter)" to use
Object.prototype.hasOwnProperty.call(chain.messageKeys, message.counter) so the
prototype method is used safely.
src/session_record.js (1)

273-273: Remove the checked-in commented logs.

If these messages still matter, route them through a debug logger or flag. Leaving disabled console.* lines in the session lifecycle just adds dead code.

Also applies to: 281-281, 301-301

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/session_record.js` at line 273, Remove the checked-in commented console.*
lines in src/session_record.js (the commented "console.info('Closing session:',
session)" and the other commented console lines) — either delete them entirely
or replace them with calls to the project's debug/logger API (e.g., logger.debug
or processLogger.debug) inside the session lifecycle functions that manage
closing/saving sessions so the messages are emitted only when debug logging is
enabled; ensure no stray commented console.* statements remain in the file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/crypto.js`:
- Around line 85-96: The code in src/crypto.js exposes sensitive MAC material in
error strings when verifying MACs; update the checks around calculateMAC, mac,
and calculatedMac to throw generic errors without including MAC hex values or
key-derived data—e.g. replace the detailed messages in the length check and
verification failure with non-sensitive messages like "Bad MAC length" and "Bad
MAC" (or a single generic "Bad MAC") and ensure no code path logs mac or
calculatedMac; keep the validation logic (calculateMAC(...).subarray(...), the
length comparison, and the constant-time XOR loop) unchanged but remove any
inclusion of mac or calculatedMac in thrown errors or logs.

In `@src/curve.js`:
- Around line 10-12: Remove the unused constant PUBLIC_KEY_DER_PREFIX from
src/curve.js: locate the declaration PUBLIC_KEY_DER_PREFIX and delete it (and
any related comments or references if present) so the file no longer contains an
unused Buffer constant leftover from the WebCrypto migration.

In `@src/keyhelper.js`:
- Line 17: The change made generateSignedPreKey and generatePreKey to be async
(returning Promises) which breaks the public API; restore backward compatibility
by reverting them to synchronous exports: remove the async keyword and ensure
both generateSignedPreKey and generatePreKey return plain objects with the same
properties (e.g., .keyPair and .signature) instead of a Promise, or
alternatively add new async-named variants (e.g., generateSignedPreKeyAsync /
generatePreKeyAsync) and keep the original function names synchronous; update
the module exports so callers of generateSignedPreKey and generatePreKey
continue to receive plain objects (also apply the same fix to the other
occurrence at the second function on line 36).

In `@src/queue_job.js`:
- Around line 21-37: withTimeout currently rejects immediately on timeout which
lets _asyncQueueExecutor advance while the original job.awaitable (the
underlying fn) is still running; change withTimeout so that when the timeout
fires it records that the call has timed out (e.g., create a TimeoutError or set
a timedOut flag) but does not resolve/reject the returned Promise until the
underlying fn() settles, then reject with the TimeoutError; ensure you still
clear the timer and clean up on fn() resolution/rejection and preserve the
original behavior when fn() finishes before ms expires—this keeps
_asyncQueueExecutor and job.awaitable serialization intact while providing a
clear timeout signal to callers.

---

Outside diff comments:
In `@src/curve.js`:
- Around line 122-134: The verifySignature function currently bypasses actual
signature checking when isInit is true (returning isInit ? true :
curveJs.verify(...)), which needs an inline comment documenting why bypassing
verification is safe during session initialization and under what
Signal-protocol conditions this is allowed; add an explicit comment next to the
isInit branch referencing verifySignature, isInit, and curveJs.verify that
states the intended behavior, security rationale, and any assumptions (e.g.,
this path is only used during initial session setup where verification is
performed elsewhere). Also clarify the async signature: either remove the async
keyword from verifySignature if it is always synchronous, or add a short inline
comment explaining that the function is intentionally async to preserve an async
interface contract across the module. Ensure the changes are local to the
verifySignature function and its surrounding comments so maintainers can easily
find and understand the rationale.

---

Nitpick comments:
In `@src/session_cipher.js`:
- Around line 227-233: Replace the direct hasOwnProperty call on the mutable
plain object to avoid shadowing: in the decrypt path where you call
maybeStepRatchet, getChain, and fillMessageKeys (look for methods
maybeStepRatchet, getChain, fillMessageKeys and the variable chain.messageKeys
in session_cipher.js), change the check
"chain.messageKeys.hasOwnProperty(message.counter)" to use
Object.prototype.hasOwnProperty.call(chain.messageKeys, message.counter) so the
prototype method is used safely.

In `@src/session_record.js`:
- Line 273: Remove the checked-in commented console.* lines in
src/session_record.js (the commented "console.info('Closing session:', session)"
and the other commented console lines) — either delete them entirely or replace
them with calls to the project's debug/logger API (e.g., logger.debug or
processLogger.debug) inside the session lifecycle functions that manage
closing/saving sessions so the messages are emitted only when debug logging is
enabled; ensure no stray commented console.* statements remain in the file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 32a518a9-8bab-41a2-ae30-7838a6fa2747

📥 Commits

Reviewing files that changed from the base of the PR and between 1c30d7d and 977bdf5.

📒 Files selected for processing (9)
  • package.json
  • src/crypto.js
  • src/curve.js
  • src/keyhelper.js
  • src/numeric_fingerprint.js
  • src/queue_job.js
  • src/session_builder.js
  • src/session_cipher.js
  • src/session_record.js

Comment thread src/crypto.js
Comment on lines +85 to +96
const calculatedMac = (await calculateMAC(key, data)).subarray(0, length);
if (mac.length !== length || calculatedMac.length !== length) {
throw new Error("Bad MAC length");
throw new Error("Bad MAC length Expected: " + length +
" Got: " + mac.length + " and " + calculatedMac.length);
}
let diff = 0;
for (let i = 0; i < length; i++) {
diff |= mac[i] ^ calculatedMac[i];
}
if (!mac.equals(calculatedMac)) {
throw new Error("Bad MAC");
if (diff !== 0) {
throw new Error("Bad MAC Expected: " + calculatedMac.toString('hex') +
" Got: " + mac.toString('hex'));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Do not leak MAC material in error strings.

src/session_cipher.js:157-160 logs decryption errors, so these messages now write the expected MAC and received MAC to logs on every verification failure. Keep MAC failures generic; the derived MAC is sensitive key-derived material.

Safer error handling
-       throw new Error("Bad MAC length Expected: " + length +
-            " Got: " + mac.length + " and " + calculatedMac.length);
+       throw new Error('Bad MAC length');
...
-         throw new Error("Bad MAC Expected: " + calculatedMac.toString('hex') +
-            " Got: " + mac.toString('hex'));
+         throw new Error('Bad MAC');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const calculatedMac = (await calculateMAC(key, data)).subarray(0, length);
if (mac.length !== length || calculatedMac.length !== length) {
throw new Error("Bad MAC length");
throw new Error("Bad MAC length Expected: " + length +
" Got: " + mac.length + " and " + calculatedMac.length);
}
let diff = 0;
for (let i = 0; i < length; i++) {
diff |= mac[i] ^ calculatedMac[i];
}
if (!mac.equals(calculatedMac)) {
throw new Error("Bad MAC");
if (diff !== 0) {
throw new Error("Bad MAC Expected: " + calculatedMac.toString('hex') +
" Got: " + mac.toString('hex'));
const calculatedMac = (await calculateMAC(key, data)).subarray(0, length);
if (mac.length !== length || calculatedMac.length !== length) {
throw new Error('Bad MAC length');
}
let diff = 0;
for (let i = 0; i < length; i++) {
diff |= mac[i] ^ calculatedMac[i];
}
if (diff !== 0) {
throw new Error('Bad MAC');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/crypto.js` around lines 85 - 96, The code in src/crypto.js exposes
sensitive MAC material in error strings when verifying MACs; update the checks
around calculateMAC, mac, and calculatedMac to throw generic errors without
including MAC hex values or key-derived data—e.g. replace the detailed messages
in the length check and verification failure with non-sensitive messages like
"Bad MAC length" and "Bad MAC" (or a single generic "Bad MAC") and ensure no
code path logs mac or calculatedMac; keep the validation logic
(calculateMAC(...).subarray(...), the length comparison, and the constant-time
XOR loop) unchanged but remove any inclusion of mac or calculatedMac in thrown
errors or logs.

Comment thread src/curve.js
Comment on lines 10 to 12
const PUBLIC_KEY_DER_PREFIX = Buffer.from([
48, 42, 48, 5, 6, 3, 43, 101, 110, 3, 33, 0
]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Unused constant PUBLIC_KEY_DER_PREFIX.

This constant is defined but never used in the file. It may be leftover from the migration to WebCrypto. Consider removing it to avoid confusion.

🧹 Proposed fix
-// DER prefixes for X25519 keys (used for WebCrypto import/export)
+// DER prefix for X25519 private keys (used for WebCrypto import/export)
 // from: https://github.com/digitalbazaar/x25519-key-agreement-key-2019/blob/master/lib/crypto.js
-const PUBLIC_KEY_DER_PREFIX = Buffer.from([
-    48, 42, 48, 5, 6, 3, 43, 101, 110, 3, 33, 0
-]);
-
 const PRIVATE_KEY_DER_PREFIX = Buffer.from([
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const PUBLIC_KEY_DER_PREFIX = Buffer.from([
48, 42, 48, 5, 6, 3, 43, 101, 110, 3, 33, 0
]);
// DER prefix for X25519 private keys (used for WebCrypto import/export)
// from: https://github.com/digitalbazaar/x25519-key-agreement-key-2019/blob/master/lib/crypto.js
const PRIVATE_KEY_DER_PREFIX = Buffer.from([
🧰 Tools
🪛 ESLint

[error] 10-10: 'PUBLIC_KEY_DER_PREFIX' is assigned a value but never used.

(no-unused-vars)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/curve.js` around lines 10 - 12, Remove the unused constant
PUBLIC_KEY_DER_PREFIX from src/curve.js: locate the declaration
PUBLIC_KEY_DER_PREFIX and delete it (and any related comments or references if
present) so the file no longer contains an unused Buffer constant leftover from
the WebCrypto migration.

Comment thread src/keyhelper.js
};

exports.generateSignedPreKey = function(identityKeyPair, signedKeyId) {
exports.generateSignedPreKey = async function(identityKeyPair, signedKeyId) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

This is a breaking public API change.

generateSignedPreKey() and generatePreKey() now return Promises instead of plain objects. Existing consumers that read .keyPair / .signature synchronously will break at runtime, so this needs either separate async exports or a semver-major release with docs/examples updated.

Also applies to: 36-36

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/keyhelper.js` at line 17, The change made generateSignedPreKey and
generatePreKey to be async (returning Promises) which breaks the public API;
restore backward compatibility by reverting them to synchronous exports: remove
the async keyword and ensure both generateSignedPreKey and generatePreKey return
plain objects with the same properties (e.g., .keyPair and .signature) instead
of a Promise, or alternatively add new async-named variants (e.g.,
generateSignedPreKeyAsync / generatePreKeyAsync) and keep the original function
names synchronous; update the module exports so callers of generateSignedPreKey
and generatePreKey continue to receive plain objects (also apply the same fix to
the other occurrence at the second function on line 36).

Comment thread src/queue_job.js
Comment on lines +21 to +37
function withTimeout(fn, ms = 15000) {
if (typeof fn !== 'function') {
throw new TypeError('fn must be a function to wrap received ' + typeof fn);
}
if (typeof ms !== 'number') {
throw new TypeError('ms must be a number to wrap received ' + typeof ms);
}
return new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error('Job timed out')), ms);
fn().then((res) => {
clearTimeout(timer);
resolve(res);
}).catch((err) => {
clearTimeout(timer);
reject(err);
});
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Preserve queue ordering even after a timeout.

Once withTimeout() rejects, _asyncQueueExecutor() immediately advances to the next item even though the original job.awaitable keeps running. That breaks the serialization guarantee this module is supposed to provide: jobs from src/session_builder.js:22-52 and src/session_cipher.js:68-186 can now overlap and mutate the same session/storage state concurrently.

Minimal direction
-                job.resolve(await withTimeout(job.awaitable)); // if the job takes longer than 15 seconds, it will be rejected
+                const work = Promise.resolve().then(job.awaitable);
+                try {
+                    job.resolve(await withTimeout(() => work));
+                } catch (e) {
+                    job.reject(e);
+                } finally {
+                    try { await work; } catch (_) {}
+                }

Also applies to: 47-47

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/queue_job.js` around lines 21 - 37, withTimeout currently rejects
immediately on timeout which lets _asyncQueueExecutor advance while the original
job.awaitable (the underlying fn) is still running; change withTimeout so that
when the timeout fires it records that the call has timed out (e.g., create a
TimeoutError or set a timedOut flag) but does not resolve/reject the returned
Promise until the underlying fn() settles, then reject with the TimeoutError;
ensure you still clear the timer and clean up on fn() resolution/rejection and
preserve the original behavior when fn() finishes before ms expires—this keeps
_asyncQueueExecutor and job.awaitable serialization intact while providing a
clear timeout signal to callers.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 9 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/queue_job.js">

<violation number="1" location="src/queue_job.js:19">
P2: Documentation says "The default timeout is 30 seconds" but the actual default is 15 seconds (`ms = 15000`). This will mislead callers about the timeout behavior.</violation>

<violation number="2" location="src/queue_job.js:47">
P1: When `withTimeout` rejects on a timeout, `fn()` keeps running in the background while the executor advances to the next queued job. This breaks the serialization guarantee that the queue is supposed to provide—two jobs can now mutate session/storage state concurrently. After a timeout rejection, the executor should still `await` the original work promise (ignoring its result) before proceeding to the next job.</violation>
</file>

<file name="src/crypto.js">

<violation number="1" location="src/crypto.js:87">
P2: Similarly, the MAC length error now includes the expected and actual lengths. Keep MAC verification errors generic to avoid leaking any information about the verification process.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

Comment thread src/queue_job.js
const job = queue[i];
try {
job.resolve(await job.awaitable());
job.resolve(await withTimeout(job.awaitable)); // if the job takes longer than 15 seconds, it will be rejected
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When withTimeout rejects on a timeout, fn() keeps running in the background while the executor advances to the next queued job. This breaks the serialization guarantee that the queue is supposed to provide—two jobs can now mutate session/storage state concurrently. After a timeout rejection, the executor should still await the original work promise (ignoring its result) before proceeding to the next job.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/queue_job.js, line 47:

<comment>When `withTimeout` rejects on a timeout, `fn()` keeps running in the background while the executor advances to the next queued job. This breaks the serialization guarantee that the queue is supposed to provide—two jobs can now mutate session/storage state concurrently. After a timeout rejection, the executor should still `await` the original work promise (ignoring its result) before proceeding to the next job.</comment>

<file context>
@@ -10,14 +10,41 @@
             const job = queue[i];
             try {
-                job.resolve(await job.awaitable());
+                job.resolve(await withTimeout(job.awaitable)); // if the job takes longer than 15 seconds, it will be rejected
             } catch(e) {
                 job.reject(e);
</file context>
Fix with Cubic

Comment thread src/queue_job.js
* This is a wrapper around the async function that will reject if it
* takes longer than the specified timeout. This is useful for
* preventing a job from hanging indefinitely. The default timeout
* is 30 seconds.
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Documentation says "The default timeout is 30 seconds" but the actual default is 15 seconds (ms = 15000). This will mislead callers about the timeout behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/queue_job.js, line 19:

<comment>Documentation says "The default timeout is 30 seconds" but the actual default is 15 seconds (`ms = 15000`). This will mislead callers about the timeout behavior.</comment>

<file context>
@@ -10,14 +10,41 @@
+* This is a wrapper around the async function that will reject if it
+* takes longer than the specified timeout.  This is useful for
+* preventing a job from hanging indefinitely.  The default timeout
+* is 30 seconds.
+*/
+function withTimeout(fn, ms = 15000) {
</file context>
Suggested change
* is 30 seconds.
* is 15 seconds.
Fix with Cubic

Comment thread src/crypto.js
const calculatedMac = (await calculateMAC(key, data)).subarray(0, length);
if (mac.length !== length || calculatedMac.length !== length) {
throw new Error("Bad MAC length");
throw new Error("Bad MAC length Expected: " + length +
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Similarly, the MAC length error now includes the expected and actual lengths. Keep MAC verification errors generic to avoid leaking any information about the verification process.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/crypto.js, line 87:

<comment>Similarly, the MAC length error now includes the expected and actual lengths. Keep MAC verification errors generic to avoid leaking any information about the verification process.</comment>

<file context>
@@ -60,31 +63,37 @@ function deriveSecrets(input, salt, info, chunks) {
+    const calculatedMac = (await calculateMAC(key, data)).subarray(0, length);
     if (mac.length !== length || calculatedMac.length !== length) {
-        throw new Error("Bad MAC length");
+       throw new Error("Bad MAC length Expected: " + length +
+            " Got: " + mac.length + " and " + calculatedMac.length);
+    }
</file context>
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant