Skip to content

fix: salt persistence and padding validation in encryption - #505

Open
saurabhhhcodes wants to merge 1 commit into
UTSAVS26:mainfrom
saurabhhhcodes:fix/py-batch-2
Open

fix: salt persistence and padding validation in encryption#505
saurabhhhcodes wants to merge 1 commit into
UTSAVS26:mainfrom
saurabhhhcodes:fix/py-batch-2

Conversation

@saurabhhhcodes

@saurabhhhcodes saurabhhhcodes commented Jul 26, 2026

Copy link
Copy Markdown

Fixed two bugs in encryption module:

  1. Salt was randomly generated on each run but never persisted - made decryption impossible. Added .salt file persistence.
  2. unpad() lacked PKCS7 padding validation - added length and bytes validation.

Summary by CodeRabbit

  • New Features

    • Added optional salt support to encryption and decryption.
    • Encryption now stores the generated salt in a companion .salt file for future decryption.
    • Added support for encrypting and decrypting folders with persisted salt information.
  • Bug Fixes

    • Improved AES-CBC padding validation to detect invalid or corrupted data.
    • Added clear handling when the required salt file is missing.

@github-actions

Copy link
Copy Markdown
Contributor

👋 Thank you for opening this pull request! We're excited to review your contribution. Please give us a moment, and we'll get back to you shortly!

Feel free to join our community on Discord to discuss more!

@github-actions
github-actions Bot requested review from UTSAVS26 and pavitraag July 26, 2026 12:27
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

AES-CBC padding validation now rejects malformed padding. Encrypted payloads can include a salt prefix, while the CLI stores salts in .salt sidecar files for subsequent decryption.

Changes

Encryption and salt handling

Layer / File(s) Summary
Validated padding and salt-prefixed payloads
pysnippets/encryption/encryption.py
unpad() validates PKCS#7-like padding, while encrypt() and decrypt() support salt-prefixed payloads through updated parameters.
CLI salt generation and recovery
pysnippets/encryption/encryption.py
Encryption writes generated salts to sidecar files; decryption reads them and reports missing salt files.

Estimated code review effort: 3 (Moderate) | ~15–30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant Encryption
  participant SaltFile
  CLI->>Encryption: Encrypt data with salt
  Encryption-->>CLI: Salt-prefixed ciphertext
  CLI->>SaltFile: Write salt
  CLI->>SaltFile: Read salt
  CLI->>Encryption: Decrypt with salt length
  Encryption-->>CLI: Plaintext
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: salt persistence and stricter padding validation in the encryption module.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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 `@pysnippets/encryption/encryption.py`:
- Around line 75-80: Update the unpadding logic to validate that data is
non-empty and its length is AES block-aligned before accessing data[-1]. Raise
ValueError for empty or non-block-aligned input, while preserving the existing
padding-length, padding-byte, and successful unpadding behavior.
- Around line 214-221: Update the encryption flow around cipher.encrypt_file and
cipher.encrypt_folder so file and folder modes are mutually exclusive: when
file_path is provided, encrypt only that file and its salt sidecar; when
folder_path is provided, encrypt only the folder without recursively
re-encrypting the selected file or any .salt sidecars. Preserve the existing
salt-writing behavior and ensure the corresponding decryption path applies the
same exclusion rules.
- Around line 214-218: Update the encryption flow around encrypt_file so the
salt sidecar is durably written before replacing the plaintext, and handle any
failed encryption result without leaving unrecoverable ciphertext. Ensure the
ciphertext and salt updates are coordinated transactionally, preserving the
existing salt_path behavior while preventing partial completion.
🪄 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 Plus

Run ID: 56dee2dd-72c1-4e1e-8cfd-1c963bc93744

📥 Commits

Reviewing files that changed from the base of the PR and between 9f6eda4 and 8abb137.

📒 Files selected for processing (1)
  • pysnippets/encryption/encryption.py

Comment on lines 75 to 80
pad_length = data[-1]
if pad_length < 1 or pad_length > AES.block_size:
raise ValueError("Invalid padding length")
if data[-pad_length:] != bytes([pad_length] * pad_length):
raise ValueError("Invalid padding bytes")
return data[:-pad_length]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject empty and non-block-aligned padded data.

Line 75 raises IndexError for empty input, and b"\x01" is accepted despite not being an AES block. Raise ValueError before indexing unless data is non-empty and block-aligned.

🤖 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 `@pysnippets/encryption/encryption.py` around lines 75 - 80, Update the
unpadding logic to validate that data is non-empty and its length is AES
block-aligned before accessing data[-1]. Raise ValueError for empty or
non-block-aligned input, while preserving the existing padding-length,
padding-byte, and successful unpadding behavior.

Comment on lines 214 to +218
cipher.encrypt_file(file_path)
# Save salt alongside encrypted data
salt_path = file_path + ".salt"
with open(salt_path, "wb") as f:
f.write(salt)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Persist recovery material before replacing plaintext.

encrypt_file() overwrites the source at Line 214 before the salt is written. A termination or write failure between these operations leaves ciphertext whose key cannot be re-derived. Make the ciphertext and sidecar update durable/transactional, at minimum persisting the salt before replacing the file and handling a failed encryption result.

🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 216-216: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(salt_path, "wb")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(open-filename-from-request)

🤖 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 `@pysnippets/encryption/encryption.py` around lines 214 - 218, Update the
encryption flow around encrypt_file so the salt sidecar is durably written
before replacing the plaintext, and handle any failed encryption result without
leaving unrecoverable ciphertext. Ensure the ciphertext and salt updates are
coordinated transactionally, preserving the existing salt_path behavior while
preventing partial completion.

Comment on lines 214 to 221
cipher.encrypt_file(file_path)
# Save salt alongside encrypted data
salt_path = file_path + ".salt"
with open(salt_path, "wb") as f:
f.write(salt)
logging.info(f"Salt saved to {salt_path} — keep this file to decrypt later")
if folder_path:
cipher.encrypt_folder(folder_path)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not recursively encrypt the selected file or its salt sidecar.

If file_path is inside folder_path, Line 214 encrypts it, Lines 216-218 create <file>.salt, and Line 221 encrypts both again. The sidecar is then ciphertext, so decryption derives a key from invalid salt bytes and cannot recover the files. Make file and folder modes mutually exclusive, or exclude the selected file and all .salt sidecars consistently in both directions.

🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 216-216: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(salt_path, "wb")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(open-filename-from-request)

🤖 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 `@pysnippets/encryption/encryption.py` around lines 214 - 221, Update the
encryption flow around cipher.encrypt_file and cipher.encrypt_folder so file and
folder modes are mutually exclusive: when file_path is provided, encrypt only
that file and its salt sidecar; when folder_path is provided, encrypt only the
folder without recursively re-encrypting the selected file or any .salt
sidecars. Preserve the existing salt-writing behavior and ensure the
corresponding decryption path applies the same exclusion rules.

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