diff --git a/.claude/hookify.warn-direct-string-slice.md b/.claude/hookify.warn-direct-string-slice.md new file mode 100644 index 00000000..5dd16b93 --- /dev/null +++ b/.claude/hookify.warn-direct-string-slice.md @@ -0,0 +1,21 @@ +--- +name: warn-direct-string-slice +enabled: true +event: file +conditions: + - field: new_text + operator: regex_match + pattern: "&\\w+\\[\\d+\\.\\.\\]|&\\w+\\[\\.\\.\\d+\\]|&\\w+\\[\\d+\\.\\.]" +--- + +**Direct string slicing detected.** + +Use `strip_prefix()` / `strip_suffix()` instead of `&str[n..]` to avoid UTF-8 boundary panics. + +```rust +// Wrong: can panic on non-ASCII +let rest = &input[2..]; + +// Correct: safe +let rest = input.strip_prefix("0x").unwrap_or(input); +``` diff --git a/.claude/hookify.warn-emoji-in-code.md b/.claude/hookify.warn-emoji-in-code.md new file mode 100644 index 00000000..4f3a6c68 --- /dev/null +++ b/.claude/hookify.warn-emoji-in-code.md @@ -0,0 +1,17 @@ +--- +name: warn-emoji-in-code +enabled: true +event: file +conditions: + - field: new_text + operator: regex_match + pattern: "[\U0001F300-\U0001F9FF\u2600-\u26FF\u2700-\u27BF\U0001FA00-\U0001FA6F\U0001FA70-\U0001FAFF]" +--- + +**Emoji detected in code or documentation.** + +Project guidelines prohibit emojis and non-ASCII characters in code, comments, and documentation. + +**Exception:** If this code is specifically handling or processing emoji/non-ASCII characters (e.g., test cases for Unicode handling), this warning can be disregarded. + +If this is not emoji-processing code, remove the emoji and use plain text instead. diff --git a/.claude/hookify.warn-panic-in-lib.md b/.claude/hookify.warn-panic-in-lib.md new file mode 100644 index 00000000..879d5be6 --- /dev/null +++ b/.claude/hookify.warn-panic-in-lib.md @@ -0,0 +1,26 @@ +--- +name: warn-panic-in-lib +enabled: true +event: file +conditions: + - field: file_path + operator: regex_match + pattern: src/.*\.rs$ + - field: new_text + operator: regex_match + pattern: \.unwrap\(\)|\.expect\(|panic!\( +--- + +**Potential panic in library code detected.** + +This project uses `unwrap_used = "deny"` and `panic = "deny"` in clippy config. Use `Result` patterns instead. + +```rust +// Wrong: panics at runtime +let val = something.unwrap(); + +// Correct: propagate error +let val = something.ok_or(MagicError::InvalidValue)?; +``` + +Note: `.unwrap()` is acceptable inside `#[cfg(test)]` modules. diff --git a/.claude/hookify.warn-unsafe-code.md b/.claude/hookify.warn-unsafe-code.md new file mode 100644 index 00000000..249c88cd --- /dev/null +++ b/.claude/hookify.warn-unsafe-code.md @@ -0,0 +1,15 @@ +--- +name: warn-unsafe-code +enabled: true +event: file +conditions: + - field: new_text + operator: regex_match + pattern: unsafe\s*\{|unsafe\s+fn|unsafe\s+impl +--- + +**Unsafe code detected.** + +This project enforces `#![forbid(unsafe_code)]` project-wide. No unsafe blocks, functions, or impls are permitted in project source code. + +If you believe unsafe is absolutely necessary, stop and discuss with the user before proceeding. diff --git a/.claude/hooks/enforce-dco-signoff.sh b/.claude/hooks/enforce-dco-signoff.sh new file mode 100755 index 00000000..2d0f8270 --- /dev/null +++ b/.claude/hooks/enforce-dco-signoff.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Claude Code PreToolUse hook: ensure all git commits include DCO sign-off (-s) + +CMD="${CLAUDE_TOOL_INPUT_command:-}" + +# Only check commands that contain "git commit" as a distinct subcommand +if ! echo "$CMD" | grep -qE '(^|[;&|] *)git commit( |$)'; then + exit 0 +fi + +# Allow if -s or --signoff is present anywhere in the command +if echo "$CMD" | grep -qE -- '(^| )-s( |$)|(^| )--signoff( |$)'; then + exit 0 +fi + +echo "BLOCK: All commits must include DCO sign-off. Add the -s flag to your git commit command." +exit 2 diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 00000000..dbe203ea --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,15 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "bash .claude/hooks/enforce-dco-signoff.sh" + } + ] + } + ] + } +} diff --git a/.claude/skills/api-design/SKILL.md b/.claude/skills/api-design/SKILL.md new file mode 100644 index 00000000..bc82116b --- /dev/null +++ b/.claude/skills/api-design/SKILL.md @@ -0,0 +1,226 @@ +--- +name: api-design +description: Rust library API design patterns including builder pattern, error handling, trait design, type safety, and CLI design with clap. +--- + +# API Design Patterns (Rust Library & CLI) + +## When to Activate + +- Designing or modifying public library API in `lib.rs` +- Adding new public types, traits, or functions +- Reviewing API ergonomics and consistency +- Designing CLI arguments and output formats +- Planning breaking vs non-breaking changes + +## Library API Design + +### Builder Pattern +```rust +// For types with many optional configuration fields +pub struct EvaluationConfig { + timeout: Duration, + max_rules: usize, + follow_symlinks: bool, +} + +impl EvaluationConfig { + pub fn builder() -> EvaluationConfigBuilder { + EvaluationConfigBuilder::default() + } +} + +pub struct EvaluationConfigBuilder { /* ... */ } + +impl EvaluationConfigBuilder { + pub fn timeout(mut self, timeout: Duration) -> Self { + self.timeout = Some(timeout); + self + } + + pub fn build(self) -> Result { + // Validate configuration + Ok(EvaluationConfig { /* ... */ }) + } +} +``` + +### Error Design + +#### Three-Tier Error Hierarchy +```rust +// Top-level: user-facing errors +pub enum LibmagicError { + Parse(ParseError), + Evaluation(EvaluationError), + Config(ConfigError), + Io(std::io::Error), +} + +// Module-level: specific to subsystem +pub enum ParseError { + InvalidSyntax { line: usize, reason: String }, + IoError(String), +} + +// Always implement std::error::Error + Display +impl std::fmt::Display for LibmagicError { /* ... */ } +impl std::error::Error for LibmagicError { /* ... */ } +``` + +#### Error Guidelines +- Use `thiserror` for deriving Error implementations +- Errors should be actionable (include line numbers, context) +- Never expose internal paths or system details in public errors +- Implement `From` conversions for ergonomic `?` usage + +### Type Safety + +#### Newtype Pattern +```rust +// Wrap primitives to prevent misuse +pub struct Offset(i64); +pub struct Score(u32); +pub struct Level(u32); + +// Prevents accidentally passing a score where an offset is expected +fn evaluate_at(offset: Offset, buffer: &[u8]) -> Result; +``` + +#### Enum-Based Type Discrimination +```rust +// Use enums to make invalid states unrepresentable +pub enum OffsetSpec { + Absolute(i64), + Indirect { base: i64, pointer_type: TypeKind }, + Relative(i64), + FromEnd(i64), +} +// Cannot have both Absolute and Relative -- the type system prevents it +``` + +### Public API Surface + +#### Minimize Exposure +```rust +// Only expose what users need +pub use crate::evaluator::EvaluationResult; +pub use crate::parser::MagicRule; + +// Keep internals private +pub(crate) use crate::evaluator::EvaluationContext; +``` + +#### Document Everything Public +```rust +/// Evaluate magic rules against a file buffer. +/// +/// # Arguments +/// * `rules` - Parsed magic rules to evaluate +/// * `buffer` - File contents to identify +/// +/// # Returns +/// The best matching result, or `None` if no rules match. +/// +/// # Errors +/// Returns `EvaluationError` if evaluation fails due to +/// invalid offsets or corrupted rule definitions. +/// +/// # Examples +/// ``` +/// use libmagic_rs::MagicDatabase; +/// +/// let db = MagicDatabase::default(); +/// let result = db.evaluate_buffer(&[0x7f, 0x45, 0x4c, 0x46])?; +/// ``` +pub fn evaluate_buffer(&self, buffer: &[u8]) -> Result, EvaluationError>; +``` + +### Trait Design +```rust +// Small, focused traits +pub trait SafeBufferAccess { + fn get_byte(&self, offset: usize) -> Option; + fn get_slice(&self, offset: usize, len: usize) -> Option<&[u8]>; + fn len(&self) -> usize; +} + +// Implement for multiple types +impl SafeBufferAccess for FileBuffer { /* ... */ } +impl SafeBufferAccess for &[u8] { /* ... */ } +``` + +## CLI Design (clap) + +### Argument Structure +```rust +#[derive(Parser)] +#[command(name = "rmagic", about = "Identify file types")] +struct Args { + /// Files to identify + #[arg(required = true)] + files: Vec, + + /// Output as JSON + #[arg(long)] + json: bool, + + /// Use custom magic file + #[arg(long, value_name = "FILE")] + magic_file: Option, +} +``` + +### CLI Conventions +- Follow GNU `file` command conventions where possible +- Short flags for common options (`-j` for JSON) +- Long flags for all options (`--json`) +- Positional arguments for files +- `--` to separate flags from file arguments +- Exit code 0 for success, 1 for errors + +### Output Format Consistency +- Text output: `filename: description` (matches GNU `file`) +- JSON output: structured with `filename`, `matches`, `metadata` +- Errors to stderr, results to stdout +- Quiet mode suppresses non-essential output + +## API Evolution + +### Non-Breaking Changes (patch/minor version) +- Adding new enum variants (if `#[non_exhaustive]`) +- Adding new optional fields to builders +- Adding new methods to existing types +- Loosening input constraints + +### Breaking Changes (major version) +- Removing or renaming public types/functions +- Changing function signatures +- Adding required fields to structs +- Tightening input constraints +- Changing error types + +### Defensive Techniques +```rust +// Mark enums as non-exhaustive for future extension +#[non_exhaustive] +pub enum TypeKind { + Byte, + Short { endian: Endianness, signed: bool }, + Long { endian: Endianness, signed: bool }, + String { max_length: Option }, + // Future: Quad, Float, Regex, etc. +} +``` + +## API Review Checklist + +Before exposing new public API: +- [ ] All public items have rustdoc with examples +- [ ] Error types are descriptive and actionable +- [ ] Builder pattern used for types with >3 optional fields +- [ ] Types prevent invalid states at compile time +- [ ] `#[non_exhaustive]` on enums that may grow +- [ ] Consistent naming with existing API surface +- [ ] `From`/`Into` conversions for ergonomic use +- [ ] `Display` and `Debug` implemented for all public types diff --git a/.claude/skills/security-review/SKILL.md b/.claude/skills/security-review/SKILL.md new file mode 100644 index 00000000..643beeb4 --- /dev/null +++ b/.claude/skills/security-review/SKILL.md @@ -0,0 +1,236 @@ +--- +name: security-review +description: Security review for Rust systems code. Covers memory safety, buffer handling, unsafe code, input validation, resource exhaustion, and supply chain security. +--- + +# Security Review (Rust Systems Code) + +## When to Activate + +- Adding new buffer access or offset resolution code +- Handling untrusted input (magic files, target files) +- Adding or reviewing dependencies +- Modifying parser or evaluator logic +- Before releases or PRs with security-sensitive changes + +## Security Checklist + +### 1. Memory Safety + +#### Bounds-Checked Buffer Access +```rust +// WRONG: Direct indexing can panic +let byte = buffer[offset]; + +// CORRECT: Bounds-checked access +let byte = buffer.get(offset).ok_or(MagicError::OutOfBounds)?; + +// CORRECT: Slice with bounds check +let slice = buffer.get(start..end).ok_or(MagicError::OutOfBounds)?; +``` + +#### Safe String Operations +```rust +// WRONG: Direct slicing can panic on non-UTF-8 boundaries +let rest = &input[2..]; + +// CORRECT: Use strip_prefix/strip_suffix +let rest = input.strip_prefix("0x").unwrap_or(input); +``` + +#### Verification Steps +- [ ] All buffer access uses `.get()` with bounds checking +- [ ] No direct indexing (`buffer[i]`) on untrusted data +- [ ] String operations use `strip_prefix`/`strip_suffix` instead of slicing +- [ ] No panicking operations (`.unwrap()`, `.expect()`, `panic!()`) in library code + +### 2. Unsafe Code Policy + +#### Zero Tolerance +```rust +// This is enforced project-wide +#![forbid(unsafe_code)] +``` + +#### Verification Steps +- [ ] `#![forbid(unsafe_code)]` present in `lib.rs` +- [ ] No `unsafe` blocks anywhere in project source +- [ ] Dependencies with `unsafe` are vetted (memmap2, byteorder, nom) +- [ ] `cargo audit` passes with no vulnerabilities + +### 3. Integer Safety + +#### Overflow Protection +```rust +// WRONG: Can overflow silently in release builds +let offset = base + adjustment; + +// CORRECT: Checked arithmetic +let offset = base.checked_add(adjustment) + .ok_or(MagicError::InvalidOffset { offset: format!("{} + {}", base, adjustment) })?; + +// CORRECT: Saturating for non-critical paths +let score = base_score.saturating_add(bonus); +``` + +#### Verification Steps +- [ ] Offset calculations use checked arithmetic +- [ ] No implicit integer truncation (e.g., `u64 as u32`) +- [ ] Cast operations use `TryFrom`/`try_into()` where overflow is possible +- [ ] Clippy pedantic lints catch suspicious casts + +### 4. Input Validation (Magic Files) + +#### Parser Robustness +```rust +// Magic files are untrusted input -- strict validation required +fn parse_magic_line(line: &str) -> Result { + // Validate line structure before parsing + // Return ParseError on invalid syntax, never panic + // Skip unrecognized directives gracefully +} +``` + +#### Verification Steps +- [ ] Parser returns `Err` on invalid syntax, never panics +- [ ] Deeply nested rules have depth limits +- [ ] Unrecognized directives are skipped with warnings +- [ ] Malformed offset/type/operator specifications produce clear errors +- [ ] Property tests fuzz the parser with arbitrary input + +### 5. Input Validation (Target Files) + +#### File Buffer Safety +```rust +// All file access through FileBuffer with bounds checking +let fb = FileBuffer::open(path)?; + +// Size limits prevent resource exhaustion +if fb.len() > MAX_FILE_SIZE { + return Err(MagicError::FileTooLarge); +} + +// Memory-mapped I/O avoids loading entire file +// Bounds checking on every access +let data = fb.get(offset, length)?; +``` + +#### Verification Steps +- [ ] File size limits enforced before processing +- [ ] Memory-mapped I/O used (not reading entire file into memory) +- [ ] All `FileBuffer` access is bounds-checked +- [ ] Truncated/corrupted files handled gracefully +- [ ] Zero-length files handled without errors + +### 6. Resource Exhaustion Prevention + +#### CPU Limits +```rust +// Evaluation timeout prevents infinite loops +let config = EvaluationConfig { + timeout: Duration::from_secs(5), + max_rules: 10_000, + ..Default::default() +}; +``` + +#### Memory Limits +```rust +// Limit collected results to prevent unbounded growth +const MAX_MATCHES: usize = 100; +if matches.len() >= MAX_MATCHES { + break; +} +``` + +#### Verification Steps +- [ ] Evaluation has configurable timeout +- [ ] Maximum rule evaluation count enforced +- [ ] Match results bounded +- [ ] No unbounded recursion in rule evaluation +- [ ] Stack depth limited for nested rules + +### 7. Supply Chain Security + +#### Dependency Audit +```bash +# Check for known vulnerabilities +cargo audit + +# Check license compliance +cargo deny check + +# Review dependency tree +cargo tree --depth 2 +``` + +#### Verification Steps +- [ ] `cargo audit` clean (no known vulnerabilities) +- [ ] `cargo deny` passes (license compliance) +- [ ] Minimal dependency surface +- [ ] `Cargo.lock` committed for reproducible builds +- [ ] All GitHub Actions pinned to SHA hashes +- [ ] Dependabot enabled for automated updates + +### 8. Error Information Leakage + +#### Safe Error Messages +```rust +// WRONG: Exposes internal paths or system info +Err(format!("Failed to read {}: {}", full_path, system_error)) + +// CORRECT: Actionable but not leaking +Err(MagicError::IoError(std::io::Error::new( + std::io::ErrorKind::NotFound, + "magic file not found" +))) +``` + +#### Verification Steps +- [ ] Error messages don't expose absolute file paths to end users +- [ ] System-level errors wrapped before surfacing to CLI +- [ ] Debug output gated behind `RUST_LOG` / verbose flags + +### 9. CLI Argument Safety + +#### Path Handling +```rust +// clap validates arguments before they reach application code +// No shell expansion or command injection possible +#[derive(Parser)] +struct Args { + /// File to identify + file: PathBuf, + + /// Magic file to use + #[arg(long)] + magic_file: Option, +} +``` + +#### Verification Steps +- [ ] CLI arguments parsed by `clap` (no manual parsing) +- [ ] File paths treated as opaque -- no string manipulation +- [ ] No shell invocation or command execution from user input +- [ ] Symlink handling considered for file access + +## Pre-Release Security Checklist + +- [ ] `#![forbid(unsafe_code)]` enforced +- [ ] `cargo clippy -- -D warnings` passes +- [ ] `cargo audit` clean +- [ ] `cargo deny check` passes +- [ ] All buffer access bounds-checked +- [ ] Integer arithmetic overflow-safe +- [ ] Property tests cover parser and evaluator +- [ ] Resource limits configured +- [ ] Error messages reviewed for information leakage +- [ ] Dependencies minimized and pinned +- [ ] Sigstore attestations configured for release artifacts + +## References + +- [Rust Secure Coding Guidelines](https://anssi-fr.github.io/rust-guide/) +- [RustSec Advisory Database](https://rustsec.org/) +- Project [Security Assurance Case](../../docs/src/security-assurance.md) +- Project [SECURITY.md](../../SECURITY.md) diff --git a/.claude/skills/strategic-compact/SKILL.md b/.claude/skills/strategic-compact/SKILL.md new file mode 100644 index 00000000..b98c0684 --- /dev/null +++ b/.claude/skills/strategic-compact/SKILL.md @@ -0,0 +1,51 @@ +--- +name: strategic-compact +description: Suggests manual context compaction at logical intervals to preserve context through task phases rather than arbitrary auto-compaction. +--- + +# Strategic Compact + +Suggests manual `/compact` at strategic points in your workflow rather than relying on arbitrary auto-compaction. + +## When to Activate + +- Running long sessions that approach context limits +- Working on multi-phase tasks (research, plan, implement, test) +- Switching between unrelated tasks within the same session +- After completing a major milestone and starting new work +- When responses slow down or become less coherent + +## Compaction Decision Guide + +| Phase Transition | Compact? | Why | +|-----------------|----------|-----| +| Research / Planning | Yes | Research context is bulky; plan is the distilled output | +| Planning / Implementation | Yes | Plan is in TodoWrite or a file; free up context for code | +| Implementation / Testing | Maybe | Keep if tests reference recent code; compact if switching focus | +| Debugging / Next feature | Yes | Debug traces pollute context for unrelated work | +| Mid-implementation | No | Losing variable names, file paths, and partial state is costly | +| After a failed approach | Yes | Clear the dead-end reasoning before trying a new approach | + +## What Survives Compaction + +| Persists | Lost | +|----------|------| +| CLAUDE.md / AGENTS.md instructions | Intermediate reasoning and analysis | +| TodoWrite task list | File contents you previously read | +| Memory files (`~/.claude/memory/`) | Multi-step conversation context | +| Git state (commits, branches) | Tool call history and counts | +| Files on disk | Nuanced user preferences stated verbally | + +## Best Practices + +1. **Compact after planning** -- Once plan is finalized in TodoWrite, compact to start fresh +2. **Compact after debugging** -- Clear error-resolution context before continuing +3. **Don't compact mid-implementation** -- Preserve context for related changes +4. **Write before compacting** -- Save important context to files or memory before compacting +5. **Use `/compact` with a summary** -- Add a custom message: `/compact Focus on implementing indirect offsets next` + +## Rust-Specific Considerations + +- After reading large source files (evaluator/mod.rs at 2,638 lines), compact before implementation +- After running `cargo test` with verbose output, compact if moving to different module +- After architecture review or exploration, compact before starting refactoring work diff --git a/.claude/skills/tdd-workflow/SKILL.md b/.claude/skills/tdd-workflow/SKILL.md new file mode 100644 index 00000000..46fa04c6 --- /dev/null +++ b/.claude/skills/tdd-workflow/SKILL.md @@ -0,0 +1,257 @@ +--- +name: tdd-workflow +description: Enforces test-driven development for Rust. Write tests first with cargo test/nextest, implement to pass, refactor, verify >85% coverage with cargo llvm-cov. +--- + +# Test-Driven Development Workflow (Rust) + +## When to Activate + +- Writing new features or functionality +- Fixing bugs or issues +- Refactoring existing code +- Adding new magic rule types or operators +- Extending parser, evaluator, or output modules + +## Core Principles + +### 1. Tests BEFORE Code +ALWAYS write tests first, then implement code to make tests pass. + +### 2. Coverage Requirements +- Minimum 85% coverage (project target per AGENTS.md) +- All edge cases covered +- Error scenarios tested +- Boundary conditions verified +- Doc examples verified with `cargo test --doc` + +### 3. Test Types + +#### Unit Tests +- Inline `#[cfg(test)]` modules alongside source +- Individual functions, parsers, evaluators +- Pure logic and data transformations + +#### Integration Tests +- In `tests/` directory with real magic files +- End-to-end rule parsing and evaluation +- CLI argument handling and output formatting + +#### Property Tests +- Use `proptest` for fuzzing magic rule evaluation +- Random input generation for parser robustness +- Boundary value exploration + +#### Benchmarks +- Use `criterion` for performance-critical code +- Evaluator hot paths, parser throughput +- Memory-mapped I/O performance + +## TDD Workflow Steps + +### Step 1: Define the Behavior +``` +Given [a magic rule with specific offset/type/operator], +When [evaluated against a file buffer with known contents], +Then [the evaluator should return the expected match result]. +``` + +### Step 2: Write Failing Tests +```rust +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new_feature_basic() { + // Arrange + let rule = MagicRule { /* ... */ }; + let buffer = &[0x7f, 0x45, 0x4c, 0x46]; + + // Act + let result = evaluate_rule(&rule, buffer); + + // Assert + assert!(result.is_ok()); + assert_eq!(result.unwrap().description, "ELF"); + } + + #[test] + fn test_new_feature_edge_case() { + // Empty buffer should not panic + let rule = MagicRule { /* ... */ }; + let result = evaluate_rule(&rule, &[]); + assert!(result.is_ok()); + assert!(result.unwrap().is_none()); + } + + #[test] + fn test_new_feature_error_case() { + // Invalid offset should return error, not panic + let rule = MagicRule { offset: OffsetSpec::Absolute(-1), /* ... */ }; + let result = evaluate_rule(&rule, &[0x00]); + assert!(result.is_err()); + } +} +``` + +### Step 3: Run Tests (They Should Fail) +```bash +cargo test test_new_feature -- --nocapture +# Tests should fail -- we haven't implemented yet +``` + +### Step 4: Implement Code +Write minimal code to make tests pass. Follow project patterns: +- Use `.get()` for bounds-checked buffer access +- Return `Result` consistently +- No `unsafe`, no `.unwrap()`, no `panic!` + +### Step 5: Run Tests Again +```bash +cargo nextest run +# All tests should pass +``` + +### Step 6: Refactor +Improve code quality while keeping tests green: +- Remove duplication +- Improve naming +- Extract modules if file exceeds 500 lines +- Ensure clippy compliance + +### Step 7: Verify Coverage +```bash +cargo llvm-cov --html +# Verify 85%+ coverage achieved +# Open target/llvm-cov/html/index.html to inspect +``` + +## Testing Patterns + +### Property-Based Testing +```rust +use proptest::prelude::*; + +proptest! { + #[test] + fn parser_never_panics_on_arbitrary_input(input in ".*") { + // Parser should return Ok or Err, never panic + let _ = parse_magic_line(&input); + } + + #[test] + fn evaluator_handles_any_buffer( + buffer in prop::collection::vec(any::(), 0..1024) + ) { + let rule = create_test_rule(); + let _ = evaluate_rule(&rule, &buffer); + } +} +``` + +### Parameterized Tests +```rust +#[test] +fn test_endianness_variants() { + let cases = vec![ + (Endianness::Big, &[0x00, 0x01u8] as &[u8], 1u64), + (Endianness::Little, &[0x01, 0x00u8] as &[u8], 1u64), + ]; + + for (endian, buffer, expected) in cases { + let result = read_short(buffer, endian); + assert_eq!(result, Ok(expected), "Failed for {:?}", endian); + } +} +``` + +### Test Fixtures +```rust +fn create_elf_header() -> Vec { + vec![0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00] +} + +fn create_test_rule() -> MagicRule { + MagicRule { + offset: OffsetSpec::Absolute(0), + typ: TypeKind::String { max_length: None }, + op: Operator::Equal, + value: Value::String("ELF".to_string()), + message: "ELF file".to_string(), + children: vec![], + level: 0, + } +} +``` + +## Test Organization + +``` +src/ + parser/ + mod.rs # #[cfg(test)] mod tests { ... } + ast.rs # #[cfg(test)] mod tests { ... } + grammar.rs # #[cfg(test)] mod tests { ... } + evaluator/ + mod.rs # #[cfg(test)] mod tests { ... } + types.rs # #[cfg(test)] mod tests { ... } + operators.rs # #[cfg(test)] mod tests { ... } + io/ + mod.rs # #[cfg(test)] mod tests { ... } + output/ + mod.rs # #[cfg(test)] mod tests { ... } +tests/ + compatibility.rs # Integration tests against GNU file + integration.rs # End-to-end rule evaluation +benches/ + evaluation.rs # Performance benchmarks +``` + +## Common Testing Mistakes to Avoid + +### WRONG: Testing internal state +```rust +assert_eq!(parser.line_number, 5); // Implementation detail +``` + +### CORRECT: Test observable behavior +```rust +let rules = parse_magic_file(input)?; +assert_eq!(rules.len(), 5); +assert_eq!(rules[0].message, "ELF"); +``` + +### WRONG: Ignoring error paths +```rust +let result = evaluate_rule(&rule, buffer).unwrap(); // Will panic +``` + +### CORRECT: Test both success and error +```rust +assert!(evaluate_rule(&rule, buffer).is_ok()); +assert!(evaluate_rule(&rule, &[]).is_ok()); // Empty buffer +assert!(evaluate_rule(&bad_rule, buffer).is_err()); // Bad rule +``` + +## Quick Reference + +```bash +# Run all tests +cargo nextest run + +# Run specific module tests +cargo test parser::grammar::tests + +# Run with output visible +cargo test -- --nocapture + +# Run doc tests +cargo test --doc + +# Coverage report +cargo llvm-cov --html + +# Benchmarks +cargo bench +``` diff --git a/.claude/skills/verification-loop/SKILL.md b/.claude/skills/verification-loop/SKILL.md new file mode 100644 index 00000000..57126838 --- /dev/null +++ b/.claude/skills/verification-loop/SKILL.md @@ -0,0 +1,110 @@ +--- +name: verification-loop +description: Comprehensive verification for Rust projects. Runs cargo check, clippy, fmt, tests, coverage, and security audit in sequence. +--- + +# Verification Loop (Rust) + +## When to Use + +- After completing a feature or significant code change +- Before creating a PR +- After refactoring +- When you want to ensure all quality gates pass + +## Verification Phases + +### Phase 1: Build Verification +```bash +cargo check 2>&1 | tail -20 +``` +If build fails, STOP and fix before continuing. + +### Phase 2: Format Check +```bash +cargo fmt -- --check 2>&1 | head -20 +``` +If formatting issues found, run `cargo fmt` to fix. + +### Phase 3: Lint Check +```bash +cargo clippy -- -D warnings 2>&1 | head -30 +``` +All clippy warnings are errors in this project. Fix before continuing. + +### Phase 4: Test Suite +```bash +# Run all tests with nextest +cargo nextest run 2>&1 | tail -50 + +# Also run doc tests (nextest doesn't run these) +cargo test --doc 2>&1 | tail -20 +``` + +Report: +- Total tests: X +- Passed: X +- Failed: X + +### Phase 5: Coverage Check +```bash +cargo llvm-cov --summary-only 2>&1 | tail -10 +``` +Target: 85%+ coverage. If below threshold, identify uncovered code. + +### Phase 6: Security Audit +```bash +cargo audit 2>&1 | tail -20 +``` + +### Phase 7: Diff Review +```bash +git diff --stat +git diff HEAD --name-only +``` + +Review each changed file for: +- Unintended changes +- Missing error handling (`.unwrap()`, direct indexing) +- Files exceeding 500-600 line guideline +- Missing tests for new code paths + +## Output Format + +After running all phases, produce a verification report: + +``` +VERIFICATION REPORT +================== + +Build: [PASS/FAIL] +Format: [PASS/FAIL] +Clippy: [PASS/FAIL] (X warnings) +Tests: [PASS/FAIL] (X/Y passed) +Doc Tests: [PASS/FAIL] +Coverage: [X%] (target: 85%) +Audit: [PASS/FAIL] (X advisories) +Diff: [X files changed] + +Overall: [READY/NOT READY] for PR + +Issues to Fix: +1. ... +2. ... +``` + +## Quick Verification (Pre-commit) + +For fast checks during development: +```bash +just ci-check +``` + +This runs the project's standard CI validation suite. + +## Full CI Verification + +Matches what runs in GitHub Actions: +```bash +cargo fmt -- --check && cargo clippy -- -D warnings && cargo nextest run && cargo test --doc && cargo audit +``` diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 52258611..2e334460 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,6 +5,11 @@ updates: schedule: interval: "weekly" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: weekly + - package-ecosystem: "devcontainers" directory: "/" schedule: diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 52de3267..1d86c9c7 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -22,6 +22,6 @@ jobs: contents: read issues: write steps: - - uses: actions/checkout@v6 - - uses: actions-rust-lang/audit@v1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions-rust-lang/audit@72c09e02f132669d52284a3323acdb503cfc1a24 # v1.2.7 name: Audit Rust Dependencies diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index a40a31f2..d24c49b4 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -25,16 +25,16 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: jdx/mise-action@v3 + - uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 with: install: true cache: true github_token: ${{ secrets.GITHUB_TOKEN }} - name: Cache cargo registry - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | ~/.cargo/registry @@ -49,7 +49,7 @@ jobs: run: cargo bench --bench parser_bench --bench evaluation_bench --bench io_bench - name: Upload benchmark results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: benchmark-results path: target/criterion/ @@ -62,18 +62,18 @@ jobs: if: github.event_name == 'pull_request' steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: jdx/mise-action@v3 + - uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 with: install: true cache: true github_token: ${{ secrets.GITHUB_TOKEN }} - name: Cache cargo registry - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | ~/.cargo/registry diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 33cc867f..bc1edd3c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,9 +28,9 @@ jobs: rust: ${{ steps.filter.outputs.rust }} docs: ${{ steps.filter.outputs.docs }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: dorny/paths-filter@v3 + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 id: filter with: filters: | @@ -54,11 +54,11 @@ jobs: needs: changes if: needs.changes.outputs.rust == 'true' steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@1.91.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: dtolnay/rust-toolchain@0dd4a6d07aedb0ef7f65e79f3e229a6c102ae2e0 # 1.91.0 with: components: llvm-tools, cargo, rustfmt, clippy - - uses: jdx/mise-action@v3 + - uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 with: install: true cache: true @@ -72,8 +72,8 @@ jobs: needs: changes if: needs.changes.outputs.rust == 'true' steps: - - uses: actions/checkout@v6 - - uses: jdx/mise-action@v3 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 with: install: true cache: true @@ -106,8 +106,8 @@ jobs: needs: [changes, test] if: needs.changes.outputs.rust == 'true' steps: - - uses: actions/checkout@v6 - - uses: jdx/mise-action@v3 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 with: install: true cache: true @@ -123,8 +123,8 @@ jobs: needs: [changes, test] if: needs.changes.outputs.rust == 'true' steps: - - uses: actions/checkout@v6 - - uses: jdx/mise-action@v3 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 with: install: true cache: true @@ -139,13 +139,13 @@ jobs: run: cargo llvm-cov report --lcov --output-path lcov.info - name: Upload to Codecov - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 with: files: lcov.info fail_ci_if_error: false token: ${{ secrets.CODECOV_TOKEN }} slug: EvilBit-Labs/libmagic-rs - - uses: qltysh/qlty-action/coverage@v2 + - uses: qltysh/qlty-action/coverage@a19242102d17e497f437d7466aa01b528537e899 # v2.2.0 with: token: ${{ secrets.QLTY_COVERAGE_TOKEN }} - files: target/lcov.info + files: lcov.info diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index b03e07fc..3dabbf10 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -19,17 +19,17 @@ jobs: name: CodeQL Analyze runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v6 - - uses: jdx/mise-action@v3 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 with: install: true cache: true github_token: ${{ secrets.GITHUB_TOKEN }} - - uses: github/codeql-action/init@v3 + - uses: github/codeql-action/init@f5c2471be782132e47a6e6f9c725e56730d6e9a3 # v3.32.3 with: languages: rust - - uses: github/codeql-action/autobuild@v3 + - uses: github/codeql-action/autobuild@f5c2471be782132e47a6e6f9c725e56730d6e9a3 # v3.32.3 - - uses: github/codeql-action/analyze@v3 + - uses: github/codeql-action/analyze@f5c2471be782132e47a6e6f9c725e56730d6e9a3 # v3.32.3 diff --git a/.github/workflows/compatibility.yml b/.github/workflows/compatibility.yml index 25b94b83..d6cb6613 100644 --- a/.github/workflows/compatibility.yml +++ b/.github/workflows/compatibility.yml @@ -24,9 +24,9 @@ jobs: os: [ubuntu-latest, windows-latest, macos-latest] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: jdx/mise-action@v3 + - uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 with: install: true cache: true @@ -43,7 +43,7 @@ jobs: - name: Upload test results on failure if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: compatibility-test-results-${{ matrix.os }} path: | diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 54d6ca44..75026e9e 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -17,8 +17,8 @@ jobs: contents: read steps: - - uses: actions/checkout@v6 - - uses: jdx/mise-action@v3 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 with: install: true cache: true diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 52fd09ba..5c51a1ec 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -24,15 +24,15 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: jdx/mise-action@v3 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 with: install: true cache: true github_token: ${{ secrets.GITHUB_TOKEN }} - name: Setup mdBook - uses: jontze/action-mdbook@v4 + uses: jontze/action-mdbook@6c0be56d14c4bf16861b00af61f50ff7400ce502 # v4.0.0 with: token: ${{ secrets.GITHUB_TOKEN }} mdbook-version: latest @@ -42,24 +42,24 @@ jobs: - name: Install mdbook plugins run: cargo binstall mdbook-tabs mdbook-i18n-helpers mdbook-alerts mdbook-yml-header mdbook-image-size --no-confirm - - name: Build rustdoc - run: | - cargo doc --no-deps --document-private-items --target-dir target - mkdir -p docs/book/api - cp -r target/doc/* docs/book/api/ - - name: Build mdBook run: | cd docs mdbook build + - name: Build rustdoc and copy into book + run: | + cargo doc --no-deps --document-private-items --target-dir target + mkdir -p docs/book/api + cp -r target/doc/* docs/book/api/ + - name: Setup Pages if: github.ref == 'refs/heads/main' - uses: actions/configure-pages@v5 + uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0 - name: Upload artifact if: github.ref == 'refs/heads/main' - uses: actions/upload-pages-artifact@v4 + uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0 with: path: docs/book @@ -73,4 +73,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 819ed501..9994c8fe 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -73,6 +73,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@v3 + uses: github/codeql-action/upload-sarif@f5c2471be782132e47a6e6f9c725e56730d6e9a3 # v3.32.3 with: sarif_file: results.sarif diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 6794838f..5d1a36ee 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -24,8 +24,8 @@ jobs: audit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: jdx/mise-action@v3 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 with: install: true cache: true diff --git a/.gitignore b/.gitignore index 8525a105..44a115df 100644 --- a/.gitignore +++ b/.gitignore @@ -53,9 +53,12 @@ debug/ target/ dist/ -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock +# Cargo.lock is committed intentionally. Although this project includes a +# library (src/lib.rs), it also ships a binary (rmagic). Committing the lock +# file ensures reproducible binary builds and auditable dependencies (via +# cargo-auditable). Library consumers are unaffected because cargo publish +# ignores Cargo.lock when resolving transitive dependencies. +# See: https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html # These are backup files generated by rustfmt **/*.rs.bk diff --git a/AGENTS.md b/AGENTS.md index 7d715bfc..9ec3a617 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,8 +34,8 @@ This document provides comprehensive guidelines for AI assistants working on the - Use memory-mapped I/O (`memmap2`) for efficient file access - Implement zero-copy operations where possible -- Use Aho-Corasick indexing for multi-pattern string searches -- Cache compiled magic rules for performance +- Use Aho-Corasick indexing for multi-pattern string searches (planned) +- Cache compiled magic rules for performance (planned) - Profile with `cargo bench` for performance regressions ### 4. Testing Required @@ -64,8 +64,16 @@ Target File → Memory Mapper → File Buffer ```rust // Core data structures in lib.rs pub struct MagicRule { /* ... */ } -pub enum TypeKind { Byte, Short, Long, String, /* ... */ } -pub enum Operator { Equal, NotEqual, Greater, /* ... */ } +pub enum TypeKind { + Byte, + Short { endian: Endianness, signed: bool }, + Long { endian: Endianness, signed: bool }, + String { max_length: Option }, +} +pub enum Operator { + Equal, NotEqual, BitwiseAnd, BitwiseAndMask(u64), +} +// Additional types and operators are planned -- see Current Limitations below // Parser module structure parser/ @@ -78,7 +86,7 @@ evaluator/ ├── mod.rs // Main evaluation engine ├── offset.rs // Offset resolution (absolute, indirect, relative) ├── types.rs // Type interpretation with endianness -└── operators.rs // Comparison and bitwise operations +└── operators.rs // Equality and bitwise operations ``` ## Code Quality Standards @@ -175,15 +183,25 @@ cargo test --doc # Test documentation examples ## Magic File Compatibility -### Supported Syntax +### Currently Implemented (v0.1.0) -- **Offsets**: Absolute, indirect, relative, and from-end specifications -- **Types**: byte, short, long, string, regex with endianness support -- **Operators**: =, !=, >, \<, & (bitwise AND), ^ (XOR) +- **Offsets**: Absolute and from-end specifications (indirect and relative are parsed but not yet evaluated) +- **Types**: `byte`, `short`, `long`, `string` with endianness support +- **Operators**: `=` (equal), `!=` (not equal), `&` (bitwise AND with optional mask) - **Nested Rules**: Hierarchical rule evaluation with proper indentation -- **String Matching**: Both exact and regex pattern matching +- **String Matching**: Exact string matching with null-termination + +### Planned Features (v1.0+) -### Binary-Safe Regex Handling +- Comparison operators: `>`, `<`, `>=`, `<=` +- Bitwise XOR operator: `^` +- Regex type: Pattern matching with binary-safe regex support +- Additional types: 64-bit integers, floats, doubles, dates +- Search type: Multi-pattern string searching + +### Future Enhancement: Binary-Safe Regex Handling + +> **Note:** The following is planned for future releases and is not yet implemented. ```rust // Use regex crate with bytes feature for binary-safe matching @@ -196,6 +214,37 @@ impl BinaryRegex for regex::bytes::Regex { } ``` +## Current Limitations (v0.1.0) + +### Type System + +- No regex/search pattern matching +- No 64-bit integer types (quad, qquad) +- No floating-point types (float, double, befloat, lefloat) +- No date/time types (date, qdate, ldate, qldate) +- String evaluation reads until first NUL or end-of-buffer by default; `max_length: Some(_)` is supported internally but no dedicated fixed-length string parser syntax exists yet + +### Operators + +- No comparison operators (`>`, `<`, `>=`, `<=`) +- No XOR operator (`^`) +- No negation operator (`~`) +- BitwiseAnd supports mask values but not all libmagic mask syntax + +### Offset Specifications + +- Indirect offsets are parsed into the AST but evaluation is not yet implemented (#37) +- Relative offsets are parsed into the AST but evaluation is not yet implemented (#38) +- Only absolute and from-end offsets are fully functional + +### Magic File Syntax + +- Limited support for special directives (only `!:strength` is parsed) +- No support for `!:mime`, `!:ext`, `!:apple` directives in evaluation +- No support for named tests or use/name directives + +See issue #52 for the planned enhancement roadmap. + ## Performance Requirements ### Critical Optimizations @@ -257,6 +306,8 @@ sample.bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV) ### Adding New Type Support +> **Note:** Currently implemented types are `Byte`, `Short`, `Long`, and `String`. Regex and other advanced types are planned for future releases. + 1. Extend `TypeKind` enum in `src/parser/ast.rs` 2. Add parsing logic in `src/parser/grammar.rs` 3. Implement reading logic in `src/evaluator/types.rs` @@ -265,6 +316,8 @@ sample.bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV) ### Adding New Operators +> **Note:** Currently implemented operators are `Equal`, `NotEqual`, and `BitwiseAnd` (with `BitwiseAndMask`). Comparison operators (`>`, `<`) and XOR (`^`) are planned for future releases. + 1. Extend `Operator` enum in `src/parser/ast.rs` 2. Add parsing logic in `src/parser/grammar.rs` 3. Implement operator logic in `src/evaluator/operators.rs` @@ -364,6 +417,21 @@ The project includes automated CI checks via `.kiro/hooks/ci-auto-fix.kiro.hook` - Security audit must pass - Performance benchmarks must not regress +### Code Review Requirements + +All pull requests require review before merging. Reviews are performed by maintainers and automated tools (CodeRabbit). Reviewers check for: + +- **Correctness**: Does the code do what it claims? Are edge cases handled? +- **Memory safety**: No unsafe code blocks (except vetted dependencies). All buffer access must use bounds checking with `.get()` methods. No raw pointer arithmetic or transmute operations. +- **Error handling**: Proper use of `Result` types, no panics in library code, no `unwrap()` or `expect()` in library code. Use `thiserror` for structured error types. +- **Tests**: New functionality has tests, existing tests still pass, edge cases and error conditions are covered. Property tests with `proptest` for complex data structures. +- **Performance**: No unnecessary allocations in hot paths, no regressions in benchmarks. Memory-mapped I/O used for file access. +- **libmagic compatibility**: Changes maintain compatibility with libmagic behavior and magic file format. Output format matches GNU `file` command expectations. +- **Style**: Follows project conventions, passes `cargo fmt` and `cargo clippy -- -D warnings` +- **Documentation**: Public APIs have rustdoc with examples, AGENTS.md updated if architecture changes + +CI must pass before merge. Branch protection enforces these checks on the `main` branch. + ## Project Context ### Current Status @@ -380,15 +448,16 @@ The project includes automated CI checks via `.kiro/hooks/ci-auto-fix.kiro.hook` - `nom`: Parser combinators - `serde`: Serialization - `clap`: CLI argument parsing -- `regex`: Pattern matching -- `aho-corasick`: Multi-pattern search +- `regex`: Pattern matching (used in tests; regex *type* for magic rules is planned) +- `aho-corasick`: Multi-pattern search (planned, not yet added) ### Development Phases -1. **MVP (v0.1)**: Basic parsing and evaluation -2. **Enhanced Features (v0.2)**: Indirect offsets, regex, caching -3. **Performance & Compatibility (v0.3)**: Optimizations, full compatibility -4. **Production Ready (v1.0)**: Stable API, complete documentation +1. **MVP (v0.1.0)** - CURRENT: Basic parsing and evaluation with byte/short/long/string types, equality and bitwise AND operators, built-in rules for 10 common formats +2. **Enhanced Features (v0.2)**: Comparison operators (`>`, `<`), indirect offset improvements, strength-based rule ordering +3. **Advanced Types (v0.3)**: Regex type, 64-bit integers, floating-point types, search patterns +4. **Full Compatibility (v0.4)**: Complete libmagic syntax support, all special directives, named tests +5. **Production Ready (v1.0)**: Stable API, complete documentation, 95%+ compatibility with GNU file ## Best Practices @@ -437,3 +506,42 @@ The project includes automated CI checks via `.kiro/hooks/ci-auto-fix.kiro.hook` - Profile with `cargo bench` for performance analysis This guide ensures consistent, high-quality development practices for the libmagic-rs project while maintaining focus on memory safety, performance, and compatibility. + +## Quick Reference + +- `.github/workflows/release.yml` is auto-generated by cargo-dist -- do not modify manually +- All `.rs` files must have copyright and SPDX headers (see any source file for format) +- `Cargo.lock` and `mise.lock` are committed for reproducible builds -- do not gitignore +- In justfile recipes, never wrap `just` in `{{ mise_exec }}` -- it's redundant +- Changelog: `just changelog`, `just changelog-version `, `just changelog-unreleased` +- Security contact: support@evilbitlabs.io (matches PGP key in SECURITY.md) + +## Open Source Quality Standards (OSSF Best Practices) + +This project has the OSSF Best Practices passing badge. Maintain these standards: + +### Every PR must: +- Sign off commits with `git commit -s` (DCO enforced by GitHub App) +- Pass CI (clippy, fmt, tests, CodeQL, cargo audit) before merge +- Include tests for new functionality -- this is policy, not optional +- Be reviewed (human or CodeRabbit) for correctness, safety, and style +- Not introduce `unsafe` code, `unwrap()`/`expect()` in library code, or panics + +### Every release must: +- Have human-readable release notes via git-cliff (not raw git log) +- Use unique SemVer identifiers (`vX.Y.Z` tags) +- Be built reproducibly (pinned toolchain, committed lock files, cargo-dist) + +### Security: +- Vulnerabilities go through private reporting (GitHub advisories or support@evilbitlabs.io), never public issues +- `cargo audit` and `cargo deny` run daily in CI -- fix findings promptly +- Medium+ severity vulnerabilities: we aim to release a fix within 90 days of confirmation (see SECURITY.md for canonical policy) +- `unsafe_code = "forbid"` is enforced project-wide via workspace lints in `Cargo.toml` -- this is a hardening mechanism, not a suggestion +- `docs/src/security-assurance.md` must be updated when new attack surface is introduced + +### Documentation: +- Public APIs require rustdoc with examples +- CONTRIBUTING.md documents code review criteria, test policy, DCO, and governance +- SECURITY.md documents vulnerability reporting with scope, safe harbor, and PGP key +- AGENTS.md must accurately reflect implemented features (not aspirational) +- `docs/src/release-verification.md` documents artifact signing for users diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..e0657783 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,53 @@ +# Changelog + +All notable changes to this project will be documented in this file. + + +## [unreleased] + +### Features + +- Add development infrastructure, automation hooks, and comprehensive documentation ([#4](https://github.com/EvilBit-Labs/libmagic-rs/pull/4)) +- Add evaluation engine with offset resolution and CI/CD automation ([#5](https://github.com/EvilBit-Labs/libmagic-rs/pull/5)) +- Create rmagic cli ([#7](https://github.com/EvilBit-Labs/libmagic-rs/pull/7)) +- Implement text magic parser (issue #11) ([#16](https://github.com/EvilBit-Labs/libmagic-rs/pull/16)) +- Parser integration, CI modernization with mise, and Dev Container support ([#26](https://github.com/EvilBit-Labs/libmagic-rs/pull/26)) +- Cli enhancements multiple files stdin magic discovery ([#27](https://github.com/EvilBit-Labs/libmagic-rs/pull/27)) +- Built-in rules build time compilation fallback ([#28](https://github.com/EvilBit-Labs/libmagic-rs/pull/28)) +- Evaluation enhancements with confidence, MIME, tags, metadata ([#29](https://github.com/EvilBit-Labs/libmagic-rs/pull/29)) +- Strength calculation & documentation improvements ([#21](https://github.com/EvilBit-Labs/libmagic-rs/pull/21)) ([#30](https://github.com/EvilBit-Labs/libmagic-rs/pull/30)) + +### Documentation + +- Comprehensive mdbook rewrite, rustdoc fixes, and test stability ([#33](https://github.com/EvilBit-Labs/libmagic-rs/pull/33)) +- **agents**: Update AGENTS.md to reflect implemented features and future plans +- **agents**: Fix inaccurate feature claims in AGENTS.md +- **readme**: Update README.md with project badges for visibility +- Improve SECURITY.md with scope, safe harbor, and responsible disclosure +- Add PGP key for encrypted vulnerability reporting +- Use support@evilbitlabs.io for security contact to match PGP key +- Add code review requirements to AGENTS.md and CONTRIBUTING.md +- Add quick reference and OSSF quality standards to CLAUDE.md +- Add DCO requirement to CONTRIBUTING.md +- Add project governance model and update development guide +- Update roadmap to match milestones and add non-goals +- Add release verification guide for signed artifacts +- Add security assurance case for OSSF gold criteria +- **readme**: Add OpenSSF Best Practices badge +- Move OSSF standards from CLAUDE.md to AGENTS.md +- Extract roadmap into ROADMAP.md to keep README concise +- Add project-level Claude Code skills for Rust development + +### Miscellaneous Tasks + +- **ci**: Add GitHub workflows for auditing, security, and documentation +- Add Contributor Covenant Code of Conduct ([#2](https://github.com/EvilBit-Labs/libmagic-rs/pull/2)) +- Pin all GitHub Actions to SHA hashes for supply chain security +- Revert SHA pins in release.yml (managed by cargo-dist) +- Add git-cliff changelog generation and simplify justfile +- Commit Cargo.lock for reproducible binary builds +- Commit mise.lock for reproducible dev tool versions +- Add SPDX license and copyright headers to all source files +- Add Claude Code hook to enforce DCO sign-off on commits +- Add hookify rules for Rust code safety warnings + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8f40ed66..cf4a3fc3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,6 +13,7 @@ Thank you for your interest in contributing to libmagic-rs! This document provid - [Documentation](#documentation) - [Submitting Changes](#submitting-changes) - [Style Guidelines](#style-guidelines) +- [Project Governance](#project-governance) ## Code of Conduct @@ -234,10 +235,41 @@ pub fn parse_rule(input: &str) -> Result { 1. **Update documentation** for any API changes 2. **Add tests** for new functionality -3. **Run the full test suite** locally +3. **Run the full test suite** locally: `just ci-check` 4. **Create a pull request** with a clear description 5. **Address review feedback** promptly +### Code Review Requirements + +All pull requests require review before merging. Reviewers check for: + +- **Correctness**: Does the code do what it claims? Are edge cases handled? +- **Safety**: No unsafe code, proper bounds checking, no panics in library code +- **Tests**: New functionality has tests, existing tests still pass +- **Style**: Follows project conventions, passes `cargo fmt` and `cargo clippy -- -D warnings` +- **Documentation**: Public APIs have rustdoc with examples, AGENTS.md updated if architecture changes +- **Performance**: No unnecessary allocations in hot paths, no regressions in benchmarks + +CI must pass before merge. This includes formatting, linting, tests, security audit, and CodeQL analysis. Branch protection enforces these checks on the `main` branch. + +### Developer Certificate of Origin (DCO) + +This project requires all contributors to sign off on their commits, certifying that they have the right to submit the code under the project's license. This is enforced by the [DCO GitHub App](https://github.com/apps/dco). + +To sign off, add `-s` to your commit command: + +```bash +git commit -s -m "feat: add new feature" +``` + +This adds a `Signed-off-by` line to your commit message: + +```text +Signed-off-by: Your Name +``` + +By signing off, you agree to the [Developer Certificate of Origin](https://developercertificate.org/). + ### PR Description Template ```markdown @@ -256,6 +288,7 @@ How were these changes tested? - [ ] No clippy warnings (`cargo clippy`) - [ ] Code formatted (`cargo fmt`) - [ ] Documentation updated +- [ ] Commits signed off (`git commit -s`) ``` ## Style Guidelines @@ -301,6 +334,31 @@ pub enum ParseError { } ``` +## Project Governance + +### Decision-Making + +libmagic-rs uses a **maintainer-driven** governance model. Decisions are made by the project maintainers through consensus on GitHub issues and pull requests. + +### Roles + +| Role | Responsibilities | Current | +|------|-----------------|---------| +| **Maintainer** | Merge PRs, manage releases, set project direction, review security reports | [@unclesp1d3r](https://github.com/unclesp1d3r), [@kmelton](https://github.com/kmelton) | +| **Contributor** | Submit issues, PRs, and participate in discussions | Anyone following this guide | + +### How Decisions Are Made + +- **Bug fixes and minor changes**: Any maintainer can review and merge +- **New features**: Discussed in a GitHub issue before implementation; maintainer approval required +- **Architecture changes**: Require agreement from both maintainers +- **Breaking API changes**: Discussed in a GitHub issue with community input; require agreement from both maintainers +- **Releases**: Prepared by any maintainer, following the [release process](docs/src/release-process.md) + +### Becoming a Maintainer + +As the project grows, active contributors who demonstrate sustained, high-quality contributions and alignment with project goals may be invited to become maintainers. + ## Getting Help - **Issues**: For bug reports and feature requests diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..2cc7ff1d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1333 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.5.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap-stdin" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d80e97074da664d8e2c49e82cba54c40b9cec15c73810d90bf6a78261ab3ccaa" +dependencies = [ + "thiserror", +] + +[[package]] +name = "clap_builder" +version = "4.5.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "criterion" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" +dependencies = [ + "alloca", + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools", + "num-traits", + "oorandom", + "page_size", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "insta" +version = "1.46.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e82db8c87c7f1ccecb34ce0c24399b8a73081427f3c7c50a5d597925356115e4" +dependencies = [ + "console", + "once_cell", + "serde", + "similar", + "tempfile", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libmagic-rs" +version = "0.1.0" +dependencies = [ + "byteorder", + "cfg-if", + "clap", + "clap-stdin", + "criterion", + "insta", + "memchr", + "memmap2", + "nix", + "nom", + "proptest", + "regex", + "serde", + "serde_json", + "tempfile", + "thiserror", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memmap2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +dependencies = [ + "libc", +] + +[[package]] +name = "nix" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225e7cfe711e0ba79a68baeddb2982723e4235247aefce1482f2f16c27865b66" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +dependencies = [ + "fastrand", + "getrandom 0.4.1", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen 0.46.0", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zerocopy" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/README.md b/README.md index 0f385b14..1b0e5135 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # libmagic-rs +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/11947/badge)](https://www.bestpractices.dev/projects/11947) +[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/EvilBit-Labs/libmagic-rs/badge)](https://scorecard.dev/viewer/?uri=github.com/EvilBit-Labs/libmagic-rs) +[![Crates.io](https://img.shields.io/crates/v/libmagic-rs)](https://crates.io/crates/libmagic-rs) +[![License](https://img.shields.io/crates/l/libmagic-rs)](https://github.com/EvilBit-Labs/libmagic-rs/blob/main/LICENSE) + A pure-Rust implementation of libmagic, the library that powers the `file` command for identifying file types. This project provides a memory-safe, efficient alternative to the C-based libmagic library. > [!NOTE] @@ -385,6 +390,16 @@ The library provides a migration path from C-based libmagic: - **Safe File Handling**: Graceful handling of truncated/corrupted files - **Fuzzing Integration**: Robustness testing with malformed inputs +### Verifying Releases + +All release artifacts are cryptographically signed via [Sigstore](https://www.sigstore.dev/) using GitHub Attestations. To verify a downloaded artifact: + +```bash +gh attestation verify --repo EvilBit-Labs/libmagic-rs +``` + +See the [release verification guide](https://evilbitlabs.io/libmagic-rs/release-verification.html) for details. + ## Contributing 1. Fork the repository @@ -410,55 +425,15 @@ This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENS ## Roadmap -### Phase 1: MVP (v0.1) - Current Focus - -**Core Infrastructure (Complete):** - -- [x] Core AST data structures with comprehensive serialization -- [x] Magic file parser for text format with hierarchical rules -- [x] Rule evaluation engine with confidence scoring -- [x] Memory-mapped file I/O with FileBuffer -- [x] Text and JSON output formatters -- [x] CLI with `--json` and `--magic-file` flags -- [x] Comprehensive error handling - -**In Progress:** - -- [ ] Multiple file support in CLI -- [ ] Stdin input support (`rmagic -`) -- [ ] Built-in fallback rules (`--use-builtin`) -- [ ] Magdir directory loading (load all files from `/usr/share/file/magic/Magdir/`) -- [ ] Strength calculation (libmagic's `!:strength` parsing) -- [ ] Complete rustdoc and mdbook documentation - -**Success Criteria:** - -- 95%+ compatibility with GNU `file` for common types (ELF, PE, ZIP, JPEG, PNG, PDF) -- >85% test coverage - -### Phase 2: Enhanced Features (v0.2) - -- [ ] Binary `.mgc` format support (deferred per OpenBSD approach) -- [ ] Indirect offset resolution -- [ ] Regex support with binary-safe matching -- [ ] Compiled rule caching for faster startup -- [ ] Additional operators and type support -- [ ] Aho-Corasick string indexing - -### Phase 3: Performance & Compatibility (v0.3) - -- [ ] Performance optimizations and benchmarking -- [ ] Full libmagic syntax compatibility -- [ ] PE/Mach-O/ELF format-specific detection -- [ ] Go build info extraction - -### Phase 4: Production Ready (v1.0) +See [ROADMAP.md](ROADMAP.md) for the full roadmap with linked issues, or [GitHub Milestones](https://github.com/EvilBit-Labs/libmagic-rs/milestones) for detailed issue tracking. -- [ ] Stable API with semver guarantees -- [ ] Migration guide from C libmagic -- [ ] Performance parity validation -- [ ] Fuzzing and security testing -- [ ] crates.io publication +| Milestone | Focus | +|-----------|-------| +| **v0.1.0** (current) | MVP: parser, evaluator, CLI, built-in rules, 94%+ test coverage | +| **v0.2.0** | Comparison operators, bitwise XOR/NOT, indirect/relative offsets, 64-bit integers | +| **v0.3.0** | Regex, float/double, date/timestamp, pascal strings, meta-types | +| **v0.4.0** | Builder API, JSON metadata, parse warnings, improved errors | +| **v1.0.0** | 95%+ GNU `file` compatibility, stable API, crates.io publication | ## Support diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 00000000..a6e0ceee --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,63 @@ +# Roadmap + +See [GitHub Milestones](https://github.com/EvilBit-Labs/libmagic-rs/milestones) for detailed issue tracking. + +## v0.1.0 - MVP (Current) + +- [x] Core AST data structures with comprehensive serialization +- [x] Magic file parser for text format with hierarchical rules +- [x] Rule evaluation engine with confidence scoring +- [x] Memory-mapped file I/O with FileBuffer +- [x] Text and JSON output formatters +- [x] CLI with multiple file support, stdin, `--json`, and `--magic-file` +- [x] Built-in fallback rules with build-time compilation +- [x] Strength calculation and `!:strength` parsing +- [x] Comprehensive error handling, rustdoc, and mdbook documentation +- [x] High test coverage across 1,058+ tests + +## v0.2.0 - Core Primitives + +- [ ] Split evaluator module into focused submodules ([#59](https://github.com/EvilBit-Labs/libmagic-rs/issues/59)) +- [ ] Disambiguate `MatchResult` types ([#60](https://github.com/EvilBit-Labs/libmagic-rs/issues/60)) +- [ ] Extract CLI tests into integration tests ([#61](https://github.com/EvilBit-Labs/libmagic-rs/issues/61)) +- [ ] Pre-create evaluator submodules for new features ([#62](https://github.com/EvilBit-Labs/libmagic-rs/issues/62)) +- [ ] Comparison operators: `>`, `<`, `>=`, `<=` ([#34](https://github.com/EvilBit-Labs/libmagic-rs/issues/34)) +- [ ] Bitwise XOR, NOT, and any-value operators ([#35](https://github.com/EvilBit-Labs/libmagic-rs/issues/35)) +- [ ] Indirect offset resolution ([#37](https://github.com/EvilBit-Labs/libmagic-rs/issues/37)) +- [ ] Relative offset resolution ([#38](https://github.com/EvilBit-Labs/libmagic-rs/issues/38)) +- [ ] Quad (64-bit integer) type ([#36](https://github.com/EvilBit-Labs/libmagic-rs/issues/36)) + +## v0.3.0 - Advanced Features + +- [ ] Convert `evaluator/types.rs` to directory module ([#63](https://github.com/EvilBit-Labs/libmagic-rs/issues/63)) +- [ ] Regex and search types ([#39](https://github.com/EvilBit-Labs/libmagic-rs/issues/39)) +- [ ] Float and double types ([#40](https://github.com/EvilBit-Labs/libmagic-rs/issues/40)) +- [ ] Date and timestamp types ([#41](https://github.com/EvilBit-Labs/libmagic-rs/issues/41)) +- [ ] Pascal string type ([#43](https://github.com/EvilBit-Labs/libmagic-rs/issues/43)) +- [ ] Meta-types: default, clear, name, use, indirect ([#42](https://github.com/EvilBit-Labs/libmagic-rs/issues/42)) + +## v0.4.0 - API and UX Polish + +- [ ] Builder pattern for `MagicDatabase` ([#45](https://github.com/EvilBit-Labs/libmagic-rs/issues/45)) +- [ ] JSON output metadata ([#46](https://github.com/EvilBit-Labs/libmagic-rs/issues/46)) +- [ ] Parse warnings for skipped rules ([#47](https://github.com/EvilBit-Labs/libmagic-rs/issues/47)) +- [ ] Improved error messages ([#49](https://github.com/EvilBit-Labs/libmagic-rs/issues/49)) +- [ ] Partial results on timeout ([#44](https://github.com/EvilBit-Labs/libmagic-rs/issues/44)) + +## v1.0.0 - Production Ready + +- [ ] 95%+ compatibility with GNU `file` ([#48](https://github.com/EvilBit-Labs/libmagic-rs/issues/48), [#57](https://github.com/EvilBit-Labs/libmagic-rs/issues/57)) +- [ ] Stable API with semver guarantees +- [ ] Migration guide from C libmagic +- [ ] Performance parity validation +- [ ] crates.io publication + +## Non-Goals + +The following are explicitly out of scope for this project: + +- **Binary `.mgc` compilation**: We follow the OpenBSD approach of using text magic files directly, not the compiled binary format +- **Drop-in C ABI replacement**: This is a Rust-native library, not a C-compatible shared library with `libmagic.so` ABI +- **MIME database management**: We detect file types via magic rules, not by maintaining a MIME type registry +- **File modification or conversion**: This is a read-only detection tool +- **Network protocol detection**: We identify file contents, not network traffic diff --git a/SECURITY.md b/SECURITY.md index 6b063d95..5c5d4196 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,100 +2,112 @@ ## Supported Versions -libmagic-rs is currently in active development as a passion project. Security updates are provided on a best-effort basis for the following versions: +| Version | Supported | +| ------- | ------------------ | +| 0.1.x | :white_check_mark: | +| < 0.1 | :x: | -| Version | Supported | Notes | -| ------- | ------------------ | ------------------------------------ | -| 1.0.x | :white_check_mark: | Production releases (when available) | -| 0.3.x | :white_check_mark: | Pre-release with security fixes | -| 0.2.x | :white_check_mark: | Pre-release with security fixes | -| 0.1.x | :white_check_mark: | MVP releases with security fixes | -| < 0.1 | :x: | Development versions | +Users on unsupported versions should upgrade to the latest release. Please review the [release notes](https://github.com/EvilBit-Labs/libmagic-rs/releases) when upgrading. -## Security Considerations - -### Memory Safety +## Reporting a Vulnerability -- **Pure Rust Implementation**: No unsafe code except in vetted dependencies -- **Bounds Checking**: All buffer access protected by bounds checking -- **Safe File Handling**: Graceful handling of truncated/corrupted files -- **Fuzzing Integration**: Robustness testing with malformed inputs +We take the security of libmagic-rs seriously. If you believe you have found a security vulnerability, please report it to us as described below. -### Input Validation +**Please do not report security vulnerabilities through public GitHub issues.** -- **Magic File Validation**: Syntax validation before parsing -- **File Size Limits**: Protection against resource exhaustion -- **Malformed Input Handling**: Safe processing of corrupted files -- **Timeout Protection**: Configurable timeouts for long-running evaluations +Instead, use one of the following channels: -### Dependencies +1. [GitHub Private Vulnerability Reporting](https://github.com/EvilBit-Labs/libmagic-rs/security/advisories/new) (preferred) +2. Email [support@evilbitlabs.io](mailto:support@evilbitlabs.io) encrypted with our [PGP key](#pgp-key) (verify the full fingerprint below before use) -- **Vetted Dependencies**: Only trusted, well-maintained crates -- **Security Audits**: Regular `cargo audit` checks -- **Minimal Attack Surface**: Minimal external dependencies -- **License Compliance**: All dependencies must have compatible licenses +Please include: -## Reporting a Vulnerability +- Description of the vulnerability +- Steps to reproduce +- Potential impact +- Suggested fix (if any) -### How to Report +### Scope -If you discover a security vulnerability in libmagic-rs, please report it responsibly: +**In scope:** -1. **Email**: Send details to -2. **GitHub Security**: Use GitHub's private vulnerability reporting feature -3. **Do NOT**: Open public issues for security vulnerabilities +- Buffer overflows or out-of-bounds reads in magic file parsing or evaluation +- Denial of service via crafted magic files or input files +- Path traversal in file input handling +- Command injection via CLI arguments +- Unsafe code in dependencies that affects libmagic-rs -### What to Include +**Out of scope:** -- Description of the vulnerability -- Steps to reproduce the issue -- Potential impact assessment -- Suggested fix (if any) -- Your contact information for follow-up +- Vulnerabilities in the original C libmagic implementation +- Issues requiring physical access to the machine running libmagic-rs +- Social engineering attacks -### Response Timeline +### What to Expect **Note**: This is a passion project with volunteer maintainers. Response times are best-effort and may vary based on maintainer availability. -- **Acknowledgment**: Best effort (typically within 1 week) -- **Initial Assessment**: Best effort (typically within 2 weeks) -- **Fix Development**: Best effort (timeline depends on severity and maintainer availability) -- **Public Disclosure**: Coordinated with fix release when possible +- We will acknowledge receipt of your report within **1 week** +- We will provide an initial assessment within **2 weeks** +- We aim to release a fix within **90 days** of confirmed vulnerabilities +- We will coordinate disclosure through a [GitHub Security Advisory](https://github.com/EvilBit-Labs/libmagic-rs/security/advisories) +- We will credit you in the advisory (unless you prefer to remain anonymous) + +### Responsible Disclosure + +We ask that you: -### Severity Levels +- Give us reasonable time to respond to issues before any disclosure +- Avoid accessing or modifying other users' data +- Avoid actions that could negatively impact other users -- **Critical**: Remote code execution, memory corruption -- **High**: Denial of service, information disclosure -- **Medium**: Limited information disclosure, minor DoS -- **Low**: Minor issues, edge cases +## Security Features -## Security Best Practices +libmagic-rs includes several security-focused features: -### For Users +- **Pure Rust implementation**: No unsafe code except in vetted dependencies +- **Bounds checking**: All buffer access protected by bounds checking +- **Safe file handling**: Graceful handling of truncated and corrupted files +- **Dependency auditing**: Regular `cargo audit` and `cargo deny` checks +- **Automated dependency updates**: Via Dependabot -- Keep libmagic-rs updated to the latest version -- Validate input files before processing -- Use appropriate file size limits -- Monitor for security advisories +## Safe Harbor -### For Developers +We support safe harbor for security researchers who: -- Follow Rust security best practices -- Use `cargo audit` regularly -- Implement proper error handling -- Test with malformed inputs -- Review all unsafe code usage +- Make a good faith effort to avoid privacy violations, data destruction, and service disruption +- Only interact with accounts you own or with explicit permission of the account holder +- Report vulnerabilities through the channels described above -## Security Acknowledgments +We will not pursue legal action against researchers who follow this policy. -We appreciate responsible disclosure and will acknowledge security researchers who help improve libmagic-rs security. Contributors will be listed in our security acknowledgments (with permission). +## PGP Key -**Note**: As a passion project, we may not always be able to provide immediate responses or fixes, but we do our best to address security issues when maintainers are available. +**Fingerprint:** `F839 4B2C F0FE C451 1B11 E721 8F71 D62B F438 2BC0` + +```text +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEaLJmxhYJKwYBBAHaRw8BAQdAaS3KAoo+AgZGR6G6+m0wT2yulC5d6zV9lf2m +TugBT+O0L3N1cHBvcnRAZXZpbGJpdGxhYnMuaW8gPHN1cHBvcnRAZXZpbGJpdGxh +YnMuaW8+iNcEExYKAH8DCwkHRRQAAAAAABwAIHNhbHRAbm90YXRpb25zLm9wZW5w +Z3Bqcy5vcmexd21FpCDfIrO7bf+T6hH/8drbGLWiuEueWvSTyw4T/QMVCggEFgAC +AQIZAQKbAwIeARYhBPg5Syzw/sRRGxHnIY9x1iv0OCvABQJpiUiCBQkIXQE5AAoJ +EI9x1iv0OCvAm2sA/AqFT6XEULJCimXX9Ve6e63RX7y2B+VoBVHt+PDaPBwkAP4j +39xBoLFI6KZJ/A7SOQBkret+VONwPqyW83xfn+E7Arg4BGiyZsYSCisGAQQBl1UB +BQEBB0ArjU33Uj/x1Kc7ldjVIM9UUCWMTwDWgw8lB/mNESb+GgMBCAeIvgQYFgoA +cAWCaLJmxgkQj3HWK/Q4K8BFFAAAAAAAHAAgc2FsdEBub3RhdGlvbnMub3BlbnBn +cGpzLm9yZ4msIB6mugSL+LkdT93+rSeNePtBY4Aj+O6TRFU9aKiQApsMFiEE+DlL +LPD+xFEbEechj3HWK/Q4K8AAALEXAQDqlsBwMP2XXzXDSnNNLg8yh1/zQcxT1zZ1 +Z26lyM7L6QD+Lya5aFe74WE3wTys5ykGuWkHYEgba+AyZNmuPhwMGAc= +=9zSi +-----END PGP PUBLIC KEY BLOCK----- +``` ## Contact -For security-related questions or concerns: +For general security questions, open a GitHub Issue. For vulnerability reports, use [Private Vulnerability Reporting](https://github.com/EvilBit-Labs/libmagic-rs/security/advisories/new) or email [support@evilbitlabs.io](mailto:support@evilbitlabs.io). + +--- -- **Email**: -- **GitHub**: [Security Advisories](https://github.com/EvilBit-Labs/libmagic-rs/security/advisories) -- **Issues**: Use private vulnerability reporting for security issues +Thank you for helping keep libmagic-rs and its users secure! diff --git a/benches/evaluation_bench.rs b/benches/evaluation_bench.rs index 6e0e6b58..2dc15543 100644 --- a/benches/evaluation_bench.rs +++ b/benches/evaluation_bench.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Evaluation benchmarks for libmagic-rs //! //! Benchmarks rule evaluation performance against various file types: diff --git a/benches/io_bench.rs b/benches/io_bench.rs index 81f2b9aa..9d61325d 100644 --- a/benches/io_bench.rs +++ b/benches/io_bench.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! I/O benchmarks for libmagic-rs //! //! Benchmarks file I/O operations including: diff --git a/benches/parser_bench.rs b/benches/parser_bench.rs index 3a19359d..b202f0e9 100644 --- a/benches/parser_bench.rs +++ b/benches/parser_bench.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Parser benchmarks for libmagic-rs //! //! Benchmarks magic file parsing performance including: diff --git a/build.rs b/build.rs index b80c0a37..11e91f0b 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + // Stub module to satisfy error.rs dependencies during build #[allow(dead_code)] mod evaluator { diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 00000000..49bd31b0 --- /dev/null +++ b/cliff.toml @@ -0,0 +1,86 @@ +# git-cliff ~ configuration file +# https://git-cliff.org/docs/configuration +# +# Lines starting with "#" are comments. +# Configuration options are organized into tables and keys. +# See documentation for more information on available options. + +[changelog] +# template for the changelog header +header = """ +# Changelog\n +All notable changes to this project will be documented in this file.\n +""" +# template for the changelog body +# https://keats.github.io/tera/docs/#introduction +body = """ +{%- if version %} +## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} +{%- else %} +## [unreleased] +{%- endif %} +{% for group, commits in commits | group_by(attribute="group") %} +### {{ group | striptags | trim | upper_first }} +{% for commit in commits %} +- {% if commit.scope %}**{{ commit.scope }}**: {% endif -%} +{% if commit.breaking %}[**breaking**] {% endif -%} +{{ commit.message | upper_first }} +{%- endfor %} +{% endfor %} +""" +# template for the changelog footer +footer = """ + +""" +# remove the leading and trailing whitespace +trim = true +# postprocessors +postprocessors = [] +# render body even when there are no releases to process +# render_always = true +# output file path +# output = "CHANGELOG.md" + +[git] +# parse the commits based on https://www.conventionalcommits.org +conventional_commits = true +# filter out the commits that are not conventional +filter_unconventional = true +# process each line of a commit as an individual commit +split_commits = false +# regex for preprocessing the commit messages +commit_preprocessors = [ + # Replace issue/PR numbers with links + { pattern = '\(#([0-9]+)\)', replace = "([#${1}](https://github.com/EvilBit-Labs/libmagic-rs/pull/${1}))" }, + # Check spelling of the commit with https://github.com/crate-ci/typos + # If the spelling is incorrect, it will be automatically fixed. + # { pattern = '.*', replace_command = 'typos --write-changes -' }, +] +# regex for parsing and grouping commits +commit_parsers = [ + { message = "^feat", group = "Features" }, + { message = "^fix\\(security\\)", group = "Security", scope = "security" }, + { message = "^fix", group = "Bug Fixes" }, + { message = "^[Dd]oc", group = "Documentation" }, + { message = "^perf", group = "Performance" }, + { message = "^refactor", group = "Refactor" }, + { message = "^style", group = "Styling" }, + { message = "^test", group = "Testing" }, + { message = "^chore\\(release\\): prepare for", skip = true }, + { message = "^chore\\(deps.*\\)", skip = true }, + { message = "^chore\\(pr\\)", skip = true }, + { message = "^chore\\(pull\\)", skip = true }, + { message = "^Merge pull request", skip = true }, + { message = "^Merge branch", skip = true }, + { message = "^chore\\(ci\\): update", skip = true }, + { message = "^chore|^ci", group = "Miscellaneous Tasks" }, + { body = ".*security", group = "Security" }, + { body = "BREAKING CHANGE:", group = "Breaking Changes" }, + { message = "^revert", group = "Revert" }, +] +# filter out the commits that are not matched by commit parsers +filter_commits = false +# sort the tags topologically +topo_order = false +# sort the commits inside sections by oldest/newest order +sort_commits = "oldest" diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 4eb2c71f..ebdaafaa 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -46,6 +46,7 @@ - [Chapter 18: Code Style & Standards](./code-style.md) - [Chapter 19: Testing Guidelines](./testing-guidelines.md) - [Chapter 20: Release Process](./release-process.md) +- [Chapter 21: Release Verification](./release-verification.md) --- @@ -55,3 +56,4 @@ - [Appendix B: Command Reference](./cli-reference.md) - [Appendix C: Magic File Examples](./magic-examples.md) - [Appendix D: Compatibility Matrix](./compatibility.md) +- [Appendix E: Security Assurance Case](./security-assurance.md) diff --git a/docs/src/development.md b/docs/src/development.md index ccd17c37..beffe543 100644 --- a/docs/src/development.md +++ b/docs/src/development.md @@ -401,8 +401,11 @@ git commit -m "test(ast): add comprehensive serialization tests" - Reference to related issues - Test coverage information - Breaking change notes (if any) -3. **Address feedback** from code review -4. **Ensure CI passes** all checks +3. **Sign off commits** with `git commit -s` (DCO required) +4. **Address feedback** from code review +5. **Ensure CI passes** all checks + +For full details on code review criteria, DCO requirements, and project governance, see [CONTRIBUTING.md](https://github.com/EvilBit-Labs/libmagic-rs/blob/main/CONTRIBUTING.md). ## Debugging diff --git a/docs/src/release-verification.md b/docs/src/release-verification.md new file mode 100644 index 00000000..cec6d583 --- /dev/null +++ b/docs/src/release-verification.md @@ -0,0 +1,69 @@ +# Release Verification + +All libmagic-rs release artifacts are cryptographically signed to ensure authenticity and integrity. This guide explains how to verify that a downloaded artifact is genuine. + +## How Releases Are Signed + +libmagic-rs uses [Sigstore](https://www.sigstore.dev/) keyless signing via GitHub Attestations. During the release build: + +1. `cargo-dist` builds release artifacts in GitHub Actions +2. `actions/attest-build-provenance` generates a signed [SLSA provenance](https://slsa.dev/) attestation for each artifact +3. The attestation is stored in GitHub's attestation ledger and Sigstore's transparency log + +**Keyless signing** means there are no long-lived private keys to manage or compromise. Each build receives an ephemeral signing certificate tied to the GitHub Actions workflow identity. + +## Verifying with GitHub CLI + +The simplest way to verify an artifact: + +```bash +# Install GitHub CLI if you haven't already +# https://cli.github.com/ + +# Download a release artifact +gh release download v0.1.0 --repo EvilBit-Labs/libmagic-rs + +# Verify the artifact +gh attestation verify rmagic-x86_64-unknown-linux-gnu.tar.xz \ + --repo EvilBit-Labs/libmagic-rs +``` + +A successful verification looks like: + +```text +Loaded digest sha256:abc123... for file rmagic-x86_64-unknown-linux-gnu.tar.xz +Loaded 1 attestation from GitHub API + +The following attestation matched the digest: + - Predicate type: https://slsa.dev/provenance/v1 + - Signer: https://github.com/EvilBit-Labs/libmagic-rs/.github/workflows/release.yml + - Build trigger: push +``` + +## What Verification Proves + +A successful verification confirms: + +- **Authenticity**: The artifact was built by the official GitHub Actions workflow in the `EvilBit-Labs/libmagic-rs` repository +- **Integrity**: The artifact has not been modified since it was built +- **Provenance**: The build was triggered by a specific commit and tag + +## Additional Integrity Checks + +### SBOM (Software Bill of Materials) + +Each release includes a CycloneDX SBOM generated by `cargo-cyclonedx`, listing all dependencies and their versions. + +### Embedded Dependency Metadata + +Release binaries are built with `cargo-auditable`, which embeds dependency information directly into the binary. You can inspect it with: + +```bash +cargo audit bin rmagic +``` + +This allows post-deployment vulnerability scanning against the RustSec Advisory Database. + +## Homebrew + +Homebrew formula installations from the `EvilBit-Labs/homebrew-tap` tap are verified through Homebrew's standard SHA256 checksum mechanism, which is populated from the GitHub Release artifacts. diff --git a/docs/src/security-assurance.md b/docs/src/security-assurance.md new file mode 100644 index 00000000..af0ebfd2 --- /dev/null +++ b/docs/src/security-assurance.md @@ -0,0 +1,146 @@ +# Security Assurance Case + +This document provides a structured argument that libmagic-rs meets its security requirements. It follows the assurance case model described in [NIST IR 7608](https://csrc.nist.gov/publications/detail/nistir/7608/final). + +## 1. Security Requirements + +libmagic-rs is a file type detection library and CLI tool. Its security requirements are: + +1. **SR-1**: Must not crash, panic, or exhibit undefined behavior when processing any input file +2. **SR-2**: Must not crash, panic, or exhibit undefined behavior when parsing any magic file +3. **SR-3**: Must not read beyond allocated buffer boundaries +4. **SR-4**: Must not allow path traversal via CLI arguments +5. **SR-5**: Must not execute arbitrary code based on file contents or magic rule definitions +6. **SR-6**: Must not consume unbounded resources (memory, CPU) during evaluation +7. **SR-7**: Must not leak sensitive information from one file evaluation to another + +## 2. Threat Model + +### 2.1 Assets + +- **Host system**: The machine running libmagic-rs +- **File contents**: Data being inspected (may be sensitive) +- **Magic rules**: Definitions that drive file type detection + +### 2.2 Threat Actors + +| Actor | Motivation | Capability | +|-------|-----------|------------| +| Malicious file author | Exploit the detection tool to gain code execution or cause DoS | Can craft arbitrary file contents | +| Malicious magic file author | Inject rules that cause crashes, resource exhaustion, or incorrect results | Can craft arbitrary magic rule syntax | +| Supply chain attacker | Compromise a dependency to inject malicious code | Can publish malicious crate versions | + +### 2.3 Attack Vectors + +| ID | Vector | Target SR | +|----|--------|-----------| +| AV-1 | Crafted file triggers buffer over-read | SR-1, SR-3 | +| AV-2 | Crafted file triggers integer overflow in offset calculation | SR-1, SR-3 | +| AV-3 | Deeply nested magic rules cause stack overflow | SR-1, SR-6 | +| AV-4 | Extremely large file causes memory exhaustion | SR-6 | +| AV-5 | Malformed magic file causes parser crash | SR-2 | +| AV-6 | CLI argument with path traversal reads unintended files | SR-4 | +| AV-7 | Compromised dependency introduces unsafe code | SR-5 | + +## 3. Trust Boundaries + +```text ++------------------------------------------------------------------+ +| Untrusted | +| +------------------+ +-------------------+ | +| | Input Files | | Magic Files | | +| | (any content) | | (user or system) | | +| +--------+---------+ +--------+----------+ | +| | | | ++-----------+-----------------------+-------------------------------+ + | | + =========|=======================|============ Trust Boundary ==== + | | ++-----------v-----------------------v-------------------------------+ +| libmagic-rs | +| | +| +----------------+ +----------------+ +--------------+ | +| | Parser | | Evaluator | | Output | | +| | - validates | | - bounds-check | | - formats | | +| | magic syntax | | all access | | results | | +| +----------------+ +----------------+ +--------------+ | +| | +| +----------------+ +----------------+ | +| | I/O Layer | | CLI | | +| | - mmap files | | - clap args | | +| | - size limits | | - validates | | +| +----------------+ +----------------+ | ++------------------------------------------------------------------+ +``` + +All data crossing the trust boundary (file contents, magic file syntax, CLI arguments) is treated as untrusted and validated before use. + +## 4. Secure Design Principles (Saltzer and Schroeder) + +| Principle | How Applied | +|-----------|-------------| +| **Economy of mechanism** | Pure Rust with minimal dependencies. Simple parser-evaluator pipeline. No plugin system, no scripting, no network I/O. | +| **Fail-safe defaults** | Workspace lint `unsafe_code = "forbid"` enforced project-wide via `Cargo.toml`. Buffer access defaults to bounds-checked `.get()` returning `None` rather than panicking. Invalid magic rules are skipped, not executed. | +| **Complete mediation** | Every buffer access is bounds-checked. Every magic file is validated during parsing. Every CLI argument is validated by `clap`. | +| **Open design** | Fully open source (Apache-2.0). Security does not depend on obscurity. All security mechanisms are publicly documented. | +| **Separation of privilege** | Parser and evaluator are separate modules with distinct responsibilities. Parse errors cannot bypass evaluation safety checks. | +| **Least privilege** | The tool only reads files; it never writes, executes, or modifies them. No network access. No elevated permissions required. | +| **Least common mechanism** | No shared mutable state between file evaluations. Each evaluation operates on its own data. No global caches that could leak information. | +| **Psychological acceptability** | CLI follows GNU `file` conventions. Error messages are descriptive and actionable. Default behavior is safe (built-in rules, no network). | + +## 5. Common Weakness Countermeasures + +### 5.1 CWE/SANS Top 25 + +| CWE | Weakness | Countermeasure | Status | +|-----|----------|---------------|--------| +| CWE-787 | Out-of-bounds write | Rust ownership prevents writes to unowned memory. Workspace-level lints in `Cargo.toml` forbid unsafe code and eliminate raw pointer writes. | Mitigated | +| CWE-79 | XSS | Not applicable (no web output). | N/A | +| CWE-89 | SQL injection | Not applicable (no database). | N/A | +| CWE-416 | Use after free | Rust ownership/borrowing system prevents use-after-free at compile time. | Mitigated | +| CWE-78 | OS command injection | No shell invocation or command execution. CLI arguments parsed by `clap`, not passed to shell. | Mitigated | +| CWE-20 | Improper input validation | All inputs validated: magic syntax validated by parser, file buffers bounds-checked, CLI args validated by `clap`. | Mitigated | +| CWE-125 | Out-of-bounds read | All buffer access uses `.get()` with bounds checking. Memory-mapped files have known size limits. | Mitigated | +| CWE-22 | Path traversal | CLI accepts file paths as arguments but only performs read-only access. No path construction from file contents. | Mitigated | +| CWE-352 | CSRF | Not applicable (no web interface). | N/A | +| CWE-434 | Unrestricted upload | Not applicable (no file upload). | N/A | +| CWE-476 | NULL pointer dereference | Rust's `Option` type eliminates null pointer dereferences at compile time. | Mitigated | +| CWE-190 | Integer overflow | Rust panics on integer overflow in debug builds. Offset calculations use checked arithmetic. | Mitigated | +| CWE-502 | Deserialization of untrusted data | Magic files are parsed with a strict grammar, not deserialized from arbitrary formats. | Mitigated | +| CWE-400 | Resource exhaustion | Evaluation timeouts prevent unbounded CPU use. Memory-mapped I/O avoids loading entire files into memory. | Mitigated | + +### 5.2 OWASP Top 10 (where applicable) + +Most OWASP Top 10 categories target web applications and are not applicable to a file detection library. The applicable items are: + +| Category | Applicability | Countermeasure | +|----------|--------------|---------------| +| A03: Injection | Partial -- magic file parsing | Strict grammar-based parser rejects invalid syntax | +| A04: Insecure Design | Applicable | Secure design principles applied throughout (see Section 4) | +| A06: Vulnerable Components | Applicable | `cargo audit` daily, `cargo deny`, Dependabot, `cargo-auditable` | +| A09: Security Logging | Partial | Evaluation errors logged; security events reported via GitHub Advisories | + +## 6. Supply Chain Security + +| Measure | Implementation | +|---------|---------------| +| Dependency auditing | `cargo audit` and `cargo deny` run daily in CI | +| Dependency updates | Dependabot configured for automated PRs | +| Pinned toolchain | Rust 1.91.0 via `rust-toolchain.toml` | +| Reproducible builds | `Cargo.lock` and `mise.lock` committed | +| Build provenance | Sigstore attestations via `actions/attest-build-provenance` | +| SBOM generation | `cargo-cyclonedx` produces CycloneDX SBOM per release | +| Binary auditing | `cargo-auditable` embeds dependency metadata in binaries | +| CI integrity | All GitHub Actions pinned to SHA hashes | +| Code review | Required on all PRs; automated by CodeRabbit with security-focused checks | + +## 7. Ongoing Assurance + +This assurance case is maintained as a living document. It is updated when: + +- New features introduce new attack surfaces +- New threat vectors are identified +- Dependencies change significantly +- Security incidents occur + +The project maintains continuous assurance through automated CI checks (clippy, CodeQL, cargo audit, cargo deny) that run on every commit. diff --git a/justfile b/justfile index ec45e614..0cb179e9 100644 --- a/justfile +++ b/justfile @@ -8,8 +8,8 @@ set ignore-comments := true # Use mise to manage all dev tools (go, pre-commit, uv, etc.) # See mise.toml for tool versions -mise_exec := "mise exec --" +mise_exec := "mise exec --" root := justfile_dir() # ============================================================================= @@ -19,26 +19,6 @@ root := justfile_dir() default: @just --list -# ============================================================================= -# CROSS-PLATFORM HELPERS (private) -# ============================================================================= - -[private, windows] -ensure-dir dir: - New-Item -ItemType Directory -Force -Path "{{ dir }}" | Out-Null - -[private, unix] -ensure-dir dir: - /bin/mkdir -p "{{ dir }}" - -[private, windows] -rmrf path: - if (Test-Path "{{ path }}") { Remove-Item "{{ path }}" -Recurse -Force } - -[private, unix] -rmrf path: - /bin/rm -rf "{{ path }}" - # ============================================================================= # SETUP AND INITIALIZATION # ============================================================================= @@ -80,17 +60,18 @@ lint-rust-min: # Format justfile fmt-justfile: - @{{ mise_exec }} just --fmt --unstable + @just --fmt --unstable # Lint justfile formatting lint-justfile: - @{{ mise_exec }} just --fmt --check --unstable + @just --fmt --check --unstable + # Main lint recipe - calls all sub-linters lint: lint-rust lint-actions lint-docs lint-justfile # Individual lint recipes lint-actions: - @{{ mise_exec }} actionlint .github/workflows/audit.yml .github/workflows/benchmarks.yml .github/workflows/ci.yml .github/workflows/codeql.yml .github/workflows/compatibility.yml .github/workflows/copilot-setup-steps.yml .github/workflows/docs.yml .github/workflows/release.yml .github/workflows/security.yml + @{{ mise_exec }} actionlint .github/workflows/audit.yml .github/workflows/benchmarks.yml .github/workflows/ci.yml .github/workflows/codeql.yml .github/workflows/compatibility.yml .github/workflows/copilot-setup-steps.yml .github/workflows/docs.yml .github/workflows/release.yml .github/workflows/scorecard.yml .github/workflows/security.yml lint-docs: @{{ mise_exec }} markdownlint-cli2 docs/**/*.md README.md @@ -113,7 +94,6 @@ pre-commit-run: format-files +FILES: @{{ mise_exec }} prettier --write --config .prettierrc.json {{ FILES }} - # ============================================================================= # BUILDING AND TESTING # ============================================================================= @@ -179,23 +159,25 @@ deny: # ============================================================================= # Private helper: run cargo llvm-cov with proper setup -[private, unix] +[private] +[unix] _coverage +args: #!/usr/bin/env bash set -euo pipefail rm -rf target/llvm-cov-target RUSTFLAGS="--cfg coverage" {{ mise_exec }} cargo llvm-cov --workspace --lcov --output-path lcov.info {{ args }} -[private, windows] +[private] +[windows] _coverage +args: Remove-Item -Recurse -Force target/llvm-cov-target -ErrorAction SilentlyContinue $env:RUSTFLAGS = "--cfg coverage"; {{ mise_exec }} cargo llvm-cov --workspace --lcov --output-path lcov.info {{ args }} coverage: - @{{ mise_exec }} just _coverage + @just _coverage coverage-check: - @{{ mise_exec }} just _coverage --fail-under-lines 9.7 + @just _coverage --fail-under-lines 85 # Generate HTML coverage report for local viewing [unix] @@ -281,7 +263,7 @@ docs-clean: [unix] docs-check: cd docs && {{ mise_exec }} mdbook build - @{{ mise_exec }} just fmt-check + @just fmt-check # Generate and serve documentation [unix] @@ -292,42 +274,23 @@ docs: @echo "mdbook requires a Unix-like environment to serve" # ============================================================================= -# GORELEASER TESTING +# CHANGELOG # ============================================================================= -# Private helper: run goreleaser with macOS SDK env configured (no-op on non-mac) -[private, unix] -_goreleaser +args: - #!/bin/bash - set -euo pipefail - if command -v xcrun >/dev/null 2>&1; then - SDKROOT_PATH=$(xcrun --sdk macosx --show-sdk-path) - export SDKROOT="${SDKROOT_PATH}" - export MACOSX_DEPLOYMENT_TARGET="11.0" - export CARGO_ZIGBUILD_SYSROOT="${SDKROOT_PATH}" - export RUSTFLAGS="${RUSTFLAGS:-} -C link-arg=-Wl,-syslibroot,${SDKROOT_PATH} -C link-arg=-F${SDKROOT_PATH}/System/Library/Frameworks" - fi - goreleaser {{ args }} - -[private, windows] -_goreleaser +args: - @{{ mise_exec }} goreleaser {{ args }} - -goreleaser-check: - @{{ mise_exec }} goreleaser check - -goreleaser-build: - @just _goreleaser build --clean - -goreleaser-snapshot: - @just _goreleaser release --snapshot --clean - -goreleaser-build-target target: - @just _goreleaser build --clean --single-target {{ target }} - -# Clean GoReleaser artifacts -goreleaser-clean: - @just rmrf dist +# Generate changelog +[group('docs')] +changelog: + @{{ mise_exec }} git-cliff --output CHANGELOG.md + +# Generate changelog for a specific version +[group('docs')] +changelog-version version: + @{{ mise_exec }} git-cliff --tag {{ version }} --output CHANGELOG.md + +# Generate changelog for unreleased changes only +[group('docs')] +changelog-unreleased: + @{{ mise_exec }} git-cliff --unreleased --output CHANGELOG.md # ============================================================================= # RELEASE MANAGEMENT diff --git a/mise.lock b/mise.lock new file mode 100644 index 00000000..83ae4cb2 --- /dev/null +++ b/mise.lock @@ -0,0 +1,126 @@ +[[tools.actionlint]] +version = "1.7.10" +backend = "aqua:rhysd/actionlint" + +[[tools.cargo-binstall]] +version = "1.17.5" +backend = "aqua:cargo-bins/cargo-binstall" + +[[tools.cargo-insta]] +version = "1.46.1" +backend = "aqua:mitsuhiko/insta" + +[[tools."cargo:cargo-audit"]] +version = "0.22.0" +backend = "cargo:cargo-audit" + +[[tools."cargo:cargo-auditable"]] +version = "0.7.2" +backend = "cargo:cargo-auditable" + +[[tools."cargo:cargo-cyclonedx"]] +version = "0.5.7" +backend = "cargo:cargo-cyclonedx" + +[[tools."cargo:cargo-deny"]] +version = "0.19.0" +backend = "cargo:cargo-deny" + +[[tools."cargo:cargo-dist"]] +version = "0.30.3" +backend = "cargo:cargo-dist" + +[[tools."cargo:cargo-llvm-cov"]] +version = "0.6.24" +backend = "cargo:cargo-llvm-cov" + +[[tools."cargo:cargo-machete"]] +version = "0.9.1" +backend = "cargo:cargo-machete" + +[[tools."cargo:cargo-nextest"]] +version = "0.9.123-b.4" +backend = "cargo:cargo-nextest" + +[[tools."cargo:cargo-outdated"]] +version = "0.17.0" +backend = "cargo:cargo-outdated" + +[[tools."cargo:cargo-release"]] +version = "0.25.22" +backend = "cargo:cargo-release" + +[[tools."cargo:git-cliff"]] +version = "2.12.0" +backend = "cargo:git-cliff" + +[[tools."cargo:mdbook"]] +version = "0.5.2" +backend = "cargo:mdbook" + +[[tools."cargo:mdbook-admonish"]] +version = "1.20.0" +backend = "cargo:mdbook-admonish" + +[[tools."cargo:mdbook-i18n-helpers"]] +version = "0.4.0" +backend = "cargo:mdbook-i18n-helpers" + +[[tools."cargo:mdbook-linkcheck"]] +version = "0.7.7" +backend = "cargo:mdbook-linkcheck" + +[[tools."cargo:mdbook-mermaid"]] +version = "0.17.0" +backend = "cargo:mdbook-mermaid" + +[[tools."cargo:mdbook-open-on-gh"]] +version = "3.0.0" +backend = "cargo:mdbook-open-on-gh" + +[[tools."cargo:mdbook-tabs"]] +version = "0.3.4" +backend = "cargo:mdbook-tabs" + +[[tools."cargo:mdbook-toc"]] +version = "0.15.3" +backend = "cargo:mdbook-toc" + +[[tools.just]] +version = "1.46.0" +backend = "aqua:casey/just" + +[[tools.lychee]] +version = "0.22.0" +backend = "aqua:lycheeverse/lychee" + +[[tools.markdownlint-cli2]] +version = "0.20.0" +backend = "npm:markdownlint-cli2" + +[[tools."pipx:mdformat"]] +version = "0.7.21" +backend = "pipx:mdformat" + +[tools."pipx:mdformat".options] +uvx_args = "--with mdformat-gfm --with mdformat-frontmatter --with mdformat-footnote --with mdformat-simple-breaks --with mdformat-gfm-alerts --with mdformat-toc --with mdformat-wikilink --with mdformat-tables" + +[[tools.pre-commit]] +version = "4.5.1" +backend = "aqua:pre-commit/pre-commit" + +[[tools.prettier]] +version = "3.8.1" +backend = "npm:prettier" + +[[tools.python]] +version = "3.13.11" +backend = "core:python" + +[[tools.rust]] +version = "1.91.0" +backend = "core:rust" + +[[tools.scorecard]] +version = "5.4.0" +backend = "aqua:ossf/scorecard" diff --git a/mise.toml b/mise.toml index e082f365..603a0db1 100644 --- a/mise.toml +++ b/mise.toml @@ -27,4 +27,6 @@ actionlint = "1.7.10" lychee = "0.22.0" markdownlint-cli2 = "0.20.0" pre-commit = "latest" -"cargo:cargo-machete" = "0.9.1" +"cargo:cargo-machete" = "0.9.1" +"cargo:git-cliff" = "2.12.0" +scorecard = "5.4.0" diff --git a/src/build_helpers.rs b/src/build_helpers.rs index eadd5074..504e14e3 100644 --- a/src/build_helpers.rs +++ b/src/build_helpers.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + /// Build-time helpers for compiling magic rules. /// /// This module contains functionality used by the build script to parse magic files diff --git a/src/builtin_rules.rs b/src/builtin_rules.rs index 65583538..ad98288b 100644 --- a/src/builtin_rules.rs +++ b/src/builtin_rules.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Built-in magic rules compiled at build time. //! //! This module contains magic rules that are compiled into the library binary diff --git a/src/error.rs b/src/error.rs index 5d1bdee8..c7263e0b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Error types for the libmagic-rs library. //! //! This module defines the error types used throughout the library for diff --git a/src/evaluator/mod.rs b/src/evaluator/mod.rs index 56fd9f67..d74c2c1a 100644 --- a/src/evaluator/mod.rs +++ b/src/evaluator/mod.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Rule evaluation engine //! //! This module contains the core evaluation logic for executing magic rules diff --git a/src/evaluator/offset.rs b/src/evaluator/offset.rs index 30b1f974..7a919029 100644 --- a/src/evaluator/offset.rs +++ b/src/evaluator/offset.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Offset resolution for magic rule evaluation //! //! This module provides functions for resolving different types of offset specifications diff --git a/src/evaluator/operators.rs b/src/evaluator/operators.rs index e307dcc1..acd6e387 100644 --- a/src/evaluator/operators.rs +++ b/src/evaluator/operators.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Operator application for magic rule evaluation //! //! This module provides functions for applying comparison and bitwise operators diff --git a/src/evaluator/strength.rs b/src/evaluator/strength.rs index 9879e0c4..0469eb76 100644 --- a/src/evaluator/strength.rs +++ b/src/evaluator/strength.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Strength calculation for magic rules //! //! This module implements the strength calculation algorithm based on libmagic's diff --git a/src/evaluator/types.rs b/src/evaluator/types.rs index 448e2752..671875f7 100644 --- a/src/evaluator/types.rs +++ b/src/evaluator/types.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Type interpretation for reading and converting bytes from file buffers //! //! This module provides functions for safely reading different data types from byte buffers diff --git a/src/io/mod.rs b/src/io/mod.rs index 52aef5e2..dac0512e 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! I/O utilities module //! //! This module provides efficient file access utilities including memory-mapped diff --git a/src/lib.rs b/src/lib.rs index 5e77f34a..9dc79112 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Rust Libmagic - A pure-Rust implementation of libmagic //! //! This library provides safe, efficient file type identification through magic rule evaluation. diff --git a/src/main.rs b/src/main.rs index ed10a902..05cd86bb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Command-line interface for libmagic-rs //! //! This binary provides a CLI tool for file type identification using magic rules, diff --git a/src/mime.rs b/src/mime.rs index 58f1f77b..70a4e299 100644 --- a/src/mime.rs +++ b/src/mime.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! MIME type mapping for file type detection //! //! This module provides MIME type mapping from file type descriptions diff --git a/src/output/json.rs b/src/output/json.rs index faeb20d9..1976137f 100644 --- a/src/output/json.rs +++ b/src/output/json.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! JSON output formatting for magic rule evaluation results //! //! This module provides JSON-specific data structures and formatting functions diff --git a/src/output/mod.rs b/src/output/mod.rs index 161a0612..abb5392e 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Output formatting module for magic rule evaluation results //! //! This module provides data structures and functionality for storing and formatting diff --git a/src/output/text.rs b/src/output/text.rs index 1cadd8be..2663ce74 100644 --- a/src/output/text.rs +++ b/src/output/text.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Text output formatting for magic rule evaluation results //! //! This module provides functionality to format evaluation results in a human-readable diff --git a/src/parser/ast.rs b/src/parser/ast.rs index 61c50b58..9036e606 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Abstract Syntax Tree definitions for magic rules //! //! This module contains the core data structures that represent parsed magic rules diff --git a/src/parser/format.rs b/src/parser/format.rs index a813d946..91ceb8f5 100644 --- a/src/parser/format.rs +++ b/src/parser/format.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Format detection for magic files. //! //! Detects whether a path points to a text magic file, a directory of magic files, diff --git a/src/parser/grammar.rs b/src/parser/grammar.rs index 0f701f9a..69c7567c 100644 --- a/src/parser/grammar.rs +++ b/src/parser/grammar.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Grammar parsing for magic files using nom parser combinators //! //! This module implements the parsing logic for magic file syntax, converting diff --git a/src/parser/hierarchy.rs b/src/parser/hierarchy.rs index 4a9496dd..1db5db5b 100644 --- a/src/parser/hierarchy.rs +++ b/src/parser/hierarchy.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Rule hierarchy building for magic file parsing. //! //! Constructs parent-child relationships between magic rules based on diff --git a/src/parser/loader.rs b/src/parser/loader.rs index b9d49ac9..c62f575a 100644 --- a/src/parser/loader.rs +++ b/src/parser/loader.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! File and directory loading for magic files. //! //! Provides functions for loading magic rules from individual files and diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 5ecf3994..fdc9bfc2 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Magic file parser module //! //! This module handles parsing of magic files into an Abstract Syntax Tree (AST) diff --git a/src/parser/preprocessing.rs b/src/parser/preprocessing.rs index 1f9e1004..ef081586 100644 --- a/src/parser/preprocessing.rs +++ b/src/parser/preprocessing.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Line preprocessing for magic file parsing. //! //! Handles comment removal, empty line filtering, line continuations, diff --git a/src/tags.rs b/src/tags.rs index 7af189df..c09d58f9 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Tag extraction for file type classification //! //! This module extracts classification tags from file type descriptions diff --git a/tests/cli_integration_tests.rs b/tests/cli_integration_tests.rs index ee4c7fa6..94058546 100644 --- a/tests/cli_integration_tests.rs +++ b/tests/cli_integration_tests.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! CLI integration tests for libmagic-rs using canonical libmagic test suite //! //! These tests verify the command-line interface functionality by running against diff --git a/tests/cli_normalization.rs b/tests/cli_normalization.rs index 5241dfe0..49ac7732 100644 --- a/tests/cli_normalization.rs +++ b/tests/cli_normalization.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Tests for CLI output normalization functionality //! //! These tests ensure that the cross-platform normalization helpers work correctly diff --git a/tests/common/mod.rs b/tests/common/mod.rs index abcb46d9..071819ee 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Common test utilities for cross-platform compatibility //! //! This module provides helpers for normalizing test outputs to ensure diff --git a/tests/compatibility_tests.rs b/tests/compatibility_tests.rs index 09930dee..a26a0c5e 100644 --- a/tests/compatibility_tests.rs +++ b/tests/compatibility_tests.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Compatibility tests for libmagic-rs //! //! These tests ensure that our implementation produces identical results to the original libmagic. diff --git a/tests/directory_loading_tests.rs b/tests/directory_loading_tests.rs index 2ab5ec95..57afbe7f 100644 --- a/tests/directory_loading_tests.rs +++ b/tests/directory_loading_tests.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Integration tests for directory loading functionality. //! //! These tests validate the `load_magic_directory()` function's behavior diff --git a/tests/evaluator_tests.rs b/tests/evaluator_tests.rs index bba2f187..3e2e2fbb 100644 --- a/tests/evaluator_tests.rs +++ b/tests/evaluator_tests.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Evaluator integration tests //! //! Tests for confidence calculation, rule ordering, and evaluation behavior diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index dd08776b..b318a08a 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! End-to-end integration tests for core flows //! //! Tests the complete workflow: load database, evaluate files/buffers, diff --git a/tests/json_integration_test.rs b/tests/json_integration_test.rs index 2f7d30fc..958a9f6a 100644 --- a/tests/json_integration_test.rs +++ b/tests/json_integration_test.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Integration tests for JSON output functionality //! //! These tests verify that the CLI correctly integrates the JSON output formatter diff --git a/tests/mime_tests.rs b/tests/mime_tests.rs index c917be04..160aaab9 100644 --- a/tests/mime_tests.rs +++ b/tests/mime_tests.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! MIME type mapping integration tests //! //! Tests for MIME type detection through the public API, diff --git a/tests/parser_integration_tests.rs b/tests/parser_integration_tests.rs index caef3686..4bf81f6f 100644 --- a/tests/parser_integration_tests.rs +++ b/tests/parser_integration_tests.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! End-to-end integration tests for magic file parser and database integration. //! //! These tests validate the complete flow from file/directory loading through diff --git a/tests/property_tests.rs b/tests/property_tests.rs index 4e486844..1c0e9819 100644 --- a/tests/property_tests.rs +++ b/tests/property_tests.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Property-based tests for libmagic-rs //! //! Uses proptest to verify properties that should hold for all valid inputs: diff --git a/tests/tags_tests.rs b/tests/tags_tests.rs index 83c7ce51..05e0ac49 100644 --- a/tests/tags_tests.rs +++ b/tests/tags_tests.rs @@ -1,3 +1,6 @@ +// Copyright (c) 2025-2026 the libmagic-rs contributors +// SPDX-License-Identifier: Apache-2.0 + //! Tag extraction integration tests //! //! Tests for keyword extraction, case insensitivity, rule path tags,