@sythos/js_barcode_universal 1.5.11 → 1.5.12

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.
package/README.md CHANGED
@@ -112,8 +112,8 @@ The `unpkg` and `jsdelivr` fields point at the IIFE bundle, so a CDN needs no in
112
112
 
113
113
  ```html
114
114
  <script src="https://unpkg.com/@sythos/js_barcode_universal"></script>
115
- <script src="https://unpkg.com/@sythos/js_barcode_universal@1.5.11"></script>
116
- <script src="https://cdn.jsdelivr.net/npm/@sythos/js_barcode_universal@1.5.11"></script>
115
+ <script src="https://unpkg.com/@sythos/js_barcode_universal@1.5.12"></script>
116
+ <script src="https://cdn.jsdelivr.net/npm/@sythos/js_barcode_universal@1.5.12"></script>
117
117
  ```
118
118
 
119
119
  Pin the version for anything you ship; the unpinned form resolves to `latest` and will move under
@@ -670,11 +670,29 @@ zero-dependency at runtime.
670
670
  These security workflows report findings and propose maintenance updates. They do not replace
671
671
  review of barcode conformance, licensing, patent status or release attestations.
672
672
 
673
+ Security reports follow the [security policy](SECURITY.md). Please use GitHub's private reporting
674
+ channel for every suspected vulnerability; public Issues are for non-security bugs after security
675
+ impact has been ruled out. Reports involving code execution, data or secret exposure, CI/release/npm
676
+ integrity or host/runner compromise should also be sent to `devsec@sythos.net`. Exploit details are
677
+ better kept private than turned into an accidental community fireworks show.
678
+
673
679
  The image I/O boundary treats camera and file rasters as untrusted input. It validates finite
674
- positive dimensions, bounds allocations to 16,777,216 pixels, accepts only byte-valued channels,
680
+ positive dimensions, bounds allocations to 16,777,216 pixels still a high limit, roughly twice
681
+ the pixel count of a standard 4K image — and accepts only byte-valued channels,
675
682
  and snapshots greyscale buffers before decoding. Malformed or oversized rasters are rejected
676
683
  before detector work begins.
677
684
 
685
+ Rendering applies the same allocation discipline before creating an SVG, PNG, `ImageData`, canvas,
686
+ WebGL or WebGPU output. Matrix dimensions, `scale`, `margin` and `barHeight` must be safe integers
687
+ within documented bounds, and the final image must be no larger than 16,384 pixels on either side
688
+ or 16,777,216 pixels in total. Invalid, fractional or oversized values are rejected with
689
+ `RangeError`; callers that expose rendering controls should handle that result rather than silently
690
+ coercing it.
691
+
692
+ The browser examples treat decoded payloads and browser error messages as text. They create DOM
693
+ nodes and set `textContent` instead of interpolating those values into HTML, so scanned content is
694
+ not interpreted as markup.
695
+
678
696
  ---
679
697
 
680
698
  ## Build provenance and artifact attestations
@@ -688,6 +706,15 @@ Attestations are related but separate records:
688
706
  - **GitHub Artifact Attestations** bind a build artifact, its digest and its build context to the
689
707
  GitHub Actions workflow that produced it. They can be verified independently of the npm registry.
690
708
 
709
+ Every third-party GitHub Action referenced by the repository workflows is pinned to its immutable
710
+ commit SHA. Dependabot tracks the action references, but an update is still reviewed and then
711
+ records a new explicit SHA rather than relying on a movable tag.
712
+
713
+ The development toolchain is also reproducible: `package-lock.json` records the resolved packages,
714
+ TypeScript is declared at an exact version, and the pull-request and release validation workflows
715
+ install the lockfile with `npm ci --ignore-scripts`. The published SDK remains free of runtime
716
+ dependencies.
717
+
691
718
  The release-specific attestation workflow is expected at
692
719
  [`.github/workflows/release.yml`](.github/workflows/release.yml). Release assets must be verified
693
720
  against this repository and their exact local file contents:
@@ -701,7 +728,7 @@ attestation confirms build provenance; it is not an ISO barcode-conformance cert
701
728
  clearance, or a guarantee that the implementation is vulnerability-free.
702
729
 
703
730
  Release automation validates the package version against the selected Git tag; it does not invent
704
- or increment versions by itself. The current release is `1.5.11`.
731
+ or increment versions by itself. The current release is `1.5.12`.
705
732
 
706
733
  ---
707
734
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Sythos Barcode Suite v1.5.11
2
+ * Sythos Barcode Suite v1.5.12
3
3
  *
4
4
  * MIT License
5
5
  *
@@ -17596,7 +17596,7 @@ function decodeStrict(image, options) {
17596
17596
  return results[0];
17597
17597
  }
17598
17598
  /** Library version, matching package.json. */
17599
- const VERSION = '1.5.11';
17599
+ const VERSION = '1.5.12';
17600
17600
 
17601
17601
  __exports.listFormats = listFormats;
17602
17602
  __exports.encode = encode;
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Sythos Barcode Suite v1.5.11
2
+ * Sythos Barcode Suite v1.5.12
3
3
  *
4
4
  * MIT License
5
5
  *
@@ -17597,7 +17597,7 @@ function decodeStrict(image, options) {
17597
17597
  return results[0];
17598
17598
  }
17599
17599
  /** Library version, matching package.json. */
17600
- const VERSION = '1.5.11';
17600
+ const VERSION = '1.5.12';
17601
17601
 
17602
17602
  __exports.listFormats = listFormats;
17603
17603
  __exports.encode = encode;
@@ -149,7 +149,7 @@
149
149
 
150
150
  function show(hits, source) {
151
151
  if (!hits.length) return false;
152
- results.innerHTML = '';
152
+ results.textContent = '';
153
153
  hits.forEach(function (h) {
154
154
  var div = document.createElement('div');
155
155
  div.className = 'hit';
@@ -243,7 +243,11 @@
243
243
  el('fileStatus').textContent =
244
244
  w + '×' + h + ' · ' + ms.toFixed(0) + ' ms · ' + hits.length + ' found';
245
245
  if (!show(hits, 'image')) {
246
- results.innerHTML = '<div class="status">No barcode found in that image.</div>';
246
+ results.textContent = '';
247
+ var status = document.createElement('div');
248
+ status.className = 'status';
249
+ status.textContent = 'No barcode found in that image.';
250
+ results.appendChild(status);
247
251
  }
248
252
  };
249
253
  img.onerror = function () {
@@ -259,20 +263,44 @@
259
263
  var stream = null;
260
264
  var raf = null;
261
265
 
266
+ function setCameraNoteNode(note) {
267
+ var container = el('camNote');
268
+ container.textContent = '';
269
+ container.appendChild(note);
270
+ }
271
+
272
+ function setCameraNote(text) {
273
+ var note = document.createElement('div');
274
+ note.className = 'note';
275
+ note.textContent = text;
276
+ setCameraNoteNode(note);
277
+ }
278
+
279
+ function appendNoteCode(note, text) {
280
+ var code = document.createElement('code');
281
+ code.textContent = text;
282
+ note.appendChild(code);
283
+ }
284
+
262
285
  var secure = window.isSecureContext ||
263
286
  location.protocol === 'https:' ||
264
287
  location.hostname === 'localhost';
265
288
 
266
289
  if (!secure) {
267
- el('camNote').innerHTML =
268
- '<div class="note">The camera needs a secure context, so it is unavailable ' +
269
- 'when this page is opened from <code>file://</code>. Serve the folder over ' +
270
- 'http and reload &mdash; for example <code>npx serve</code> &mdash; or use ' +
271
- 'the image input above, which works anywhere.</div>';
290
+ var secureNote = document.createElement('div');
291
+ secureNote.className = 'note';
292
+ secureNote.appendChild(document.createTextNode(
293
+ 'The camera needs a secure context, so it is unavailable when this page is opened from '));
294
+ appendNoteCode(secureNote, 'file://');
295
+ secureNote.appendChild(document.createTextNode(
296
+ '. Serve the folder over http and reload — for example '));
297
+ appendNoteCode(secureNote, 'npx serve');
298
+ secureNote.appendChild(document.createTextNode(
299
+ ' — or use the image input above, which works anywhere.'));
300
+ setCameraNoteNode(secureNote);
272
301
  el('camStart').disabled = true;
273
302
  } else if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
274
- el('camNote').innerHTML =
275
- '<div class="note">This browser does not expose a camera API.</div>';
303
+ setCameraNote('This browser does not expose a camera API.');
276
304
  el('camStart').disabled = true;
277
305
  }
278
306
 
@@ -292,8 +320,7 @@
292
320
  }).then(function () {
293
321
  loop();
294
322
  }).catch(function (e) {
295
- el('camNote').innerHTML =
296
- '<div class="note">Camera unavailable: ' + String(e.message || e) + '</div>';
323
+ setCameraNote('Camera unavailable: ' + String(e.message || e));
297
324
  });
298
325
  });
299
326
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sythos/js_barcode_universal",
3
- "version": "1.5.11",
3
+ "version": "1.5.12",
4
4
  "description": "Read and write barcodes in JavaScript and TypeScript with zero runtime dependencies. TypeScript sources compile to the JavaScript runtime and bundles. QR Code, Micro QR, rMQR, FrameQR Code, Aztec Code and Rune, PDF417 variants, GS1 DataBar Omnidirectional/Truncated, EAN supplements and one-dimensional formats.",
5
5
  "author": {
6
6
  "name": "Sythos",
@@ -191,6 +191,6 @@
191
191
  "types:api": "node tools/check-public-types.mjs"
192
192
  },
193
193
  "devDependencies": {
194
- "typescript": "^7.0.2"
194
+ "typescript": "7.0.2"
195
195
  }
196
196
  }
package/src/index.d.ts CHANGED
@@ -360,4 +360,4 @@ export declare function decodeStrict(image: {
360
360
  height: number;
361
361
  }, options?: object): DecodeResult;
362
362
  /** Library version, matching package.json. */
363
- export declare const VERSION = "1.5.11";
363
+ export declare const VERSION = "1.5.12";
package/src/index.js CHANGED
@@ -779,4 +779,4 @@ export function decodeStrict(image, options) {
779
779
  return results[0];
780
780
  }
781
781
  /** Library version, matching package.json. */
782
- export const VERSION = '1.5.11';
782
+ export const VERSION = '1.5.12';
@@ -41,6 +41,40 @@ import { BitMatrix } from '../core/bit-matrix.js';
41
41
  * @property {string} [light] Colour of clear modules, or 'none' for transparent.
42
42
  * @property {number} [barHeight] For 1D symbols: total bar height in pixels.
43
43
  */
44
+ // Keep renderer allocations aligned with the image decoder's resource limits.
45
+ const MAX_RENDER_DIMENSION = 16384;
46
+ const MAX_RENDER_PIXELS = 16777216;
47
+ const MAX_RENDER_SCALE = MAX_RENDER_DIMENSION;
48
+ const MAX_RENDER_MARGIN = Math.floor((MAX_RENDER_DIMENSION - 1) / 2);
49
+ const MAX_RENDER_BAR_HEIGHT = MAX_RENDER_DIMENSION;
50
+ function boundedInteger(value, name, defaultValue, minimum, maximum) {
51
+ const resolved = value ?? defaultValue;
52
+ if (!Number.isSafeInteger(resolved)) {
53
+ throw new RangeError(`Render option "${name}" must be a finite safe integer between ${minimum} and ${maximum}, got ${resolved}`);
54
+ }
55
+ if (resolved < minimum || resolved > maximum) {
56
+ throw new RangeError(`Render option "${name}" must be between ${minimum} and ${maximum}, got ${resolved}`);
57
+ }
58
+ return resolved;
59
+ }
60
+ function validateMatrixDimensions(matrix) {
61
+ if (!matrix || !Number.isSafeInteger(matrix.width) || !Number.isSafeInteger(matrix.height)
62
+ || matrix.width < 1 || matrix.height < 1
63
+ || matrix.width > MAX_RENDER_DIMENSION || matrix.height > MAX_RENDER_DIMENSION) {
64
+ throw new RangeError(`Render matrix dimensions must be positive safe integers no larger than ${MAX_RENDER_DIMENSION}`);
65
+ }
66
+ }
67
+ function validatePixelDimensions(width, height) {
68
+ if (!Number.isSafeInteger(width) || !Number.isSafeInteger(height)
69
+ || width < 1 || height < 1
70
+ || width > MAX_RENDER_DIMENSION || height > MAX_RENDER_DIMENSION) {
71
+ throw new RangeError(`Render dimensions must be positive safe integers no larger than ${MAX_RENDER_DIMENSION}`);
72
+ }
73
+ const pixels = width * height;
74
+ if (!Number.isSafeInteger(pixels) || pixels > MAX_RENDER_PIXELS) {
75
+ throw new RangeError(`Render image contains too many pixels: ${pixels} (maximum ${MAX_RENDER_PIXELS})`);
76
+ }
77
+ }
44
78
  /**
45
79
  * Expand and pad the matrix, and resolve every dimension.
46
80
  *
@@ -53,18 +87,26 @@ import { BitMatrix } from '../core/bit-matrix.js';
53
87
  * @param {RenderOptions} options
54
88
  */
55
89
  export function normalizeOptions(matrix, options = {}) {
56
- const scale = Math.max(1, Math.floor(options.scale ?? 8));
57
- const margin = Math.max(0, Math.floor(options.margin ?? 4));
90
+ validateMatrixDimensions(matrix);
91
+ const scale = boundedInteger(options.scale, 'scale', 8, 1, MAX_RENDER_SCALE);
92
+ const margin = boundedInteger(options.margin, 'margin', 4, 0, MAX_RENDER_MARGIN);
58
93
  const dark = options.dark ?? '#000000';
59
94
  const light = options.light ?? '#ffffff';
60
- const barHeight = options.barHeight ?? null;
61
- let base = matrix;
95
+ const barHeight = options.barHeight == null
96
+ ? null
97
+ : boundedInteger(options.barHeight, 'barHeight', 1, 1, MAX_RENDER_BAR_HEIGHT);
62
98
  const is1D = matrix.height === 1;
99
+ const targetPixels = is1D
100
+ ? barHeight ?? Math.max(40, Math.round(matrix.width * scale * 0.15))
101
+ : null;
102
+ const rows = is1D ? Math.max(1, Math.round(targetPixels / scale)) : matrix.height;
103
+ const sourceWidth = matrix.width + margin * 2;
104
+ const sourceHeight = rows + margin * 2;
105
+ validatePixelDimensions(sourceWidth * scale, sourceHeight * scale);
106
+ let base = matrix;
63
107
  if (is1D) {
64
108
  // Default to a bar height that stays scannable: tall enough that a laser
65
109
  // crossing at a slight angle still passes through the whole symbol.
66
- const targetPixels = barHeight ?? Math.max(40, Math.round(matrix.width * scale * 0.15));
67
- const rows = Math.max(1, Math.round(targetPixels / scale));
68
110
  base = new BitMatrix(matrix.width, rows);
69
111
  for (let x = 0; x < matrix.width; x++) {
70
112
  if (!matrix.get(x, 0))
package/src/ts/index.d.ts CHANGED
@@ -360,4 +360,4 @@ export declare function decodeStrict(image: {
360
360
  height: number;
361
361
  }, options?: object): DecodeResult;
362
362
  /** Library version, matching package.json. */
363
- export declare const VERSION = "1.5.11";
363
+ export declare const VERSION = "1.5.12";
package/src/ts/index.ts CHANGED
@@ -787,4 +787,4 @@ export function decodeStrict(image, options) {
787
787
  }
788
788
 
789
789
  /** Library version, matching package.json. */
790
- export const VERSION = '1.5.11';
790
+ export const VERSION = '1.5.12';
@@ -45,6 +45,54 @@ import { BitMatrix } from '../core/bit-matrix.js';
45
45
  * @property {number} [barHeight] For 1D symbols: total bar height in pixels.
46
46
  */
47
47
 
48
+ // Keep renderer allocations aligned with the image decoder's resource limits.
49
+ const MAX_RENDER_DIMENSION = 16_384;
50
+ const MAX_RENDER_PIXELS = 16_777_216;
51
+ const MAX_RENDER_SCALE = MAX_RENDER_DIMENSION;
52
+ const MAX_RENDER_MARGIN = Math.floor((MAX_RENDER_DIMENSION - 1) / 2);
53
+ const MAX_RENDER_BAR_HEIGHT = MAX_RENDER_DIMENSION;
54
+
55
+ function boundedInteger(value, name, defaultValue, minimum, maximum) {
56
+ const resolved = value ?? defaultValue;
57
+ if (!Number.isSafeInteger(resolved)) {
58
+ throw new RangeError(
59
+ `Render option "${name}" must be a finite safe integer between ${minimum} and ${maximum}, got ${resolved}`
60
+ );
61
+ }
62
+ if (resolved < minimum || resolved > maximum) {
63
+ throw new RangeError(
64
+ `Render option "${name}" must be between ${minimum} and ${maximum}, got ${resolved}`
65
+ );
66
+ }
67
+ return resolved;
68
+ }
69
+
70
+ function validateMatrixDimensions(matrix) {
71
+ if (!matrix || !Number.isSafeInteger(matrix.width) || !Number.isSafeInteger(matrix.height)
72
+ || matrix.width < 1 || matrix.height < 1
73
+ || matrix.width > MAX_RENDER_DIMENSION || matrix.height > MAX_RENDER_DIMENSION) {
74
+ throw new RangeError(
75
+ `Render matrix dimensions must be positive safe integers no larger than ${MAX_RENDER_DIMENSION}`
76
+ );
77
+ }
78
+ }
79
+
80
+ function validatePixelDimensions(width, height) {
81
+ if (!Number.isSafeInteger(width) || !Number.isSafeInteger(height)
82
+ || width < 1 || height < 1
83
+ || width > MAX_RENDER_DIMENSION || height > MAX_RENDER_DIMENSION) {
84
+ throw new RangeError(
85
+ `Render dimensions must be positive safe integers no larger than ${MAX_RENDER_DIMENSION}`
86
+ );
87
+ }
88
+ const pixels = width * height;
89
+ if (!Number.isSafeInteger(pixels) || pixels > MAX_RENDER_PIXELS) {
90
+ throw new RangeError(
91
+ `Render image contains too many pixels: ${pixels} (maximum ${MAX_RENDER_PIXELS})`
92
+ );
93
+ }
94
+ }
95
+
48
96
  /**
49
97
  * Expand and pad the matrix, and resolve every dimension.
50
98
  *
@@ -57,20 +105,29 @@ import { BitMatrix } from '../core/bit-matrix.js';
57
105
  * @param {RenderOptions} options
58
106
  */
59
107
  export function normalizeOptions(matrix, options = {}) {
60
- const scale = Math.max(1, Math.floor(options.scale ?? 8));
61
- const margin = Math.max(0, Math.floor(options.margin ?? 4));
108
+ validateMatrixDimensions(matrix);
109
+ const scale = boundedInteger(options.scale, 'scale', 8, 1, MAX_RENDER_SCALE);
110
+ const margin = boundedInteger(options.margin, 'margin', 4, 0, MAX_RENDER_MARGIN);
62
111
  const dark = options.dark ?? '#000000';
63
112
  const light = options.light ?? '#ffffff';
64
- const barHeight = options.barHeight ?? null;
113
+ const barHeight = options.barHeight == null
114
+ ? null
115
+ : boundedInteger(options.barHeight, 'barHeight', 1, 1, MAX_RENDER_BAR_HEIGHT);
65
116
 
66
- let base = matrix;
67
117
  const is1D = matrix.height === 1;
118
+ const targetPixels = is1D
119
+ ? barHeight ?? Math.max(40, Math.round(matrix.width * scale * 0.15))
120
+ : null;
121
+ const rows = is1D ? Math.max(1, Math.round(targetPixels / scale)) : matrix.height;
122
+ const sourceWidth = matrix.width + margin * 2;
123
+ const sourceHeight = rows + margin * 2;
124
+ validatePixelDimensions(sourceWidth * scale, sourceHeight * scale);
125
+
126
+ let base = matrix;
68
127
 
69
128
  if (is1D) {
70
129
  // Default to a bar height that stays scannable: tall enough that a laser
71
130
  // crossing at a slight angle still passes through the whole symbol.
72
- const targetPixels = barHeight ?? Math.max(40, Math.round(matrix.width * scale * 0.15));
73
- const rows = Math.max(1, Math.round(targetPixels / scale));
74
131
  base = new BitMatrix(matrix.width, rows);
75
132
  for (let x = 0; x < matrix.width; x++) {
76
133
  if (!matrix.get(x, 0)) continue;