zapier-platform-core 17.7.0 → 17.7.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zapier-platform-core",
3
- "version": "17.7.0",
3
+ "version": "17.7.1",
4
4
  "description": "The core SDK for CLI apps in the Zapier Developer Platform.",
5
5
  "repository": "zapier/zapier-platform",
6
6
  "homepage": "https://platform.zapier.com/",
@@ -63,7 +63,7 @@
63
63
  "node-fetch": "2.7.0",
64
64
  "oauth-sign": "0.9.0",
65
65
  "semver": "7.7.1",
66
- "zapier-platform-schema": "17.7.0"
66
+ "zapier-platform-schema": "17.7.1"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@types/node-fetch": "^2.6.11",
@@ -2,19 +2,44 @@
2
2
 
3
3
  const _ = require('lodash');
4
4
  const querystring = require('querystring');
5
+
6
+ const { scrub, findSensitiveValues } = require('@zapier/secret-scrubber');
7
+
5
8
  const { replaceHeaders } = require('./middleware-utils');
6
9
  const { FORM_TYPE } = require('../../tools/http');
7
10
  const errors = require('../../errors');
11
+ const {
12
+ findSensitiveValuesFromAuthData,
13
+ } = require('../../tools/secret-scrubber');
8
14
 
9
- const _throwForStatus = (response) => {
15
+ const buildSensitiveValues = (bundle) => {
16
+ const authData = bundle?.authData || {};
17
+ const result = [
18
+ ...findSensitiveValuesFromAuthData(authData),
19
+ ...findSensitiveValues(process.env),
20
+ ];
21
+ return [...new Set(result)];
22
+ };
23
+
24
+ const _throwForStatus = (response, bundle) => {
10
25
  // calling this always throws, regardless of the skipThrowForStatus value
11
26
  // eslint-disable-next-line yoda
12
27
  if (400 <= response.status && response.status < 600) {
28
+ // Create a cleaned version of the response to avoid sensitive data leaks
29
+ try {
30
+ // Find sensitive values from environment variables and bundle authData
31
+ const sensitiveValues = buildSensitiveValues(bundle);
32
+ if (sensitiveValues.length > 0 && response?.request?.url) {
33
+ response.request.url = scrub(response.request.url, sensitiveValues);
34
+ }
35
+ } catch (err) {
36
+ // don't fail the whole request if we can't scrub for some reason
37
+ }
13
38
  throw new errors.ResponseError(response);
14
39
  }
15
40
  };
16
41
 
17
- const prepareRawResponse = (resp, request) => {
42
+ const prepareRawResponse = (resp, request, bundle) => {
18
43
  // TODO: if !2xx should we go ahead and get response.content for them?
19
44
  // retain the response signature for raw control
20
45
  const extendedResp = {
@@ -24,7 +49,7 @@ const prepareRawResponse = (resp, request) => {
24
49
  const outResp = _.extend(resp, extendedResp, replaceHeaders(resp));
25
50
 
26
51
  outResp.throwForStatus = () => {
27
- _throwForStatus(outResp);
52
+ _throwForStatus(outResp, bundle);
28
53
  };
29
54
 
30
55
  Object.defineProperty(outResp, 'content', {
@@ -39,7 +64,7 @@ const prepareRawResponse = (resp, request) => {
39
64
  return outResp;
40
65
  };
41
66
 
42
- const prepareContentResponse = async (resp, request) => {
67
+ const prepareContentResponse = async (resp, request, bundle) => {
43
68
  // TODO: does it make sense to not trim the signature? more equivalence to raw...
44
69
  const content = await resp.text();
45
70
 
@@ -67,14 +92,14 @@ const prepareContentResponse = async (resp, request) => {
67
92
  } catch (_e) {}
68
93
 
69
94
  outResp.throwForStatus = () => {
70
- _throwForStatus(outResp);
95
+ _throwForStatus(outResp, bundle);
71
96
  };
72
97
 
73
98
  return outResp;
74
99
  };
75
100
 
76
101
  // Provide a standardized plain JS responseObj for common consumption, or raw response for streaming.
77
- const prepareResponse = (resp) => {
102
+ const prepareResponse = (resp, z, bundle) => {
78
103
  const request = resp.input;
79
104
  delete resp.input;
80
105
 
@@ -82,7 +107,7 @@ const prepareResponse = (resp) => {
82
107
  ? prepareRawResponse
83
108
  : prepareContentResponse;
84
109
 
85
- return responseFunc(resp, request);
110
+ return responseFunc(resp, request, bundle);
86
111
  };
87
112
 
88
113
  module.exports = prepareResponse;
@@ -15,16 +15,8 @@ const {
15
15
  SAFE_LOG_KEYS,
16
16
  } = require('../constants');
17
17
  const { unheader } = require('./http');
18
- const {
19
- scrub,
20
- findSensitiveValues,
21
- recurseExtract,
22
- } = require('@zapier/secret-scrubber');
23
- // not really a public function, but it came from here originally
24
- const {
25
- isUrlWithSecrets,
26
- isSensitiveKey,
27
- } = require('@zapier/secret-scrubber/lib/convenience');
18
+ const { scrub, findSensitiveValues } = require('@zapier/secret-scrubber');
19
+ const { findSensitiveValuesFromAuthData } = require('./secret-scrubber');
28
20
 
29
21
  // The payload size per request to stream logs. This should be slighly lower
30
22
  // than the limit (16 MB) on the server side.
@@ -32,70 +24,8 @@ const LOG_STREAM_BYTES_LIMIT = 15 * 1024 * 1024;
32
24
 
33
25
  const DEFAULT_LOGGER_TIMEOUT = 200;
34
26
 
35
- // Will be initialized lazily
36
- const SAFE_AUTH_DATA_KEYS = new Set();
37
-
38
27
  const sleep = promisify(setTimeout);
39
28
 
40
- const initSafeAuthDataKeys = () => {
41
- // An authData key in (safePrefixes x safeSuffixes) is considered safe to log
42
- // uncensored
43
- const safePrefixes = [
44
- 'account',
45
- 'bot_user',
46
- 'cloud',
47
- 'cloud_site',
48
- 'company',
49
- 'domain',
50
- 'email',
51
- 'environment',
52
- 'location',
53
- 'org',
54
- 'organization',
55
- 'project',
56
- 'region',
57
- 'scope',
58
- 'scopes',
59
- 'site',
60
- 'subdomain',
61
- 'team',
62
- 'token_type',
63
- 'user',
64
- 'workspace',
65
- ];
66
- const safeSuffixes = ['', '_id', '_name', 'id', 'name'];
67
- for (const prefix of safePrefixes) {
68
- for (const suffix of safeSuffixes) {
69
- SAFE_AUTH_DATA_KEYS.add(prefix + suffix);
70
- }
71
- }
72
- };
73
-
74
- const isUrl = (value) => {
75
- if (!value || typeof value !== 'string') {
76
- return false;
77
- }
78
- const commonProtocols = [
79
- 'https://',
80
- 'http://',
81
- 'ftp://',
82
- 'ftps://',
83
- 'file://',
84
- ];
85
- for (const protocol of commonProtocols) {
86
- if (value.startsWith(protocol)) {
87
- try {
88
- // eslint-disable-next-line no-new
89
- new URL(value);
90
- return true;
91
- } catch (e) {
92
- return false;
93
- }
94
- }
95
- }
96
- return false;
97
- };
98
-
99
29
  const MAX_LENGTH = 3500;
100
30
  const truncateString = (str) => simpleTruncate(str, MAX_LENGTH, ' [...]');
101
31
 
@@ -188,35 +118,12 @@ const attemptFindSecretsInStr = (s, isGettingNewSecret) => {
188
118
  return findSensitiveValues(parsedRespContent);
189
119
  };
190
120
 
191
- const isSafeAuthDataKey = (key) => {
192
- if (SAFE_AUTH_DATA_KEYS.size === 0) {
193
- initSafeAuthDataKeys();
194
- }
195
- return SAFE_AUTH_DATA_KEYS.has(key.toLowerCase());
196
- };
197
-
198
121
  const buildSensitiveValues = (event, data) => {
199
122
  const bundle = event.bundle || {};
200
123
  const authData = bundle.authData || {};
201
- // for the most part, we should censor all the values from authData
202
- // the exception is safe urls, which should be filtered out - we want those to be logged
203
- // but, we _should_ censor-no-matter-what sensitive keys, even if their value is a safe url
204
- // this covers the case where someone's password is a valid url ¯\_(ツ)_/¯
205
- const sensitiveAuthData = recurseExtract(authData, (key, value) => {
206
- if (isSensitiveKey(key)) {
207
- return true;
208
- }
209
- if (isSafeAuthDataKey(key)) {
210
- return false;
211
- }
212
- if (isUrl(value) && !isUrlWithSecrets(value)) {
213
- return false;
214
- }
215
- return true;
216
- });
217
124
 
218
125
  const result = [
219
- ...sensitiveAuthData,
126
+ ...findSensitiveValuesFromAuthData(authData),
220
127
  ...findSensitiveValues(process.env),
221
128
  ...findSensitiveValues(data),
222
129
  ];
@@ -0,0 +1,98 @@
1
+ 'use strict';
2
+
3
+ const { recurseExtract } = require('@zapier/secret-scrubber');
4
+ const {
5
+ isUrlWithSecrets,
6
+ isSensitiveKey,
7
+ } = require('@zapier/secret-scrubber/lib/convenience');
8
+
9
+ // Will be initialized lazily
10
+ const SAFE_AUTH_DATA_KEYS = new Set();
11
+
12
+ const initSafeAuthDataKeys = () => {
13
+ // An authData key in (safePrefixes x safeSuffixes) is considered safe to log
14
+ // uncensored
15
+ const safePrefixes = [
16
+ 'account',
17
+ 'bot_user',
18
+ 'cloud',
19
+ 'cloud_site',
20
+ 'company',
21
+ 'domain',
22
+ 'email',
23
+ 'environment',
24
+ 'location',
25
+ 'org',
26
+ 'organization',
27
+ 'project',
28
+ 'region',
29
+ 'scope',
30
+ 'scopes',
31
+ 'site',
32
+ 'subdomain',
33
+ 'team',
34
+ 'token_type',
35
+ 'user',
36
+ 'workspace',
37
+ ];
38
+ const safeSuffixes = ['', '_id', '_name', 'id', 'name'];
39
+ for (const prefix of safePrefixes) {
40
+ for (const suffix of safeSuffixes) {
41
+ SAFE_AUTH_DATA_KEYS.add(prefix + suffix);
42
+ }
43
+ }
44
+ };
45
+
46
+ const isSafeAuthDataKey = (key) => {
47
+ if (SAFE_AUTH_DATA_KEYS.size === 0) {
48
+ initSafeAuthDataKeys();
49
+ }
50
+ return SAFE_AUTH_DATA_KEYS.has(key.toLowerCase());
51
+ };
52
+
53
+ const isUrl = (value) => {
54
+ if (!value || typeof value !== 'string') {
55
+ return false;
56
+ }
57
+ const commonProtocols = [
58
+ 'https://',
59
+ 'http://',
60
+ 'ftp://',
61
+ 'ftps://',
62
+ 'file://',
63
+ ];
64
+ for (const protocol of commonProtocols) {
65
+ if (value.startsWith(protocol)) {
66
+ try {
67
+ // eslint-disable-next-line no-new
68
+ new URL(value);
69
+ return true;
70
+ } catch (e) {
71
+ return false;
72
+ }
73
+ }
74
+ }
75
+ return false;
76
+ };
77
+
78
+ const findSensitiveValuesFromAuthData = (authData) =>
79
+ recurseExtract(authData, (key, value) => {
80
+ // for the most part, we should censor all the values from authData
81
+ // the exception is safe urls, which should be filtered out - we want those to be logged
82
+ // but, we _should_ censor-no-matter-what sensitive keys, even if their value is a safe url
83
+ // this covers the case where someone's password is a valid url ¯\_(ツ)_/¯
84
+ if (isSensitiveKey(key)) {
85
+ return true;
86
+ }
87
+ if (isSafeAuthDataKey(key)) {
88
+ return false;
89
+ }
90
+ if (isUrl(value) && !isUrlWithSecrets(value)) {
91
+ return false;
92
+ }
93
+ return true;
94
+ });
95
+
96
+ module.exports = {
97
+ findSensitiveValuesFromAuthData,
98
+ };
@@ -4,7 +4,7 @@
4
4
  * files, and/or the schema-to-ts tool and run its CLI to regenerate
5
5
  * these typings.
6
6
  *
7
- * zapier-platform-schema version: 17.6.0
7
+ * zapier-platform-schema version: 17.7.1
8
8
  * schema-to-ts compiler version: 0.1.0
9
9
  */
10
10
  import type {
@@ -131,7 +131,8 @@ export interface BaseApp {
131
131
 
132
132
  /**
133
133
  * Represents a simplified semver string, from `0.0.0` to
134
- * `999.999.999`.
134
+ * `999.999.999` with optional simplified label. They need to be
135
+ * case-insensitive unique.
135
136
  */
136
137
  export type Version = string;
137
138
 
@@ -938,6 +939,12 @@ export interface BasicPollingOperation<
938
939
  * for the window is exceeded.
939
940
  */
940
941
  throttle?: ThrottleObject;
942
+
943
+ /**
944
+ * (Experimental) Should empty strings, `null`, `undefined`, and
945
+ * empty Arrays or objects be removed from `inputData`?
946
+ */
947
+ skipCleanArrayInputData?: boolean;
941
948
  }
942
949
 
943
950
  /**
@@ -1027,6 +1034,12 @@ export interface BasicHookOperation<
1027
1034
  * this belongs to a resource that has a top-level sample
1028
1035
  */
1029
1036
  sample?: Record<string, unknown>;
1037
+
1038
+ /**
1039
+ * (Experimental) Should empty strings, `null`, `undefined`, and
1040
+ * empty Arrays or objects be removed from `inputData`?
1041
+ */
1042
+ skipCleanArrayInputData?: boolean;
1030
1043
  }
1031
1044
 
1032
1045
  /**
@@ -1088,6 +1101,12 @@ export interface BasicHookToPollOperation<
1088
1101
  */
1089
1102
  sample?: Record<string, unknown>;
1090
1103
 
1104
+ /**
1105
+ * (Experimental) Should empty strings, `null`, `undefined`, and
1106
+ * empty Arrays or objects be removed from `inputData`?
1107
+ */
1108
+ skipCleanArrayInputData?: boolean;
1109
+
1091
1110
  /**
1092
1111
  * The maximum amount of time to wait between polling requests in
1093
1112
  * seconds. Minimum value is 20s and will default to 20 if not set,
@@ -1159,6 +1178,12 @@ export interface BasicActionOperation {
1159
1178
  * for the window is exceeded.
1160
1179
  */
1161
1180
  throttle?: ThrottleObject;
1181
+
1182
+ /**
1183
+ * (Experimental) Should empty strings, `null`, `undefined`, and
1184
+ * empty Arrays or objects be removed from `inputData`?
1185
+ */
1186
+ skipCleanArrayInputData?: boolean;
1162
1187
  }
1163
1188
 
1164
1189
  /** Represents the fundamental mechanics of a search. */
@@ -1229,6 +1254,12 @@ export interface BasicSearchOperation<
1229
1254
  * for the window is exceeded.
1230
1255
  */
1231
1256
  throttle?: ThrottleObject;
1257
+
1258
+ /**
1259
+ * (Experimental) Should empty strings, `null`, `undefined`, and
1260
+ * empty Arrays or objects be removed from `inputData`?
1261
+ */
1262
+ skipCleanArrayInputData?: boolean;
1232
1263
  }
1233
1264
 
1234
1265
  /** Represents the fundamental mechanics of a create. */
@@ -1299,6 +1330,12 @@ export interface BasicCreateOperation<
1299
1330
  */
1300
1331
  throttle?: ThrottleObject;
1301
1332
 
1333
+ /**
1334
+ * (Experimental) Should empty strings, `null`, `undefined`, and
1335
+ * empty Arrays or objects be removed from `inputData`?
1336
+ */
1337
+ skipCleanArrayInputData?: boolean;
1338
+
1302
1339
  /**
1303
1340
  * Currently an **internal-only** feature. Zapier uses this
1304
1341
  * configuration for creating objects in bulk with `performBuffer`.
@@ -1382,6 +1419,12 @@ export interface BasicOperation {
1382
1419
  * for the window is exceeded.
1383
1420
  */
1384
1421
  throttle?: ThrottleObject;
1422
+
1423
+ /**
1424
+ * (Experimental) Should empty strings, `null`, `undefined`, and
1425
+ * empty Arrays or objects be removed from `inputData`?
1426
+ */
1427
+ skipCleanArrayInputData?: boolean;
1385
1428
  }
1386
1429
 
1387
1430
  /**
@@ -1466,11 +1509,7 @@ export interface PlainOutputField {
1466
1509
  }
1467
1510
 
1468
1511
  /** An array or collection of input field groups. */
1469
- export type InputFieldGroups = {
1470
- key: Key;
1471
- label: string;
1472
- emphasize: boolean;
1473
- }[];
1512
+ export type InputFieldGroups = InputFieldGroup[];
1474
1513
 
1475
1514
  /**
1476
1515
  * Zapier uses this configuration to ensure this action is performed
@@ -1681,6 +1720,18 @@ export interface PlainInputField {
1681
1720
  group?: Key;
1682
1721
  }
1683
1722
 
1723
+ /** Object for visual grouping of input fields. */
1724
+ export interface InputFieldGroup {
1725
+ /** The unique identifier for this group. */
1726
+ key: Key;
1727
+
1728
+ /** The human-readable name for the group. */
1729
+ label?: string;
1730
+
1731
+ /** Whether this group should be visually emphasized in the UI. */
1732
+ emphasize?: boolean;
1733
+ }
1734
+
1684
1735
  /**
1685
1736
  * Reference a resource by key and the data it returns. In the
1686
1737
  * format of: `{resource_key}.{foreign_key}(.{human_label_key})`.