semseal
Semantic fingerprints and drift detection for configuration files.
Semseal compares configuration by meaning, not by bytes. Key order, whitespace, comments, YAML-versus-JSON syntax, and equivalent numeric spellings do not create false alarms. Rules can ignore volatile fields, mask secret values while still checking that they exist, and treat selected arrays as unordered multisets.
It is useful in deployment pipelines, Portainer stacks, infrastructure repositories, release archives, audits, migration validation, and any workflow where a plain checksum is too fragile and a schema-specific tool is too narrow.
Highlights
Заголовок раздела «Highlights»- JSON, YAML, and dotenv support.
- Cross-format comparison: JSON and YAML can be semantically equivalent.
- Stable SHA-256 fingerprints independent of formatting and object key order.
- Exact, JSON-Pointer-based diffs.
*and**path globs.- Ignore rules for generated values.
- Mask rules for secrets: presence matters, contents do not.
- Unordered-array rules with duplicate-sensitive multiset semantics.
- Reproducible directory baselines and drift verification.
- Safe YAML loading; arbitrary object deserialization and aliases are disabled.
- No runtime gems: only the Ruby standard library.
- Exit codes designed for CI:
0match,1drift,2input/tool error.
Installation
Заголовок раздела «Installation»Ruby 3.1 or newer is required.
From a cloned repository:
gem build semseal.gemspecgem install ./semseal-1.0.0.gemFor local development without installation:
ruby -Ilib bin/semseal helpQuick start
Заголовок раздела «Quick start»Compare a JSON file with a YAML file:
semseal compare config.json config.yamlOutput when they mean the same thing:
equivalentsha256: 8be5d4…Print a stable semantic fingerprint:
semseal hash production.yamlInspect exactly what Semseal hashes:
semseal explain production.yamlCreate and verify a baseline for a configuration tree:
semseal baseline ./deploysemseal verify ./deployThe baseline command writes ./deploy/.semseal.json atomically. The manifest
contains semantic digests and the exact rules and file-selection settings used
to create them.
Rules use JSON Pointer paths. /database/password addresses a mapping key;
/services/0/name addresses an array element. Escape / as ~1 and ~ as
~0 inside a path segment.
Wildcards:
*matches exactly one path segment.**matches zero or more path segments.
Example semseal.yml:
ignore: - /metadata/generated_at - /services/*/runtime/pid
mask: - /database/password - /services/*/token
set_arrays: - /features - /services/*/allowed_hostsUse it with any file command:
semseal compare --rules semseal.yml old.yaml new.jsonsemseal hash --rules semseal.yml deploy.yamlsemseal baseline --rules semseal.yml ./deployRules can also be added directly:
semseal compare \ --ignore '/metadata/generated_at' \ --mask '/database/password' \ --set-array '/features' \ a.yaml b.yamlIgnore versus mask
Заголовок раздела «Ignore versus mask»ignore removes the path from the semantic document. A missing ignored path and
an existing ignored path are equivalent.
mask replaces the value with a stable marker. Therefore the path must exist on
both sides, but its contents may differ. This is ideal for passwords, tokens,
certificates, and generated secret material.
Unordered arrays
Заголовок раздела «Unordered arrays»An array selected by set_arrays is sorted by each element’s canonical form.
Order is ignored but duplicates are preserved. Thus ["a", "a", "b"] is not
equivalent to ["a", "b", "b"].
Commands
Заголовок раздела «Commands»compare
Заголовок раздела «compare»semseal compare [options] LEFT RIGHTImportant options:
--rules FILE--ignore POINTER_GLOB--mask POINTER_GLOB--set-array POINTER_GLOB--left-format json|yaml|env--right-format json|yaml|env--max-differences N--jsonText output identifies added (+), removed (-), changed (~), and type-changed
(!) paths. JSON output is stable and suitable for automation.
semseal hash [options] FILEPrints the SHA-256 digest of a collision-safe, type-tagged canonical semantic encoding.
explain
Заголовок раздела «explain»semseal explain [options] FILEPrints a human-readable canonical JSON view. This makes every normalization decision inspectable; hashing itself uses an unambiguous type-tagged encoding.
baseline
Заголовок раздела «baseline»semseal baseline [options] DIRECTORYScans .json, .yaml, .yml, .env, and .env.* files. Symlinks and VCS
metadata directories are skipped. Useful options:
-o, --output FILE--include GLOB--exclude GLOB--jsonWhen at least one --include is supplied, only matching relative paths are
sealed. Exclusions are applied afterward and are stored in the manifest.
semseal verify [options] DIRECTORYReports missing, extra, and semantically changed configuration files. It always uses the rules and selection patterns embedded in the manifest.
Dotenv behavior
Заголовок раздела «Dotenv behavior»Semseal parses ordinary KEY=VALUE files, including export, blank values,
single- and double-quoted values, multiline quoted values, escaped characters,
inline comments, and backslash continuations. Values remain strings and variable
references are not expanded, so fingerprints do not depend on the caller’s
environment.
Library API
Заголовок раздела «Library API»require "semseal"
rules = Semseal::Rules.new( ignore: ["/metadata/generated_at"], mask: ["/database/password"], set_arrays: ["/features"])
engine = Semseal::Engine.new(rules: rules)comparison = engine.compare("config.json", "config.yaml")
puts comparison.equalputs comparison.left.digestcomparison.differences.each do |difference| puts "#{difference.kind}: #{Semseal::Path.pointer(difference.path)}"endDirectory API:
manifest = Semseal::Manifest.build("./deploy", rules: rules)manifest.write("./deploy/.semseal.json")
result = Semseal::Manifest.load("./deploy/.semseal.json") .verify("./deploy", manifest_path: "./deploy/.semseal.json")
abort "configuration drift" unless result.okSecurity model
Заголовок раздела «Security model»Semseal never executes configuration content. YAML is loaded with arbitrary object classes, symbols, and aliases disabled. Standard YAML date/time scalars are accepted and normalized to ISO 8601 strings. Directory scans skip symlinks, preventing traversal outside the selected root. Masked secrets are replaced before canonical output, hashing, diffs, and manifests are generated.
A semantic digest proves equality under the selected rules; it is not a digital signature. Sign or commit the generated manifest when authenticity is required.
Development
Заголовок раздела «Development»ruby -Ilib -Itest -e 'Dir["test/**/*_test.rb"].sort.each { |f| require_relative f }'# or, with rake available:rake testBuild the gem:
gem build semseal.gemspecLicense
Заголовок раздела «License»MIT © 2026 localzet
Исходный код · MIT