{"record":{"id":"4e830700e166a88b","repo":"pydantic/monty","slug":"raw-classinstance-markers-are-not-accepted-wrap-the-object","errorCode":null,"errorMessage":"raw ClassInstance markers are not accepted — wrap the object in ClassInstance(...)","messagePattern":"raw ClassInstance markers are not accepted — wrap the object in ClassInstance\\(\\.\\.\\.\\)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"crates/monty-js/ts/classInstance.ts","lineNumber":540,"sourceCode":"  if (Array.isArray(value)) {\n    return walkArray(value, walk)\n  }\n  if (value instanceof Map) {\n    return walkMap(value, walk)\n  }\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/**","sourceCodeStart":522,"sourceCodeEnd":558,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty-js/ts/classInstance.ts#L522-L558","documentation":"Identity-bearing ClassInstance wire markers ({ type: 'ClassInstance', instanceId: ... }) are produced only by the internal prepare walk; host code must never hold one. One arriving at prepareInner is treated as forged — e.g. embedded in attacker-controlled JSON to impersonate a registered instance — and rejected with this TypeError. Wrap real host objects in the ClassInstance(...) function instead.","triggerScenarios":"Passing a raw marker object (an object with a type/ClassInstance marker, typically round-tripped from a previous restore or parsed from untrusted JSON) as an input, external-function result, or ClassInstance attribute instead of the actual host object wrapped in ClassInstance(...).","commonSituations":"Caching the wire-format output of a previous call and feeding it back in; storing 'restore' output in a database and re-sending it; accepting host objects from untrusted JSON that happens to contain marker-shaped keys.","solutions":["Wrap the original host object with ClassInstance(obj, ClassType(MyClass)) instead of passing the marker","If you serialized inputs earlier, persist the original object (or a host-side reference) rather than the marker object","Strip or re-map marker-shaped fields from untrusted JSON before passing it as sandbox input"],"exampleFix":"// before\nsession.feedRun('use(obj)', { inputs: { obj: { type: 'ClassInstance', instanceId: '...' } } });\n// after\nsession.feedRun('use(obj)', { inputs: { obj: ClassInstance(myObject, MyClassType) } });","handlingStrategy":"type-guard","validationCode":"function looksLikeClassInstanceMarker(v) {\n  return typeof v === 'object' && v !== null &&\n    (v.type === 'ClassInstance' || ('instanceId' in v && !('__montyWrapper' in v)));\n}\nif (looksLikeClassInstanceMarker(inputs.obj)) throw new Error('pass the original object wrapped in ClassInstance(...)');","typeGuard":"function isWrappedInstance(v) {\n  return typeof v === 'object' && v !== null && typeof v.__montyWrapper !== 'undefined';\n}","tryCatchPattern":"try {\n  await session.feedRun(code, { inputs });\n} catch (e) {\n  if (e instanceof TypeError && e.message.includes('raw ClassInstance markers are not accepted')) {\n    throw new Error('caller passed a wire marker; wrap the original object with ClassInstance(obj, ClassType(Cls))');\n  }\n  throw e;\n}","preventionTips":["Never persist or re-send wire-format marker objects; keep original host references","Sanitize untrusted JSON before it reaches feedRun inputs","Centralize all input wrapping in one helper so ClassInstance(...) is never skipped"],"tags":["typescript","class-instance","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"}