{"record":{"id":"e90714b846604fbe","repo":"paperclipai/paperclip","slug":"invalid-teams-upload-binding","errorCode":null,"errorMessage":"Invalid Teams upload binding","messagePattern":"Invalid Teams upload binding","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/chat-teams-file-consent.ts","lineNumber":370,"sourceCode":"    this.#bindingDigest = digest(binding);\n    this.#expiresAt = Date.parse(binding.expiresAt);\n    this.#byteSize = binding.byteSize;\n    this.#sha256 = binding.sha256;\n    Object.freeze(this);\n  }\n  toJSON() {\n    return undefined;\n  }\n  async seal(\n    context: TeamsFilePrivateContext,\n    binding: TeamsFileConsentBinding,\n  ) {\n    if (\n      !this.matches(binding) ||\n      context.companyId !== binding.companyId ||\n      context.endpointId !== binding.endpointId\n    )\n      throw new Error(\"Invalid Teams upload binding\");\n    return sealPrivate(context, \"upload\", {\n      bindingDigest: digest(binding),\n      info: this.#info,\n      confirmed: this.#confirmed,\n      putStarted: this.#putStarted,\n    });\n  }\n  static async restore(\n    context: TeamsFilePrivateContext,\n    binding: TeamsFileConsentBinding,\n    material: TeamsFileCiphertext,\n  ) {\n    if (\n      context.companyId !== binding.companyId ||\n      context.endpointId !== binding.endpointId\n    )\n      throw new Error(\"Invalid Teams upload binding\");\n    const value = z","sourceCodeStart":352,"sourceCodeEnd":388,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-teams-file-consent.ts#L352-L388","documentation":"UploadCapability.seal persists the private upload capability (uploadInfo, confirmed/putStarted flags, bindingDigest) into encrypted private state. It throws this error when the capability's stored bindingDigest does not match the digest of the provided binding, or when the context's companyId/endpointId differ from the binding — i.e. you are sealing an upload against a different or stale consent binding or transfer context.","triggerScenarios":"Calling sealTeamsFileUpload (which delegates to upload.seal) with a binding that was regenerated after the capability was created (any field change changes digest(binding)); sealing with a context from a different company/endpoint; passing a capability restored from another transfer.","commonSituations":"Re-creating the binding (e.g. after editing filename, byteSize, or expiresAt) while holding an old UploadCapability; concurrent updates that replaced the current binding row; mixing contexts across environments where company/endpoint UUIDs differ; restoring an upload from transfer A and sealing under transfer B's binding.","solutions":["Use the exact same immutable binding instance/data the UploadCapability was constructed from; do not mutate or regenerate binding fields (filename, byteSize, sha256, expiresAt, token) between capability creation and seal.","Verify context.companyId and context.endpointId equal binding.companyId/endpointId before sealing.","Call upload.matches(binding) first to confirm the digest alignment and regenerate the capability from the consent event if it returns false.","If the binding legitimately changed (e.g. new file version), restart the consent flow: create a fresh binding, send a new consent card, and derive a new UploadCapability."],"exampleFix":"// before\nbinding.expiresAt = newExpiry;\nawait sealTeamsFileUpload(context, binding, upload); // digest mismatch\n// after\n// keep binding immutable; issue a new consent flow if expiry must change\nawait sealTeamsFileUpload(context, originalBinding, upload);","handlingStrategy":"validation","validationCode":"if (!(upload instanceof UploadCapability) || !upload.matches(binding) || context.companyId !== binding.companyId || context.endpointId !== binding.endpointId) {\n  throw new Error('upload/binding/context mismatch before seal');\n}","typeGuard":"function canSealUpload(u: TeamsFileUploadCapability, b: TeamsFileConsentBinding, ctx: TeamsFilePrivateContext): boolean {\n  return u.matches(b) && ctx.companyId === b.companyId && ctx.endpointId === b.endpointId;\n}","tryCatchPattern":"try {\n  const material = await sealTeamsFileUpload(context, binding, upload);\n} catch (err) {\n  if (err instanceof Error && err.message === 'Invalid Teams upload binding') {\n    // binding drifted from capability; regenerate capability from the consent event or restart the flow\n  }\n}","preventionTips":["Treat the binding as immutable for the lifetime of an UploadCapability; never patch fields like expiresAt or byteSize.","Call upload.matches(binding) before sealing and fail fast on drift.","Keep one binding per transfer and derive capabilities only from that binding's consent event."],"tags":["teams","upload","scope-mismatch","capability"],"backgroundTag":"invalid-argument-value","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}