paperclipai/paperclip · error

Invalid Teams upload binding

Error message

Invalid Teams upload binding

What it means

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.

Source

Thrown at server/src/services/chat-teams-file-consent.ts:370

    this.#bindingDigest = digest(binding);
    this.#expiresAt = Date.parse(binding.expiresAt);
    this.#byteSize = binding.byteSize;
    this.#sha256 = binding.sha256;
    Object.freeze(this);
  }
  toJSON() {
    return undefined;
  }
  async seal(
    context: TeamsFilePrivateContext,
    binding: TeamsFileConsentBinding,
  ) {
    if (
      !this.matches(binding) ||
      context.companyId !== binding.companyId ||
      context.endpointId !== binding.endpointId
    )
      throw new Error("Invalid Teams upload binding");
    return sealPrivate(context, "upload", {
      bindingDigest: digest(binding),
      info: this.#info,
      confirmed: this.#confirmed,
      putStarted: this.#putStarted,
    });
  }
  static async restore(
    context: TeamsFilePrivateContext,
    binding: TeamsFileConsentBinding,
    material: TeamsFileCiphertext,
  ) {
    if (
      context.companyId !== binding.companyId ||
      context.endpointId !== binding.endpointId
    )
      throw new Error("Invalid Teams upload binding");
    const value = z

View on GitHub (pinned to 01ad858492)

Solutions

  1. 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.
  2. Verify context.companyId and context.endpointId equal binding.companyId/endpointId before sealing.
  3. Call upload.matches(binding) first to confirm the digest alignment and regenerate the capability from the consent event if it returns false.
  4. 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.

Example fix

// before
binding.expiresAt = newExpiry;
await sealTeamsFileUpload(context, binding, upload); // digest mismatch
// after
// keep binding immutable; issue a new consent flow if expiry must change
await sealTeamsFileUpload(context, originalBinding, upload);
Defensive patterns

Strategy: validation

Validate before calling

if (!(upload instanceof UploadCapability) || !upload.matches(binding) || context.companyId !== binding.companyId || context.endpointId !== binding.endpointId) {
  throw new Error('upload/binding/context mismatch before seal');
}

Type guard

function canSealUpload(u: TeamsFileUploadCapability, b: TeamsFileConsentBinding, ctx: TeamsFilePrivateContext): boolean {
  return u.matches(b) && ctx.companyId === b.companyId && ctx.endpointId === b.endpointId;
}

Try / catch

try {
  const material = await sealTeamsFileUpload(context, binding, upload);
} catch (err) {
  if (err instanceof Error && err.message === 'Invalid Teams upload binding') {
    // binding drifted from capability; regenerate capability from the consent event or restart the flow
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/e90714b846604fbe. Report an issue: GitHub.