@marcuspuchalla/nachos 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/LICENSE +674 -0
  3. package/README.md +345 -0
  4. package/dist/chunk-2FUTHZQQ.cjs +755 -0
  5. package/dist/chunk-2FUTHZQQ.cjs.map +1 -0
  6. package/dist/chunk-2HBCILJS.cjs +2034 -0
  7. package/dist/chunk-2HBCILJS.cjs.map +1 -0
  8. package/dist/chunk-7CFYWHS6.js +742 -0
  9. package/dist/chunk-7CFYWHS6.js.map +1 -0
  10. package/dist/chunk-PD72MVTX.cjs +160 -0
  11. package/dist/chunk-PD72MVTX.cjs.map +1 -0
  12. package/dist/chunk-ZDZ2B5PE.js +149 -0
  13. package/dist/chunk-ZDZ2B5PE.js.map +1 -0
  14. package/dist/chunk-ZRPJUEIZ.js +2020 -0
  15. package/dist/chunk-ZRPJUEIZ.js.map +1 -0
  16. package/dist/encoder/index.cjs +57 -0
  17. package/dist/encoder/index.cjs.map +1 -0
  18. package/dist/encoder/index.d.cts +72 -0
  19. package/dist/encoder/index.d.ts +72 -0
  20. package/dist/encoder/index.js +4 -0
  21. package/dist/encoder/index.js.map +1 -0
  22. package/dist/index.cjs +606 -0
  23. package/dist/index.cjs.map +1 -0
  24. package/dist/index.d.cts +494 -0
  25. package/dist/index.d.ts +494 -0
  26. package/dist/index.js +523 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/metafile-cjs.json +1 -0
  29. package/dist/metafile-esm.json +1 -0
  30. package/dist/parser/index.cjs +85 -0
  31. package/dist/parser/index.cjs.map +1 -0
  32. package/dist/parser/index.d.cts +72 -0
  33. package/dist/parser/index.d.ts +72 -0
  34. package/dist/parser/index.js +4 -0
  35. package/dist/parser/index.js.map +1 -0
  36. package/dist/types-DvNlfbKB.d.cts +301 -0
  37. package/dist/types-DvNlfbKB.d.ts +301 -0
  38. package/dist/useCborSimpleEncoder-ButVU988.d.cts +268 -0
  39. package/dist/useCborSimpleEncoder-TVxzNJ_9.d.ts +268 -0
  40. package/dist/useCborTag-B_iaShG6.d.ts +142 -0
  41. package/dist/useCborTag-BfTIV8HM.d.cts +142 -0
  42. package/package.json +102 -0
  43. package/src/__tests__/public-api.test.ts +326 -0
  44. package/src/encoder/__tests__/cbor-collection-encoder.test.ts +331 -0
  45. package/src/encoder/__tests__/cbor-integer-encoder.test.ts +283 -0
  46. package/src/encoder/__tests__/cbor-simple-encoder.test.ts +224 -0
  47. package/src/encoder/__tests__/cbor-string-encoder.test.ts +345 -0
  48. package/src/encoder/__tests__/cbor-tag-encoder.test.ts +565 -0
  49. package/src/encoder/composables/#useCborTagEncoder.ts# +158 -0
  50. package/src/encoder/composables/useCborCollectionEncoder.ts +424 -0
  51. package/src/encoder/composables/useCborEncoder.ts +203 -0
  52. package/src/encoder/composables/useCborIntegerEncoder.ts +188 -0
  53. package/src/encoder/composables/useCborSimpleEncoder.ts +266 -0
  54. package/src/encoder/composables/useCborStringEncoder.ts +266 -0
  55. package/src/encoder/composables/useCborTagEncoder.ts +158 -0
  56. package/src/encoder/index.ts +35 -0
  57. package/src/encoder/types.ts +88 -0
  58. package/src/encoder/utils.ts +80 -0
  59. package/src/index.ts +434 -0
  60. package/src/parser/__tests__/ast-tree-structure.test.ts +311 -0
  61. package/src/parser/__tests__/cbor-collection-errors.test.ts +296 -0
  62. package/src/parser/__tests__/cbor-collection.test.ts +369 -0
  63. package/src/parser/__tests__/cbor-deterministic-encoding.test.ts +432 -0
  64. package/src/parser/__tests__/cbor-diagnostic.test.ts +333 -0
  65. package/src/parser/__tests__/cbor-duplicate-keys.test.ts +235 -0
  66. package/src/parser/__tests__/cbor-float-errors.test.ts +222 -0
  67. package/src/parser/__tests__/cbor-float.test.ts +502 -0
  68. package/src/parser/__tests__/cbor-integer-errors.test.ts +139 -0
  69. package/src/parser/__tests__/cbor-integer.test.ts +200 -0
  70. package/src/parser/__tests__/cbor-map-duplicate-keys.test.ts +403 -0
  71. package/src/parser/__tests__/cbor-parser-errors.test.ts +126 -0
  72. package/src/parser/__tests__/cbor-security-dos-protection.test.ts +503 -0
  73. package/src/parser/__tests__/cbor-sequences.test.ts +150 -0
  74. package/src/parser/__tests__/cbor-source-map.test.ts +321 -0
  75. package/src/parser/__tests__/cbor-standard-tags.test.ts +340 -0
  76. package/src/parser/__tests__/cbor-string-errors.test.ts +227 -0
  77. package/src/parser/__tests__/cbor-string.test.ts +224 -0
  78. package/src/parser/__tests__/cbor-tag-advanced.test.ts +500 -0
  79. package/src/parser/__tests__/cbor-tag-errors.test.ts +447 -0
  80. package/src/parser/__tests__/cbor-tag-source-map.test.ts +360 -0
  81. package/src/parser/__tests__/cbor-tag.test.ts +684 -0
  82. package/src/parser/__tests__/extreme-edge-cases.test.ts +146 -0
  83. package/src/parser/__tests__/pathBuilder.test.ts +256 -0
  84. package/src/parser/__tests__/rfc-test-vectors.test.ts +607 -0
  85. package/src/parser/__tests__/security-limits.test.ts +248 -0
  86. package/src/parser/__tests__/utils-errors.test.ts +127 -0
  87. package/src/parser/composables/useCborCollection.ts +509 -0
  88. package/src/parser/composables/useCborDiagnostic.ts +381 -0
  89. package/src/parser/composables/useCborFloat.ts +256 -0
  90. package/src/parser/composables/useCborInteger.ts +114 -0
  91. package/src/parser/composables/useCborParser.ts +951 -0
  92. package/src/parser/composables/useCborString.ts +330 -0
  93. package/src/parser/composables/useCborStringTypes.ts +129 -0
  94. package/src/parser/composables/useCborTag.ts +739 -0
  95. package/src/parser/index.ts +56 -0
  96. package/src/parser/types.ts +371 -0
  97. package/src/parser/utils/pathBuilder.ts +259 -0
  98. package/src/parser/utils.ts +398 -0
  99. package/src/utils/__tests__/logger.test.ts +186 -0
  100. package/src/utils/logger.ts +96 -0
@@ -0,0 +1,126 @@
1
+ /**
2
+ * CBOR Parser Error Handling Tests
3
+ * Tests all error cases and edge cases for 100% code coverage
4
+ */
5
+
6
+ import { describe, it, expect } from 'vitest'
7
+ import { useCborParser } from '../composables/useCborParser'
8
+
9
+ describe('useCborParser - Error Handling', () => {
10
+ describe('Input Validation Errors', () => {
11
+ it('should throw error for empty hex string', () => {
12
+ const { parse } = useCborParser()
13
+ expect(() => parse('')).toThrow('Empty hex string')
14
+ })
15
+
16
+ it('should throw error for whitespace-only hex string', () => {
17
+ const { parse } = useCborParser()
18
+ expect(() => parse(' ')).toThrow('Empty hex string')
19
+ })
20
+
21
+ it('should throw error for odd-length hex string', () => {
22
+ const { parse } = useCborParser()
23
+ expect(() => parse('1')).toThrow('Hex string must have even length')
24
+ })
25
+
26
+ it('should throw error for hex string with odd length (3 chars)', () => {
27
+ const { parse } = useCborParser()
28
+ expect(() => parse('123')).toThrow('Hex string must have even length')
29
+ })
30
+
31
+ it('should throw error for invalid hex characters', () => {
32
+ const { parse } = useCborParser()
33
+ expect(() => parse('1g')).toThrow('Invalid hex character')
34
+ })
35
+
36
+ it('should throw error for hex with special characters', () => {
37
+ const { parse } = useCborParser()
38
+ expect(() => parse('12@4')).toThrow('Invalid hex character')
39
+ })
40
+
41
+ it('should throw error for hex with spaces not removed', () => {
42
+ const { parse } = useCborParser()
43
+ // Spaces should be removed, but test with invalid chars after spaces
44
+ expect(() => parse('ZZ')).toThrow('Invalid hex character')
45
+ })
46
+ })
47
+
48
+ describe('Unknown Major Type Error', () => {
49
+ it('should never throw unknown major type error (all types 0-7 are valid)', () => {
50
+ // This tests the default case in the switch statement
51
+ // However, extractCborHeader can only return 0-7, so this is defensive programming
52
+ // We can't actually trigger this without mocking, but we have the code path
53
+
54
+ // All major types should be handled:
55
+ const { parse } = useCborParser()
56
+
57
+ // MT 0: Integer
58
+ expect(() => parse('00')).not.toThrow()
59
+
60
+ // MT 1: Negative integer
61
+ expect(() => parse('20')).not.toThrow()
62
+
63
+ // MT 2: Byte string
64
+ expect(() => parse('40')).not.toThrow()
65
+
66
+ // MT 3: Text string
67
+ expect(() => parse('60')).not.toThrow()
68
+
69
+ // MT 4: Array
70
+ expect(() => parse('80')).not.toThrow()
71
+
72
+ // MT 5: Map
73
+ expect(() => parse('a0')).not.toThrow()
74
+
75
+ // MT 6: Tag
76
+ expect(() => parse('c000')).not.toThrow()
77
+
78
+ // MT 7: Float/Simple
79
+ expect(() => parse('f4')).not.toThrow()
80
+ })
81
+ })
82
+
83
+ describe('parseWithSourceMap', () => {
84
+ it('should delegate to parse for now', () => {
85
+ const { parseWithSourceMap } = useCborParser()
86
+
87
+ // Test with various types
88
+ expect(parseWithSourceMap('00').value).toBe(0)
89
+ expect(parseWithSourceMap('20').value).toBe(-1)
90
+ expect(parseWithSourceMap('60').value).toBe('')
91
+ expect(parseWithSourceMap('f5').value).toBe(true)
92
+ })
93
+
94
+ it('should return same result as parse', () => {
95
+ const { parse, parseWithSourceMap } = useCborParser()
96
+
97
+ const testCases = ['00', '1864', '6449455446', '83010203', 'a0', 'f4']
98
+
99
+ for (const hexString of testCases) {
100
+ const parseResult = parse(hexString)
101
+ const sourceMapResult = parseWithSourceMap(hexString)
102
+ expect(sourceMapResult.value).toEqual(parseResult.value)
103
+ expect(sourceMapResult.bytesRead).toBe(parseResult.bytesRead)
104
+ }
105
+ })
106
+ })
107
+
108
+ describe('Hex String Cleaning', () => {
109
+ it('should handle hex with spaces', () => {
110
+ const { parse } = useCborParser()
111
+
112
+ // Should remove spaces before parsing
113
+ expect(parse('18 64').value).toBe(100)
114
+ expect(parse('83 01 02 03').value).toEqual([1, 2, 3])
115
+ })
116
+
117
+ it('should handle hex with tabs and newlines', () => {
118
+ const { parse } = useCborParser()
119
+
120
+ // Should remove all whitespace
121
+ expect(parse('18\t64').value).toBe(100)
122
+ expect(parse('18\n64').value).toBe(100)
123
+ expect(parse('18\r\n64').value).toBe(100)
124
+ })
125
+ })
126
+ })
@@ -0,0 +1,503 @@
1
+ /**
2
+ * CBOR Denial of Service (DoS) Protection Tests
3
+ *
4
+ * Tests for critical security vulnerabilities:
5
+ * - CVE-2020-28491: Bignum memory exhaustion
6
+ * - RUSTSEC-2019-0025: Tag nesting stack overflow
7
+ * - Indefinite-length break code validation
8
+ * - Float canonical encoding
9
+ *
10
+ * These tests verify protections against known CBOR implementation vulnerabilities.
11
+ */
12
+
13
+ import { describe, it, expect } from 'vitest'
14
+ import { useCborTag } from '../composables/useCborTag'
15
+ import { useCborParser } from '../composables/useCborParser'
16
+ import { useCborCollection } from '../composables/useCborCollection'
17
+
18
+ describe('CVE-2020-28491: Bignum Memory Exhaustion Protection', () => {
19
+ describe('Tag 2: Positive Bignum Size Limits', () => {
20
+ it('should accept tag 2 bignum within size limit', () => {
21
+ const { parseTag } = useCborTag()
22
+
23
+ // Tag 2 with 512-byte bignum (within 1KB default limit)
24
+ const bignumBytes = '00'.repeat(512)
25
+ const bignum = `c2590200${bignumBytes}` // c2 = tag 2, 590200 = byte string of 512 bytes
26
+
27
+ const result = parseTag(bignum, { limits: { maxBignumBytes: 1024 } })
28
+
29
+ expect(result.value.tag).toBe(2)
30
+ expect(typeof result.value.value).toBe('bigint')
31
+ // 512 bytes of 0x00 = BigInt 0
32
+ expect(result.value.value).toBe(0n)
33
+ })
34
+
35
+ it('should reject tag 2 bignum exceeding size limit', () => {
36
+ const { parseTag } = useCborTag()
37
+
38
+ // Tag 2 with 2KB bignum (exceeds 1KB limit)
39
+ const bignumBytes = '00'.repeat(2048)
40
+ const bignum = `c2590800${bignumBytes}` // 2048 bytes
41
+
42
+ expect(() => parseTag(bignum, { limits: { maxBignumBytes: 1024 } }))
43
+ .toThrow(/bignum.*exceeds.*1024 bytes/i)
44
+ })
45
+
46
+ it('should reject tag 2 with massive bignum (memory DoS)', () => {
47
+ const { parseTag } = useCborTag()
48
+
49
+ // Tag 2 with 2048-byte bignum (exceeds 1024 byte limit)
50
+ // c2 = tag 2, 590800 = byte string of 2048 bytes
51
+ const bignumBytes = '00'.repeat(2048)
52
+ const bignum = `c2590800${bignumBytes}`
53
+
54
+ expect(() => parseTag(bignum, { limits: { maxBignumBytes: 1024 } }))
55
+ .toThrow(/bignum.*exceeds.*1024 bytes/i)
56
+ })
57
+
58
+ it('should accept tag 2 with exactly max size', () => {
59
+ const { parseTag } = useCborTag()
60
+
61
+ // Exactly 1024 bytes (at limit)
62
+ const bignumBytes = 'ff'.repeat(1024)
63
+ const bignum = `c2590400${bignumBytes}`
64
+
65
+ const result = parseTag(bignum, { limits: { maxBignumBytes: 1024 } })
66
+
67
+ expect(result.value.tag).toBe(2)
68
+ expect(typeof result.value.value).toBe('bigint')
69
+ // 1024 bytes of 0xff should be a large positive bigint
70
+ // 2^(1024*8) - 1
71
+ expect(result.value.value > 0n).toBe(true)
72
+ })
73
+
74
+ it('should reject tag 2 with 1 byte over limit', () => {
75
+ const { parseTag } = useCborTag()
76
+
77
+ // 1025 bytes (1 over limit)
78
+ const bignumBytes = 'ff'.repeat(1025)
79
+ const bignum = `c2590401${bignumBytes}`
80
+
81
+ expect(() => parseTag(bignum, { limits: { maxBignumBytes: 1024 } }))
82
+ .toThrow(/bignum.*exceeds.*1024 bytes/i)
83
+ })
84
+
85
+ it('should use default bignum limit when not specified', () => {
86
+ const { parseTag } = useCborTag()
87
+
88
+ // 2KB bignum with no limit specified (should use default 1KB)
89
+ const bignumBytes = 'aa'.repeat(2048)
90
+ const bignum = `c2590800${bignumBytes}`
91
+
92
+ expect(() => parseTag(bignum)) // No options = use defaults
93
+ .toThrow(/bignum.*exceeds/i)
94
+ })
95
+
96
+ it('should allow custom bignum limits', () => {
97
+ const { parseTag } = useCborTag()
98
+
99
+ // 2KB bignum with 4KB limit (should pass)
100
+ const bignumBytes = 'bb'.repeat(2048)
101
+ const bignum = `c2590800${bignumBytes}`
102
+
103
+ const result = parseTag(bignum, { limits: { maxBignumBytes: 4096 } })
104
+
105
+ expect(result.value.tag).toBe(2)
106
+ expect(typeof result.value.value).toBe('bigint')
107
+ // 2048 bytes of 0xbb should be a large positive bigint
108
+ expect(result.value.value > 0n).toBe(true)
109
+ })
110
+
111
+ it('should accept empty bignum (edge case)', () => {
112
+ const { parseTag } = useCborTag()
113
+
114
+ // Tag 2 with empty byte string (0 bytes)
115
+ const bignum = 'c240' // c2 = tag 2, 40 = empty byte string
116
+
117
+ const result = parseTag(bignum, { limits: { maxBignumBytes: 1024 } })
118
+
119
+ expect(result.value.tag).toBe(2)
120
+ expect(typeof result.value.value).toBe('bigint')
121
+ // Empty byte string = BigInt 0
122
+ expect(result.value.value).toBe(0n)
123
+ })
124
+ })
125
+
126
+ describe('Tag 3: Negative Bignum Size Limits', () => {
127
+ it('should accept tag 3 bignum within size limit', () => {
128
+ const { parseTag } = useCborTag()
129
+
130
+ // Tag 3 with 256-byte bignum
131
+ const bignumBytes = 'ff'.repeat(256)
132
+ const bignum = `c3590100${bignumBytes}` // c3 = tag 3, 590100 = 256 bytes
133
+
134
+ const result = parseTag(bignum, { limits: { maxBignumBytes: 1024 } })
135
+
136
+ expect(result.value.tag).toBe(3)
137
+ expect(typeof result.value.value).toBe('bigint')
138
+ // Tag 3 = -1 - n, where n is the bignum value
139
+ // 256 bytes of 0xff should be a large negative bigint
140
+ expect(result.value.value < 0n).toBe(true)
141
+ })
142
+
143
+ it('should reject tag 3 bignum exceeding size limit', () => {
144
+ const { parseTag } = useCborTag()
145
+
146
+ // Tag 3 with 4KB bignum (exceeds 1KB limit)
147
+ const bignumBytes = 'ff'.repeat(4096)
148
+ const bignum = `c3591000${bignumBytes}` // 4096 bytes
149
+
150
+ expect(() => parseTag(bignum, { limits: { maxBignumBytes: 1024 } }))
151
+ .toThrow(/bignum.*exceeds.*1024 bytes/i)
152
+ })
153
+
154
+ it('should handle both tag 2 and tag 3 with same limit', () => {
155
+ const { parseTag } = useCborTag()
156
+
157
+ const limit = { maxBignumBytes: 512 }
158
+
159
+ // Tag 2 with 512 bytes (OK)
160
+ const bignum2 = `c2590200${'aa'.repeat(512)}`
161
+ expect(() => parseTag(bignum2, { limits: limit })).not.toThrow()
162
+
163
+ // Tag 3 with 512 bytes (OK)
164
+ const bignum3 = `c3590200${'bb'.repeat(512)}`
165
+ expect(() => parseTag(bignum3, { limits: limit })).not.toThrow()
166
+
167
+ // Tag 2 with 513 bytes (FAIL)
168
+ const bignum2Fail = `c2590201${'cc'.repeat(513)}`
169
+ expect(() => parseTag(bignum2Fail, { limits: limit })).toThrow()
170
+
171
+ // Tag 3 with 513 bytes (FAIL)
172
+ const bignum3Fail = `c3590201${'dd'.repeat(513)}`
173
+ expect(() => parseTag(bignum3Fail, { limits: limit })).toThrow()
174
+ })
175
+ })
176
+
177
+ describe('Other Tags (Non-Bignum) Should Not Be Limited', () => {
178
+ it('should NOT apply bignum limit to tag 1 (epoch time)', () => {
179
+ const { parseTag } = useCborTag()
180
+
181
+ // Tag 1 with large integer (not a bignum)
182
+ const epochTag = 'c11a514b67b0' // Tag 1 with 1363896240
183
+
184
+ const result = parseTag(epochTag, { limits: { maxBignumBytes: 100 } })
185
+
186
+ expect(result.value.tag).toBe(1)
187
+ expect(result.value.value).toBe(1363896240)
188
+ })
189
+
190
+ it('should NOT apply bignum limit to tag 24 (embedded CBOR)', () => {
191
+ const { parseTag } = useCborTag()
192
+
193
+ // Tag 24 with byte string containing CBOR (not a bignum)
194
+ const embeddedCBOR = 'd8184401020304' // Tag 24 with h'01020304'
195
+
196
+ const result = parseTag(embeddedCBOR, { limits: { maxBignumBytes: 2 } })
197
+
198
+ expect(result.value.tag).toBe(24)
199
+ expect(result.value.value).toBeInstanceOf(Uint8Array)
200
+ })
201
+
202
+ it('should NOT apply bignum limit to tag 258 (set)', () => {
203
+ const { parseTag } = useCborTag()
204
+
205
+ // Tag 258 with array (not a bignum)
206
+ const setTag = 'd9010283010203' // Tag 258 with [1, 2, 3]
207
+
208
+ const result = parseTag(setTag, { limits: { maxBignumBytes: 2 } })
209
+
210
+ expect(result.value.tag).toBe(258)
211
+ expect(result.value.value).toBeInstanceOf(Array)
212
+ })
213
+ })
214
+ })
215
+
216
+ describe('RUSTSEC-2019-0025: Tag Nesting Stack Overflow Protection', () => {
217
+ describe('Tag Nesting Depth Limits', () => {
218
+ it('should accept tag nesting within depth limit', () => {
219
+ const { parseTag } = useCborTag()
220
+
221
+ // 10 nested tags (within 64 default limit)
222
+ const nested = 'c0'.repeat(10) + '00' // Tag 0 nested 10 times
223
+
224
+ const result = parseTag(nested, { limits: { maxTagDepth: 64 } })
225
+
226
+ expect(result.value.tag).toBe(0)
227
+ // Nested structure: tag 0 -> tag 0 -> ... -> 0
228
+ })
229
+
230
+ it('should reject tag nesting exceeding depth limit', () => {
231
+ const { parseTag } = useCborTag()
232
+
233
+ // 100 nested tags (exceeds 64 default limit)
234
+ const nested = 'c0'.repeat(100) + '00'
235
+
236
+ expect(() => parseTag(nested, { limits: { maxTagDepth: 64 } }))
237
+ .toThrow(/tag nesting depth.*exceeds.*64/i)
238
+ })
239
+
240
+ it('should reject tag nesting at exactly limit + 1', () => {
241
+ const { parseTag } = useCborTag()
242
+
243
+ // 11 nested tags with limit of 10 (should fail at 11th)
244
+ const nested = 'c0'.repeat(11) + '00'
245
+
246
+ expect(() => parseTag(nested, { limits: { maxTagDepth: 10 } }))
247
+ .toThrow(/tag nesting depth.*exceeds.*10/i)
248
+ })
249
+
250
+ it('should accept tag nesting at exactly the limit', () => {
251
+ const { parseTag } = useCborTag()
252
+
253
+ // 10 nested tags with limit of 10 (should pass)
254
+ const nested = 'c0'.repeat(10) + '00'
255
+
256
+ const result = parseTag(nested, { limits: { maxTagDepth: 10 } })
257
+
258
+ expect(result.value.tag).toBe(0)
259
+ })
260
+
261
+ it('should use default tag depth limit when not specified', () => {
262
+ const { parseTag } = useCborTag()
263
+
264
+ // 100 nested tags (exceeds default 64)
265
+ const nested = 'c0'.repeat(100) + '00'
266
+
267
+ expect(() => parseTag(nested)) // No options = use defaults
268
+ .toThrow(/tag nesting depth.*exceeds/i)
269
+ })
270
+
271
+ it('should allow custom tag depth limits', () => {
272
+ const { parseTag } = useCborTag()
273
+
274
+ // 100 nested tags with 150 limit (should pass)
275
+ const nested = 'c0'.repeat(100) + '00'
276
+
277
+ const result = parseTag(nested, { limits: { maxTagDepth: 150 } })
278
+
279
+ expect(result.value.tag).toBe(0)
280
+ })
281
+
282
+ it('should handle nested tags with different tag numbers', () => {
283
+ const { parseTag } = useCborTag()
284
+
285
+ // Tag 1 -> Tag 2 -> Tag 3 -> 0
286
+ const nested = 'c1c2c300' // Tag 1( Tag 2( Tag 3( 0 )))
287
+
288
+ const result = parseTag(nested, { limits: { maxTagDepth: 5 } })
289
+
290
+ expect(result.value.tag).toBe(1)
291
+ expect(result.value.value.tag).toBe(2)
292
+ expect((result.value.value as any).value).toHaveProperty('tag', 3)
293
+ })
294
+
295
+ it('should reject deeply nested mixed tags', () => {
296
+ const { parseTag } = useCborTag()
297
+
298
+ // Mix of tag numbers, deeply nested
299
+ const nested = 'c0c1c2c3c0c1c2c3c0c1c2' + '00' // 11 tags
300
+
301
+ expect(() => parseTag(nested, { limits: { maxTagDepth: 10 } }))
302
+ .toThrow(/tag nesting depth.*exceeds/i)
303
+ })
304
+
305
+ it('should handle tag depth separate from collection depth', () => {
306
+ const { parseTag } = useCborTag()
307
+
308
+ // Tag 1 containing array (tag depth 1, collection depth 1)
309
+ const tagWithArray = 'c183010203' // Tag 1([1, 2, 3])
310
+
311
+ const result = parseTag(tagWithArray, {
312
+ limits: { maxTagDepth: 5, maxDepth: 5 }
313
+ })
314
+
315
+ expect(result.value.tag).toBe(1)
316
+ expect(result.value.value).toEqual([1, 2, 3])
317
+ })
318
+
319
+ it('should prevent stack overflow with minimal input (< 1KB)', () => {
320
+ const { parseTag } = useCborTag()
321
+
322
+ // RUSTSEC-2019-0025: Small input (< 1KB) that causes stack overflow
323
+ // 200 nested tags = 400 bytes of input
324
+ const nested = 'c0'.repeat(200) + '00'
325
+
326
+ expect(() => parseTag(nested, { limits: { maxTagDepth: 64 } }))
327
+ .toThrow(/tag nesting depth.*exceeds/i)
328
+
329
+ // Verify input is indeed small
330
+ expect(nested.length).toBeLessThan(1000)
331
+ })
332
+ })
333
+ })
334
+
335
+ describe('Indefinite-Length Break Code Validation', () => {
336
+ describe('Indefinite-Length Arrays - Break Code Required', () => {
337
+ it('should accept indefinite array with proper break code', () => {
338
+ const { parseArray } = useCborCollection()
339
+
340
+ // Indefinite array: [_ 1, 2, 3, BREAK]
341
+ const indefinite = '9f010203ff' // 9f = indefinite array, ff = break
342
+
343
+ const result = parseArray(indefinite, { allowIndefinite: true })
344
+
345
+ expect(Array.isArray(result.value)).toBe(true)
346
+ expect((result.value as any[])[0]).toBe(1)
347
+ expect((result.value as any[])[1]).toBe(2)
348
+ expect((result.value as any[])[2]).toBe(3)
349
+ })
350
+
351
+ it('should throw error for indefinite array without break code', () => {
352
+ const { parseArray } = useCborCollection()
353
+
354
+ // Indefinite array missing break: [_ 1, 2, 3 (NO FF)
355
+ const noBreak = '9f010203' // Missing 0xff
356
+
357
+ expect(() => parseArray(noBreak, { allowIndefinite: true }))
358
+ .toThrow(/missing.*break/i)
359
+ })
360
+
361
+ it('should accept empty indefinite array with break', () => {
362
+ const { parseArray } = useCborCollection()
363
+
364
+ // Indefinite array: [_ BREAK]
365
+ const emptyIndefinite = '9fff' // Just start + break
366
+
367
+ const result = parseArray(emptyIndefinite, { allowIndefinite: true })
368
+
369
+ expect(Array.isArray(result.value)).toBe(true)
370
+ expect(result.value).toHaveLength(0)
371
+ })
372
+
373
+ it('should handle nested indefinite arrays with breaks', () => {
374
+ const { parseArray } = useCborCollection()
375
+
376
+ // [_ [_ 1 BREAK] BREAK]
377
+ const nestedIndefinite = '9f9f01ffff'
378
+
379
+ const result = parseArray(nestedIndefinite, { allowIndefinite: true })
380
+
381
+ expect(Array.isArray(result.value)).toBe(true)
382
+ expect(result.value).toHaveLength(1)
383
+ expect(Array.isArray((result.value as any[])[0])).toBe(true)
384
+ expect((result.value as any[])[0][0]).toBe(1)
385
+ })
386
+ })
387
+
388
+ describe('Indefinite-Length Maps - Break Code Required', () => {
389
+ it('should accept indefinite map with proper break code', () => {
390
+ const { parseMap } = useCborCollection()
391
+
392
+ // Indefinite map: {_ "a": 1, "b": 2, BREAK}
393
+ const indefinite = 'bf616101616202ff' // bf = indefinite map, ff = break
394
+
395
+ const result = parseMap(indefinite, { allowIndefinite: true })
396
+
397
+ expect(result.value).toEqual(new Map([['a', 1], ['b', 2]]))
398
+ })
399
+
400
+ it('should throw error for indefinite map without break code', () => {
401
+ const { parseMap } = useCborCollection()
402
+
403
+ // Indefinite map missing break: {_ "a": 1 (NO FF)
404
+ const noBreak = 'bf616101' // Missing 0xff
405
+
406
+ expect(() => parseMap(noBreak, { allowIndefinite: true }))
407
+ .toThrow(/missing.*break/i)
408
+ })
409
+
410
+ it('should accept empty indefinite map with break', () => {
411
+ const { parseMap } = useCborCollection()
412
+
413
+ // Indefinite map: {_ BREAK}
414
+ const emptyIndefinite = 'bfff'
415
+
416
+ const result = parseMap(emptyIndefinite, { allowIndefinite: true })
417
+
418
+ expect(result.value).toEqual(new Map())
419
+ })
420
+ })
421
+
422
+ describe('Indefinite-Length in Strict Mode', () => {
423
+ it('should reject indefinite arrays in canonical/strict mode', () => {
424
+ const { parseArray } = useCborCollection()
425
+
426
+ const indefinite = '9f010203ff'
427
+
428
+ expect(() => parseArray(indefinite, { validateCanonical: true }))
429
+ .toThrow(/indefinite.*not allowed/i)
430
+ })
431
+
432
+ it('should reject indefinite maps in canonical/strict mode', () => {
433
+ const { parseMap } = useCborCollection()
434
+
435
+ const indefinite = 'bf616101ff'
436
+
437
+ expect(() => parseMap(indefinite, { validateCanonical: true }))
438
+ .toThrow(/indefinite.*not allowed/i)
439
+ })
440
+
441
+ it('should enforce strict mode disallows indefinite encoding', () => {
442
+ const { parse } = useCborParser()
443
+
444
+ const indefiniteArray = '9f0102ff'
445
+
446
+ expect(() => parse(indefiniteArray, { strict: true }))
447
+ .toThrow(/indefinite.*not allowed/i)
448
+ })
449
+ })
450
+ })
451
+
452
+ describe('Combined Security Protections', () => {
453
+ it('should handle all limits simultaneously', () => {
454
+ const { parse } = useCborParser()
455
+
456
+ // Complex nested structure within all limits
457
+ const complexCBOR = 'c1a2616183010203616282c240c300'
458
+ // Tag 1( {"a": [1,2,3], "b": [Tag 2(h''), Tag 3(0)]} )
459
+
460
+ const result = parse(complexCBOR, {
461
+ limits: {
462
+ maxDepth: 10,
463
+ maxTagDepth: 5,
464
+ maxBignumBytes: 1024,
465
+ maxArrayLength: 100,
466
+ maxMapSize: 100
467
+ }
468
+ })
469
+
470
+ expect(result.value.tag).toBe(1)
471
+ })
472
+
473
+ it('should reject when any single limit is exceeded', () => {
474
+ const { parseTag } = useCborTag()
475
+
476
+ // Exceeds tag depth but not bignum size
477
+ const deepTags = 'c0'.repeat(100) + '00'
478
+
479
+ expect(() => parseTag(deepTags, {
480
+ limits: {
481
+ maxTagDepth: 10,
482
+ maxBignumBytes: 10000 // Very permissive
483
+ }
484
+ })).toThrow(/tag nesting depth/i)
485
+ })
486
+
487
+ it('should use strict mode to enable all security validations', () => {
488
+ const { parse } = useCborParser()
489
+
490
+ // Non-canonical integer (wasteful encoding)
491
+ expect(() => parse('180a', { strict: true }))
492
+ .toThrow(/non-canonical/i)
493
+
494
+ // Indefinite length
495
+ expect(() => parse('9f01ff', { strict: true }))
496
+ .toThrow(/indefinite.*not allowed/i)
497
+
498
+ // Would reject duplicate keys if map had them
499
+ const duplicateMap = 'a2616101616102'
500
+ expect(() => parse(duplicateMap, { strict: true }))
501
+ .toThrow(/duplicate/i)
502
+ })
503
+ })