Skip to content

Commit f951f12

Browse files
committed
Fix prototype-colliding names in execution values
1 parent 123e958 commit f951f12

2 files changed

Lines changed: 72 additions & 11 deletions

File tree

src/execution/__tests__/variables-test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,15 @@ const TestType = new GraphQLObjectType({
129129
type: TestNestedInputObject,
130130
defaultValue: 'Hello World',
131131
}),
132+
fieldWithPrototypeNamedArgument: {
133+
type: GraphQLString,
134+
args: {
135+
toString: { type: GraphQLString },
136+
},
137+
resolve(_, args) {
138+
return args.toString === undefined ? 'missing' : inspect(args.toString);
139+
},
140+
},
132141
list: fieldWithInputArg({ type: new GraphQLList(GraphQLString) }),
133142
nnList: fieldWithInputArg({
134143
type: new GraphQLNonNull(new GraphQLList(GraphQLString)),
@@ -1064,6 +1073,20 @@ describe('Execute: Handles inputs', () => {
10641073
},
10651074
});
10661075
});
1076+
1077+
it('does not expose prototype argument names when omitted', () => {
1078+
const result = executeQuery(`
1079+
{
1080+
fieldWithPrototypeNamedArgument
1081+
}
1082+
`);
1083+
1084+
expect(result).to.deep.equal({
1085+
data: {
1086+
fieldWithPrototypeNamedArgument: 'missing',
1087+
},
1088+
});
1089+
});
10671090
});
10681091

10691092
describe('getVariableValues: limit maximum number of coercion errors', () => {
@@ -1136,4 +1159,45 @@ describe('Execute: Handles inputs', () => {
11361159
});
11371160
});
11381161
});
1162+
1163+
describe('getVariableValues: own-property names', () => {
1164+
const doc = parse(`
1165+
query ($toString: String) {
1166+
fieldWithNullableStringInput(input: $toString)
1167+
}
1168+
`);
1169+
1170+
const operation = doc.definitions[0];
1171+
invariant(operation.kind === Kind.OPERATION_DEFINITION);
1172+
const { variableDefinitions } = operation;
1173+
invariant(variableDefinitions != null);
1174+
1175+
it('does not expose prototype variable names when omitted', () => {
1176+
const result = getVariableValues(schema, variableDefinitions, {});
1177+
if (!('coerced' in result)) {
1178+
throw new Error('Expected coerced variable values.');
1179+
}
1180+
const coerced = result.coerced as { toString?: unknown } | undefined;
1181+
if (coerced == null) {
1182+
throw new Error('Expected coerced variable values.');
1183+
}
1184+
1185+
expect(coerced.toString).to.equal(undefined);
1186+
});
1187+
1188+
it('still returns provided variables with colliding names', () => {
1189+
const result = getVariableValues(schema, variableDefinitions, {
1190+
toString: 'value',
1191+
});
1192+
if (!('coerced' in result)) {
1193+
throw new Error('Expected coerced variable values.');
1194+
}
1195+
const coerced = result.coerced as { toString?: unknown } | undefined;
1196+
if (coerced == null) {
1197+
throw new Error('Expected coerced variable values.');
1198+
}
1199+
1200+
expect(coerced.toString).to.equal('value');
1201+
});
1202+
});
11391203
});

src/execution/values.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,8 @@ type CoercedVariableValues =
3232
* provided variable definitions and arbitrary input. If the input cannot be
3333
* parsed to match the variable definitions, a GraphQLError will be thrown.
3434
*
35-
* Note: The returned value is a plain Object with a prototype, since it is
36-
* exposed to user code. Care should be taken to not pull values from the
37-
* Object prototype.
35+
* Note: The returned value uses a null prototype to avoid collisions with
36+
* JavaScript's own property names.
3837
*/
3938
export function getVariableValues(
4039
schema: GraphQLSchema,
@@ -138,16 +137,15 @@ function coerceVariableValues(
138137
);
139138
}
140139

141-
return { ...coercedValues };
140+
return coercedValues;
142141
}
143142

144143
/**
145144
* Prepares an object map of argument values given a list of argument
146145
* definitions and list of argument AST nodes.
147146
*
148-
* Note: The returned value is a plain Object with a prototype, since it is
149-
* exposed to user code. Care should be taken to not pull values from the
150-
* Object prototype.
147+
* Note: The returned value uses a null prototype to avoid collisions with
148+
* JavaScript's own property names.
151149
*/
152150
export function getArgumentValues(
153151
def: GraphQLField<unknown, unknown> | GraphQLDirective,
@@ -222,7 +220,7 @@ export function getArgumentValues(
222220
}
223221
coercedValues[name] = coercedValue;
224222
}
225-
return { ...coercedValues };
223+
return coercedValues;
226224
}
227225

228226
/**
@@ -232,9 +230,8 @@ export function getArgumentValues(
232230
*
233231
* If the directive does not exist on the node, returns undefined.
234232
*
235-
* Note: The returned value is a plain Object with a prototype, since it is
236-
* exposed to user code. Care should be taken to not pull values from the
237-
* Object prototype.
233+
* Note: The returned value uses a null prototype to avoid collisions with
234+
* JavaScript's own property names.
238235
*/
239236
export function getDirectiveValues(
240237
directiveDef: GraphQLDirective,

0 commit comments

Comments
 (0)