Adding Mutation Strategies¶
Crucible's mutation engine is designed for extensibility. Each mutation strategy implements a simple interface and is registered in a category with a selection weight.
The Strategy Interface¶
Every strategy must:
Name()— Return a unique dot-separated name likemetadata.my_mutationMutate()— Modify thegguf.Filein place using the provided RNG for randomness
Step-by-Step: Adding a New Strategy¶
1. Choose a Category¶
| Category | Weight | When to use |
|---|---|---|
CategoryHeader | 10% | Targets header fields (magic, version, counts) |
CategoryMetadata | 35% | Targets metadata KV pairs |
CategoryTensorInfo | 35% | Targets tensor info blocks |
CategoryAlignment | 5% | Targets alignment/padding |
CategoryData | 5% | Targets tensor data section |
CategoryConsistency | 10% | Cross-field mismatches |
2. Implement the Strategy¶
Create your strategy in the appropriate file under pkg/mutator/:
pkg/mutator/metadata.go
// metadataUnicodeExploitMutator injects Unicode edge cases
// that may confuse string length calculations.
type metadataUnicodeExploitMutator struct{}
func (m *metadataUnicodeExploitMutator) Name() string {
return "metadata.unicode_exploit"
}
func (m *metadataUnicodeExploitMutator) Mutate(f *gguf.File, rng *rand.Rand) {
idx := randMetadataIdx(f, rng)
// Strings where byte length != character count
payloads := []string{
"\xc0\x80", // Overlong null
"\xed\xa0\x80\xed\xb0\x80", // Surrogate pair
"\xef\xbb\xbf" + "normal", // BOM prefix
}
f.Metadata[idx].Key = payloads[rng.IntN(len(payloads))]
}
3. Register the Strategy¶
Add it to the factory function in the same file:
pkg/mutator/metadata.go
func MetadataStrategies() []Strategy {
return []Strategy{
// ... existing strategies ...
&metadataUnicodeExploitMutator{}, // (1)!
}
}
- The mutator orchestrator automatically picks it up through
registerDefaults().
That's it — no other registration needed. The orchestrator discovers strategies through the factory functions.
4. Test It¶
pkg/mutator/mutator_test.go
func TestUnicodeExploit(t *testing.T) {
f := createTestFile()
s := &metadataUnicodeExploitMutator{}
rng := rand.New(rand.NewPCG(42, 42))
s.Mutate(f, rng)
// Verify mutation was applied
// (check that a key contains non-UTF8 bytes, etc.)
_, err := gguf.Marshal(f)
if err != nil {
t.Logf("marshal error (may be expected): %v", err)
}
}
Run tests:
Helper Functions¶
The mutator package provides helpers for common patterns:
| Helper | Purpose |
|---|---|
ensureMetadata(f, rng) | Add a dummy metadata entry if none exist |
ensureTensor(f, rng) | Add a dummy tensor if none exist |
randMetadataIdx(f, rng) | Get a random metadata index (creates one if empty) |
randTensorIdx(f, rng) | Get a random tensor index (creates one if empty) |
Design Principles¶
Mutation Design Tips
- Target real parser code paths: Study the target source (e.g.,
gguf.c) to understand what values trigger interesting behavior - Use edge values: 0, 1, MAX, off-by-one from boundaries
- Create mismatches: The worst bugs come from fields that disagree with each other
- Keep mutations small: 1-3 targeted changes per file find more bugs than total corruption
- Use the RNG: Always use the provided
rngparameter for deterministic reproduction