@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.esm.js CHANGED
@@ -2014,7 +2014,7 @@ utils$3.reduceDescriptors(AxiosHeaders$1.prototype, ({ value }, key) => {
2014
2014
 
2015
2015
  utils$3.freezeMethods(AxiosHeaders$1);
2016
2016
 
2017
- const REDACTED = '[REDACTED ****]';
2017
+ const REDACTED$1 = '[REDACTED ****]';
2018
2018
 
2019
2019
  function hasOwnOrPrototypeToJSON(source) {
2020
2020
  if (utils$3.hasOwnProp(source, 'toJSON')) {
@@ -2069,7 +2069,7 @@ function redactConfig(config, redactKeys) {
2069
2069
 
2070
2070
  result = Object.create(null);
2071
2071
  for (const [key, value] of Object.entries(source)) {
2072
- const reducedValue = lowerKeys.has(key.toLowerCase()) ? REDACTED : visit(value);
2072
+ const reducedValue = lowerKeys.has(key.toLowerCase()) ? REDACTED$1 : visit(value);
2073
2073
  if (!utils$3.isUndefined(reducedValue)) {
2074
2074
  result[key] = reducedValue;
2075
2075
  }
@@ -14690,6 +14690,18 @@ var setToStringTag = esSetTostringtag;
14690
14690
  var hasOwn$1 = hasown;
14691
14691
  var populate = populate$1;
14692
14692
 
14693
+ /**
14694
+ * Escape CR, LF, and `"` in a multipart `name`/`filename` parameter, so a field
14695
+ * name or filename can not break out of its header line to inject headers or
14696
+ * smuggle additional parts. Matches the WHATWG HTML multipart/form-data encoding.
14697
+ *
14698
+ * @param {string} str - the parameter value to escape
14699
+ * @returns {string} the escaped value
14700
+ */
14701
+ function escapeHeaderParam(str) {
14702
+ return String(str).replace(/\r/g, '%0D').replace(/\n/g, '%0A').replace(/"/g, '%22');
14703
+ }
14704
+
14693
14705
  /**
14694
14706
  * Create readable "multipart/form-data" streams.
14695
14707
  * Can be used to submit forms
@@ -14855,7 +14867,7 @@ FormData$2.prototype._multiPartHeader = function (field, value, options) {
14855
14867
  var contents = '';
14856
14868
  var headers = {
14857
14869
  // add custom disposition as third element or keep it two elements if not
14858
- 'Content-Disposition': ['form-data', 'name="' + field + '"'].concat(contentDisposition || []),
14870
+ 'Content-Disposition': ['form-data', 'name="' + escapeHeaderParam(field) + '"'].concat(contentDisposition || []),
14859
14871
  // if no content type. allow it to be empty array
14860
14872
  'Content-Type': [].concat(contentType || [])
14861
14873
  };
@@ -14909,7 +14921,7 @@ FormData$2.prototype._getContentDisposition = function (value, options) { // esl
14909
14921
  }
14910
14922
 
14911
14923
  if (filename) {
14912
- return 'filename="' + filename + '"';
14924
+ return 'filename="' + escapeHeaderParam(filename) + '"';
14913
14925
  }
14914
14926
  };
14915
14927
 
@@ -61933,6 +61945,99 @@ class StrictMap extends Map {
61933
61945
  }
61934
61946
  }
61935
61947
 
61948
+ /**
61949
+ * Redaction helpers for keeping auth tokens and other secrets out of logs.
61950
+ *
61951
+ * Auth tokens live in axios `error.config.headers` — e.g. `Authorization: Bearer
61952
+ * <userToken>` and `X-Asm-Auth: <dxASMToken>`. Anything that serializes a whole
61953
+ * error / config / headers object (`JSON.stringify`, an AxiosError's `.toJSON()`,
61954
+ * `console.log(err)`, or a logger that deep-prints its argument) will spill them.
61955
+ * These helpers strip the secrets before anything is logged or re-thrown.
61956
+ */
61957
+ const REDACTED = '[REDACTED]';
61958
+ /** Lowercased object-key names whose values must never be logged. */
61959
+ const SENSITIVE_KEYS = new Set([
61960
+ 'authorization',
61961
+ 'x-asm-auth',
61962
+ 'cookie',
61963
+ 'set-cookie',
61964
+ 'proxy-authorization',
61965
+ 'usertoken',
61966
+ 'dxasmtoken',
61967
+ 'tenanttoken',
61968
+ 'agenttoken',
61969
+ 'accesstoken',
61970
+ 'refreshtoken',
61971
+ 'password',
61972
+ 'apikey',
61973
+ 'x-api-key',
61974
+ ]);
61975
+ /** Matches `Bearer <token>` anywhere in a string. */
61976
+ const BEARER = /Bearer\s+\S+/gi;
61977
+ function redactString(value) {
61978
+ return value.replace(BEARER, `Bearer ${REDACTED}`);
61979
+ }
61980
+ /**
61981
+ * Returns a deep copy of `value` with sensitive object keys masked and any
61982
+ * `Bearer <token>` substrings scrubbed. Safe to pass anything: it is
61983
+ * circular-reference safe (axios errors are circular), and `Error` instances are
61984
+ * reduced to `{ name, message, stack }` (all scrubbed) so their token-bearing
61985
+ * `config`/`request`/`response` are dropped entirely.
61986
+ */
61987
+ function redactValue(value, seen = new WeakSet()) {
61988
+ if (typeof value === 'string')
61989
+ return redactString(value);
61990
+ if (value === null || typeof value !== 'object')
61991
+ return value;
61992
+ if (seen.has(value))
61993
+ return '[Circular]';
61994
+ seen.add(value);
61995
+ if (Array.isArray(value)) {
61996
+ return value.map((entry) => redactValue(entry, seen));
61997
+ }
61998
+ if (value instanceof Error) {
61999
+ return {
62000
+ name: value.name,
62001
+ message: redactString(value.message),
62002
+ stack: value.stack ? redactString(value.stack) : undefined,
62003
+ };
62004
+ }
62005
+ const out = {};
62006
+ for (const [key, entry] of Object.entries(value)) {
62007
+ out[key] = SENSITIVE_KEYS.has(key.toLowerCase())
62008
+ ? REDACTED
62009
+ : redactValue(entry, seen);
62010
+ }
62011
+ return out;
62012
+ }
62013
+ /**
62014
+ * Strips auth tokens from an (possibly Axios) error IN PLACE and returns it, so it
62015
+ * stays a throwable `Error` with its stack intact but no longer carries the token
62016
+ * in `config.headers`, `response.config.headers`, or the raw request header string.
62017
+ * Non-error / non-axios values are returned unchanged.
62018
+ *
62019
+ * Use this at the axios boundary (a response error interceptor) so every catch,
62020
+ * re-throw, and top-level handler downstream sees a token-free error.
62021
+ */
62022
+ function sanitizeAxiosError(err) {
62023
+ if (!err || typeof err !== 'object')
62024
+ return err;
62025
+ const e = err;
62026
+ // Only touch things that look like axios errors.
62027
+ if (!e.isAxiosError && !e.config && !e.response)
62028
+ return err;
62029
+ if (e.config?.headers)
62030
+ e.config.headers = redactValue(e.config.headers);
62031
+ if (e.response?.config?.headers) {
62032
+ e.response.config.headers = redactValue(e.response.config.headers);
62033
+ }
62034
+ // Node's ClientRequest keeps the raw request (incl. the Authorization line) in `_header`.
62035
+ if (e.request && typeof e.request._header === 'string') {
62036
+ e.request._header = redactString(e.request._header);
62037
+ }
62038
+ return err;
62039
+ }
62040
+
61936
62041
  /**
61937
62042
  * Service for APM agents: query by name/regex, list names/statuses, traces,
61938
62043
  * thread dumps, and license/device reporting.
@@ -64575,12 +64680,15 @@ class AsmService {
64575
64680
  baseURL =
64576
64681
  this.dxSaaSService.dxdoConfiguration.dxConfiguration.dxASMbaseAPIURL;
64577
64682
  }
64578
- this.localClient = createClient({
64683
+ const client = createClient({
64579
64684
  baseURL,
64580
64685
  headers: {
64581
64686
  'X-Asm-Auth': this.dxSaaSService.dxdoConfiguration.dxConfiguration.dxASMToken,
64582
64687
  },
64583
64688
  });
64689
+ // Strip the X-Asm-Auth token from any error before it can be logged/propagated.
64690
+ client.instance.interceptors.response.use((response) => response, (error) => Promise.reject(sanitizeAxiosError(error)));
64691
+ this.localClient = client;
64584
64692
  }
64585
64693
  return this.localClient;
64586
64694
  }
@@ -64593,7 +64701,7 @@ class AsmService {
64593
64701
  return foldersList.data;
64594
64702
  })
64595
64703
  .catch((error) => {
64596
- this.log.error(error);
64704
+ this.log.error('ASM foldersList failed', error instanceof Error ? error.message : String(error));
64597
64705
  throw error;
64598
64706
  });
64599
64707
  }
@@ -67330,56 +67438,47 @@ const coerce$1 = (version, options) => {
67330
67438
  };
67331
67439
  var coerce_1 = coerce$1;
67332
67440
 
67333
- var lrucache;
67334
- var hasRequiredLrucache;
67335
-
67336
- function requireLrucache () {
67337
- if (hasRequiredLrucache) return lrucache;
67338
- hasRequiredLrucache = 1;
67339
-
67340
- class LRUCache {
67341
- constructor () {
67342
- this.max = 1000;
67343
- this.map = new Map();
67344
- }
67345
-
67346
- get (key) {
67347
- const value = this.map.get(key);
67348
- if (value === undefined) {
67349
- return undefined
67350
- } else {
67351
- // Remove the key from the map and add it to the end
67352
- this.map.delete(key);
67353
- this.map.set(key, value);
67354
- return value
67355
- }
67356
- }
67441
+ class LRUCache {
67442
+ constructor () {
67443
+ this.max = 1000;
67444
+ this.map = new Map();
67445
+ }
67357
67446
 
67358
- delete (key) {
67359
- return this.map.delete(key)
67360
- }
67447
+ get (key) {
67448
+ const value = this.map.get(key);
67449
+ if (value === undefined) {
67450
+ return undefined
67451
+ } else {
67452
+ // Remove the key from the map and add it to the end
67453
+ this.map.delete(key);
67454
+ this.map.set(key, value);
67455
+ return value
67456
+ }
67457
+ }
67361
67458
 
67362
- set (key, value) {
67363
- const deleted = this.delete(key);
67459
+ delete (key) {
67460
+ return this.map.delete(key)
67461
+ }
67364
67462
 
67365
- if (!deleted && value !== undefined) {
67366
- // If cache is full, delete the least recently used item
67367
- if (this.map.size >= this.max) {
67368
- const firstKey = this.map.keys().next().value;
67369
- this.delete(firstKey);
67370
- }
67463
+ set (key, value) {
67464
+ const deleted = this.delete(key);
67371
67465
 
67372
- this.map.set(key, value);
67373
- }
67466
+ if (!deleted && value !== undefined) {
67467
+ // If cache is full, delete the least recently used item
67468
+ if (this.map.size >= this.max) {
67469
+ const firstKey = this.map.keys().next().value;
67470
+ this.delete(firstKey);
67471
+ }
67374
67472
 
67375
- return this
67376
- }
67377
- }
67473
+ this.map.set(key, value);
67474
+ }
67378
67475
 
67379
- lrucache = LRUCache;
67380
- return lrucache;
67476
+ return this
67477
+ }
67381
67478
  }
67382
67479
 
67480
+ var lrucache = LRUCache;
67481
+
67383
67482
  var range;
67384
67483
  var hasRequiredRange;
67385
67484
 
@@ -67601,7 +67700,7 @@ function requireRange () {
67601
67700
 
67602
67701
  range = Range;
67603
67702
 
67604
- const LRU = requireLrucache();
67703
+ const LRU = lrucache;
67605
67704
  const cache = new LRU();
67606
67705
 
67607
67706
  const parseOptions = parseOptions_1;
@@ -70229,6 +70328,9 @@ class DxSaasService {
70229
70328
  .replace(/-/g, '')
70230
70329
  .toLowerCase();
70231
70330
  this.initializeProxyIfNecessary();
70331
+ // Strip auth tokens from any error this client surfaces, so nothing downstream
70332
+ // (handleAxiosError, re-throws, top-level catches) can log the Authorization header.
70333
+ this._httpClient.interceptors.response.use((response) => response, (error) => Promise.reject(sanitizeAxiosError(error)));
70232
70334
  if (process$1.env.DXDO_LOG_REQUESTS) {
70233
70335
  log.info('Logging requests');
70234
70336
  const requestLogFile = process$1.env.DXDO_REQUEST_LOG_FILE ??
@@ -71188,20 +71290,21 @@ class DxSaasService {
71188
71290
  }
71189
71291
  static handleAxiosError(reason, isStream = false) {
71190
71292
  if (reason.isAxiosError && reason.response) {
71191
- DxSaasService.staticLog.debug('[Axios Error DEBUG]', reason.toJSON());
71192
- DxSaasService.staticLog.error('[Axios Error BASIC]', `${reason.request.method} ${reason.config?.url} ${JSON.stringify(reason.config?.params ?? {})}`);
71193
- DxSaasService.staticLog.error('[Axios Error HEADER]', `${JSON.stringify(reason.config?.headers)}`);
71194
- DxSaasService.staticLog.error('[Axios Error BODY]', reason.config?.data ?? {});
71293
+ // NB: request headers are already token-scrubbed by the response interceptor;
71294
+ // redactValue is belt-and-suspenders in case this is ever called off-path.
71295
+ DxSaasService.staticLog.debug('[Axios Error DEBUG]', redactValue(reason.toJSON()));
71296
+ DxSaasService.staticLog.error('[Axios Error BASIC]', `${reason.request.method} ${reason.config?.url} ${JSON.stringify(redactValue(reason.config?.params ?? {}))}`);
71297
+ DxSaasService.staticLog.error('[Axios Error BODY]', redactValue(reason.config?.data ?? {}));
71195
71298
  if (isStream) {
71196
71299
  DxSaasService.staticLog.error(`[Axios Error STATUS] ${reason.response.status} / ${reason.message} `);
71197
71300
  }
71198
71301
  else {
71199
- DxSaasService.staticLog.error(`[Axios Error STATUS] ${reason.response.status} / ${reason.message} / ${JSON.stringify(reason.response.data)}`);
71302
+ DxSaasService.staticLog.error(`[Axios Error STATUS] ${reason.response.status} / ${reason.message} / ${JSON.stringify(redactValue(reason.response.data))}`);
71200
71303
  }
71201
71304
  return new SimpleHTTPError(reason.response.status, reason.message);
71202
71305
  }
71203
71306
  else
71204
- return reason; // stupid.
71307
+ return sanitizeAxiosError(reason); // scrub before propagating
71205
71308
  }
71206
71309
  static async legacyAuth(v3Configuration) {
71207
71310
  return this.legacyAxaRequest(v3Configuration, 'GET', 'ess/security/v1/me', false, false);