{"record":{"id":"4934e58d686be791","repo":"paperclipai/paperclip","slug":"invalid-teams-private-binding","errorCode":null,"errorMessage":"Invalid Teams private binding","messagePattern":"Invalid Teams private binding","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/chat-teams-file-consent.ts","lineNumber":166,"sourceCode":"    )\n      throw new Error();\n    return envelope.value;\n  } catch {\n    throw new Error(\"Teams private state could not be restored\");\n  }\n}\n\nexport async function sealTeamsFileConsentBinding(\n  context: TeamsFilePrivateContext,\n  binding: TeamsFileConsentBinding,\n) {\n  const parsed = parseTeamsFileConsentBinding(binding);\n  if (\n    !parsed ||\n    parsed.companyId !== context.companyId ||\n    parsed.endpointId !== context.endpointId\n  )\n    throw new Error(\"Invalid Teams private binding\");\n  return sealPrivate(context, \"binding\", parsed);\n}\n\nexport async function restoreTeamsFileConsentBinding(\n  context: TeamsFilePrivateContext,\n  material: TeamsFileCiphertext,\n) {\n  const binding = parseTeamsFileConsentBinding(\n    await openPrivate(context, \"binding\", material),\n  );\n  if (\n    !binding ||\n    binding.companyId !== context.companyId ||\n    binding.endpointId !== context.endpointId\n  )\n    throw new Error(\"Invalid Teams private binding\");\n  return binding;\n}","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-teams-file-consent.ts#L148-L184","documentation":"sealTeamsFileConsentBinding encrypts a TeamsFileConsentBinding into private sealed state for persistence. Before sealing it re-validates the binding against bindingSchema (parseTeamsFileConsentBinding) and requires the binding's companyId and endpointId to match the sealing context. Any mismatch or schema violation throws this error instead of sealing untrustworthy material.","triggerScenarios":"Calling sealTeamsFileConsentBinding with a binding that fails zod validation (missing/extra fields, non-UUID ids, bad filename, wrong token format like a non-pcfc_ token, byteSize over the 60MB cap, expiresAt not ISO datetime), or with a valid binding whose companyId/endpointId differ from context.companyId/context.endpointId.","commonSituations":"Constructing a binding by hand instead of via createTeamsFileConsentBinding; binding created for a different company/endpoint than the transfer context; upgrading code that added strict schema fields while old persisted objects are re-sealed; cross-tenant copy/paste of fixtures in tests.","solutions":["Create the binding with createTeamsFileConsentBinding so the schema, token and freeze are applied, rather than hand-assembling the object.","Check binding.companyId === context.companyId && binding.endpointId === context.endpointId before sealing; use the context matching the binding's origin.","Run parseTeamsFileConsentBinding(binding) yourself and inspect the zod failure to find the offending field (e.g. token prefix, filename characters, expiresAt format).","If migrating old data, re-create the binding through createTeamsFileConsentBinding with corrected fields (new UUIDs, ISO expiresAt) before sealing."],"exampleFix":"// before\nawait sealTeamsFileConsentBinding(otherCompanyContext, binding);\n// after\nif (binding.companyId !== context.companyId || binding.endpointId !== context.endpointId) {\n  throw new Error(\"binding does not belong to this transfer context\");\n}\nawait sealTeamsFileConsentBinding(context, binding);","handlingStrategy":"validation","validationCode":"const parsed = parseTeamsFileConsentBinding(binding);\nif (!parsed || parsed.companyId !== context.companyId || parsed.endpointId !== context.endpointId) {\n  throw new Error('binding invalid or scope mismatch before seal');\n}","typeGuard":"function isBindingForContext(b: unknown, ctx: TeamsFilePrivateContext): b is TeamsFileConsentBinding {\n  const p = parseTeamsFileConsentBinding(b);\n  return !!p && p.companyId === ctx.companyId && p.endpointId === ctx.endpointId;\n}","tryCatchPattern":"try {\n  const material = await sealTeamsFileConsentBinding(context, binding);\n} catch (err) {\n  if (err instanceof Error && err.message === 'Invalid Teams private binding') {\n    // log binding field diffs and rebuild via createTeamsFileConsentBinding\n  }\n}","preventionTips":["Always create bindings via createTeamsFileConsentBinding; never hand-assemble.","Assert companyId/endpointId equality with the context at every call site.","Keep bindings frozen and treat them as immutable values in transfers."],"tags":["validation","zod","teams","scope-mismatch"],"backgroundTag":"schema-validation-failed","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}