Skip to content

Commit 8c54001

Browse files
committed
fix: reject trailing item template json
1 parent cac8afb commit 8c54001

3 files changed

Lines changed: 17 additions & 6 deletions

File tree

internal/itemstore/file_store.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,10 @@ func decodeSnapshotStrict(raw []byte, snapshot *Snapshot) error {
9393
if err := decoder.Decode(snapshot); err != nil {
9494
return err
9595
}
96-
if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) {
96+
if err := decoder.Decode(&struct{}{}); err != io.EOF {
97+
if err == nil {
98+
return errors.New("unexpected trailing JSON value")
99+
}
97100
return err
98101
}
99102
return nil

internal/itemstore/file_store_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ func TestFileStoreLoadRejectsMalformedOrInvalidSnapshot(t *testing.T) {
9292
t.Fatalf("expected ErrInvalidSnapshot for unknown item-template field, got %v", err)
9393
}
9494

95+
trailingJSON := []byte("{\"templates\":[{\"vnum\":27001,\"name\":\"Small Red Potion\",\"stackable\":true,\"max_count\":200}]}{}")
96+
if err := os.WriteFile(path, trailingJSON, 0o644); err != nil {
97+
t.Fatalf("write trailing-json snapshot: %v", err)
98+
}
99+
if _, err := store.Load(); !errors.Is(err, ErrInvalidSnapshot) {
100+
t.Fatalf("expected ErrInvalidSnapshot for trailing item-template JSON, got %v", err)
101+
}
102+
95103
zeroVnum := Snapshot{Templates: []Template{{Vnum: 0, Name: "Broken", Stackable: true, MaxCount: 1}}}
96104
if err := store.Save(zeroVnum); !errors.Is(err, ErrInvalidSnapshot) {
97105
t.Fatalf("expected ErrInvalidSnapshot for zero vnum, got %v", err)

spec/protocol/item-template-store-bootstrap.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ Each template must pass the current `internal/itemstore` validation before the r
3939

4040
The store normalizes and persists deterministic JSON: template names and effect messages are trimmed, equipment slot names are normalized, and templates are sorted by `vnum`.
4141

42-
## Unknown-field hardening
42+
## Strict JSON hardening
4343

44-
The file-backed loader now rejects unknown JSON fields instead of silently accepting them.
44+
The file-backed loader now rejects unknown JSON fields and trailing JSON values instead of silently accepting them.
4545

46-
This is a fail-closed authoring guard: if a snapshot contains unowned metadata such as a future effect field, the runtime must reject the snapshot rather than booting while ignoring that metadata. This keeps item behavior template-backed only for fields the repository currently owns and tests.
46+
This is a fail-closed authoring guard: if a snapshot contains unowned metadata such as a future effect field, or multiple concatenated top-level JSON values, the runtime must reject the snapshot rather than booting while ignoring or only partially reading that metadata. This keeps item behavior template-backed only for fields the repository currently owns and tests.
4747

4848
## Bootstrap fallback
4949

@@ -55,11 +55,11 @@ If the default item-template file is missing, the minimal runtime still uses the
5555

5656
Missing-file fallback is a bootstrap compatibility aid, not the final production item-data model.
5757

58-
Malformed snapshots, invalid templates, duplicate `vnum` entries, and snapshots with unknown JSON fields are fatal for runtime construction.
58+
Malformed snapshots, invalid templates, duplicate `vnum` entries, snapshots with unknown JSON fields, and snapshots with trailing JSON values are fatal for runtime construction.
5959

6060
## Tests
6161

6262
Current coverage:
6363

64-
- `internal/itemstore` freezes deterministic save/load behavior, validation failures, anti-flag metadata round trips, use/equip effect metadata, and unknown-field rejection on load.
64+
- `internal/itemstore` freezes deterministic save/load behavior, validation failures, anti-flag metadata round trips, use/equip effect metadata, and strict load rejection for unknown fields or trailing JSON values.
6565
- Runtime item-use, equip, merchant, drop/pickup, and drag-to-item stack slices resolve only through loaded template metadata or the deterministic missing-file fallback described above.

0 commit comments

Comments
 (0)