@elastic/synthetics 1.7.2 → 1.9.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.
@@ -25,7 +25,7 @@
25
25
 
26
26
  import { mkdir, rm, readFile } from 'fs/promises';
27
27
  import { extname, join } from 'path';
28
- import { LineCounter, parseDocument, YAMLSeq, YAMLMap } from 'yaml';
28
+ import { LineCounter, parseDocument, Document, YAMLSeq, YAMLMap } from 'yaml';
29
29
  import { bold, red } from 'kleur/colors';
30
30
  import { Bundler } from './bundler';
31
31
  import { SYNTHETICS_PATH, totalist, indent, warn } from '../helpers';
@@ -167,8 +167,8 @@ export async function createLightweightMonitors(
167
167
  ) {
168
168
  const lwFiles = new Set<string>();
169
169
  // Filter monitor files based on the provided pattern
170
- const pattern = options.pattern
171
- ? new RegExp(options.pattern, 'i')
170
+ const pattern = options.grepOpts?.pattern
171
+ ? new RegExp(options.grepOpts?.pattern, 'i')
172
172
  : /.(yml|yaml)$/;
173
173
  const ignore = /(node_modules|.github)/;
174
174
  await totalist(workDir, (rel, abs) => {
@@ -188,8 +188,9 @@ export async function createLightweightMonitors(
188
188
  const lineCounter = new LineCounter();
189
189
  const parsedDoc = parseDocument(content, {
190
190
  lineCounter,
191
+ merge: true,
191
192
  keepSourceTokens: true,
192
- });
193
+ }) as Document.Parsed;
193
194
  // Skip other yml files that are not relevant
194
195
  const monitorSeq = parsedDoc.get('heartbeat.monitors') as YAMLSeq<YAMLMap>;
195
196
  if (!monitorSeq) {
@@ -203,21 +204,38 @@ export async function createLightweightMonitors(
203
204
  warnOnce = true;
204
205
  }
205
206
 
207
+ // Store the offsets of each monitor in the sequence to construct the source
208
+ // location later for capturing the error
209
+ const offsets = [];
206
210
  for (const monNode of monitorSeq.items) {
211
+ offsets.push(monNode.srcToken.offset);
212
+ }
213
+
214
+ const mergedConfig = parsedDoc.toJS()[
215
+ 'heartbeat.monitors'
216
+ ] as Array<MonitorConfig>;
217
+ for (let i = 0; i < mergedConfig.length; i++) {
218
+ const monitor = mergedConfig[i];
207
219
  // Skip browser monitors from the YML files
208
- if (monNode.get('type') === 'browser') {
220
+ if (monitor['type'] === 'browser') {
209
221
  continue;
210
222
  }
211
- const config = monNode.toJSON();
212
- const { line, col } = lineCounter.linePos(monNode.srcToken.offset);
223
+ const { line, col } = lineCounter.linePos(offsets[i]);
213
224
  try {
214
- const mon = buildMonitorFromYaml(config, options);
225
+ /**
226
+ * Build the monitor object from the yaml config along with global configuration
227
+ * and perform the match based on the provided filters
228
+ */
229
+ const mon = buildMonitorFromYaml(monitor, options);
230
+ if (!mon.isMatch(options.grepOpts?.match, options.grepOpts?.tags)) {
231
+ continue;
232
+ }
215
233
  mon.setSource({ file, line, column: col });
216
234
  monitors.push(mon);
217
235
  } catch (e) {
218
236
  let outer = bold(`Aborted: ${e}\n`);
219
237
  outer += indent(
220
- `* ${config.id || config.name} - ${file}:${line}:${col}\n`
238
+ `* ${monitor.id || monitor.name} - ${file}:${line}:${col}\n`
221
239
  );
222
240
  throw red(outer);
223
241
  }
@@ -154,8 +154,41 @@ function formatTLS(tls: SecurityDetails) {
154
154
  };
155
155
  }
156
156
 
157
+ /**
158
+ * List of wildcard header keys that should be sanitized/redacted from the request/response headers
159
+ *
160
+ * Spec followed by APM agent is used as a reference.
161
+ * https://github.com/elastic/apm/blob/36a5abd49ff156c80cf0c9e2e1eac919873cb18b/specs/agents/sanitization.md?plain=1#L27
162
+ */
163
+ const SANITIZE_HEADER_KEYS = [
164
+ /^password$/i,
165
+ /^secret$/i,
166
+ /^.*key$/i,
167
+ /^.*token.*$/i,
168
+ /^.*session.*$/i,
169
+ /^.*auth.*$/i,
170
+ /^cookie$/i,
171
+ /^set\x2dcookie$/i,
172
+ ];
173
+ export function redactKeys(obj: Record<string, string> = {}) {
174
+ const result = {};
175
+ for (const key of Object.keys(obj)) {
176
+ const shouldRedact = SANITIZE_HEADER_KEYS.some(regex => regex.test(key));
177
+ result[key] = shouldRedact ? '[REDACTED]' : obj[key];
178
+ }
179
+ return result;
180
+ }
181
+
157
182
  export function formatNetworkFields(network: NetworkInfo) {
158
183
  const { request, response, url, browser } = network;
184
+ // Perform redaction on the headers before writing results
185
+ if (request?.headers) {
186
+ request.headers = redactKeys(request.headers);
187
+ }
188
+ if (response?.headers) {
189
+ response.headers = redactKeys(response.headers);
190
+ }
191
+
159
192
  const ecs = {
160
193
  // URL would be parsed and mapped by heartbeat
161
194
  url,