{"record":{"id":"65009e807d131aa7","repo":"twentyhq/twenty","slug":"keydescriptor-element-is-missing","errorCode":null,"errorMessage":"KeyDescriptor element is missing","messagePattern":"KeyDescriptor element is missing","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/twenty-front/src/modules/settings/security/utils/parseSAMLMetadataFromXMLFile.ts","lineNumber":82,"sourceCode":"  | { success: true; data: z.infer<typeof validator> }\n  | { success: false; reason: string } => {\n  try {\n    const parser = new DOMParser();\n    const xmlDoc = parser.parseFromString(xmlString, 'application/xml');\n    if (xmlDoc.getElementsByTagName('parsererror').length > 0) {\n      throw new Error('File is not valid XML');\n    }\n\n    const entityDescriptor = getByPrefixAndKey(xmlDoc, 'EntityDescriptor');\n    if (!entityDescriptor)\n      throw new Error('EntityDescriptor element is missing');\n\n    const IDPSSODescriptor = getByPrefixAndKey(xmlDoc, 'IDPSSODescriptor');\n    if (!IDPSSODescriptor)\n      throw new Error('IDPSSODescriptor element is missing');\n\n    const keyDescriptors = getByPrefixAndKey(IDPSSODescriptor, 'KeyDescriptor');\n    if (!keyDescriptors) throw new Error('KeyDescriptor element is missing');\n\n    const keyInfo = getByPrefixAndKey(keyDescriptors, 'KeyInfo');\n    if (!keyInfo) throw new Error('KeyInfo element is missing');\n\n    const x509Data = getByPrefixAndKey(keyInfo, 'X509Data');\n    if (!x509Data) throw new Error('X509Data element is missing');\n\n    const x509Certificate = getByPrefixAndKey(\n      x509Data,\n      'X509Certificate',\n    )?.textContent?.trim();\n    if (!x509Certificate)\n      throw new Error('X509Certificate is missing or empty');\n\n    const singleSignOnServices = getAllByPrefixAndKey(\n      IDPSSODescriptor,\n      'SingleSignOnService',\n    ).map((service) => ({","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/twentyhq/twenty/blob/1f5dd2bbd2a8da3419c8cfd52dd545c0024df1a6/packages/twenty-front/src/modules/settings/security/utils/parseSAMLMetadataFromXMLFile.ts#L64-L100","documentation":"Thrown by parseSAMLMetadataFromXMLFile when an IdP metadata XML parses successfully and contains an IDPSSODescriptor, but no <KeyDescriptor> child can be located. The lookup (getByPrefixAndKey) only searches a fixed namespace-prefix allowlist (md, ns0, ns2, dsig, ds) plus the bare tag name, so a valid element under a different prefix is treated as absent. KeyDescriptor is the SAML container for the IdP signing certificate.","triggerScenarios":"Uploading an XML file that is valid SAML metadata and has IDPSSODescriptor but no KeyDescriptor inside it (e.g., SP metadata, or an IdP that omits the signing role). Also triggered when KeyDescriptor exists but uses a namespace prefix not in the allowlist (md/ns0/ns2/dsig/ds), since getByPrefixAndKey will not match it.","commonSituations":"User uploads the Service Provider metadata XML by mistake instead of the Identity Provider metadata. IdP exports metadata with a custom/uncommon namespace prefix. Metadata is for a different role descriptor (e.g., AttributeAuthority) that has no KeyDescriptor.","solutions":["Confirm the uploaded file is the IdP metadata (must contain <EntityDescriptor> with an <IDPSSODescriptor>).","Open the XML and verify a <KeyDescriptor> element exists as a direct child of <IDPSSODescriptor>.","If KeyDescriptor uses an unusual namespace prefix, add that prefix to the `allPrefix` array in parseSAMLMetadataFromXMLFile.ts or re-export metadata from the IdP with a standard prefix (md/ds)."],"exampleFix":"// before: <ns5:KeyDescriptor use=\"signing\"> ... </ns5:KeyDescriptor> (prefix ns5 not in allowlist)\n// after:  <md:KeyDescriptor use=\"signing\"> ... </md:KeyDescriptor>","handlingStrategy":"validation","validationCode":"const validateIdPMetadataShape = (xml: string): string[] => {\n  const doc = new DOMParser().parseFromString(xml, 'application/xml');\n  if (doc.getElementsByTagName('parsererror').length) return ['File is not valid XML'];\n  const prefixes = ['md', 'ns0', 'ns2', 'dsig', 'ds', ''];\n  const has = (parent: Document | Element, tag: string) =>\n    prefixes.some((p) => parent.getElementsByTagName(p ? `${p}:${tag}` : tag).length > 0);\n  const errs: string[] = [];\n  if (!has(doc, 'EntityDescriptor')) errs.push('EntityDescriptor element is missing');\n  if (!has(doc, 'IDPSSODescriptor')) errs.push('IDPSSODescriptor element is missing');\n  if (!has(doc, 'KeyDescriptor')) errs.push('KeyDescriptor element is missing');\n  return errs;\n};","typeGuard":"const isLikelyIdPMetadata = (xml: string): boolean => {\n  const doc = new DOMParser().parseFromString(xml, 'application/xml');\n  return doc.getElementsByTagName('parsererror').length === 0\n    && /IDPSSODescriptor/.test(xml)\n    && /KeyDescriptor/.test(xml);\n};","tryCatchPattern":"// parseSAMLMetadataFromXMLFile already returns { success, reason }; use that\nconst res = parseSAMLMetadataFromXMLFile(xml);\nif (!res.success) {\n  showFormError(res.reason); // e.g. 'KeyDescriptor element is missing'\n  return;\n}","preventionTips":["Always fetch IdP metadata directly from the IdP's well-known URL rather than copy-pasting.","Validate the XML shape client-side before submission and show the missing-element name to the user.","If your IdP uses a non-standard namespace prefix, extend the allPrefix allowlist in parseSAMLMetadataFromXMLFile.ts."],"tags":["saml","sso","xml","enterprise","validation"],"backgroundTag":null,"analyzedSha":"1f5dd2bbd2a8da3419c8cfd52dd545c0024df1a6","analyzedAt":"2026-08-12T15:37:27.593Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}