Verifying a DBOM Document

Pick a sample document, watch it pass or fail, then run the same check in your own stack.

Sample documents

Three files to try immediately. Select one to see the document and its expected output.

valid.dbom.json
input
{
  "schema_version": "0.1",
  "id": "dbom-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "created_at": "2024-06-01T12:00:00Z",
  "source": {
    "uri": "s3://acme-data/exports/transactions-2024-06.parquet",
    "hash": {
      "algorithm": "sha256",
      "value": "3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c"
    },
    "format": "parquet"
  },
  "signature": {
    "algorithm": "sha256",
    "value": "4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d",
    "signer": "github:acme-data-platform"
  },
  "lineage": [
    {
      "step": 1,
      "description": "Raw export from payments database",
      "tool": "acme-exporter/1.4.2",
      "input_hash": "n/a",
      "output_hash": "1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b"
    },
    {
      "step": 2,
      "description": "PII redaction",
      "tool": "presidio/2.2.354",
      "input_hash": "1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b",
      "output_hash": "3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c"
    }
  ]
}
terminal output ✓ PASSED
Verifying: valid.dbom.json

[1/2] Structural
  ✓ conforms to schema

[2/2] Provenance
  ✓ step 1 input_hash is n/a
  ✓ step 2 input_hash matches step 1 output_hash
  ✓ final step output_hash matches source.hash.value

✓ PASSED
invalid-structural.dbom.json
input
{
  "schema_version": "0.2",
  "id": "dbom-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "created_at": "2024-06-01T12:00:00Z",
  "source": {
    "uri": "s3://acme-data/exports/transactions-2024-06.parquet",
    "hash": {
      "algorithm": "md5",
      "value": "3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c"
    },
    "format": "parquet"
  },
  "signature": {
    "algorithm": "sha256",
    "value": "4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d",
    "signer": "github:acme-data-platform"
  },
  "lineage": [
    {
      "step": 1,
      "description": "Raw export from payments database",
      "tool": "acme-exporter/1.4.2",
      "input_hash": "n/a",
      "output_hash": "3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c"
    }
  ]
}
deliberate errors
✗ schema_version "0.2" — must be "0.1"
✗ source.hash.algorithm "md5" — only "sha256" is allowed
terminal output ✗ FAILED
Verifying: invalid-structural.dbom.json

[1/2] Structural
  ✗ [root] Expected '0.1'
  ✗ [source → hash → algorithm] 'md5' is not one of ['sha256']

[2/2] Provenance
  ✓ hash chain is consistent

✗ FAILED (2 error(s))
invalid-provenance.dbom.json
input
{
  "schema_version": "0.1",
  "id": "dbom-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "created_at": "2024-06-01T12:00:00Z",
  "source": {
    "uri": "s3://acme-data/exports/transactions-2024-06.parquet",
    "hash": {
      "algorithm": "sha256",
      "value": "3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c"
    },
    "format": "parquet"
  },
  "signature": {
    "algorithm": "sha256",
    "value": "4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d",
    "signer": "github:acme-data-platform"
  },
  "lineage": [
    {
      "step": 1,
      "description": "Raw export from payments database",
      "tool": "acme-exporter/1.4.2",
      "input_hash": "n/a",
      "output_hash": "1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b"
    },
    {
      "step": 2,
      "description": "PII redaction",
      "tool": "presidio/2.2.354",
      "input_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "output_hash": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
    }
  ]
}
deliberate errors
✗ step 2 input_hash (aaa…) ≠ step 1 output_hash (1a2b…)
✗ final output_hash (bbb…) ≠ source.hash.value (3b4c…)
terminal output ✗ FAILED
Verifying: invalid-provenance.dbom.json

[1/2] Structural
  ✓ conforms to schema

[2/2] Provenance
  ✗ step 2 input_hash does not match step 1 output_hash
  ✗ final step output_hash does not match source.hash.value

✗ FAILED (2 error(s))

How verification works

Three layers, always run in order. Failing layer 1 doesn't skip layer 2 — you get all errors at once.

Layer 1
Available

Structural

Fetch the schema from usemakoto.dev/schema/v0.1.json and validate against it. Catches wrong types, unsupported enum values, and missing fields.

Layer 2
Available

Provenance

Walk the lineage array and verify each step's input_hash chains to the previous output_hash. Final hash must match source.hash.value.

Layer 3
Coming soon

Cryptographic

Verify signature.value against the public key resolved from signature.signer. Signing spec not yet published.

Provenance rules
lineage[0].input_hash must be "n/a"
Each step's input_hash must equal the previous step's output_hash
The final step's output_hash must equal source.hash.value

Run it

Install, download the script, run it. Full source on each language page.

BASH curl + jsonschema CLI
Full script →
pip install jsonschema[format-nongpl]
./verify-dbom.sh valid.dbom.json
# ✓ PASSED
PYTHON jsonschema + requests
Full script →
pip install jsonschema[format-nongpl] requests
python verify_dbom.py valid.dbom.json
# ✓ PASSED
JS Node.js · ajv
Full script →
npm install ajv ajv-formats
node verify-dbom.mjs valid.dbom.json
# ✓ PASSED
GO gojsonschema
Full script →
go get github.com/xeipuuv/gojsonschema
go run verify_dbom.go valid.dbom.json
# ✓ PASSED

All implementations exit 0 on pass, 1 on any failure — safe to use in CI.

Render-Safe Verification

For text-like artifacts (code, configuration, prompts, scripts), digest validation and signature validation may both succeed while the artifact still fails trust policy due to misleading or non-obvious rendering characteristics. A signed, hash-valid artifact is not necessarily safe to trust at face value.

Trusted bytes are not the same thing as trusted appearance.

In March 2026, the Glassworm campaign used invisible Unicode variation selectors to hide malicious payloads in code that passed every code review and signature check.

Recommended verification order

  1. Fetch the artifact
  2. Verify provenance and signature (L2+)
  3. Verify digest matches subject.digest
  4. Run rendering/visibility analysis
  5. Apply policy (pass / warn / fail)
  6. Trust or reject

Optional analysis.rendering extension

Makoto defines an optional, non-breaking extension for text-like artifacts. Producers may emit this block and verifiers may policy-check it. Makoto strongly recommends it for any artifact where render-review trust matters.

analysis.rendering schema
{
  "analysis": {
    "rendering": {
      "scanner": {
        "name": "makoto-text-visibility",
        "version": "0.1.0"
      },
      "normalization": "NFC",
      "contains_invisible_codepoints": true,
      "families": ["variation_selectors"],
      "ranges": ["U+FE00-U+FE0F", "U+E0100-U+E01EF"],
      "counts": {
        "total_codepoints": 412,
        "invisible_codepoints": 91
      },
      "patterns": [
        {
          "id": "decode-then-execute",
          "severity": "high",
          "matched": false
        }
      ],
      "verdict": "fail"
    }
  }
}

Terminology note

The specific character ranges discussed in the March 2026 attack are Unicode variation selectors: U+FE00–U+FE0F (Variation Selectors) and U+E0100–U+E01EF (Variation Selectors Supplement). Documentation may use plain-English language like “invisible Unicode” for accessibility, but these exact ranges should not be mislabeled as private-use characters.

See also: Invisible Unicode example · Demo 06 · D9: Display-Layer Obfuscation