Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,4 @@ tests/
CONTRIBUTING.md
TESTING.md
.safety-project.ini
pytest.ini
pytest.ini
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ updates:
include: "scope"
labels:
- "dependencies"
- "github-actions"
- "github-actions"
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,4 @@ jobs:
with:
name: security-reports
path: |
bandit-report.json
bandit-report.json
12 changes: 6 additions & 6 deletions .github/workflows/dependencies.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
run: |
# Update main requirements
pip-compile --upgrade requirements.in || echo "No requirements.in found, skipping main requirements update"

# Update test requirements
pip-compile --upgrade requirements-test.in || echo "No requirements-test.in found, skipping test requirements update"

Expand All @@ -48,18 +48,18 @@ jobs:
title: 'chore: automated dependency updates'
body: |
## Automated Dependency Updates

This PR contains automated updates to project dependencies.

### Changes
- Updated Python package dependencies to latest compatible versions
- Ran security checks on updated dependencies

### Review Checklist
- [ ] All tests pass
- [ ] No new security vulnerabilities introduced
- [ ] Breaking changes are documented

**Note**: This PR was created automatically by the dependency update workflow.
branch: automated/dependency-updates
delete-branch: true
Expand All @@ -79,4 +79,4 @@ jobs:
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
23 changes: 15 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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'

Expand Down Expand Up @@ -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:
Expand All @@ -125,16 +125,14 @@ 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]
runs-on: ubuntu-latest
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:
Expand All @@ -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:
Expand Down Expand Up @@ -183,4 +180,14 @@ jobs:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64
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
2 changes: 1 addition & 1 deletion .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,4 @@ jobs:
name: crypto-analysis-reports
path: |
bandit-crypto-report.json
semgrep-report.json
semgrep-report.json
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -276,4 +276,4 @@ secrets.txt
.pre-commit-config.yaml.bak

# Ruff cache
.ruff_cache/
.ruff_cache/
43 changes: 43 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: check-yaml
- id: check-toml
- id: check-json
- id: end-of-file-fixer
- id: trailing-whitespace
- id: check-merge-conflict
- id: check-added-large-files
- id: check-case-conflict
- id: check-docstring-first
- id: debug-statements
- id: name-tests-test
args: ['--pytest-test-first']

- repo: https://github.com/psf/black
rev: 24.8.0
hooks:
- id: black
args: ['--line-length=120']

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.8
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.11.2
hooks:
- id: mypy
additional_dependencies: [types-cryptography, types-setuptools]
args: [--ignore-missing-imports, --no-strict-optional, --disable-error-code=no-any-return]

- repo: https://github.com/PyCQA/bandit
rev: 1.7.10
hooks:
- id: bandit
args: ['-c', 'pyproject.toml', '--severity-level', 'medium']
additional_dependencies: ['bandit[toml]']
1 change: 0 additions & 1 deletion .safety-project.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@
id = git-safe
url = /codebases/git-safe/findings
name = git-safe

2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,4 +307,4 @@ Contributors are recognized in:
- Release notes for significant contributions
- Special thanks in documentation updates

Thank you for contributing to git-safe! 🔒
Thank you for contributing to git-safe! 🔒
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ USER gituser

# Set the default command
ENTRYPOINT ["git-safe"]
CMD ["--help"]
CMD ["--help"]
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.
15 changes: 7 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -67,7 +67,7 @@ git commit -m "Add secret config"
### From PyPI (Recommended)

```bash
pip install git-safe
pip install gitsafe-cli
```

### From Source
Expand Down Expand Up @@ -252,7 +252,7 @@ ID (4 bytes) + LENGTH (4 bytes) + DATA (LENGTH bytes)
```bash
# Export for team members
git-safe export-key teammate@company.com

# Team member imports
git-safe unlock --gpg-keyfile shared-key.gpg
```
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -420,4 +420,3 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
- [cryptography](https://cryptography.io/) - Modern cryptographic operations
- [python-gnupg](https://gnupg.readthedocs.io/) - GPG integration
- [pathspec](https://python-path-specification.readthedocs.io/) - .gitattributes pattern matching

8 changes: 4 additions & 4 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,14 @@ Common fixtures are available in `conftest.py`:
def test_encrypt_decrypt_roundtrip(self, sample_keys, sample_data):
"""Test that encryption and decryption are inverse operations"""
aes_key, hmac_key = sample_keys

# Encrypt
nonce = generate_nonce(sample_data)
encrypted = ctr_encrypt(aes_key, nonce, sample_data)

# Decrypt
decrypted = ctr_decrypt(aes_key, nonce, encrypted)

# Verify
assert decrypted == sample_data
assert encrypted != sample_data # Ensure it was actually encrypted
Expand Down Expand Up @@ -290,4 +290,4 @@ If tests are failing:
2. Run individual test files to isolate issues
3. Use verbose mode (`-v`) for more details
4. Check that all dependencies are installed
5. Ensure you're running tests from the project root directory
5. Ensure you're running tests from the project root directory
4 changes: 2 additions & 2 deletions git-safe
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ sys.path.insert(0, str(Path(__file__).parent))

from git_safe.cli import main

if __name__ == '__main__':
sys.exit(main())
if __name__ == "__main__":
sys.exit(main())
46 changes: 20 additions & 26 deletions git_safe/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import hashlib
import hmac
import os
import struct

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
Expand All @@ -25,31 +24,26 @@ 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:
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down Expand Up @@ -157,4 +157,4 @@ exclude_lines = [

[tool.bandit]
exclude_dirs = ["tests"]
skips = ["B101", "B601"]
skips = ["B101", "B601"]
4 changes: 2 additions & 2 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts =
addopts =
-v
--tb=short
--strict-markers
Expand All @@ -17,4 +17,4 @@ markers =
unit: marks tests as unit tests
filterwarnings =
ignore::DeprecationWarning
ignore::PendingDeprecationWarning
ignore::PendingDeprecationWarning
Loading
Loading