Skip to content

Use UUIDv5 instead of MD5 for Sigma rule UUIDs - #59

Merged
lenny-ts merged 3 commits into
lenny-ts:mainfrom
dchaudhari7177:sigma-uuidv5
Aug 30, 2026
Merged

Use UUIDv5 instead of MD5 for Sigma rule UUIDs#59
lenny-ts merged 3 commits into
lenny-ts:mainfrom
dchaudhari7177:sigma-uuidv5

Conversation

@dchaudhari7177

Copy link
Copy Markdown
Contributor

Closes #23

The old output was not a UUID

Worth stating, because it is a stronger reason than MD5 being deprecated. The MD5 path formatted a 16-byte digest as 8-4-4-4-12 — the right shape, but the version and variant bits were whatever the hash happened to produce. For one real rule title:

old (md5)   : c2d4c279-4f95-6de9-330b-2d11465619ca
                            ^                ^
                     version nibble 6   variant nibble 3
new (uuidv5): 639e02a2-80dc-5298-ba6b-d1dcf21a3eac
                            ^                ^
                     version 5          variant 8 (RFC 4122)

Version 6 with variant 3 is not a valid UUID under any RFC, so a Sigma consumer validating the id field would reject the rule. That is now fixed alongside the MD5 concern.

Namespace

const sigmaNamespace = "a64194cb-8d9e-5cf8-a2c3-a0ddce730456"

Derived exactly as the issue specifies — uuidV5(uuid.NameSpaceDNS, "caddy-analyzer") — then hardcoded, since it must never change. TestSigmaNamespaceIsDerivedFromTheProjectName recomputes the derivation and asserts it equals the constant, so the provenance is documented and checked rather than left in a comment.

No new dependency

github.com/google/uuid is not in go.mod, and the issue offered vendoring as the fallback. I implemented RFC 4122 §4.3 against stdlib crypto/sha1 instead — twelve lines:

h := sha1.New()
h.Write(namespace[:])
h.Write([]byte(name))
copy(u[:], h.Sum(nil))
u[6] = (u[6] & 0x0f) | 0x50 // version 5
u[8] = (u[8] & 0x3f) | 0x80 // RFC 4122 variant

Adding a module for that seemed the wrong trade in a project with six direct dependencies. The obvious risk is a hand-rolled UUID being subtly wrong, so it is checked against RFC 4122's own published test vector: uuidV5(NameSpaceDNS, "python.org") must equal 886313e1-3b8a-5372-9b90-0c9aee199e5d. It does.

SHA-1 here is a naming scheme rather than a security property — version 5 specifies it — and there is a comment saying so, so this does not read as swapping one broken hash for another.

Compatibility

Rules exported before this change carry different UUIDs. Unavoidable: the identifier is derived from the algorithm. Anyone with published rules regenerates once, and identifiers are stable from here on. Called out in the CHANGELOG under Unreleased.

Tests

cmd/export_sigma_uuid_test.go, eight cases: the RFC vector, the namespace derivation, the namespace parsing, a regex asserting version-5 and RFC-4122-variant nibbles across four titles (including an empty one and a non-ASCII one), determinism across calls, distinctness across titles, and parseUUID accepting the canonical form while rejecting five malformed inputs.

Verification

go build ./..., go vet ./... clean; go test ./... passes in every package. crypto/md5 is no longer imported anywhere under cmd/.

gofmt -l lists this file, but it lists untouched files such as cmd/root.go too — my checkout is CRLF. gofmt -d on an LF-normalised copy produces no diff.

Comment thread cmd/export_sigma.go

@lenny-ts lenny-ts left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since PRs #49#59 are all being merged together, please update the [Unreleased] section in CHANGELOG.md with the cumulative changes. Here's ready-to-paste content matching the project's changelog format:

## [Unreleased]

### Added
- **Operational tuning flags (#56)**: `--geo-cache-ttl` (default 24h), `--geo-cache-size` (default 50000), and `--iptables-timeout` (default 10s) replace compile-time constants, allowing runtime tuning without recompilation. Values can also be set in `caddy-analyzer.json` under the `tuning` key; CLI flags take precedence. — @dchaudhari7177

### Fixed
- **Active filters not shown in report header (#51)**: `--max-latency`, `--min-size`, `--max-size`, `--level`, and `--ops-only` were parsed but silently omitted from the "Filters:" line in report output. — @dchaudhari7177
- **`validateIP` / `validateIPOrCIDR` duplication (#49)**: unified into a single `validateIP` that accepts both bare IPs and CIDRs. — @dchaudhari7177

### Changed
- **Sigma rule UUIDs are now RFC 4122 version 5 (#59)**: `sigmaUUID` derived its identifier from MD5 and formatted the digest as `8-4-4-4-12`, which looks like a UUID but is not one — the version and variant bits were whatever the hash produced, so a consumer validating the `id` field would reject the rule. Identifiers are now `uuidV5(sigmaNamespace, "caddy-analyzer:sigma:" + title)`, matching Sigma's own convention. The namespace is derived once as `uuidV5(DNS, "caddy-analyzer")` and hardcoded as a constant, so identifiers stay stable across runs and releases. **Rules exported before this release carry different UUIDs**; regenerate them once. Implemented against `crypto/sha1` rather than adding a module dependency, and checked against RFC 4122's published test vector. — @dchaudhari7177
- **GitHub Actions SHA-pinned (#55)**: all third-party actions (`actions/checkout`, `actions/configure-pages`, `actions/upload-pages-artifact`, `actions/deploy-pages`, `goreleaser/goreleaser-action`, `softprops/action-gh-release`) pinned to full commit SHAs with version comments for supply-chain hardening. — @dchaudhari7177
- **`emitReport` extracted in `runFollowMode` (#53)**: reduced code duplication in the follow-mode interval logic. — @dchaudhari7177
- **`top` dimension help text updated (#54)**: `country` and `asn` added to the documented dimension list. — @dchaudhari7177
- **`tail --detect` example added to root help (#58)**. — @dchaudhari7177

### Docs
- **`parseTime` tests (#50)**: coverage for RFC3339, relative durations, empty input guard. — @dchaudhari7177
- **`formatDurationDelta` zero-case documented (#52)**: comment and tests explaining why `0` returns `"0ms"` instead of `"N/A"`. — @dchaudhari7177
- **Subcommands docs updated (#56)**: new tuning flags documented in the CLI reference table. — @dchaudhari7177
@lenny-ts

Copy link
Copy Markdown
Owner

The Flags Reference table in README.md is missing the 3 new tuning flags from your PR #56.

Please add them:

GeoIP flags

After the --ua-rotation row (or near the other GeoIP flags):

| --geo-cache-ttl | | 24h | How long a resolved GeoIP lookup stays cached. 0 disables caching (much slower on busy traffic). Overrides tuning.geo_cache_ttl in config |
| --geo-cache-size | | 50000 | Max cached GeoIP lookups. 0 disables caching. Raise on high-traffic servers; lower on low-memory boxes. Overrides tuning.geo_cache_size in config |

Guard flags

After the --cred-stuffing-limit row (or near the guard flags):

| --iptables-timeout | | 10s | Timeout for each iptables invocation (guard). Raise for huge rulesets on busy boxes. Overrides tuning.iptables_timeout in config |
@lenny-ts

Copy link
Copy Markdown
Owner

Hi @dchaudhari7177, I went ahead and pushed the review fixes directly since I want to include this PR in the next release. Thanks!

@lenny-ts
lenny-ts merged commit f4a4fbb into lenny-ts:main Aug 30, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants