Skip to content

Commit 62aed74

Browse files
committed
added secret support in script via safe flag and changed the secret resolver function to be single instead of interface
1 parent af4420a commit 62aed74

File tree

7 files changed

+469
-432
lines changed

7 files changed

+469
-432
lines changed

lib/runner/extensions/event.command.js

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,46 @@ var _ = require('lodash'),
3838

3939
getCookieDomain, // fn
4040
postProcessContext, // fn
41-
sanitizeFiles; // fn
41+
sanitizeFiles, // fn
42+
maskUnsafeSecrets; // fn
43+
44+
/**
45+
* Clones variable scopes and masks variables in unsafeSecretKeys (Set of "scopeName:variableKey").
46+
* Used to hide non-safe secret values from scripts while keeping them available for request substitution.
47+
*
48+
* @param {Object} context - Context with environment, globals, collectionVariables
49+
* @param {Set} unsafeSecretKeys - Set of "scopeName:variableKey" for secrets with safe=false
50+
* @returns {Object} - Context with cloned, masked scopes
51+
*/
52+
maskUnsafeSecrets = function (context, unsafeSecretKeys) {
53+
if (!unsafeSecretKeys || unsafeSecretKeys.size === 0) {
54+
return context;
55+
}
56+
57+
var scopeNames = ['environment', 'globals', 'collectionVariables'],
58+
maskedContext = _.assign({}, context);
59+
60+
scopeNames.forEach(function (scopeName) {
61+
var scope = context[scopeName],
62+
maskedScope;
63+
64+
if (!scope || !sdk.VariableScope.isVariableScope(scope)) {
65+
return;
66+
}
67+
68+
maskedScope = new sdk.VariableScope(scope.toJSON ? scope.toJSON() : scope);
69+
70+
maskedScope.values.each(function (variable) {
71+
if (variable && unsafeSecretKeys.has(scopeName + ':' + variable.key)) {
72+
variable.set(undefined);
73+
}
74+
});
75+
76+
maskedContext[scopeName] = maskedScope;
77+
});
78+
79+
return maskedContext;
80+
};
4281

4382
postProcessContext = function (execution, failures) { // function determines whether the event needs to abort
4483
var error;
@@ -514,6 +553,12 @@ module.exports = {
514553
const currentEventItem = event.parent && event.parent(),
515554

516555
executeScript = ({ resolvedPackages }, done) => {
556+
var contextToUse = _.pick(payload.context, SAFE_CONTEXT_VARIABLES);
557+
558+
if (payload.context._unsafeSecretKeys) {
559+
contextToUse = maskUnsafeSecrets(contextToUse, payload.context._unsafeSecretKeys);
560+
}
561+
517562
// finally execute the script
518563
this.host.execute(event, {
519564
id: executionId,
@@ -522,7 +567,7 @@ module.exports = {
522567
// @todo: Expose this as a property in Collection SDK's Script
523568
timeout: payload.scriptTimeout,
524569
cursor: scriptCursor,
525-
context: _.pick(payload.context, SAFE_CONTEXT_VARIABLES),
570+
context: contextToUse,
526571
resolvedPackages: resolvedPackages,
527572

528573
disabledAPIs: !_.get(this, 'options.script.requestResolver') ?

lib/runner/extensions/item.command.js

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ module.exports = {
124124

125125
ctxTemplate,
126126
self = this,
127-
secretResolvers = this.state.secretResolvers,
127+
secretResolver = this.state.secretResolver,
128128
secretResolutionPayload,
129129
executeItemFlow;
130130

@@ -150,8 +150,11 @@ module.exports = {
150150

151151
/**
152152
* Execute the main item flow (delay, prerequest, request, test)
153+
*
154+
* @param {Array} [secretResolutionErrors] - Errors from partial secret resolution
155+
* @param {Set} [unsafeSecretKeys] - Set of scopeName:variableKey for secrets with safe=false
153156
*/
154-
executeItemFlow = function () {
157+
executeItemFlow = function (secretResolutionErrors, unsafeSecretKeys) {
155158
self.queueDelay(function () {
156159
// create the context object for scripts to run
157160
ctxTemplate = {
@@ -161,7 +164,8 @@ module.exports = {
161164
globals: globals,
162165
environment: environment,
163166
data: data,
164-
request: item.request
167+
request: item.request,
168+
_unsafeSecretKeys: unsafeSecretKeys
165169
};
166170

167171
// @todo make it less nested by coding Instruction.thenQueue
@@ -279,13 +283,17 @@ module.exports = {
279283
};
280284

281285
// trigger an event saying that item has been processed
282-
this.triggers.item(null, coords, item, visualizerResult);
286+
this.triggers.item(null, coords, item, visualizerResult,
287+
secretResolutionErrors && secretResolutionErrors.length ?
288+
{ secretResolutionErrors } : undefined);
283289
}.bind(this));
284290
}
285291
else {
286292
// trigger an event saying that item has been processed
287293
// @todo - should this trigger receive error?
288-
this.triggers.item(null, coords, item, null);
294+
this.triggers.item(null, coords, item, null,
295+
secretResolutionErrors && secretResolutionErrors.length ?
296+
{ secretResolutionErrors } : undefined);
289297
}
290298

291299
// reset mutated request with original request instance
@@ -310,12 +318,11 @@ module.exports = {
310318
};
311319

312320
// Resolve secrets before pre-request scripts so scripts can access resolved values via related pm APIs
313-
if (secretResolvers) {
314-
resolveSecrets(secretResolutionPayload, secretResolvers, function (err) {
321+
if (secretResolver) {
322+
resolveSecrets(secretResolutionPayload, secretResolver, function (err, secErrors, unsafeKeys) {
315323
if (err) {
316-
// Secret resolution failed - this is fatal for the current item
317-
// (same pattern as pre-request script skip)
318-
self.triggers.item(err, coords, item, null, { isSecretResolutionFailed: true });
324+
// Fatal: stop immediately, request is never executed
325+
self.triggers.item(err, coords, item, null, { hasSecretResolutionFailed: true });
319326

320327
callback && callback.call(self, err, {
321328
request: null
@@ -324,7 +331,7 @@ module.exports = {
324331
return next();
325332
}
326333

327-
executeItemFlow();
334+
executeItemFlow(secErrors, unsafeKeys);
328335
});
329336
}
330337
else {

lib/runner/extensions/request.command.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,21 @@ module.exports = {
1212

1313
process: {
1414
request (payload, next) {
15-
var self = this,
16-
abortOnError = _.has(payload, 'abortOnError') ? payload.abortOnError : this.options.abortOnError,
15+
var abortOnError = _.has(payload, 'abortOnError') ? payload.abortOnError : this.options.abortOnError,
1716

18-
// helper function to trigger `response` callback and complete the command
17+
// helper function to trigger `response` callback anc complete the command
1918
complete = function (err, nextPayload) {
2019
// nextPayload will be empty for unhandled errors
2120
// trigger `response` callback
2221
// nextPayload.response will be empty for error flows
2322
// the `item` argument is resolved and mutated here
24-
nextPayload && self.triggers.response(err, nextPayload.coords, nextPayload.response,
23+
nextPayload && this.triggers.response(err, nextPayload.coords, nextPayload.response,
2524
nextPayload.request, nextPayload.item, nextPayload.cookies, nextPayload.history);
2625

2726
// the error is passed twice to allow control between aborting the error vs just
2827
// bubbling it up
2928
return next(err && abortOnError ? err : null, nextPayload, err);
30-
},
31-
29+
}.bind(this),
3230
context = createItemContext(payload);
3331

3432
// resolve variables in item and auth
@@ -40,7 +38,7 @@ module.exports = {
4038
// we do not queue `httprequest` instruction here,
4139
// queueing will unblock the item command to prepare for the next `event` instruction
4240
// at this moment request is not fulfilled, and we want to block it
43-
self.immediate('httprequest', payload)
41+
this.immediate('httprequest', payload)
4442
.done(function (nextPayload, err) {
4543
// change signature to error first
4644
complete(err, nextPayload);

lib/runner/index.js

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,17 @@ _.assign(Runner.prototype, {
9797
* @param {String} [options.entrypoint.lookupStrategy=idOrName] strategy to lookup the entrypoint [idOrName, path]
9898
* @param {Array<String>} [options.entrypoint.path] path to lookup
9999
* @param {Object} [options.run] Run-specific options, such as options related to the host
100-
* @param {Object} [options.secretResolvers] - Object mapping provider names to resolver configs.
101-
* Keys must match secret.source.provider (e.g. postman, azure, 1password, aws, hashicorp).
102-
* Each resolver config may have:
103-
* - {String} id - (Required) Unique identifier for the resolver (same as secret.source.provider)
104-
* - {String} name - (Required) Human-readable name for the resolver
105-
* - {Function} resolver - (Required) Function(secret, context) that returns resolved value or Promise
106-
* - {Number} [timeout=5000] - (Optional) Timeout in milliseconds (default: 5000)
107-
* - {Number} [retryCount=0] - (Optional) Number of retry attempts on failure (default: 0)
100+
* @param {Function} [options.secretResolver] - Function({ secrets, payload }, callback) that resolves secrets.
101+
* Receives: secrets (array of { scopeName, scope, variable, context }), payload ({ item, environment,
102+
* globals, collectionVariables }). Callback is (err, result).
103+
* On fatal error: callback(err) — request execution stops.
104+
* On success: callback(null, result) where result is Array<{ resolvedValue?: string,
105+
* error?: Error, safe?: boolean }>;
106+
* result[i] corresponds to secrets[i].
107+
* resolvedValue: resolved string (undefined if failed/skipped).
108+
* error: Error when resolution failed for particular secret.
109+
* safe: if true, value is exposed to scripts via pm.environment/pm.variables;
110+
* if false/undefined, masked from scripts. Runtime applies values.
108111
*
109112
* @param {Function} callback -
110113
*/
@@ -156,7 +159,7 @@ _.assign(Runner.prototype, {
156159
localVariables: options.localVariables,
157160
certificates: options.certificates,
158161
proxies: options.proxies,
159-
secretResolvers: options.secretResolvers
162+
secretResolver: options.secretResolver
160163
}, runOptions)));
161164
});
162165
}

0 commit comments

Comments
 (0)