Samoyed
A modern, fast, and secure Git hooks manager written in Rust. Samoyed is inspired by Husky with improved performance, better error handling, and enhanced security features.
You don't have to fuss with that pesky package.json file in your projects anymore! 🤌

Key Rotation Policy
Signing keys are rotated every 10 years or immediately upon suspected compromise. Rotation involves generating new keys, updating GitHub secrets, and notifying users via security advisory.
Test Coverage
Features
- 🚀 Fast: Built with Rust for optimal performance
- 🔒 Secure: Comprehensive path validation and security checks
- 🛡️ Robust: Detailed error handling with actionable suggestions
- 🧪 Well-tested: Comprehensive test coverage with extensive integration tests
- 🌍 Cross-platform: Supports Linux, macOS, and Windows
- 📦 Minimal dependencies: Small set of essential Rust dependencies
Installation
Samoyed is published on crates.io:
Verifying Release Signatures
All Samoyed release binaries are cryptographically signed with GPG to ensure authenticity and integrity. This protects against tampering and supply chain attacks.
Quick Verification
For users who want to quickly verify a download:
# Download and import our public key
|
# Verify a binary (replace with your downloaded file)
Step-by-Step Verification Instructions
1. Import the Samoyed Public Key
Download and import our signing key from the latest release:
# Download the public key
# Import the key into your GPG keyring
2. Verify Key Fingerprint
Critical Security Step: Always verify the key fingerprint matches our official key:
Expected output should show:
pub ed25519 2025-01-01 [SC] [expires: 2035-01-01]
02D1 B70C F6D8 41EE E687 6E13 F7A6 F833 1CBB C51F
uid Behrang Saeedzadeh <hello@behrang.org>
sub ed25519 2025-01-01 [E] [expires: 2035-01-01]
⚠️ Security Warning: If the fingerprint doesn't match exactly, do not proceed. This could indicate a compromised release.
3. Download Release Files
Download the binary and its signature from the releases page:
# Example for Linux x86_64 (adjust for your platform)
4. Verify the Signature
Expected Output (successful verification):
gpg: Signature made [date] using EdDSA key ID 1CBBC51F
gpg: Good signature from "Behrang Saeedzadeh <hello@behrang.org>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
The warning is normal unless you've explicitly trusted our key in your keyring.
5. Verify Checksums (Optional but Recommended)
For additional security, verify the SHA256 checksums:
# Download checksums and signature
# Verify checksums signature
# Verify your binary's checksum
Platform-Specific Instructions
Linux
Linux distributions typically have GPG pre-installed. If not:
# Ubuntu/Debian
&&
# RHEL/CentOS/Fedora
# or for older systems: sudo yum install gnupg2
# Arch Linux
macOS
Install GPG using Homebrew:
# Install Homebrew if not already installed
# Install GPG
Alternative: Download GPG Suite for a GUI experience.
Windows
Option 1: GPG4Win (Recommended)
- Download and install GPG4Win
- Use Kleopatra (GUI) or
gpgcommand in Command Prompt/PowerShell
Option 2: Git for Windows If you have Git for Windows installed, GPG is included:
# Use Git Bash terminal
gpg --version
Option 3: Windows Subsystem for Linux (WSL)
# In WSL terminal
&&
PowerShell Example:
# Import key
gpg --import samoyed-release-public.key
# Verify signature
gpg --verify samoyed-0.1.10-x86_64-pc-windows-msvc.zip.asc samoyed-0.1.10-x86_64-pc-windows-msvc.zip
Troubleshooting Common Issues
"gpg: command not found"
Cause: GPG is not installed or not in your PATH.
Solutions:
- Linux: Install via package manager (see platform instructions above)
- macOS: Install via Homebrew:
brew install gnupg - Windows: Install GPG4Win or use Git Bash
"gpg: Can't check signature: No public key"
Cause: You haven't imported our public key yet.
Solution:
# Import the public key first
|
"gpg: BAD signature"
Cause: The file has been tampered with or corrupted.
Solutions:
- Re-download the file and signature from GitHub releases
- Verify the download URL - ensure you're downloading from
github.com/nutthead/samoyed - Check file integrity - compare file size with the release page
- Report the issue if problem persists
"WARNING: This key is not certified with a trusted signature"
Cause: This is normal behavior. GPG warns when you haven't explicitly trusted a key.
This is NOT an error. The important part is seeing "Good signature from...".
To remove the warning (optional):
# Trust our key (do this only after verifying the fingerprint)
# Type "trust" then "5" (ultimate trust) then "y" then "quit"
"gpg: signing failed: Inappropriate ioctl for device"
Cause: GPG agent configuration issue (usually on headless systems).
Solution:
# Add to your ~/.bashrc or ~/.zshrc to make permanent
Signature file not found
Cause: You're trying to verify a file without downloading its .asc signature.
Solution: Always download both files:
# Download binary
# Download signature
Old GPG version compatibility
Cause: Very old GPG versions (< 2.1) may not support Ed25519 keys.
Solution:
- Update GPG to version 2.1 or newer
- Check version:
gpg --version - Most systems from 2014+ have compatible versions
Network issues downloading keys
Cause: Firewall or network restrictions.
Alternative methods:
# Method 1: Direct download
# Method 2: From repository
# Method 3: Manual import (copy key content and save to file)
Verification Best Practices
- Always verify signatures before extracting or running binaries
- Verify the key fingerprint against multiple sources (GitHub, documentation, social media)
- Use secure download channels - always download from
github.com/nutthead/samoyed - Keep GPG updated - use recent versions for best security and compatibility
- Report suspicious activity - if verification fails, report it immediately
Security Contact
For security-related issues or questions about signature verification:
- GitHub Issues: Security-related issues
- Email: hello@behrang.org (for private security reports)
Migration Guide
Upgrading from Dual-Binary Versions (v0.1.x)
⚠️ Important: Samoyed has migrated from a dual-binary architecture (samoyed + samoyed-hook) to a unified single binary (samoyed with hook subcommand).
What Changed
- Before:
exec samoyed-hook "pre-commit" "$@" - After:
exec samoyed hook "pre-commit" "$@"
Migration Steps
-
Update Samoyed: Install the latest version
-
Re-initialize Your Hooks: This updates your
.samoyed/_/*files to use the new unified binary# Option 1: Standard re-initialization (recommended) # Safe to run multiple times, updates hooks only if needed # Option 2: Force re-initialization (explicit migration) # Explicitly overwrites all hook files to ensure complete migrationWhen to use each option:
- Use
samoyed initfor normal migration (safe, non-destructive) - Use
samoyed init -f _if you want to force complete re-creation of all hook files
- Use
-
Verify Migration: Check that your hook files now reference
samoyed hook# Should show: exec samoyed hook "$(basename "$0")" "$@"
Deprecation Timeline
- Now:
samoyed-hookbinary still works but shows deprecation warnings - September 1, 2025:
samoyed-hookbinary will be removed entirely - Recommended: Migrate immediately to avoid future issues
Troubleshooting
If you encounter issues:
- Ensure you have the latest version:
samoyed --version - Re-run
samoyed initto refresh all hook files - Check that
samoyed hook --helpworks
Quick Start
Initialize Git hooks in your repository:
This will:
- Configure Git to use
.samoyed/_as the hooks directory - Create the hooks directory structure
- Install hook files that delegate to the
samoyedbinary
Usage
Basic Commands
# Initialize hooks (one-time setup)
# Install hooks with custom directory
Architecture
Samoyed uses a three-layer architecture that provides both flexibility and performance:
Binary Component
samoyed: Unified binary for both CLI interface and hook execution
Execution Flow
sequenceDiagram
participant Git as Git
participant Hook as .samoyed/_/pre-commit
participant Runner as samoyed hook
participant Config as samoyed.toml
participant Script as .samoyed/scripts/pre-commit
participant Shell as Shell
Git->>Hook: git commit triggers
Hook->>Runner: exec samoyed hook "pre-commit" "$@"
Runner->>Config: 1. Check for [hooks] pre-commit
alt Command found in samoyed.toml
Config-->>Runner: "cargo fmt --check && cargo clippy"
Runner->>Shell: Execute command via shell
Shell-->>Runner: Exit code
else No command in samoyed.toml
Runner->>Script: 2. Look for .samoyed/scripts/pre-commit
alt Script exists
Runner->>Script: Execute script file
Script-->>Runner: Exit code
else No script found
Runner-->>Git: Exit silently (0)
end
end
Runner-->>Git: Propagate exit code
Directory Structure
When you run samoyed init, three key components are created:
1. samoyed.toml - Primary Configuration
Raison d'être: The primary configuration mechanism where you define commands for each hook.
[]
= "cargo fmt --check && cargo clippy -- -D warnings"
= "cargo test --release"
2. .samoyed/_/ - Git Hook Delegation Layer
Raison d'être: Git integration. These files tell Git "when you want to run a hook, call samoyed hook instead."
All files contain identical delegation code:
#!/usr/bin/env sh
Git's core.hooksPath=.samoyed/_ points here, so git commit → .samoyed/_/pre-commit → samoyed hook.
3. .samoyed/scripts/ - Fallback & Examples
Raison d'être: Fallback mechanism for complex scenarios and examples for users who prefer script files.
Two-Tier Lookup System
The hook runner implements a sophisticated two-tier lookup:
- Primary: Check
samoyed.tomlfor a command string - Fallback: Look for executable script in
.samoyed/scripts/
This provides maximum flexibility:
- Simple cases: Use TOML configuration for straightforward commands
- Complex cases: Use full script files for multi-line logic or complex workflows
Environment Variables
SAMOYED=0- Skip all hook execution (useful for CI/deployment)SAMOYED=1- Normal execution mode (default)SAMOYED=2- Enable debug mode with detailed script tracing
Performance Design
This architecture ensures minimal overhead:
- Git only spawns one process:
samoyed hook - No file system scanning during execution
- Direct command execution via shell when possible
- Graceful fallback with silent exit when no hooks are defined
Development
Prerequisites
- Rust 1.85+ (Rust 2024 edition)
- Git
Building
# Build debug version
# Build release version
# Run tests
# Run benchmarks
Testing
The project uses comprehensive testing with dependency injection:
# Run all tests
# Run specific test categories
# Run platform-specific tests
Code Coverage
Generate coverage reports:
Tarpaulin is configured in .tarpaulin.toml to store reports in <target/tarpaulin/coverage/>.
Contributing
- Let's discuss
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
- Inspired by Husky
- Built with 🤪 in Rust