From f6b94da3d014f755d0f36347ba4b1e0b5829f7a7 Mon Sep 17 00:00:00 2001 From: Hernan Monserrat <16483541+hemonserrat@users.noreply.github.com> Date: Fri, 12 Sep 2025 21:54:04 -0700 Subject: [PATCH 1/2] fix: update deprecated actions, add Docker support, and publish as gitsafe-cli --- .github/workflows/release.yml | 23 +++++++++++++++-------- README.md | 12 ++++++------ pyproject.toml | 2 +- setup.py | 2 +- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4869185..29a4b4f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' @@ -38,7 +38,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' @@ -113,7 +113,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '-') environment: name: pypi - url: https://pypi.org/p/git-safe + url: https://pypi.org/p/gitsafe-cli permissions: id-token: write steps: @@ -125,8 +125,6 @@ jobs: - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 - with: - password: ${{ secrets.PYPI_API_TOKEN }} publish-test-pypi: needs: [test, build] @@ -134,7 +132,7 @@ jobs: if: contains(github.ref, '-') || github.event_name == 'workflow_dispatch' environment: name: testpypi - url: https://test.pypi.org/p/git-safe + url: https://test.pypi.org/p/gitsafe-cli permissions: id-token: write steps: @@ -147,7 +145,6 @@ jobs: - name: Publish to Test PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: - password: ${{ secrets.TEST_PYPI_API_TOKEN }} repository-url: https://test.pypi.org/legacy/ docker: @@ -183,4 +180,14 @@ jobs: push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - platforms: linux/amd64,linux/arm64 \ No newline at end of file + platforms: linux/amd64,linux/arm64 + + - name: Update Docker Hub Description + uses: peter-evans/dockerhub-description@v4 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + repository: ${{ secrets.DOCKER_USERNAME }}/git-safe + short-description: "Effortless file encryption for your git repos—pattern-matched, secure, and keyfile-flexible." + readme-filepath: ./README.md + enable-url-completion: true \ No newline at end of file diff --git a/README.md b/README.md index 77c954a..6fa3153 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ [![CI](https://github.com/hemonserrat/git-safe/workflows/CI/badge.svg)](https://github.com/hemonserrat/git-safe/actions/workflows/ci.yml) [![Security Scan](https://github.com/hemonserrat/git-safe/workflows/Security%20Scan/badge.svg)](https://github.com/hemonserrat/git-safe/actions/workflows/security.yml) [![codecov](https://codecov.io/gh/hemonserrat/git-safe/branch/main/graph/badge.svg)](https://codecov.io/gh/hemonserrat/git-safe) -[![PyPI version](https://badge.fury.io/py/git-safe.svg)](https://badge.fury.io/py/git-safe) -[![Python versions](https://img.shields.io/pypi/pyversions/git-safe.svg)](https://pypi.org/project/git-safe/) +[![PyPI version](https://badge.fury.io/py/gitsafe-cli.svg)](https://badge.fury.io/py/gitsafe-cli) +[![Python versions](https://img.shields.io/pypi/pyversions/gitsafe-cli.svg)](https://pypi.org/project/gitsafe-cli/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) @@ -33,7 +33,7 @@ Effortless file encryption for your git repos—pattern-matched, secure, and key ```bash # Install from PyPI -pip install git-safe +pip install gitsafe-cli # Or install from source git clone https://github.com/hemonserrat/git-safe.git @@ -67,7 +67,7 @@ git commit -m "Add secret config" ### From PyPI (Recommended) ```bash -pip install git-safe +pip install gitsafe-cli ``` ### From Source @@ -360,8 +360,8 @@ gpg --version # Should show GPG version **Import errors**: If you encounter import errors, try reinstalling: ```bash -pip uninstall git-safe -pip install git-safe +pip uninstall gitsafe-cli +pip install gitsafe-cli ``` ### Keyfile Issues diff --git a/pyproject.toml b/pyproject.toml index c6abb20..c399cd8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools>=61.0", "wheel"] build-backend = "setuptools.build_meta" [project] -name = "git-safe" +name = "gitsafe-cli" version = "1.0.0" authors = [ {name = "Hernan Monserrat"}, diff --git a/setup.py b/setup.py index d1485c2..94a8a04 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ ] setup( - name="git-safe", + name="gitsafe-cli", version="1.0.0", author="Hernan Monserrat", author_email="", From bc349e91ecb3bf18dbd041556bf5e733c80f14d9 Mon Sep 17 00:00:00 2001 From: Hernan Monserrat <16483541+hemonserrat@users.noreply.github.com> Date: Fri, 12 Sep 2025 22:01:52 -0700 Subject: [PATCH 2/2] security: replace manual CTR implementation with proper cryptography library - Remove insecure manual CTR mode using ECB cipher - Implement proper AES-256-CTR using cryptography library modes.CTR() - Fix potential counter overflow and nonce reuse vulnerabilities - Maintain backward compatibility with existing encrypted files - All tests pass (110/110) Fixes: Use of broken/weak cryptographic algorithm in crypto.py --- git_safe/crypto.py | 49 +++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/git_safe/crypto.py b/git_safe/crypto.py index 07dab6b..d7f9cc2 100644 --- a/git_safe/crypto.py +++ b/git_safe/crypto.py @@ -25,31 +25,30 @@ def ctr_decrypt(aes_key: bytes, nonce: bytes, data: bytes) -> bytes: Returns: Decrypted data """ - # Create cipher in ECB mode for manual CTR implementation - cipher = Cipher(algorithms.AES(aes_key), modes.ECB(), backend=default_backend()) # nosec B305 - encryptor = cipher.encryptor() - - out = bytearray() - - for off in range(0, len(data), BLOCK_SIZE): - block = data[off : off + BLOCK_SIZE] - # Create counter by combining nonce and block counter - counter_bytes = nonce + struct.pack(">I", off // BLOCK_SIZE) - - # Pad or truncate to exactly BLOCK_SIZE (16 bytes) for ECB mode - if len(counter_bytes) < BLOCK_SIZE: - # Pad with zeros - ctr = counter_bytes + b"\x00" * (BLOCK_SIZE - len(counter_bytes)) - else: - # Truncate to block size - ctr = counter_bytes[:BLOCK_SIZE] - - stream = encryptor.update(ctr) - out.extend(b ^ s for b, s in zip(block, stream, strict=False)) - - # Finalize the encryptor (required by cryptography library) - encryptor.finalize() - return bytes(out) + if not data: + return b"" + + # Prepare the initial counter value by combining nonce with counter + # Pad or truncate nonce to fit in the counter space + if len(nonce) < BLOCK_SIZE: + # Pad with zeros, leaving space for counter at the end + counter_prefix = nonce + b"\x00" * (BLOCK_SIZE - 4 - len(nonce)) + initial_counter = counter_prefix + b"\x00\x00\x00\x00" # 32-bit counter starts at 0 + else: + # Truncate nonce and reserve last 4 bytes for counter + counter_prefix = nonce[:BLOCK_SIZE - 4] + initial_counter = counter_prefix + b"\x00\x00\x00\x00" + + # Use proper CTR mode from cryptography library + cipher = Cipher( + algorithms.AES(aes_key), + modes.CTR(initial_counter), + backend=default_backend() + ) + decryptor = cipher.decryptor() + + result = decryptor.update(data) + decryptor.finalize() + return result def ctr_encrypt(aes_key: bytes, nonce: bytes, data: bytes) -> bytes: