dicom-curate 0.40.2 → 0.40.3

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.
@@ -86462,6 +86462,9 @@ var md5 = /* @__PURE__ */ createHasher(() => new _MD5());
86462
86462
  // src/hash.ts
86463
86463
  var import_js_crc = __toESM(require_crc(), 1);
86464
86464
  var DEFAULT_HASH_PART_SIZE = 5 * 1024 * 1024;
86465
+ function phiSafeToken(input) {
86466
+ return bytesToHex(sha256(new TextEncoder().encode(input))).slice(0, 16);
86467
+ }
86465
86468
  async function hashStream(stream, hashMethod, hashPartSize) {
86466
86469
  switch (hashMethod) {
86467
86470
  case "sha256":
@@ -86830,7 +86833,7 @@ async function curateOne({
86830
86833
  // fileInfo, keeping it in the private log. See #283.
86831
86834
  anomalies: [`Could not parse file as DICOM data`],
86832
86835
  errors: [`File is not a valid DICOM file or is corrupted`],
86833
- sourceInstanceUID: `invalid_${fileInfo.name.replace(/[^a-zA-Z0-9]/g, "_")}`,
86836
+ sourceInstanceUID: `invalid_${phiSafeToken(`${fileInfo.path}/${fileInfo.name}`)}`,
86834
86837
  fileInfo: {
86835
86838
  name: fileInfo.name,
86836
86839
  size: fileInfo.size,
@@ -80171,6 +80171,9 @@ var md5 = /* @__PURE__ */ createHasher(() => new _MD5());
80171
80171
  // src/hash.ts
80172
80172
  var import_js_crc = __toESM(require_crc(), 1);
80173
80173
  var DEFAULT_HASH_PART_SIZE = 5 * 1024 * 1024;
80174
+ function phiSafeToken(input) {
80175
+ return bytesToHex(sha256(new TextEncoder().encode(input))).slice(0, 16);
80176
+ }
80174
80177
  async function hashStream(stream, hashMethod, hashPartSize) {
80175
80178
  switch (hashMethod) {
80176
80179
  case "sha256":
@@ -80539,7 +80542,7 @@ async function curateOne({
80539
80542
  // fileInfo, keeping it in the private log. See #283.
80540
80543
  anomalies: [`Could not parse file as DICOM data`],
80541
80544
  errors: [`File is not a valid DICOM file or is corrupted`],
80542
- sourceInstanceUID: `invalid_${fileInfo.name.replace(/[^a-zA-Z0-9]/g, "_")}`,
80545
+ sourceInstanceUID: `invalid_${phiSafeToken(`${fileInfo.path}/${fileInfo.name}`)}`,
80543
80546
  fileInfo: {
80544
80547
  name: fileInfo.name,
80545
80548
  size: fileInfo.size,
package/dist/esm/hash.js CHANGED
@@ -800,6 +800,9 @@ var sha256 = /* @__PURE__ */ createHasher(
800
800
  // src/hash.ts
801
801
  var import_js_crc = __toESM(require_crc(), 1);
802
802
  var DEFAULT_HASH_PART_SIZE = 5 * 1024 * 1024;
803
+ function phiSafeToken(input) {
804
+ return bytesToHex(sha256(new TextEncoder().encode(input))).slice(0, 16);
805
+ }
803
806
  async function hash(buffer, hashMethod, hashPartSize) {
804
807
  switch (hashMethod) {
805
808
  case "sha256":
@@ -975,7 +978,8 @@ function crc64Hex(input) {
975
978
  }
976
979
  export {
977
980
  hash,
978
- hashStream
981
+ hashStream,
982
+ phiSafeToken
979
983
  };
980
984
  /*! Bundled license information:
981
985
 
package/dist/esm/index.js CHANGED
@@ -88025,6 +88025,9 @@ var md5 = /* @__PURE__ */ createHasher(() => new _MD5());
88025
88025
  // src/hash.ts
88026
88026
  var import_js_crc = __toESM(require_crc(), 1);
88027
88027
  var DEFAULT_HASH_PART_SIZE = 5 * 1024 * 1024;
88028
+ function phiSafeToken(input) {
88029
+ return bytesToHex(sha256(new TextEncoder().encode(input))).slice(0, 16);
88030
+ }
88028
88031
  async function hash(buffer, hashMethod, hashPartSize) {
88029
88032
  switch (hashMethod) {
88030
88033
  case "sha256":
@@ -88433,7 +88436,7 @@ async function curateOne({
88433
88436
  // fileInfo, keeping it in the private log. See #283.
88434
88437
  anomalies: [`Could not parse file as DICOM data`],
88435
88438
  errors: [`File is not a valid DICOM file or is corrupted`],
88436
- sourceInstanceUID: `invalid_${fileInfo.name.replace(/[^a-zA-Z0-9]/g, "_")}`,
88439
+ sourceInstanceUID: `invalid_${phiSafeToken(`${fileInfo.path}/${fileInfo.name}`)}`,
88437
88440
  fileInfo: {
88438
88441
  name: fileInfo.name,
88439
88442
  size: fileInfo.size,
@@ -94710,7 +94713,9 @@ async function dispatchMappingJobs() {
94710
94713
  const hasReadErrors = !!errors && errors.length > 0;
94711
94714
  if (hasReadErrors) {
94712
94715
  const scanErrorResult = {
94713
- sourceInstanceUID: `scan_${fileInfo.name.replace(/[^a-zA-Z0-9]/g, "_")}`,
94716
+ // phiSafeToken rather than the sanitised name: this result reaches
94717
+ // the server-bound log, so the UID must not encode the filename.
94718
+ sourceInstanceUID: `scan_${phiSafeToken(`${fileInfo.path}/${fileInfo.name}`)}`,
94714
94719
  // Intentionally no outputFilePath: an unread file has no
94715
94720
  // de-identified output path, and we must not place the raw input
94716
94721
  // path in the server-bound channel.
@@ -86462,6 +86462,9 @@ var md5 = /* @__PURE__ */ createHasher(() => new _MD5());
86462
86462
  // src/hash.ts
86463
86463
  var import_js_crc = __toESM(require_crc(), 1);
86464
86464
  var DEFAULT_HASH_PART_SIZE = 5 * 1024 * 1024;
86465
+ function phiSafeToken(input) {
86466
+ return bytesToHex(sha256(new TextEncoder().encode(input))).slice(0, 16);
86467
+ }
86465
86468
  async function hashStream(stream, hashMethod, hashPartSize) {
86466
86469
  switch (hashMethod) {
86467
86470
  case "sha256":
@@ -86830,7 +86833,7 @@ async function curateOne({
86830
86833
  // fileInfo, keeping it in the private log. See #283.
86831
86834
  anomalies: [`Could not parse file as DICOM data`],
86832
86835
  errors: [`File is not a valid DICOM file or is corrupted`],
86833
- sourceInstanceUID: `invalid_${fileInfo.name.replace(/[^a-zA-Z0-9]/g, "_")}`,
86836
+ sourceInstanceUID: `invalid_${phiSafeToken(`${fileInfo.path}/${fileInfo.name}`)}`,
86834
86837
  fileInfo: {
86835
86838
  name: fileInfo.name,
86836
86839
  size: fileInfo.size,
@@ -93110,7 +93113,9 @@ async function dispatchMappingJobs() {
93110
93113
  const hasReadErrors = !!errors && errors.length > 0;
93111
93114
  if (hasReadErrors) {
93112
93115
  const scanErrorResult = {
93113
- sourceInstanceUID: `scan_${fileInfo.name.replace(/[^a-zA-Z0-9]/g, "_")}`,
93116
+ // phiSafeToken rather than the sanitised name: this result reaches
93117
+ // the server-bound log, so the UID must not encode the filename.
93118
+ sourceInstanceUID: `scan_${phiSafeToken(`${fileInfo.path}/${fileInfo.name}`)}`,
93114
93119
  // Intentionally no outputFilePath: an unread file has no
93115
93120
  // de-identified output path, and we must not place the raw input
93116
93121
  // path in the server-bound channel.
@@ -37137,7 +37137,15 @@ var DEFAULT_EXCLUDED_FILETYPES = [
37137
37137
  ".ds_store"
37138
37138
  ];
37139
37139
  function safeReadErrorMessage(error2) {
37140
- const detail = error2 instanceof Error ? `${error2.name}: ${error2.message}` : String(error2);
37140
+ let detail = "unknown error";
37141
+ if (error2 && typeof error2 === "object") {
37142
+ const { code, name } = error2;
37143
+ if (typeof code === "string" && code) {
37144
+ detail = code;
37145
+ } else if (typeof name === "string" && name) {
37146
+ detail = name;
37147
+ }
37148
+ }
37141
37149
  return `Unable to read file (filesystem error): ${detail}`;
37142
37150
  }
37143
37151
  var keepScanning = true;
@@ -1,4 +1,12 @@
1
1
  import type { THashMethod } from './types';
2
+ /**
3
+ * Derive a short, stable, PHI-safe token from a string (e.g. a file path).
4
+ * Used to build synthetic sourceInstanceUIDs for files that failed to read or
5
+ * parse, where embedding the (sanitised) filename would leak PHI into the
6
+ * server-bound log. Same input always yields the same token, so results stay
7
+ * correlatable across passes and runs.
8
+ */
9
+ export declare function phiSafeToken(input: string): string;
2
10
  export declare function hash(buffer: ArrayBuffer, hashMethod: THashMethod, hashPartSize?: number): Promise<string>;
3
11
  /**
4
12
  * Computes a hash over a stream of data chunks without materializing the full
@@ -54699,6 +54699,16 @@
54699
54699
  var crcExports = requireCrc();
54700
54700
 
54701
54701
  const DEFAULT_HASH_PART_SIZE = 5 * 1024 * 1024; // 5 MB — matches @aws-sdk/lib-storage default
54702
+ /**
54703
+ * Derive a short, stable, PHI-safe token from a string (e.g. a file path).
54704
+ * Used to build synthetic sourceInstanceUIDs for files that failed to read or
54705
+ * parse, where embedding the (sanitised) filename would leak PHI into the
54706
+ * server-bound log. Same input always yields the same token, so results stay
54707
+ * correlatable across passes and runs.
54708
+ */
54709
+ function phiSafeToken(input) {
54710
+ return bytesToHex(sha256(new TextEncoder().encode(input))).slice(0, 16);
54711
+ }
54702
54712
  /**
54703
54713
  * Computes a hash over a stream of data chunks without materializing the full
54704
54714
  * data into memory at once.
@@ -55137,7 +55147,7 @@
55137
55147
  // fileInfo, keeping it in the private log. See #283.
55138
55148
  anomalies: [`Could not parse file as DICOM data`],
55139
55149
  errors: [`File is not a valid DICOM file or is corrupted`],
55140
- sourceInstanceUID: `invalid_${fileInfo.name.replace(/[^a-zA-Z0-9]/g, '_')}`,
55150
+ sourceInstanceUID: `invalid_${phiSafeToken(`${fileInfo.path}/${fileInfo.name}`)}`,
55141
55151
  fileInfo: {
55142
55152
  name: fileInfo.name,
55143
55153
  size: fileInfo.size,
@@ -68864,13 +68874,24 @@
68864
68874
  ];
68865
68875
  /**
68866
68876
  * Build a PHI-safe error message for a file that could not be read during
68867
- * scanning. Intentionally omits the filename/path so the string is safe to
68868
- * place in `errors` (which is shared between the private and server-bound
68869
- * logs). The raw path/name is carried separately in `fileInfo` so it appears
68870
- * only in the private (input) log.
68877
+ * scanning. Only the error code/name is included never `error.message`,
68878
+ * because node fs errors embed the full raw path in the message (e.g.
68879
+ * "ENOENT: no such file or directory, stat '/path/to/file.dcm'") and this
68880
+ * string goes into `errors`, which is shared between the private and
68881
+ * server-bound logs. The raw path/name is carried separately in `fileInfo`
68882
+ * so it appears only in the private (input) log.
68871
68883
  */
68872
68884
  function safeReadErrorMessage(error) {
68873
- const detail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
68885
+ let detail = 'unknown error';
68886
+ if (error && typeof error === 'object') {
68887
+ const { code, name } = error;
68888
+ if (typeof code === 'string' && code) {
68889
+ detail = code;
68890
+ }
68891
+ else if (typeof name === 'string' && name) {
68892
+ detail = name;
68893
+ }
68894
+ }
68874
68895
  return `Unable to read file (filesystem error): ${detail}`;
68875
68896
  }
68876
68897
  let keepScanning = true;
@@ -126358,6 +126379,16 @@
126358
126379
  var crcExports = requireCrc();
126359
126380
 
126360
126381
  const DEFAULT_HASH_PART_SIZE = 5 * 1024 * 1024; // 5 MB — matches @aws-sdk/lib-storage default
126382
+ /**
126383
+ * Derive a short, stable, PHI-safe token from a string (e.g. a file path).
126384
+ * Used to build synthetic sourceInstanceUIDs for files that failed to read or
126385
+ * parse, where embedding the (sanitised) filename would leak PHI into the
126386
+ * server-bound log. Same input always yields the same token, so results stay
126387
+ * correlatable across passes and runs.
126388
+ */
126389
+ function phiSafeToken(input) {
126390
+ return bytesToHex(sha256(new TextEncoder().encode(input))).slice(0, 16);
126391
+ }
126361
126392
  async function hash(buffer, hashMethod, hashPartSize) {
126362
126393
  switch (hashMethod) {
126363
126394
  case 'sha256':
@@ -126857,7 +126888,7 @@
126857
126888
  // fileInfo, keeping it in the private log. See #283.
126858
126889
  anomalies: [`Could not parse file as DICOM data`],
126859
126890
  errors: [`File is not a valid DICOM file or is corrupted`],
126860
- sourceInstanceUID: `invalid_${fileInfo.name.replace(/[^a-zA-Z0-9]/g, '_')}`,
126891
+ sourceInstanceUID: `invalid_${phiSafeToken(`${fileInfo.path}/${fileInfo.name}`)}`,
126861
126892
  fileInfo: {
126862
126893
  name: fileInfo.name,
126863
126894
  size: fileInfo.size,
@@ -140883,7 +140914,9 @@
140883
140914
  const hasReadErrors = !!errors && errors.length > 0;
140884
140915
  if (hasReadErrors) {
140885
140916
  const scanErrorResult = {
140886
- sourceInstanceUID: `scan_${fileInfo.name.replace(/[^a-zA-Z0-9]/g, '_')}`,
140917
+ // phiSafeToken rather than the sanitised name: this result reaches
140918
+ // the server-bound log, so the UID must not encode the filename.
140919
+ sourceInstanceUID: `scan_${phiSafeToken(`${fileInfo.path}/${fileInfo.name}`)}`,
140887
140920
  // Intentionally no outputFilePath: an unread file has no
140888
140921
  // de-identified output path, and we must not place the raw input
140889
140922
  // path in the server-bound channel.