{"record":{"id":"c7c3f36f6ec6ff54","repo":"pydantic/monty","slug":"raw-type-markers-are-not-accepted-pass-the-class-through","errorCode":null,"errorMessage":"raw Type markers are not accepted — pass the class through ClassType(...)","messagePattern":"raw Type markers are not accepted — pass the class through ClassType\\(\\.\\.\\.\\)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"crates/monty-js/ts/classInstance.ts","lineNumber":545,"sourceCode":"  }\n  if (value instanceof Set) {\n    return walkSet(value, walk)\n  }\n  if (value instanceof Uint8Array) {\n    return value\n  }\n  const marker = readTypeMarker(value)\n  if (marker === 'ClassInstance') {\n    // Identity-bearing markers are produced internally by this walk, never\n    // held by host code (`restore` maps them to the original object or a\n    // MontyClassProxy). One arriving here is forged — e.g. embedded\n    // in attacker-controlled JSON to impersonate a registered instance.\n    throw new TypeError('raw ClassInstance markers are not accepted — wrap the object in ClassInstance(...)')\n  }\n  if (marker === 'Type' && (value as { classType?: unknown }).classType !== undefined) {\n    // Same reasoning for a host-class marker; builtin `Type` markers\n    // (`{ value: 'int' }`) carry no identity and pass through.\n    throw new TypeError('raw Type markers are not accepted — pass the class through ClassType(...)')\n  }\n  if (marker !== undefined) {\n    return value\n  }\n  if (isPlainObject(value)) {\n    return walkPlainObject(value as Record<string, unknown>, walk)\n  }\n  throw new TypeError(\n    `Cannot convert ${constructorName(value)} instance to a Monty value — wrap it in ClassInstance(...)`,\n  )\n}\n\n/**\n * Inbound walk over a sandbox value reaching the host: maps `ClassInstance`\n * markers to the original wrapped object when the id is in `store` (identity\n * preserved), else to a [`MontyClassProxy`] proxy with recursively\n * restored attrs; maps a host-class `Type` marker to the registered class\n * object the same way (an unregistered class stays a marker); recurses into","sourceCodeStart":527,"sourceCodeEnd":563,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty-js/ts/classInstance.ts#L527-L563","documentation":"A Type marker carrying a classType (a host-class marker) is only produced internally by the prepare walk; host code must pass classes through the ClassType(...) wrapper function instead. A raw one arriving at prepareInner is rejected with this TypeError, since it could be forged to impersonate a registered host class. Builtin Type markers (e.g. { value: 'int' }) carry no identity and pass through.","triggerScenarios":"Passing a raw host-class Type marker object (round-tripped wire output or marker-shaped untrusted JSON) as an input, external-function result, or attribute where a class is expected, instead of calling ClassType(MyClass).","commonSituations":"Feeding back previously serialized wire values; passing class references extracted from restore() output; constructing marker-like objects by hand from documentation of the wire format.","solutions":["Pass the class itself through ClassType(MyClass) instead of a raw marker object","Re-create the wrapper from the live class rather than caching wire-format output","Sanitize untrusted JSON so marker-shaped objects never reach the sandbox boundary"],"exampleFix":"// before\nsession.feedRun('make(K)', { inputs: { K: { type: 'Type', classType: 'MyClass' } } });\n// after\nsession.feedRun('make(K)', { inputs: { K: ClassType(MyClass) } });","handlingStrategy":"type-guard","validationCode":"function looksLikeTypeMarker(v) {\n  return typeof v === 'object' && v !== null && v.type === 'Type' && v.classType !== undefined;\n}\nif (looksLikeTypeMarker(inputs.K)) throw new Error('pass the class via ClassType(MyClass)');","typeGuard":"function isClassTypeWrapper(v) {\n  return typeof v === 'function' || (typeof v === 'object' && v !== null && v.__montyIsClassType === true);\n}","tryCatchPattern":"try {\n  await session.feedRun(code, { inputs });\n} catch (e) {\n  if (e instanceof TypeError && e.message.includes('raw Type markers are not accepted')) {\n    throw new Error('pass the class through ClassType(MyClass), not a marker object');\n  }\n  throw e;\n}","preventionTips":["Always wrap host classes with ClassType(...) at the call site","Do not cache or hand-build the wire representation of classes","Reject marker-shaped keys when accepting untrusted JSON as sandbox input"],"tags":["typescript","class-type","forged-marker","security"],"backgroundTag":"invalid-argument-format","analyzedSha":"adc986b362e3961f407868cb118a99fe831b9e61","analyzedAt":"2026-09-13T19:19:18.698Z","contentChangedAt":"2026-09-13T19:19:18.698Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}