Skip to content

BUG: Schema validator rejects every NIP-92-conformant imeta tag — including the canonical example from the spec #30

@davotoula

Description

@davotoula

The validator wired up in schema_validation.go (loading nostr-protocol/registry-of-kinds/schema.yaml) rejects every imeta tag that conforms to https://github.com/nostr-protocol/nips/blob/master/92.md, with:

blocked: schema validation failed: tag[N][M]: invalid tag 'imeta', missing index M

M always equals len(imeta_tag). The validator demands one more entry than the tag has, so any imeta of any length fails — including the canonical example from NIP-92 itself.


What NIP-92 says

▎ The imeta tag is variadic, and each entry is a space-delimited key/value pair.
▎ Each imeta tag MUST have a url, and at least one other field. imeta MAY include any field specified by https://github.com/nostr-protocol/nips/blob/master/94.md. There SHOULD be only one imeta tag per URL.

The spec rules per entry:

  1. Every entry past index 0 ("imeta") is a single string of the form "key SPACE value".
  2. The tag is variadic — any number of entries ≥ 2 (i.e. url + ≥ 1 other) is valid. The spec defines no upper bound and no per-position requirement beyond "must include url."

The canonical NIP-92 example (from nips/92.md):

[                                                                                                                                                                                                                             
   "imeta",                              
   "url https://nostr.build/i/my-image.jpg",                                                                                                                                                                                 
   "m image/jpeg",                                                                                                                                                                                                             
   "blurhash eVF$^OI:${M{o#*0-nNFxakD-?xVM}WEWB%iNKxvR-oetmo#R-aen$",                                                                                                                                                        
   "dim 3024x4032",                                                                                                                                                                                                            
   "alt A scenic photo overlooking the coast of Costa Rica",                                                                                                                                                                 
   "x <sha256 hash as specified in NIP 94>",                                                                                                                                                                                   
   "fallback https://nostrcheck.me/alt1.jpg",                                                                                                                                                                                  
   "fallback https://void.cat/alt1.jpg" 
 ]        

This is 9 elements (indices 0–8). When sent to wss://pyramid.fiatjaf.com/, the validator returns tag[N][9]: invalid tag 'imeta', missing index 9. The spec's own example does not pass.


Live repro from a real Amethyst publish

The client emitted (8 elements, indices 0–7, every entry well-formed under NIP-92):

[                                     
   "imeta",                                                                                                                                                                                                                    
   "url https://npub175fgy25f6gmf5wrtl6c7dp7v6fkwk6an8eemnpqhfxdmjp2tlu0snrfm3q.blossom.band/a27f4c02678fe5390e34580fa1b74c9e44ef161c62819bc798331599f2846d47.jpg",
   "x a27f4c02678fe5390e34580fa1b74c9e44ef161c62819bc798331599f2846d47",                                                                                                                                                       
   "size 168257",                                                                                                                                                                                                              
   "m image/jpeg",                                                                                                                                                                                                             
   "dim 1068x1901",                                                                                                                                                                                                            
   "blurhash ]LI#u#$*~Wj[MyTft7M|WYWByXM{D*%LxuK7t7oJIpj]TLf6RjWVoyJXNGxZt7R+T1s:jaRkbHTKR*t7oeo0pJoff*R+Rj",                                                                                                                  
   "ox a27f4c02678fe5390e34580fa1b74c9e44ef161c62819bc798331599f2846d47"                                                                                                                                                       
 ]                   

url ✅ present (NIP-92 MUST). At least one additional NIP-94 field ✅ (x, size, m, dim, blurhash, ox). Each entry is a space-delimited key/value pair ✅. No leading/trailing whitespace ✅.

Pyramid response:

blocked: schema validation failed: tag[2][8]: invalid tag 'imeta', missing index 8

When alt text was added (9 elements), the failing index moved to [9]. There is no length at which the validator accepts the tag.


Root cause

The schema:

# nostr-protocol/registry-of-kinds/schema.yaml
_imetatag: &imetatag                    
  name: imeta                                                                                                                                                                                                                 
  next:                                                                                                                                                                                                                     
    type: imeta                                                                                                                                                                                                               
    required: true                                                                                                                                                                                                            
    variadic: true  

…combined with validateNext in the schema package:

func (v *Validator) validateNext(tag nostr.Tag, index int, this *ContentSpec) (failedIndex int, err error) {                                                                                                                  
    if len(tag) <= index {                                                                                                                                                                                                    
        if this.Required {                                                                                                                                                                                                  
            return index, fmt.Errorf("invalid tag '%s', missing index %d", tag[0], index)                                                                                                                                     
        }              
        return -1, nil                                                                                                                                                                                                        
    }                                                                                                                                                                                                                         
    // … isTrimmed + type-validator pass …
    if this.Variadic {                                                                                                                                                                                                        
        if len(tag) >= index {                      // ← off-by-one
            return v.validateNext(tag, index+1, this)                                                                                                                                                                         
        }                                                                                                                                                                                                                   
    }                                  
    // …                                                                                                                                                                                                                    
}

Trace for any well-formed imeta (length L, indices 0..L-1), entering at index=1 with {Required: true, Variadic: true}:

  • index=1..L-1: each value passes the type validator, then hits the Variadic branch. len(tag) >= index is true, recurses with index+1.
  • index=L: len(tag) <= index (L <= L) is true. Required: true ⇒ returns "missing index L".

So the validator's recursion always overshoots by one when Variadic && Required. NIP-92 places no lower bound on the variadic tail length — it only mandates url + at least one other field, both of which are validated
before the recursion ever reaches the overshoot.


Why this is a spec violation

NIP-92 says the tag is variadic. A variadic field, by definition, accepts any non-negative count past its required minimum. The pyramid validator instead behaves as if every position past the start were individually
required — which contradicts the variadic semantics and is unsatisfiable (any extra entry would just push M higher).

The validator effectively makes NIP-92's example unpublishable on this relay, which is a regression that would propagate to every client author that targets the spec example as a conformance test.


Affected clients

Reproduced on Amethyst (Android, latest debug build). Because the failing path is "any well-formed NIP-92 tag," every Nostr client that publishes image notes — Damus, Snort, Iris, Coracle, primal, gossip, Amethyst, etc. —
will be blocked at pyramid for this reason.

Environment


Sources used in this draft:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions