Skip to content

feat: extract crypto helpers and add comprehensive AES-256-GCM test s…#79

Merged
deekshithgowda85 merged 2 commits into
deekshithgowda85:prodfrom
trivikramkalagi91-commits:feature/aes-crypto-tests
May 31, 2026
Merged

feat: extract crypto helpers and add comprehensive AES-256-GCM test s…#79
deekshithgowda85 merged 2 commits into
deekshithgowda85:prodfrom
trivikramkalagi91-commits:feature/aes-crypto-tests

Conversation

@trivikramkalagi91-commits
Copy link
Copy Markdown
Contributor

@trivikramkalagi91-commits trivikramkalagi91-commits commented May 31, 2026

Pull Request

Summary

This PR resolves the issue by extracting the internal cryptographic utilities from lib/env-store.ts into a modular, dedicated file and adding a comprehensive unit testing suite.

Related Issue

Closes: #21

What Changed

  • Code Refactoring: Extracted the encrypt, decrypt, and getKey functions out of lib/env-store.ts and into a new, reusable module at lib/crypto.ts.
  • Bug Fix: Fixed an edge-case logic error where empty string payloads ("") evaluated as falsy and caused unexpected decryption validation failures.
  • Testing Infrastructure: Added Jest, ts-jest, and @types/jest configurations, and hooked up the "test": "jest" execution script in package.json.
  • Test Suite: Created tests/crypto.test.ts containing 9 distinct assertions verifying round-trip integrity (with standard, long, and unicode inputs), proper iv:tag:ciphertext formatting rules, unique Initialization Vector generation, and strict anti-tampering behavior.

Verification

  • I ran npm test and all 9 tests pass flawlessly with 100% success locally.
  • Verified that environment dependencies are cleanly isolated using test lifecycle hooks.

BELOW IS THE RESULT WHEN I RAN 'npm test':

secdev@0.1.0 test
jest

PASS tests/crypto.test.ts
Crypto Utils
Round-Trip Data Integrity
√ should successfully encrypt and decrypt a standard string (4 ms)
√ should handle empty strings (1 ms)
√ should handle long strings
√ should handle special characters and unicode
Format and Delimiter Validation
√ should return a string separated by two colons into 3 parts (2 ms)
Initialization Vector (IV) Uniqueness
√ should generate a unique ciphertext for the same plaintext
Robust Error & Tamper Handling
√ should throw an error if the input string is malformed (13 ms)
√ should throw an error if the ciphertext is modified (11 ms)
√ should throw an error if the authentication tag is altered (1 ms)

Test Suites: 1 passed, 1 total
Tests: 9 passed, 9 total
Snapshots: 0 total
Time: 0.502 s, estimated 1 s
Ran all test suites.

NOTE: hi i accept that i worked using antigravity to solve this issue and also i tried to understand what it is actually instead of simply making a PR and what i understood basuc is tht adding a test cases which can be usable everywhere for example by making a seperate tool box as lib/crypto.ts which is not fixed for only one thing now it is seperated.

Summary by CodeRabbit

  • Tests

    • Added comprehensive test suite for encryption and decryption with edge case validation and tamper-resistance verification.
  • Chores

    • Added Jest testing and ESLint configuration.
    • Added development dependencies for testing infrastructure.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 31, 2026

@trivikramkalagi91-commits is attempting to deploy a commit to the Deekshith Gowda HS's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

Review Change Stack

Warning

Review limit reached

@trivikramkalagi91-commits, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 53 minutes and 5 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: de6adef5-c862-4390-96d4-fbdc80d2852f

📥 Commits

Reviewing files that changed from the base of the PR and between cabb787 and 803d558.

📒 Files selected for processing (1)
  • jest.config.js
📝 Walkthrough

Walkthrough

This PR refactors inline AES-256-GCM crypto logic into a dedicated lib/crypto.ts module, updates lib/env-store.ts to use it, adds Jest test infrastructure, and introduces a comprehensive test suite validating encryption/decryption round-trip behavior, format contracts, uniqueness, and tamper resistance.

Changes

Crypto Helpers Extraction and Testing

Layer / File(s) Summary
Crypto module with AES-256-GCM helpers
lib/crypto.ts
getKey() derives a 32-byte SHA-256 key from NEXTAUTH_SECRET with a dev fallback; encrypt() generates a random 12-byte IV, performs AES-256-GCM encryption, and returns the format ivHex:tagHex:encHex; decrypt() parses and validates that format, reconstructs the decipher with the IV and key, sets the GCM auth tag, and returns decrypted UTF-8 plaintext.
Environment variable storage refactor
lib/env-store.ts
Removes inline key-derivation and AES-256-GCM helpers; imports encrypt and decrypt from ./crypto and continues to use them in env var read/write operations with the same error handling and encryption behavior.
Test infrastructure setup
jest.config.js, package.json
Jest configuration sets testEnvironment to Node and applies ts-jest preset for TypeScript. package.json adds test and lint scripts and introduces jest, ts-jest, and @types/jest as dev dependencies.
Crypto unit tests
__tests__/crypto.test.ts
Test suite validates round-trip encryption/decryption correctness for normal strings, empty strings, large 10K-character strings, and unicode/special-character inputs. Asserts ciphertext format as three colon-delimited hex-like parts, verifies IV/tag uniqueness on repeated encryptions, and confirms error-throwing on malformed and tampered ciphertext/authentication-tag data.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A crypto module hops so clean,
With tests that catch what's in between,
IV, tag, and ciphertext align,
Round-trip journeys—all divine!
Jest runs fast, the helpers shine. 🔐✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: extracting crypto helpers into a reusable module and adding comprehensive AES-256-GCM tests.
Linked Issues check ✅ Passed The PR fully addresses issue #21 by extracting crypto helpers to lib/crypto.ts and adding comprehensive tests in tests/crypto.test.ts covering round-trip correctness, format validation, and tamper-resistance.
Out of Scope Changes check ✅ Passed All changes align with linked issue objectives. The extraction of crypto helpers, test infrastructure setup (Jest/ts-jest), test suite implementation, and refactoring of lib/env-store.ts to use the new module are all within scope.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@trivikramkalagi91-commits
Copy link
Copy Markdown
Contributor Author

hi @deekshithgowda85 i am a gssoc contributor pls review the PR i tried it to understand the issue and worked on it using AI.

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: 3

🧹 Nitpick comments (1)
__tests__/crypto.test.ts (1)

46-59: ⚡ Quick win

Strengthen format contract assertions for IV/tag lengths.

Line 46-59 currently checks “hex-like and non-empty” only. Add exact length checks (IV=24 hex chars, tag=32 hex chars) to lock the AES-256-GCM contract from lib/crypto.ts.

Suggested test hardening
       const [iv, tag, enc] = parts;
       expect(iv.length).toBeGreaterThan(0);
       expect(tag.length).toBeGreaterThan(0);
       expect(enc.length).toBeGreaterThan(0);
+      expect(iv).toHaveLength(24);  // 12-byte IV in hex
+      expect(tag).toHaveLength(32); // 16-byte GCM auth tag in hex
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@__tests__/crypto.test.ts` around lines 46 - 59, The test should assert exact
hex-lengths to lock the AES-256-GCM contract: after calling encrypt(...) and
splitting the ciphertext into parts, add assertions that the IV hex string
length equals 24 and the tag hex string length equals 32 (leave the encrypted
payload check as non-empty) — update the test that references encrypt to replace
the current non-empty IV/tag checks with exact length assertions while keeping
the existing hex-regex validations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@jest.config.js`:
- Around line 1-10: Replace the CommonJS require by using ES module
imports/exports: import createDefaultPreset from "ts-jest" (or import {
createDefaultPreset } from "ts-jest" depending on the package export) and
replace module.exports with export default, then keep the existing usage of
createDefaultPreset() and tsJestTransformCfg (the transform assignment)
unchanged so the config still sets testEnvironment and transform; update the
file to use ESM syntax so createDefaultPreset, tsJestTransformCfg and the
exported Jest config no longer use require().

In `@lib/crypto.ts`:
- Around line 3-5: Replace the insecure fallback in getKey so the app fails
closed: in the getKey function check process.env.NEXTAUTH_SECRET (referencing
getKey and the createHash call) and if it's undefined/empty throw a clear error
(e.g., "NEXTAUTH_SECRET must be set") instead of using
"default-dev-secret-32-chars-long"; when present, continue to derive the Buffer
via createHash("sha256").update(raw).digest(); ensure tests set NEXTAUTH_SECRET
explicitly.
- Around line 18-20: The current destructuring const [ivHex, tagHex, dataHex] =
ciphertext.split(":") allows extra segments to be ignored; change the logic so
you first assign the result to a variable (e.g., const parts =
ciphertext.split(":")) and validate parts.length === 3 before destructuring or
using parts[0..2]; if the length is not exactly 3, throw the existing "Invalid
ciphertext format" error. Update the code path around the ivHex/tagHex/dataHex
validation to use parts to ensure tampered payloads like
`${validCiphertext}:junk` are rejected.

---

Nitpick comments:
In `@__tests__/crypto.test.ts`:
- Around line 46-59: The test should assert exact hex-lengths to lock the
AES-256-GCM contract: after calling encrypt(...) and splitting the ciphertext
into parts, add assertions that the IV hex string length equals 24 and the tag
hex string length equals 32 (leave the encrypted payload check as non-empty) —
update the test that references encrypt to replace the current non-empty IV/tag
checks with exact length assertions while keeping the existing hex-regex
validations.
🪄 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: 31e65a9c-74bb-4cd5-8596-b3830ef1786c

📥 Commits

Reviewing files that changed from the base of the PR and between 1b1e993 and cabb787.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • __tests__/crypto.test.ts
  • jest.config.js
  • lib/crypto.ts
  • lib/env-store.ts
  • package.json

Comment thread jest.config.js
Comment on lines +1 to +10
const { createDefaultPreset } = require("ts-jest");

const tsJestTransformCfg = createDefaultPreset().transform;

/** @type {import("jest").Config} **/
module.exports = {
testEnvironment: "node",
transform: {
...tsJestTransformCfg,
},
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 | ⚡ Quick win

Replace require usage to unblock lint CI.

Line 1 violates the enforced ESLint rule and is currently breaking the pipeline. Switch to a ts-jest preset config that does not require require().

Suggested fix
-const { createDefaultPreset } = require("ts-jest");
-
-const tsJestTransformCfg = createDefaultPreset().transform;
-
 /** `@type` {import("jest").Config} **/
 module.exports = {
+  preset: "ts-jest",
   testEnvironment: "node",
-  transform: {
-    ...tsJestTransformCfg,
-  },
 };
📝 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 { createDefaultPreset } = require("ts-jest");
const tsJestTransformCfg = createDefaultPreset().transform;
/** @type {import("jest").Config} **/
module.exports = {
testEnvironment: "node",
transform: {
...tsJestTransformCfg,
},
/** `@type` {import("jest").Config} **/
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
};
🧰 Tools
🪛 ESLint

[error] 1-1: A require() style import is forbidden.

(@typescript-eslint/no-require-imports)

🪛 GitHub Actions: CI / 0_Lint and Build.txt

[error] 1-1: ESLint error: A require() style import is forbidden. (@typescript-eslint/no-require-imports)

🪛 GitHub Actions: CI / Lint and Build

[error] 1-1: ESLint (@typescript-eslint/no-require-imports): A require() style import is forbidden.

🪛 GitHub Check: Lint and Build

[failure] 1-1:
A require() style import is forbidden

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@jest.config.js` around lines 1 - 10, Replace the CommonJS require by using ES
module imports/exports: import createDefaultPreset from "ts-jest" (or import {
createDefaultPreset } from "ts-jest" depending on the package export) and
replace module.exports with export default, then keep the existing usage of
createDefaultPreset() and tsJestTransformCfg (the transform assignment)
unchanged so the config still sets testEnvironment and transform; update the
file to use ESM syntax so createDefaultPreset, tsJestTransformCfg and the
exported Jest config no longer use require().

Comment thread lib/crypto.ts
Comment on lines +3 to +5
export function getKey(): Buffer {
const raw = process.env.NEXTAUTH_SECRET ?? "default-dev-secret-32-chars-long";
return createHash("sha256").update(raw).digest();
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 | ⚡ Quick win

Fail closed when NEXTAUTH_SECRET is missing.

Falling back to a hard-coded secret makes every misconfigured deployment encrypt env vars with the same known key, so DB-backed secrets are effectively recoverable. Require an explicit secret here and let tests set one deliberately.

Suggested fix
 export function getKey(): Buffer {
-  const raw = process.env.NEXTAUTH_SECRET ?? "default-dev-secret-32-chars-long";
+  const raw = process.env.NEXTAUTH_SECRET;
+  if (!raw) {
+    throw new Error("NEXTAUTH_SECRET must be set");
+  }
   return createHash("sha256").update(raw).digest();
 }
📝 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
export function getKey(): Buffer {
const raw = process.env.NEXTAUTH_SECRET ?? "default-dev-secret-32-chars-long";
return createHash("sha256").update(raw).digest();
export function getKey(): Buffer {
const raw = process.env.NEXTAUTH_SECRET;
if (!raw) {
throw new Error("NEXTAUTH_SECRET must be set");
}
return createHash("sha256").update(raw).digest();
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crypto.ts` around lines 3 - 5, Replace the insecure fallback in getKey so
the app fails closed: in the getKey function check process.env.NEXTAUTH_SECRET
(referencing getKey and the createHash call) and if it's undefined/empty throw a
clear error (e.g., "NEXTAUTH_SECRET must be set") instead of using
"default-dev-secret-32-chars-long"; when present, continue to derive the Buffer
via createHash("sha256").update(raw).digest(); ensure tests set NEXTAUTH_SECRET
explicitly.

Comment thread lib/crypto.ts
Comment on lines +18 to +20
const [ivHex, tagHex, dataHex] = ciphertext.split(":");
if (!ivHex || !tagHex || dataHex === undefined) {
throw new Error("Invalid ciphertext format");
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 | ⚡ Quick win

Reject ciphertexts that contain extra : segments.

split(":") + destructuring ignores any fourth segment, so a tampered payload like ${validCiphertext}:junk still decrypts successfully. Enforce exactly three parts before creating the decipher.

Suggested fix
 export function decrypt(ciphertext: string): string {
-  const [ivHex, tagHex, dataHex] = ciphertext.split(":");
-  if (!ivHex || !tagHex || dataHex === undefined) {
+  const parts = ciphertext.split(":");
+  if (parts.length !== 3) {
+    throw new Error("Invalid ciphertext format");
+  }
+  const [ivHex, tagHex, dataHex] = parts;
+  if (!ivHex || !tagHex || dataHex === undefined) {
     throw new Error("Invalid ciphertext format");
   }
📝 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 [ivHex, tagHex, dataHex] = ciphertext.split(":");
if (!ivHex || !tagHex || dataHex === undefined) {
throw new Error("Invalid ciphertext format");
const parts = ciphertext.split(":");
if (parts.length !== 3) {
throw new Error("Invalid ciphertext format");
}
const [ivHex, tagHex, dataHex] = parts;
if (!ivHex || !tagHex || dataHex === undefined) {
throw new Error("Invalid ciphertext format");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crypto.ts` around lines 18 - 20, The current destructuring const [ivHex,
tagHex, dataHex] = ciphertext.split(":") allows extra segments to be ignored;
change the logic so you first assign the result to a variable (e.g., const parts
= ciphertext.split(":")) and validate parts.length === 3 before destructuring or
using parts[0..2]; if the length is not exactly 3, throw the existing "Invalid
ciphertext format" error. Update the code path around the ivHex/tagHex/dataHex
validation to use parts to ensure tampered payloads like
`${validCiphertext}:junk` are rejected.

@deekshithgowda85 deekshithgowda85 merged commit 2d65a13 into deekshithgowda85:prod May 31, 2026
2 of 3 checks passed
@trivikramkalagi91-commits
Copy link
Copy Markdown
Contributor Author

thank you

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Add tests for AES encrypt/decrypt helpers

2 participants