# stale-nft-metadata

> Policy-selected current NFT metadata retains an old auxiliary payload across same-history burn/remint.

Canonical: https://nightshade.dicksonwu.xyz/rules/stale-nft-metadata.html
Release: Nightshade 0.1.1 (v0.1.1)

| Field | Value |
| --- | --- |
| Rule ID | `stale-nft-metadata` |
| Severity | low |
| Confidence | varies |
| Maturity | experimental |
| Selection | Opt-in + policy |
| Policy intent | required |
| Default enabled | no |
| Category | Policy-dependent rules |
| Engine facts used | module-identities, collection-observations, successful-histories, current-incarnation-policy |
| Release | v0.1.1 |

## Selection and scope

Not selected by default. Select it with `--detectors` or the `detectors` list in nightshade.json, and declare the matching application intent in `policies`. Selecting it alone does not supply that intent.

```sh
./.nightshade/node_modules/.bin/nightshade ./contracts --detectors stale-nft-metadata
./.nightshade/node_modules/.bin/nightshade detector show stale-nft-metadata
```

The first command runs only this rule; `--detectors` takes a comma-separated list. The second prints the rule's metadata from your installed version.

Severity is review priority. It does not express exploitability or impact. Confidence is detector metadata, not a probability. All built-in rules are experimental.

Compiler scope: the engine supports the pinned profiles 0.28.0, 0.29.0, 0.30.0, 0.31.1, 0.33.0-rc.2, 0.34.0, 0.35.0-rc.1 and 0.35.0, with ZKIR2 by default. Support for this rule can be narrower; the rule documentation below states any narrower scope.

## Corpus checks

Synthetic corpus checks for this rule in the 0.1.1 proof run. A check counts only with compiler acceptance and complete analysis.

| Positive detected | Corrected quiet | Lookalike quiet | Other negative quiet | Incomplete |
| ---: | ---: | ---: | ---: | ---: |
| 2/2 | 2/2 | 2/2 | 0/0 | 0 |

No deviations were recorded for this rule.

These are behavior checks on synthetic fixtures, not a precision or recall estimate. See [evaluation](https://nightshade.dicksonwu.xyz/evaluation.md#synthetic-corpus).

## Rule documentation

From https://github.com/DicksonWu654/nightshade/blob/v0.1.1/docs/detectors/stale-nft-metadata.md at v0.1.1.

Availability: opt-in with an explicit `currentNftMetadata` policy; low severity,
high confidence in the recorded preservation. Reports a separate application
metadata cache that returns the exact payload stored before an authenticated
same-ID NFT burn/remint on one complete modeled successful history.

### Intent and example

Select this rule only when the cache serves the **current incarnation** of a
token. Intentional archives must remain unconfigured. Names, URI contents and
library imports do not establish this intent.

```json
{
  "detectors": ["stale-nft-metadata"],
  "policies": {
    "currentNftMetadata": [
      { "owners": "_owners", "metadata": "cachedURI", "consumer": "replaceToken" }
    ]
  }
}
```

Selectors are unambiguous canonical declaration names or full symbol IDs. The
ownership selector must identify the authenticated NFT instance's `_owners`.
The separate cache must be a root `Map<Uint<128>, Opaque<"string">>` and the
consumer an exported circuit returning `Opaque<"string">`.

```compact
NFT__mint(owner, tokenId);
cachedURI.insert(disclose(tokenId), disclose(oldURI));
NFT__burn(tokenId);
NFT__mint(owner, tokenId);
return cachedURI.lookup(disclose(tokenId));
```

Clear the auxiliary cache during burn, or supply the new incarnation's metadata
before returning it. The pinned NFT already clears its own `_tokenURIs` on burn;
this rule concerns the independently declared application cache.

### Evidence boundary

This rule uses exact NonFungibleToken and Utils
source bytes from compact-contracts revision
`ec1331cd8195694c747d291e8fc8ab6d2914a976`, qualified for
`compact-0.34.0-zkir2`. NFT SHA-256:
`fe4d2b77a34d3f05a3b351d590ad55b2af5ca903c765bcd6946dceedbeecaed1`;
Utils SHA-256:
`215d6374bb22a7c950bb43d046f6380f2c432fb841a2fcead2a35766d159f314`.
Modified providers and namesakes do not acquire the library's lifecycle identity.
A configured relation against either reports a rule-scoped
`nft-metadata-provider-unavailable` diagnostic and no NFT finding. An absent
policy remains not applicable. Untrusted dependency bytes receive the same
incomplete result; provider absence cannot certify a declared relation.

Ordered collection operations and actual writes establish a cache insertion,
canonical burn's zero-owner write, later canonical mint's owner write and final
lookup, all for the same immutable token key and NFT instance. Returned scalar
identity must equal the old inserted payload. Any later potentially overlapping
cache mutation withholds preservation; any potentially overlapping owner write
after remint also withholds it. Different dynamic keys can alias; distinct literal
keys can be proved disjoint. Unknown keys never establish equality.

Observation ordering has a deliberately narrow source boundary: the consumer's
root block contains only expression/binding statements followed by a direct live
cache lookup return. Its key is an effect-free reference, literal or default,
optionally wrapped in casts or `disclose`. Saved reads, helper returns, effectful
lookup keys and root control flow cannot establish this ordering. Unsupported
terminal shapes, unknown relevant keys/values, custom ownership updates,
transaction/context effects, unavailable exact observations, unqualified profiles
and rule allowance exhaustion produce scoped incomplete diagnostics. Baseline and
unrelated rule coverage remain independent.
The observation helper marks its overall result incomplete when it emits any
diagnostic, while retaining supported same-history observations even when another
history for the same ownership/cache/consumer relation is unavailable. Diagnostics
identify that relation in their message and are deduplicated by relation and code.
Allowance exhaustion invalidates the entire bounded pass.

The experimental Counter-query schema 1.2 does not run this isolated observation
pass. An applicable configured NFT consumer therefore reports unavailable
collection observations there; use the normal scanner for this rule.

### Remaining obligations

## Suppress a match

If a match is intentional, suppress it on the line before with the exact rule ID and a reason of at least eight characters:

```compact
// nightshade-disable-next-line stale-nft-metadata -- Reason this match is intentional
```

Diagnostics cannot be suppressed, and an unmatched directive for a selected rule makes analysis incomplete. See [configuration](https://nightshade.dicksonwu.xyz/configuration.md#suppress-an-intentional-match) for fingerprint suppressions and baselines.

## References

- [Rule source document at v0.1.1](https://github.com/DicksonWu654/nightshade/blob/v0.1.1/docs/detectors/stale-nft-metadata.md)
- [Catalog entry in rules.json](https://nightshade.dicksonwu.xyz/rules.json)
- [All rules](https://nightshade.dicksonwu.xyz/rules.md#policy-dependent-rules)
- [Limitations](https://nightshade.dicksonwu.xyz/limitations.md)
