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 = zView on GitHub (pinned to 01ad858492)
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.
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
- 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.
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
- Invalid Teams private binding
- Teams upload is not confirmed
- Teams upload bytes do not match consent
- ${prefix}: the capability must be an object.
- DUPLEX_CHANNEL_CAPABILITY_DENIED
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/e90714b846604fbe.
Report an issue: GitHub.