@dx-do/client 6.3.4 → 6.4.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.
package/dist/index.cjs.js CHANGED
@@ -2036,7 +2036,7 @@ utils$3.reduceDescriptors(AxiosHeaders$1.prototype, ({ value }, key) => {
2036
2036
 
2037
2037
  utils$3.freezeMethods(AxiosHeaders$1);
2038
2038
 
2039
- const REDACTED = '[REDACTED ****]';
2039
+ const REDACTED$1 = '[REDACTED ****]';
2040
2040
 
2041
2041
  function hasOwnOrPrototypeToJSON(source) {
2042
2042
  if (utils$3.hasOwnProp(source, 'toJSON')) {
@@ -2091,7 +2091,7 @@ function redactConfig(config, redactKeys) {
2091
2091
 
2092
2092
  result = Object.create(null);
2093
2093
  for (const [key, value] of Object.entries(source)) {
2094
- const reducedValue = lowerKeys.has(key.toLowerCase()) ? REDACTED : visit(value);
2094
+ const reducedValue = lowerKeys.has(key.toLowerCase()) ? REDACTED$1 : visit(value);
2095
2095
  if (!utils$3.isUndefined(reducedValue)) {
2096
2096
  result[key] = reducedValue;
2097
2097
  }
@@ -14712,6 +14712,18 @@ var setToStringTag = esSetTostringtag;
14712
14712
  var hasOwn$1 = hasown;
14713
14713
  var populate = populate$1;
14714
14714
 
14715
+ /**
14716
+ * Escape CR, LF, and `"` in a multipart `name`/`filename` parameter, so a field
14717
+ * name or filename can not break out of its header line to inject headers or
14718
+ * smuggle additional parts. Matches the WHATWG HTML multipart/form-data encoding.
14719
+ *
14720
+ * @param {string} str - the parameter value to escape
14721
+ * @returns {string} the escaped value
14722
+ */
14723
+ function escapeHeaderParam(str) {
14724
+ return String(str).replace(/\r/g, '%0D').replace(/\n/g, '%0A').replace(/"/g, '%22');
14725
+ }
14726
+
14715
14727
  /**
14716
14728
  * Create readable "multipart/form-data" streams.
14717
14729
  * Can be used to submit forms
@@ -14877,7 +14889,7 @@ FormData$1.prototype._multiPartHeader = function (field, value, options) {
14877
14889
  var contents = '';
14878
14890
  var headers = {
14879
14891
  // add custom disposition as third element or keep it two elements if not
14880
- 'Content-Disposition': ['form-data', 'name="' + field + '"'].concat(contentDisposition || []),
14892
+ 'Content-Disposition': ['form-data', 'name="' + escapeHeaderParam(field) + '"'].concat(contentDisposition || []),
14881
14893
  // if no content type. allow it to be empty array
14882
14894
  'Content-Type': [].concat(contentType || [])
14883
14895
  };
@@ -14931,7 +14943,7 @@ FormData$1.prototype._getContentDisposition = function (value, options) { // esl
14931
14943
  }
14932
14944
 
14933
14945
  if (filename) {
14934
- return 'filename="' + filename + '"';
14946
+ return 'filename="' + escapeHeaderParam(filename) + '"';
14935
14947
  }
14936
14948
  };
14937
14949
 
@@ -61955,6 +61967,99 @@ class StrictMap extends Map {
61955
61967
  }
61956
61968
  }
61957
61969
 
61970
+ /**
61971
+ * Redaction helpers for keeping auth tokens and other secrets out of logs.
61972
+ *
61973
+ * Auth tokens live in axios `error.config.headers` — e.g. `Authorization: Bearer
61974
+ * <userToken>` and `X-Asm-Auth: <dxASMToken>`. Anything that serializes a whole
61975
+ * error / config / headers object (`JSON.stringify`, an AxiosError's `.toJSON()`,
61976
+ * `console.log(err)`, or a logger that deep-prints its argument) will spill them.
61977
+ * These helpers strip the secrets before anything is logged or re-thrown.
61978
+ */
61979
+ const REDACTED = '[REDACTED]';
61980
+ /** Lowercased object-key names whose values must never be logged. */
61981
+ const SENSITIVE_KEYS = new Set([
61982
+ 'authorization',
61983
+ 'x-asm-auth',
61984
+ 'cookie',
61985
+ 'set-cookie',
61986
+ 'proxy-authorization',
61987
+ 'usertoken',
61988
+ 'dxasmtoken',
61989
+ 'tenanttoken',
61990
+ 'agenttoken',
61991
+ 'accesstoken',
61992
+ 'refreshtoken',
61993
+ 'password',
61994
+ 'apikey',
61995
+ 'x-api-key',
61996
+ ]);
61997
+ /** Matches `Bearer <token>` anywhere in a string. */
61998
+ const BEARER = /Bearer\s+\S+/gi;
61999
+ function redactString(value) {
62000
+ return value.replace(BEARER, `Bearer ${REDACTED}`);
62001
+ }
62002
+ /**
62003
+ * Returns a deep copy of `value` with sensitive object keys masked and any
62004
+ * `Bearer <token>` substrings scrubbed. Safe to pass anything: it is
62005
+ * circular-reference safe (axios errors are circular), and `Error` instances are
62006
+ * reduced to `{ name, message, stack }` (all scrubbed) so their token-bearing
62007
+ * `config`/`request`/`response` are dropped entirely.
62008
+ */
62009
+ function redactValue(value, seen = new WeakSet()) {
62010
+ if (typeof value === 'string')
62011
+ return redactString(value);
62012
+ if (value === null || typeof value !== 'object')
62013
+ return value;
62014
+ if (seen.has(value))
62015
+ return '[Circular]';
62016
+ seen.add(value);
62017
+ if (Array.isArray(value)) {
62018
+ return value.map((entry) => redactValue(entry, seen));
62019
+ }
62020
+ if (value instanceof Error) {
62021
+ return {
62022
+ name: value.name,
62023
+ message: redactString(value.message),
62024
+ stack: value.stack ? redactString(value.stack) : undefined,
62025
+ };
62026
+ }
62027
+ const out = {};
62028
+ for (const [key, entry] of Object.entries(value)) {
62029
+ out[key] = SENSITIVE_KEYS.has(key.toLowerCase())
62030
+ ? REDACTED
62031
+ : redactValue(entry, seen);
62032
+ }
62033
+ return out;
62034
+ }
62035
+ /**
62036
+ * Strips auth tokens from an (possibly Axios) error IN PLACE and returns it, so it
62037
+ * stays a throwable `Error` with its stack intact but no longer carries the token
62038
+ * in `config.headers`, `response.config.headers`, or the raw request header string.
62039
+ * Non-error / non-axios values are returned unchanged.
62040
+ *
62041
+ * Use this at the axios boundary (a response error interceptor) so every catch,
62042
+ * re-throw, and top-level handler downstream sees a token-free error.
62043
+ */
62044
+ function sanitizeAxiosError(err) {
62045
+ if (!err || typeof err !== 'object')
62046
+ return err;
62047
+ const e = err;
62048
+ // Only touch things that look like axios errors.
62049
+ if (!e.isAxiosError && !e.config && !e.response)
62050
+ return err;
62051
+ if (e.config?.headers)
62052
+ e.config.headers = redactValue(e.config.headers);
62053
+ if (e.response?.config?.headers) {
62054
+ e.response.config.headers = redactValue(e.response.config.headers);
62055
+ }
62056
+ // Node's ClientRequest keeps the raw request (incl. the Authorization line) in `_header`.
62057
+ if (e.request && typeof e.request._header === 'string') {
62058
+ e.request._header = redactString(e.request._header);
62059
+ }
62060
+ return err;
62061
+ }
62062
+
61958
62063
  /**
61959
62064
  * Service for APM agents: query by name/regex, list names/statuses, traces,
61960
62065
  * thread dumps, and license/device reporting.
@@ -64597,12 +64702,15 @@ class AsmService {
64597
64702
  baseURL =
64598
64703
  this.dxSaaSService.dxdoConfiguration.dxConfiguration.dxASMbaseAPIURL;
64599
64704
  }
64600
- this.localClient = createClient({
64705
+ const client = createClient({
64601
64706
  baseURL,
64602
64707
  headers: {
64603
64708
  'X-Asm-Auth': this.dxSaaSService.dxdoConfiguration.dxConfiguration.dxASMToken,
64604
64709
  },
64605
64710
  });
64711
+ // Strip the X-Asm-Auth token from any error before it can be logged/propagated.
64712
+ client.instance.interceptors.response.use((response) => response, (error) => Promise.reject(sanitizeAxiosError(error)));
64713
+ this.localClient = client;
64606
64714
  }
64607
64715
  return this.localClient;
64608
64716
  }
@@ -64615,7 +64723,7 @@ class AsmService {
64615
64723
  return foldersList.data;
64616
64724
  })
64617
64725
  .catch((error) => {
64618
- this.log.error(error);
64726
+ this.log.error('ASM foldersList failed', error instanceof Error ? error.message : String(error));
64619
64727
  throw error;
64620
64728
  });
64621
64729
  }
@@ -67352,56 +67460,47 @@ const coerce$1 = (version, options) => {
67352
67460
  };
67353
67461
  var coerce_1 = coerce$1;
67354
67462
 
67355
- var lrucache;
67356
- var hasRequiredLrucache;
67357
-
67358
- function requireLrucache () {
67359
- if (hasRequiredLrucache) return lrucache;
67360
- hasRequiredLrucache = 1;
67361
-
67362
- class LRUCache {
67363
- constructor () {
67364
- this.max = 1000;
67365
- this.map = new Map();
67366
- }
67367
-
67368
- get (key) {
67369
- const value = this.map.get(key);
67370
- if (value === undefined) {
67371
- return undefined
67372
- } else {
67373
- // Remove the key from the map and add it to the end
67374
- this.map.delete(key);
67375
- this.map.set(key, value);
67376
- return value
67377
- }
67378
- }
67463
+ class LRUCache {
67464
+ constructor () {
67465
+ this.max = 1000;
67466
+ this.map = new Map();
67467
+ }
67379
67468
 
67380
- delete (key) {
67381
- return this.map.delete(key)
67382
- }
67469
+ get (key) {
67470
+ const value = this.map.get(key);
67471
+ if (value === undefined) {
67472
+ return undefined
67473
+ } else {
67474
+ // Remove the key from the map and add it to the end
67475
+ this.map.delete(key);
67476
+ this.map.set(key, value);
67477
+ return value
67478
+ }
67479
+ }
67383
67480
 
67384
- set (key, value) {
67385
- const deleted = this.delete(key);
67481
+ delete (key) {
67482
+ return this.map.delete(key)
67483
+ }
67386
67484
 
67387
- if (!deleted && value !== undefined) {
67388
- // If cache is full, delete the least recently used item
67389
- if (this.map.size >= this.max) {
67390
- const firstKey = this.map.keys().next().value;
67391
- this.delete(firstKey);
67392
- }
67485
+ set (key, value) {
67486
+ const deleted = this.delete(key);
67393
67487
 
67394
- this.map.set(key, value);
67395
- }
67488
+ if (!deleted && value !== undefined) {
67489
+ // If cache is full, delete the least recently used item
67490
+ if (this.map.size >= this.max) {
67491
+ const firstKey = this.map.keys().next().value;
67492
+ this.delete(firstKey);
67493
+ }
67396
67494
 
67397
- return this
67398
- }
67399
- }
67495
+ this.map.set(key, value);
67496
+ }
67400
67497
 
67401
- lrucache = LRUCache;
67402
- return lrucache;
67498
+ return this
67499
+ }
67403
67500
  }
67404
67501
 
67502
+ var lrucache = LRUCache;
67503
+
67405
67504
  var range;
67406
67505
  var hasRequiredRange;
67407
67506
 
@@ -67623,7 +67722,7 @@ function requireRange () {
67623
67722
 
67624
67723
  range = Range;
67625
67724
 
67626
- const LRU = requireLrucache();
67725
+ const LRU = lrucache;
67627
67726
  const cache = new LRU();
67628
67727
 
67629
67728
  const parseOptions = parseOptions_1;
@@ -70251,6 +70350,9 @@ class DxSaasService {
70251
70350
  .replace(/-/g, '')
70252
70351
  .toLowerCase();
70253
70352
  this.initializeProxyIfNecessary();
70353
+ // Strip auth tokens from any error this client surfaces, so nothing downstream
70354
+ // (handleAxiosError, re-throws, top-level catches) can log the Authorization header.
70355
+ this._httpClient.interceptors.response.use((response) => response, (error) => Promise.reject(sanitizeAxiosError(error)));
70254
70356
  if (process__namespace.env.DXDO_LOG_REQUESTS) {
70255
70357
  log.info('Logging requests');
70256
70358
  const requestLogFile = process__namespace.env.DXDO_REQUEST_LOG_FILE ??
@@ -71210,20 +71312,21 @@ class DxSaasService {
71210
71312
  }
71211
71313
  static handleAxiosError(reason, isStream = false) {
71212
71314
  if (reason.isAxiosError && reason.response) {
71213
- DxSaasService.staticLog.debug('[Axios Error DEBUG]', reason.toJSON());
71214
- DxSaasService.staticLog.error('[Axios Error BASIC]', `${reason.request.method} ${reason.config?.url} ${JSON.stringify(reason.config?.params ?? {})}`);
71215
- DxSaasService.staticLog.error('[Axios Error HEADER]', `${JSON.stringify(reason.config?.headers)}`);
71216
- DxSaasService.staticLog.error('[Axios Error BODY]', reason.config?.data ?? {});
71315
+ // NB: request headers are already token-scrubbed by the response interceptor;
71316
+ // redactValue is belt-and-suspenders in case this is ever called off-path.
71317
+ DxSaasService.staticLog.debug('[Axios Error DEBUG]', redactValue(reason.toJSON()));
71318
+ DxSaasService.staticLog.error('[Axios Error BASIC]', `${reason.request.method} ${reason.config?.url} ${JSON.stringify(redactValue(reason.config?.params ?? {}))}`);
71319
+ DxSaasService.staticLog.error('[Axios Error BODY]', redactValue(reason.config?.data ?? {}));
71217
71320
  if (isStream) {
71218
71321
  DxSaasService.staticLog.error(`[Axios Error STATUS] ${reason.response.status} / ${reason.message} `);
71219
71322
  }
71220
71323
  else {
71221
- DxSaasService.staticLog.error(`[Axios Error STATUS] ${reason.response.status} / ${reason.message} / ${JSON.stringify(reason.response.data)}`);
71324
+ DxSaasService.staticLog.error(`[Axios Error STATUS] ${reason.response.status} / ${reason.message} / ${JSON.stringify(redactValue(reason.response.data))}`);
71222
71325
  }
71223
71326
  return new SimpleHTTPError(reason.response.status, reason.message);
71224
71327
  }
71225
71328
  else
71226
- return reason; // stupid.
71329
+ return sanitizeAxiosError(reason); // scrub before propagating
71227
71330
  }
71228
71331
  static async legacyAuth(v3Configuration) {
71229
71332
  return this.legacyAxaRequest(v3Configuration, 'GET', 'ess/security/v1/me', false, false);