├── test ├── contract │ ├── data │ │ ├── common_vulnerabilities │ │ │ ├── billion_laughs │ │ │ │ ├── billion_laughs.formatted.xml │ │ │ │ ├── billion_laughs.test.json │ │ │ │ └── billion_laughs.xml │ │ │ ├── external_entity_expansion │ │ │ │ ├── external_entity_expansion.formatted.xml │ │ │ │ ├── external_entity_expansion.xml │ │ │ │ └── external_entity_expansion.test.json │ │ │ ├── error_bad_audience │ │ │ │ └── error_bad_audience.test.json │ │ │ ├── quadratic_blowup │ │ │ │ └── quadratic_blowup.test.json │ │ │ ├── error_no_signatures │ │ │ │ ├── error_no_signatures.test.json │ │ │ │ ├── unsigned_response.formatted.xml │ │ │ │ └── unsigned_response.xml │ │ │ ├── not_before_already_passed │ │ │ │ └── not_before_already_passed.test.json │ │ │ ├── not_after_already_passed │ │ │ │ └── not_after_already_passed.test.json │ │ │ ├── processing_instructions_not_allowed │ │ │ │ └── processing_instructions_not_allowed.test.json │ │ │ ├── comments_not_allowed_in_attribute │ │ │ │ └── comments_not_allowed_in_attribute.test.json │ │ │ ├── embedded_comment_in_nameid │ │ │ │ └── embedded_comment_in_nameid.test.json │ │ │ ├── comments_not_allowed_in_nameid │ │ │ │ └── comments_not_allowed_in_nameid.test.json │ │ │ └── embedded_comment_in_attribute │ │ │ │ └── embedded_comment_in_attribute.test.json │ │ ├── error_cases │ │ │ ├── empty_response.test.json │ │ │ ├── invalid_xml.test.json │ │ │ ├── missing_signature.test.json │ │ │ └── missing_signature.xml │ │ ├── security_validations │ │ │ ├── unsigned_encrypted_assertion.test.json │ │ │ ├── doctype_without_entity.test.json │ │ │ ├── multiple_assertions.test.json │ │ │ ├── multiple_response_elements.test.json │ │ │ ├── forbidden_authn_request.test.json │ │ │ ├── too_many_transforms.test.json │ │ │ ├── malformed_signature_uri.test.json │ │ │ ├── nonexistent_id_reference.test.json │ │ │ ├── invalid_transform_algorithm.test.json │ │ │ ├── invalid_canonicalization_method.test.json │ │ │ ├── unsigned_encrypted_assertion.xml │ │ │ ├── forbidden_authn_request.xml │ │ │ ├── multiple_assertions.xml │ │ │ ├── multiple_response_elements.xml │ │ │ ├── invalid_transform_algorithm.xml │ │ │ ├── invalid_canonicalization_method.xml │ │ │ ├── too_many_transforms.xml │ │ │ └── doctype_without_entity.xml │ │ ├── valid │ │ │ ├── okta_sp_initiated │ │ │ │ └── okta_sp_initiated.test.json │ │ │ ├── onelogin_sp_initiated │ │ │ │ └── onelogin_sp_initiated.test.json │ │ │ ├── okta_idp_initiated │ │ │ │ └── okta_idp_initiated.test.json │ │ │ ├── okta_sp_initiated_groups │ │ │ │ └── okta_sp_initiated_groups.test.json │ │ │ ├── okta_sp_initiated_explicit_username │ │ │ │ └── okta_sp_initiated_explicit_username.test.json │ │ │ ├── pingone │ │ │ │ ├── pingone_idp_initiated_sha_512 │ │ │ │ │ └── pingone_idp_initiated_sha_512.test.json │ │ │ │ ├── pingone_groups_and_emails_as_attrs │ │ │ │ │ └── pingone_groups_and_emails_as_attrs.test.json │ │ │ │ ├── pingone_idp_initiated_only_response_signed │ │ │ │ │ ├── pingone_idp_initiated_only_response_signed.test.json │ │ │ │ │ ├── idp_initiated_only_response_signed.xml │ │ │ │ │ └── idp_initiated_only_response_signed.formatted.xml │ │ │ │ ├── pingone_idp_initiated_only_assertion_signed │ │ │ │ │ ├── pingone_idp_initiated_only_assertion_signed.test.json │ │ │ │ │ ├── idp_initiated_only_assertion_signed.xml │ │ │ │ │ └── idp_initiated_only_assertion_signed.formatted.xml │ │ │ │ ├── pingone_idp_initiated_response_signed_assertions_signed │ │ │ │ │ └── pingone_idp_initiated_response_signed_assertions_signed.test.json │ │ │ │ └── pingone_sp_initiated_response_signed_assertions_signed │ │ │ │ │ └── pingone_sp_initiated_response_signed_assertions_signed.test.json │ │ │ ├── google │ │ │ │ ├── google_sp_initiated │ │ │ │ │ ├── google_sp_initiated.test.json │ │ │ │ │ ├── sp_initiated.xml │ │ │ │ │ └── sp_initiated.formatted.xml │ │ │ │ ├── google_idp_initiated │ │ │ │ │ ├── google_idp_initiated.test.json │ │ │ │ │ ├── idp_initiated.xml │ │ │ │ │ └── idp_initiated.formatted.xml │ │ │ │ ├── google_groups_and_attributes │ │ │ │ │ ├── google_groups_and_attributes.test.json │ │ │ │ │ ├── groups_and_attributes.xml │ │ │ │ │ └── groups_and_attributes.formatted.xml │ │ │ │ └── google_sp_initiated_response_signed │ │ │ │ │ ├── google_sp_initiated_response_signed.test.json │ │ │ │ │ ├── sp_initiated_response_signed.xml │ │ │ │ │ └── sp_initiated_response_signed.formatted.xml │ │ │ ├── keycloak_sp_initiated │ │ │ │ └── keycloak_sp_initiated.test.json │ │ │ ├── keycloak_idp_initiated │ │ │ │ ├── keycloak_idp_initiated.test.json │ │ │ │ └── keycloak_idp_initiated_response.xml │ │ │ └── onelogin_idp_initiated │ │ │ │ └── onelogin_idp_initiated.test.json │ │ ├── security_validations_malicious │ │ │ ├── invalid_transform_attack.test.json │ │ │ ├── too_many_transforms_attack.test.json │ │ │ ├── multiple_assertions_attack.test.json │ │ │ ├── doctype_simple_attack.xml │ │ │ ├── doctype_simple_attack.test.json │ │ │ ├── hmac_signature_method.test.json │ │ │ ├── doctype_entity_attack.test.json │ │ │ ├── forbidden_authnrequest_attack.test.json │ │ │ ├── invalid_canonicalization_attack.test.json │ │ │ ├── multiple_responses_attack.test.json │ │ │ ├── malformed_uri_working_base.test.json │ │ │ ├── digestvalue_wrapping_attack.test.json │ │ │ ├── digestvalue_location_mismatch.test.json │ │ │ ├── nonexistent_id_working_base.test.json │ │ │ ├── doctype_entity_attack.xml │ │ │ ├── forbidden_authnrequest_attack.xml │ │ │ ├── multiple_assertions_attack.xml │ │ │ ├── multiple_responses_attack.xml │ │ │ ├── invalid_canonicalization_attack.xml │ │ │ ├── invalid_transform_attack.xml │ │ │ ├── too_many_transforms_attack.xml │ │ │ ├── README.md │ │ │ ├── hmac_signature_method.xml │ │ │ ├── digestvalue_wrapping_attack.xml │ │ │ └── digestvalue_location_mismatch.xml │ │ ├── vulnerabilities │ │ │ ├── xml_comment_injection.test.json │ │ │ ├── processing_instructions.test.json │ │ │ ├── multiple_signedinfo.test.json │ │ │ ├── processing_instructions.xml │ │ │ ├── multiple_signedinfo.xml │ │ │ └── xml_comment_injection.xml │ │ ├── digest_vuln │ │ │ ├── digest_vuln.test.json │ │ │ ├── poc.xml │ │ │ └── poc.formatted.xml │ │ └── multiple_signedinfo_nodes_vuln │ │ │ └── multiple_signedinfo_nodes_vuln.test.json │ ├── runner.test.ts │ └── loader.ts └── basic.test.js ├── .gitignore ├── .husky └── pre-commit ├── tsconfig.test.json ├── tsconfig.json ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.yml │ ├── bug_report.yml │ └── cve_request.yml └── workflows │ └── on-pr.yml ├── .eslintrc.js ├── src ├── index.ts └── errors.ts ├── package.json ├── examples └── basic-usage.js ├── CLAUDE.md ├── SECURITY.md └── CONTRACT_TESTING.md /test/contract/data/common_vulnerabilities/billion_laughs/billion_laughs.formatted.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | *.tgz 4 | .DS_Store 5 | .env 6 | .env.local 7 | .env.production.local 8 | .env.test.local 9 | coverage/ 10 | *.log 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | .idea/ -------------------------------------------------------------------------------- /test/contract/data/common_vulnerabilities/external_entity_expansion/external_entity_expansion.formatted.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | ]> 6 | &xxe; 7 | -------------------------------------------------------------------------------- /test/contract/data/common_vulnerabilities/external_entity_expansion/external_entity_expansion.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | ]> 5 | &xxe; 6 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | echo "Running format" 5 | yarn format 6 | echo "Running lint" 7 | yarn lint 8 | echo "Running build" 9 | yarn build 10 | echo "Running test" 11 | yarn test 12 | echo "Checking diff..." 13 | git diff --exit-code -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./test-dist", 5 | "rootDir": ".", 6 | "declaration": false, 7 | "types": ["jest", "node"] 8 | }, 9 | "include": ["test/**/*.ts", "src/**/*.ts"], 10 | "exclude": ["node_modules", "dist", "test-dist"] 11 | } 12 | -------------------------------------------------------------------------------- /test/contract/data/error_cases/empty_response.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Empty SAML Response", 3 | "description": "Should reject empty response_xml", 4 | "input": { 5 | "response_xml": "" 6 | }, 7 | "shouldSucceed": false, 8 | "expectedError": "missing required field: SAMLResponse", 9 | "expectedErrorCode": "validation_error" 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/security_validations/unsigned_encrypted_assertion.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Unsigned Encrypted Assertion - Valid Case", 3 | "description": "Should accept unsigned encrypted assertions when the response is signed", 4 | "input": { 5 | "response_xml": "!embed:base64:file://unsigned_encrypted_assertion.xml" 6 | }, 7 | "shouldSucceed": true, 8 | "expectedErrorCode": null 9 | } 10 | -------------------------------------------------------------------------------- /test/contract/data/error_cases/invalid_xml.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Invalid XML Structure", 3 | "description": "Should reject non-SAML XML content", 4 | "input": { 5 | "response_xml": "!embed:base64:not a saml response" 6 | }, 7 | "shouldSucceed": false, 8 | "expectedError": "document does not contain a SAML Response element", 9 | "expectedErrorCode": "xml_validation_error" 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/valid/okta_sp_initiated/okta_sp_initiated.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Okta SP Initiated", 3 | "description": "Should pass validation for Okta SP initiated SAML response", 4 | "comment": "Test metadata generated from personal Okta acct", 5 | "input": { 6 | "response_xml": "!embed:base64:file://okta_personal_sp_initiated_response.xml" 7 | }, 8 | "mockTime": "2022-11-17T22:15:54.0060Z", 9 | "shouldSucceed": true 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/valid/onelogin_sp_initiated/onelogin_sp_initiated.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "OneLogin SP Initiated", 3 | "description": "Should pass validation for OneLogin SP initiated SAML response", 4 | "comment": ["Test response generated from personal OneLogin acct."], 5 | "input": { 6 | "response_xml": "!embed:base64:file://sp_initiated.xml" 7 | }, 8 | "mockTime": "2022-11-17T23:02:26.0000Z", 9 | "shouldSucceed": true 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/error_cases/missing_signature.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Missing Signature", 3 | "description": "Should reject SAML responses with no signature on either response or assertion", 4 | "input": { 5 | "response_xml": "!embed:base64:file://missing_signature.xml" 6 | }, 7 | "shouldSucceed": false, 8 | "expectedError": "one of response or assertion must be signed", 9 | "expectedErrorCode": "saml_assertion_not_signed" 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/valid/okta_idp_initiated/okta_idp_initiated.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Okta IDP Initiated", 3 | "description": "Should pass validation for Okta IDP initiated SAML response", 4 | "comment": "Test metadata generated from personal Okta acct", 5 | "input": { 6 | "response_xml": "!embed:base64:file://okta_personal_idp_initiated_response.xml" 7 | }, 8 | "mockTime": "2022-11-04T03:07:29.7940Z", 9 | "shouldSucceed": true 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/valid/okta_sp_initiated_groups/okta_sp_initiated_groups.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Okta SP Initiated Groups", 3 | "description": "Should pass validation for Okta SP initiated with groups", 4 | "comment": "Test metadata generated from personal Okta acct", 5 | "input": { 6 | "response_xml": "!embed:base64:file://okta_personal_sp_initiated_groups_response.xml" 7 | }, 8 | "mockTime": "2022-11-17T22:30:38.9190Z", 9 | "shouldSucceed": true 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/security_validations/doctype_without_entity.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DOCTYPE without Entity - String Level Validation", 3 | "description": "Should detect and block DOCTYPE declarations without entities at string level", 4 | "input": { 5 | "response_xml": "!embed:base64:file://doctype_without_entity.xml" 6 | }, 7 | "shouldSucceed": false, 8 | "expectedError": "DOCTYPE detected and blocked", 9 | "expectedErrorCode": "xml_validation_error" 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/security_validations/multiple_assertions.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Multiple Assertions - Element Count Validation", 3 | "description": "Should detect and block multiple Assertion elements", 4 | "input": { 5 | "response_xml": "!embed:base64:file://multiple_assertions.xml" 6 | }, 7 | "shouldSucceed": false, 8 | "expectedError": "Found 2 of Assertions/EncryptedAssertion elements. Only one allowed", 9 | "expectedErrorCode": "xml_validation_error" 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/common_vulnerabilities/billion_laughs/billion_laughs.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Billion Laughs XML DOS Attack", 3 | "description": "Tests protection against billion laughs XML DOS attack using external entity expansion", 4 | "input": { 5 | "response_xml": "!embed:base64:file://billion_laughs.xml" 6 | }, 7 | "shouldSucceed": false, 8 | "expectedError": "Invalid input: External Entities are forbidden", 9 | "expectedErrorCode": "xml_validation_error" 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/security_validations/multiple_response_elements.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Multiple Response Elements - Element Count Validation", 3 | "description": "Should detect and block multiple Response elements", 4 | "input": { 5 | "response_xml": "!embed:base64:file://multiple_response_elements.xml" 6 | }, 7 | "shouldSucceed": false, 8 | "expectedError": "document contains multiple SAML Response elements", 9 | "expectedErrorCode": "xml_validation_error" 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/invalid_transform_attack.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Invalid Transform Attack", 3 | "description": "Should fail validateTransforms due to invalid transform algorithm", 4 | "comment": "Tests that validateTransforms blocks invalid transform algorithms", 5 | "input": { 6 | "response_xml": "!embed:base64:file://invalid_transform_attack.xml" 7 | }, 8 | "shouldSucceed": false, 9 | "expectedError": "Unexpected transform algorithm" 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/vulnerabilities/xml_comment_injection.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "XML Comment Injection Vulnerability", 3 | "description": "Should detect and block SAML responses with XML comments", 4 | "input": { 5 | "response_xml": "!embed:base64:file://xml_comment_injection.xml" 6 | }, 7 | "mockTime": "2022-11-22T20:25:00Z", 8 | "shouldSucceed": false, 9 | "expectedError": "response contained illegal XML comments", 10 | "expectedErrorCode": "xml_validation_error" 11 | } 12 | -------------------------------------------------------------------------------- /test/contract/data/security_validations/forbidden_authn_request.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Forbidden AuthnRequest Element - Element Count Validation", 3 | "description": "Should detect and block forbidden AuthnRequest elements in responses", 4 | "input": { 5 | "response_xml": "!embed:base64:file://forbidden_authn_request.xml" 6 | }, 7 | "shouldSucceed": false, 8 | "expectedError": "Found 1 AuthnRequest elements. None allowed in SAML responses", 9 | "expectedErrorCode": "xml_validation_error" 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/security_validations/too_many_transforms.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Too Many Transforms - Transform Validation", 3 | "description": "Should detect and block signature references with too many transforms", 4 | "input": { 5 | "response_xml": "!embed:base64:file://too_many_transforms.xml" 6 | }, 7 | "shouldSucceed": false, 8 | "mockTime": "2022-11-17T22:15:54.006Z", 9 | "expectedError": "Too many transforms: 3. Maximum 2 allowed", 10 | "expectedErrorCode": "xml_validation_error" 11 | } 12 | -------------------------------------------------------------------------------- /test/contract/data/security_validations/malformed_signature_uri.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Malformed Signature URI - Signature URI Validation", 3 | "description": "Should detect and block malformed signature URI references", 4 | "input": { 5 | "response_xml": "!embed:base64:file://malformed_signature_uri.xml" 6 | }, 7 | "mockTime": "2022-11-17T22:15:54.006Z", 8 | "shouldSucceed": false, 9 | "expectedError": "Malformed URI: http://evil.com/malicious", 10 | "expectedErrorCode": "xml_validation_error" 11 | } 12 | -------------------------------------------------------------------------------- /test/contract/data/valid/okta_sp_initiated_explicit_username/okta_sp_initiated_explicit_username.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Okta SP Initiated Explicit Username", 3 | "description": "Should pass validation for Okta SP initiated with explicit username", 4 | "comment": "Test metadata generated from personal Okta acct", 5 | "input": { 6 | "response_xml": "!embed:base64:file://okta_personal_sp_initiated_explicit_username.xml" 7 | }, 8 | "mockTime": "2022-11-17T22:34:06.7180Z", 9 | "shouldSucceed": true 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/too_many_transforms_attack.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Too Many Transforms Attack", 3 | "description": "Should fail validateTransforms due to too many transform elements", 4 | "comment": "Tests that validateTransforms blocks excessive transform elements (max 2 allowed)", 5 | "input": { 6 | "response_xml": "!embed:base64:file://too_many_transforms_attack.xml" 7 | }, 8 | "shouldSucceed": false, 9 | "expectedError": "Too many transforms: 3. Maximum 2 allowed" 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/vulnerabilities/processing_instructions.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Processing Instructions Vulnerability", 3 | "description": "Should always detect and block SAML responses with processing instructions", 4 | "input": { 5 | "response_xml": "!embed:base64:file://processing_instructions.xml" 6 | }, 7 | "mockTime": "2022-11-04T03:07:29Z", 8 | "shouldSucceed": false, 9 | "expectedError": "response contained illegal processing instructions", 10 | "expectedErrorCode": "xml_validation_error" 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "lib": ["ES2020"], 5 | "module": "commonjs", 6 | "declaration": true, 7 | "outDir": "./dist", 8 | "rootDir": "./src", 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "resolveJsonModule": true, 14 | "moduleResolution": "node" 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules", "dist", "**/*.test.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/multiple_assertions_attack.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Multiple Assertions Attack", 3 | "description": "Should fail validateElementCounts due to multiple assertion elements", 4 | "comment": "Tests that validateElementCounts blocks multiple assertions", 5 | "input": { 6 | "response_xml": "!embed:base64:file://multiple_assertions_attack.xml" 7 | }, 8 | "shouldSucceed": false, 9 | "expectedError": "Found 2 of Assertions/EncryptedAssertion elements. Only one allowed" 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/doctype_simple_attack.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | http://www.test.com/issuer 5 | 6 | -------------------------------------------------------------------------------- /test/contract/data/security_validations/nonexistent_id_reference.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Non-existent ID Reference - Signature URI Validation", 3 | "description": "Should detect and block signature references to non-existent IDs", 4 | "input": { 5 | "response_xml": "!embed:base64:file://nonexistent_id_reference.xml" 6 | }, 7 | "mockTime": "2022-11-17T22:15:54.006Z", 8 | "shouldSucceed": false, 9 | "expectedError": "URI references non-existent ID: nonexistent_id_reference", 10 | "expectedErrorCode": "xml_validation_error" 11 | } 12 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/doctype_simple_attack.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DOCTYPE Simple Attack - performStringLevelValidation", 3 | "description": "Should fail performStringLevelValidation due to simple DOCTYPE declaration without entities", 4 | "comment": "Tests that performStringLevelValidation blocks simple DOCTYPE declarations", 5 | "input": { 6 | "response_xml": "!embed:base64:file://doctype_simple_attack.xml" 7 | }, 8 | "shouldSucceed": false, 9 | "expectedError": "DOCTYPE detected and blocked" 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/hmac_signature_method.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "HMAC SignatureMethod Blocked - Security Validation", 3 | "description": "Should detect and block HMAC-based signature methods which are unsuitable for SAML", 4 | "input": { 5 | "response_xml": "!embed:base64:file://hmac_signature_method.xml" 6 | }, 7 | "shouldSucceed": false, 8 | "expectedError": "HMAC-based SignatureMethod blocked: http://www.w3.org/2000/09/xmldsig#hmac-sha1", 9 | "expectedErrorCode": "xml_validation_error" 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/vulnerabilities/multiple_signedinfo.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Multiple SignedInfo Elements", 3 | "description": "Should detect and block SAML responses with multiple SignedInfo elements in a single signature", 4 | "input": { 5 | "response_xml": "!embed:base64:file://multiple_signedinfo.xml" 6 | }, 7 | "mockTime": "2022-11-04T03:07:29Z", 8 | "shouldSucceed": false, 9 | "expectedError": "response contained multiple SignedInfo elements in a single signature", 10 | "expectedErrorCode": "xml_validation_error" 11 | } 12 | -------------------------------------------------------------------------------- /test/contract/data/security_validations/invalid_transform_algorithm.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Invalid Transform Algorithm - Transform Validation", 3 | "description": "Should detect and block invalid transform algorithms", 4 | "input": { 5 | "response_xml": "!embed:base64:file://invalid_transform_algorithm.xml" 6 | }, 7 | "mockTime": "2022-11-17T22:15:54.006Z", 8 | "shouldSucceed": false, 9 | "expectedError": "Unexpected transform algorithm: http://malicious.com/evil-transform", 10 | "expectedErrorCode": "xml_validation_error" 11 | } 12 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/doctype_entity_attack.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DOCTYPE Entity Attack - XML Parser Validation", 3 | "description": "Should fail XML parser validation due to DOCTYPE with entity declaration (not performStringLevelValidation)", 4 | "comment": "Tests that XML parser blocks DOCTYPE declarations with entities", 5 | "input": { 6 | "response_xml": "!embed:base64:file://doctype_entity_attack.xml" 7 | }, 8 | "shouldSucceed": false, 9 | "expectedError": "External Entities are forbidden" 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/forbidden_authnrequest_attack.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Forbidden AuthnRequest Attack", 3 | "description": "Should fail validateElementCounts due to forbidden AuthnRequest element", 4 | "comment": "Tests that validateElementCounts blocks forbidden elements in responses", 5 | "input": { 6 | "response_xml": "!embed:base64:file://forbidden_authnrequest_attack.xml" 7 | }, 8 | "shouldSucceed": false, 9 | "expectedError": "Found 1 AuthnRequest elements. None allowed in SAML responses" 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/invalid_canonicalization_attack.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Invalid Canonicalization Attack", 3 | "description": "Should fail validateCanonicalizationMethod due to invalid canonicalization algorithm", 4 | "comment": "Tests that validateCanonicalizationMethod blocks invalid canonicalization algorithms", 5 | "input": { 6 | "response_xml": "!embed:base64:file://invalid_canonicalization_attack.xml" 7 | }, 8 | "shouldSucceed": false, 9 | "expectedError": "Invalid CanonicalizationMethod algorithm" 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/valid/pingone/pingone_idp_initiated_sha_512/pingone_idp_initiated_sha_512.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PingOne IDP Initiated SHA-512", 3 | "description": "Should pass validation for PingOne IDP initiated with SHA-512 signing", 4 | "comment": [ 5 | "Test response generated from personal PingOne acct.", 6 | "Both assertion and response are signed using SHA512" 7 | ], 8 | "input": { 9 | "response_xml": "!embed:base64:file://sha_512.xml" 10 | }, 11 | "mockTime": "2022-11-18T22:48:08.6880Z", 12 | "shouldSucceed": true 13 | } 14 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/multiple_responses_attack.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Multiple Responses Attack - validateSAMLResponseStructure", 3 | "description": "Should fail validateSAMLResponseStructure due to multiple response elements", 4 | "comment": "Tests that validateSAMLResponseStructure blocks multiple response elements", 5 | "input": { 6 | "response_xml": "!embed:base64:file://multiple_responses_attack.xml" 7 | }, 8 | "shouldSucceed": false, 9 | "expectedError": "document contains multiple SAML Response elements" 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/valid/google/google_sp_initiated/google_sp_initiated.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Google SP Initiated", 3 | "description": "Should pass validation for Google Workspace SP initiated SAML response", 4 | "comment": [ 5 | "Test data generated from stytchdev Google Worksace testing account", 6 | "To connect with minisp running locally - spin up an NGROK instance" 7 | ], 8 | "input": { 9 | "response_xml": "!embed:base64:file://sp_initiated.xml" 10 | }, 11 | "mockTime": "2022-11-22T20:27:15.8820Z", 12 | "shouldSucceed": true 13 | } 14 | -------------------------------------------------------------------------------- /test/contract/data/valid/google/google_idp_initiated/google_idp_initiated.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Google IDP Initiated", 3 | "description": "Should pass validation for Google Workspace IDP initiated SAML response", 4 | "comment": [ 5 | "Test data generated from stytchdev Google Worksace testing account", 6 | "To connect with minisp running locally - spin up an NGROK instance" 7 | ], 8 | "input": { 9 | "response_xml": "!embed:base64:file://idp_initiated.xml" 10 | }, 11 | "mockTime": "2022-11-22T18:45:38.6230Z", 12 | "shouldSucceed": true 13 | } 14 | -------------------------------------------------------------------------------- /test/contract/data/valid/pingone/pingone_groups_and_emails_as_attrs/pingone_groups_and_emails_as_attrs.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PingOne Groups and Emails as Attributes", 3 | "description": "Should pass validation for PingOne with groups and email attributes", 4 | "comment": [ 5 | "Test response generated from personal PingOne acct.", 6 | "Includes email and group claims" 7 | ], 8 | "input": { 9 | "response_xml": "!embed:base64:file://groups_and_email_as_attributes.xml" 10 | }, 11 | "mockTime": "2022-11-18T22:26:34.4840Z", 12 | "shouldSucceed": true 13 | } 14 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/malformed_uri_working_base.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Malformed URI Attack - validateSignatureURI", 3 | "description": "Should fail validateSignatureURI due to malformed URI that doesn't start with #", 4 | "comment": "Tests that validateSignatureURI blocks malformed URI references", 5 | "input": { 6 | "response_xml": "!embed:base64:file://malformed_uri_working_base.xml" 7 | }, 8 | "mockTime": "2022-11-17T22:15:54.006Z", 9 | "shouldSucceed": false, 10 | "expectedError": "Malformed URI: http://evil.com/malicious" 11 | } 12 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/digestvalue_wrapping_attack.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DigestValue Wrapping Attack - Multiple DigestValue Elements", 3 | "description": "Should detect and block multiple DigestValue elements that could enable wrapping attacks", 4 | "input": { 5 | "response_xml": "!embed:base64:file://digestvalue_wrapping_attack.xml" 6 | }, 7 | "shouldSucceed": false, 8 | "expectedError": "Expected exactly one DigestValue per signature, found 2. Multiple DigestValues can enable wrapping attacks", 9 | "expectedErrorCode": "xml_validation_error" 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/security_validations/invalid_canonicalization_method.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Invalid Canonicalization Method - Canonicalization Method Validation", 3 | "description": "Should detect and block invalid canonicalization algorithms", 4 | "input": { 5 | "response_xml": "!embed:base64:file://invalid_canonicalization_method.xml" 6 | }, 7 | "mockTime": "2022-11-17T22:15:54.006Z", 8 | "shouldSucceed": false, 9 | "expectedError": "Invalid CanonicalizationMethod algorithm: http://www.w3.org/TR/2001/REC-xml-c14n-20010315", 10 | "expectedErrorCode": "xml_validation_error" 11 | } 12 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/digestvalue_location_mismatch.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DigestValue Location Mismatch - Wrapping Attack Prevention", 3 | "description": "Should detect DigestValue elements placed outside proper Reference context", 4 | "input": { 5 | "response_xml": "!embed:base64:file://digestvalue_location_mismatch.xml" 6 | }, 7 | "shouldSucceed": false, 8 | "expectedError": "Found 2 DigestValue elements but only 1 are in proper signature Reference context. Potential DigestValue wrapping attack detected", 9 | "expectedErrorCode": "xml_validation_error" 10 | } 11 | -------------------------------------------------------------------------------- /test/contract/data/valid/pingone/pingone_idp_initiated_only_response_signed/pingone_idp_initiated_only_response_signed.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PingOne IDP Initiated Only Response Signed", 3 | "description": "Should pass validation for PingOne IDP initiated with only response signed", 4 | "comment": [ 5 | "Test response generated from personal PingOne acct.", 6 | "Only the response is signed" 7 | ], 8 | "input": { 9 | "response_xml": "!embed:base64:file://idp_initiated_only_response_signed.xml" 10 | }, 11 | "mockTime": "2022-11-18T22:50:32.3120Z", 12 | "shouldSucceed": true 13 | } 14 | -------------------------------------------------------------------------------- /test/contract/data/valid/keycloak_sp_initiated/keycloak_sp_initiated.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Keycloak SP Initiated", 3 | "description": "Should pass validation for Keycloak SP initiated SAML response", 4 | "comment": [ 5 | "Test saml response sent for test keycloak acct. ", 6 | "The response we attempt to validate has extra characters encoded ", 7 | "that must be stripped by our transformations." 8 | ], 9 | "input": { 10 | "response_xml": "!embed:base64:file://keycloak_sp_initiated_response.xml" 11 | }, 12 | "mockTime": "2022-11-23T00:58:06.045Z", 13 | "shouldSucceed": true 14 | } 15 | -------------------------------------------------------------------------------- /test/contract/data/valid/pingone/pingone_idp_initiated_only_assertion_signed/pingone_idp_initiated_only_assertion_signed.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PingOne IDP Initiated Only Assertion Signed", 3 | "description": "Should pass validation for PingOne IDP initiated with only assertion signed", 4 | "comment": [ 5 | "Test response generated from personal PingOne acct.", 6 | "Only the assertion is signed" 7 | ], 8 | "input": { 9 | "response_xml": "!embed:base64:file://idp_initiated_only_assertion_signed.xml" 10 | }, 11 | "mockTime": "2022-11-18T22:51:32.1870Z", 12 | "shouldSucceed": true 13 | } 14 | -------------------------------------------------------------------------------- /test/contract/data/error_cases/missing_signature.xml: -------------------------------------------------------------------------------- 1 | http://test.idphttp://test.idpuser@example.com -------------------------------------------------------------------------------- /test/contract/data/common_vulnerabilities/error_bad_audience/error_bad_audience.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SAML Assertion Audience Mismatch", 3 | "description": "Should detect and reject SAML assertions with mismatched audience", 4 | "comment": "Test metadata generated from personal Okta acct", 5 | "input": { 6 | "response_xml": "!embed:base64:file://okta_personal_idp_initiated_response.xml" 7 | }, 8 | "skip": true, 9 | "shouldSucceed": false, 10 | "expectedError": "SAML assertion audience mismatch. Expected: INVALID sp_entity_id Received: example", 11 | "expectedErrorCode": "saml_assertion_audience_mismatch" 12 | } 13 | -------------------------------------------------------------------------------- /test/contract/data/valid/keycloak_idp_initiated/keycloak_idp_initiated.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Keycloak IDP Initiated", 3 | "description": "Should pass validation for Keycloak IDP initiated SAML response", 4 | "comment": [ 5 | "Test saml response sent for test keycloak acct via IDP initiation. ", 6 | "The response we attempt to validate has extra characters encoded ", 7 | "that must be stripped by our transformations." 8 | ], 9 | "input": { 10 | "response_xml": "!embed:base64:file://keycloak_idp_initiated_response.xml" 11 | }, 12 | "mockTime": "2022-11-23T19:15:17.022Z", 13 | "shouldSucceed": true 14 | } 15 | -------------------------------------------------------------------------------- /test/contract/data/common_vulnerabilities/quadratic_blowup/quadratic_blowup.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Quadratic Blowup XML DOS Attack", 3 | "description": "Should detect and block quadratic blowup XML DOS attack", 4 | "comment": [ 5 | "XML DOS attack: Quadratic Blowup:", 6 | "https://www.acunetix.com/vulnerabilities/web/xml-quadratic-blowup-denial-of-service-attack/" 7 | ], 8 | "input": { 9 | "response_xml": "!embed:base64:file://quadratic_blowup.xml" 10 | }, 11 | "shouldSucceed": false, 12 | "expectedError": "Invalid input: External Entities are forbidden", 13 | "expectedErrorCode": "xml_validation_error" 14 | } 15 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/nonexistent_id_working_base.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Nonexistent ID URI Attack - validateSignatureURI", 3 | "description": "Should fail validateSignatureURI due to URI referencing nonexistent ID", 4 | "comment": "Tests that validateSignatureURI blocks references to nonexistent IDs", 5 | "input": { 6 | "response_xml": "!embed:base64:file://nonexistent_id_working_base.xml" 7 | }, 8 | "mockTime": "2022-11-17T22:10:20.006Z", 9 | "shouldSucceed": false, 10 | "expectedError": "URI references non-existent ID: nonexistent_id_reference", 11 | "expectedErrorCode": "xml_validation_error" 12 | } 13 | -------------------------------------------------------------------------------- /test/contract/data/common_vulnerabilities/error_no_signatures/error_no_signatures.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Missing SAML Signatures", 3 | "description": "Should detect and reject SAML responses without required signatures", 4 | "comment": [ 5 | "Failure case - neither response nor assertions are signed", 6 | "Taken from https://www.samltool.com/generic_sso_res.php" 7 | ], 8 | "input": { 9 | "response_xml": "!embed:base64:file://unsigned_response.xml" 10 | }, 11 | "shouldSucceed": false, 12 | "expectedError": "Invalid input: one of response or assertion must be signed", 13 | "expectedErrorCode": "saml_assertion_not_signed" 14 | } 15 | -------------------------------------------------------------------------------- /test/contract/data/digest_vuln/digest_vuln.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "XML Signature Bypass via DigestValue Comment", 3 | "description": "Should detect and block XML signature bypass via DigestValue comment injection", 4 | "comment": [ 5 | "Validates that if we receive a SAML containing comments, we fail", 6 | "Solves XML Signature Bypass via DigestValue Comment" 7 | ], 8 | "input": { 9 | "response_xml": "!embed:base64:file://poc.xml" 10 | }, 11 | "mockTime": "2024-12-08T09:50:00Z", 12 | "shouldSucceed": false, 13 | "expectedError": "Invalid input: response contained illegal XML comments", 14 | "expectedErrorCode": "xml_validation_error" 15 | } 16 | -------------------------------------------------------------------------------- /test/contract/data/valid/google/google_groups_and_attributes/google_groups_and_attributes.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Google Groups and Attributes", 3 | "description": "Should pass validation for Google Workspace with groups and attributes", 4 | "comment": [ 5 | "Test data generated from stytchdev Google Worksace testing account", 6 | "Adds two groups, some filled attributes, and some nil attributes", 7 | "To connect with minisp running locally - spin up an NGROK instance" 8 | ], 9 | "input": { 10 | "response_xml": "!embed:base64:file://groups_and_attributes.xml" 11 | }, 12 | "mockTime": "2022-11-22T20:34:25.8350Z", 13 | "shouldSucceed": true 14 | } 15 | -------------------------------------------------------------------------------- /test/contract/data/valid/pingone/pingone_idp_initiated_response_signed_assertions_signed/pingone_idp_initiated_response_signed_assertions_signed.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PingOne IDP Initiated Response and Assertions Signed", 3 | "description": "Should pass validation for PingOne IDP initiated with both response and assertion signed using SHA256", 4 | "comment": [ 5 | "Test response generated from personal PingOne acct.", 6 | "Both assertion and response are signed using SHA256" 7 | ], 8 | "input": { 9 | "response_xml": "!embed:base64:file://idp_initiated_response_signed_assertions_signed.xml" 10 | }, 11 | "mockTime": "2022-11-18T22:19:11.4220Z", 12 | "shouldSucceed": true 13 | } 14 | -------------------------------------------------------------------------------- /test/contract/data/common_vulnerabilities/external_entity_expansion/external_entity_expansion.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "XML External Entity (XXE) Attack", 3 | "description": "Should detect and block XML external entity attacks", 4 | "comment": [ 5 | "XML External Entity Attack", 6 | "We disable all external entity validation in our parsing logic", 7 | "https://owasp.org/www-community/vulnerabilities/XML_External_Entity_(XXE)_Processing" 8 | ], 9 | "input": { 10 | "response_xml": "!embed:base64:file://external_entity_expansion.xml" 11 | }, 12 | "shouldSucceed": false, 13 | "expectedError": "Invalid input: External Entities are forbidden", 14 | "expectedErrorCode": "xml_validation_error" 15 | } 16 | -------------------------------------------------------------------------------- /test/contract/data/valid/pingone/pingone_sp_initiated_response_signed_assertions_signed/pingone_sp_initiated_response_signed_assertions_signed.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PingOne SP Initiated Response Signed Assertions Signed", 3 | "description": "Should pass validation for PingOne SP initiated with both response and assertion signing", 4 | "comment": [ 5 | "Test response generated from personal PingOne acct.", 6 | "SP initiated via minisp", 7 | "Both assertion and response are signed using SHA512" 8 | ], 9 | "input": { 10 | "response_xml": "!embed:base64:file://sp_initiated_response_signed_assertions_signed.xml" 11 | }, 12 | "mockTime": "2022-11-18T22:55:44.4670Z", 13 | "shouldSucceed": true 14 | } 15 | -------------------------------------------------------------------------------- /test/contract/data/common_vulnerabilities/not_before_already_passed/not_before_already_passed.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SAML Assertion Not Yet Valid (NotBefore)", 3 | "description": "Should detect and reject SAML assertions that are not yet valid", 4 | "comment": [ 5 | "Test with future timestamps - uses XML with NotBefore in the future", 6 | "Mock currentTime is set to earlier than NotBefore, so assertion should not yet be valid" 7 | ], 8 | "input": { 9 | "response_xml": "!embed:base64:file://future_not_before_response.xml" 10 | }, 11 | "mockTime": "2025-07-29T20:00:00.000Z", 12 | "shouldSucceed": false, 13 | "expectedError": "SAML assertion not yet valid", 14 | "expectedErrorCode": "saml_assertion_not_yet_valid" 15 | } 16 | -------------------------------------------------------------------------------- /test/contract/data/multiple_signedinfo_nodes_vuln/multiple_signedinfo_nodes_vuln.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Multiple SignedInfo Nodes Vulnerability", 3 | "description": "Should properly detect and handle multiple SignedInfo elements in a single signature", 4 | "comment": [ 5 | "Validates that if we receive a SAML assertion with multiple SignedInfo elements in a single Signature, we fail", 6 | "Solves XML Signature Bypass via Multiple SignedInfo References" 7 | ], 8 | "input": { 9 | "response_xml": "!embed:base64:file://poc.xml" 10 | }, 11 | "mockTime": "2022-11-04T03:05:00Z", 12 | "shouldSucceed": false, 13 | "expectedError": "Invalid input: response contained multiple SignedInfo elements", 14 | "expectedErrorCode": "xml_validation_error" 15 | } 16 | -------------------------------------------------------------------------------- /test/contract/data/valid/google/google_sp_initiated_response_signed/google_sp_initiated_response_signed.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Google SP Initiated Response Signed", 3 | "description": "Should pass validation for Google Workspace SP initiated with response signing", 4 | "comment": [ 5 | "Test data generated from stytchdev Google Worksace testing account", 6 | "Google will sign the Response or the Assertion, but not both!", 7 | "By default, the Assertion is signed. This login has the Response signed instead", 8 | "To connect with minisp running locally - spin up an NGROK instance" 9 | ], 10 | "input": { 11 | "response_xml": "!embed:base64:file://sp_initiated_response_signed.xml" 12 | }, 13 | "mockTime": "2022-11-22T20:31:07.2390Z", 14 | "shouldSucceed": true 15 | } 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: 🧑‍💻 Managed Version Support (Stytch Slack) 4 | url: https://join.slack.com/t/stytch/shared_invite/zt-2n6jvvgzm-fAyxi_kr1uqGc15UJqxiPw 5 | about: For SaaS-managed version support, please use the Stytch Slack. 6 | 7 | issue_templates: 8 | - name: 🐞 OSS Bug Report 9 | description: Report unexpected behavior in the open-source SAML Shield library 10 | file: bug_report.yml 11 | 12 | - name: ✨ OSS Feature Request 13 | description: Suggest a feature or improvement for the OSS library 14 | file: feature_request.yml 15 | 16 | - name: 🛡️ Request Coverage for Existing CVE 17 | description: Submit a public CVE you'd like SAML Shield to proactively block 18 | file: cve_request.yml 19 | -------------------------------------------------------------------------------- /test/contract/data/common_vulnerabilities/not_after_already_passed/not_after_already_passed.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SAML Assertion Expired (NotAfter)", 3 | "description": "Should detect and reject SAML assertions that have expired", 4 | "comment": [ 5 | "Test with expired timestamps - uses dedicated test that mocks time to day after NotOnOrAfter", 6 | "The timestamps in the XML are from 2022-11-17, so with mocked time of day after, they should be expired" 7 | ], 8 | "input": { 9 | "response_xml": "!embed:base64:file://okta_personal_sp_initiated_groups_response.xml" 10 | }, 11 | "mockTime": "2022-11-18T22:30:38.917Z", 12 | "shouldSucceed": false, 13 | "expectedError": "SAML assertion expired: clocks skewed too much", 14 | "expectedErrorCode": "saml_assertion_expired" 15 | } 16 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["plugin:@typescript-eslint/recommended", "prettier"], 3 | parser: "@typescript-eslint/parser", 4 | parserOptions: { 5 | ecmaFeatures: { 6 | jsx: true, 7 | }, 8 | ecmaVersion: 2018, 9 | sourceType: "module", 10 | }, 11 | plugins: ["prettier"], 12 | rules: { 13 | "@typescript-eslint/ban-ts-comment": "off", 14 | "@typescript-eslint/no-explicit-any": "error", 15 | "prettier/prettier": ["error"], 16 | }, 17 | overrides: [ 18 | { 19 | files: ["*.test.ts"], 20 | rules: { 21 | "@typescript-eslint/no-explicit-any": 0, 22 | }, 23 | }, 24 | { 25 | files: ["**/__mocks__/*.ts"], 26 | rules: { 27 | "@typescript-eslint/explicit-module-boundary-types": 0, 28 | }, 29 | }, 30 | ], 31 | }; 32 | -------------------------------------------------------------------------------- /.github/workflows/on-pr.yml: -------------------------------------------------------------------------------- 1 | name: On Pull Requests [PR] 2 | on: 3 | pull_request: 4 | branches: [main] 5 | jobs: 6 | node-lint-build-test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | 11 | - name: Use Node 22.16.0 12 | uses: actions/setup-node@v3 13 | with: 14 | node-version: 22.16.0 15 | cache: "yarn" 16 | 17 | - name: Install dependencies 18 | run: yarn install --immutable 19 | 20 | - name: Run linter 21 | run: yarn lint 22 | 23 | - name: Build Packages 24 | run: yarn build 25 | 26 | - name: Check formatting 27 | run: | 28 | yarn format 29 | 30 | - name: Check for changes in the build output 31 | run: | 32 | git diff --exit-code 33 | 34 | - name: Run tests 35 | run: yarn test 36 | -------------------------------------------------------------------------------- /test/contract/data/common_vulnerabilities/billion_laughs/billion_laughs.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | ]> 13 | &lol9; 14 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/doctype_entity_attack.xml: -------------------------------------------------------------------------------- 1 | 2 | ]> 3 | 4 | http://www.test.com/issuer 5 | 6 | 7 | http://www.test.com/issuer 8 | 9 | &xxe; 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/contract/data/valid/onelogin_idp_initiated/onelogin_idp_initiated.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "OneLogin IDP Initiated", 3 | "description": "Should pass validation for OneLogin IDP initiated SAML response", 4 | "comment": [ 5 | "Test metadata generated from personal OneLogin acct.", 6 | "Dashboard settings: ", 7 | "Audience is set to test_audience_entity_id.", 8 | "Recipient is set to test_audience_recipient", 9 | "ACS URL is set to http://localhost:5001/callback", 10 | "NameID is set to email.", 11 | "Groups is set to User Roles - single value output.", 12 | "Hobbies is set to AD/LDAP CN Extraction (Multi-value output)", 13 | "Roles is set to Semicolon Delimited input (Multi-value output)" 14 | ], 15 | "input": { 16 | "response_xml": "!embed:base64:file://idp_initiated.xml" 17 | }, 18 | "mockTime": "2022-11-17T22:45:34.0000Z", 19 | "shouldSucceed": true 20 | } 21 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/forbidden_authnrequest_attack.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://www.test.com/issuer 4 | 5 | 6 | http://www.test.com/issuer 7 | 8 | 9 | http://www.test.com/issuer 10 | 11 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/multiple_assertions_attack.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://www.test.com/issuer 4 | 5 | 6 | http://www.test.com/issuer 7 | 8 | 9 | http://www.test.com/issuer 10 | 11 | -------------------------------------------------------------------------------- /test/contract/data/common_vulnerabilities/processing_instructions_not_allowed/processing_instructions_not_allowed.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Processing Instructions Not Allowed", 3 | "description": "Should detect and block XML processing instructions in SAML responses", 4 | "comment": [ 5 | "Test metadata generated from personal Okta acct", 6 | "Stronger safety precaution after #incident-2025-06-03-yet-another-saml-canonicalization-vuln - ban all processing instructions", 7 | "To evaluate test case:", 8 | "diff contract_tests/data/v1_validate_saml_response/assets/misc/processing_instruction_in_nameid.formatted.xml \\", 9 | "contract_tests/data/v1_validate_saml_response/assets/okta_personal/okta_personal_sp_initiated_groups_response.formatted.xml" 10 | ], 11 | "input": { 12 | "response_xml": "!embed:base64:file://processing_instruction_in_nameid.xml" 13 | }, 14 | "mockTime": "2022-11-04T03:05:00Z", 15 | "shouldSucceed": false, 16 | "expectedError": "Invalid input: response contained illegal processing instructions", 17 | "expectedErrorCode": "xml_validation_error" 18 | } 19 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @stytch/samlshield - A Node.js library for linting and validating SAML responses 3 | * 4 | * Security-first design to protect against common SAML vulnerabilities including: 5 | * - XML External Entity (XXE) attacks 6 | * - XML comment injection vulnerabilities 7 | * - Multiple SignedInfo element attacks 8 | * - Processing instruction injection 9 | * - Signature validation bypass 10 | */ 11 | 12 | // Main validation functions 13 | export { 14 | validateSAMLResponse, 15 | safeValidateSAMLResponse, 16 | ValidateArgs, 17 | ValidateResult, 18 | } from "./validate"; 19 | 20 | // XML utilities 21 | export { 22 | Selector, 23 | createSelector, 24 | xmlStringToDOM, 25 | xmlBase64ToDOM, 26 | } from "./xml"; 27 | 28 | // Error classes 29 | export { 30 | SAMLShieldError, 31 | ValidationError, 32 | XMLValidationError, 33 | XPathError, 34 | XMLExpectedSingletonError, 35 | XMLExpectedOptionalSingletonError, 36 | SAMLExpectedAtLeastOneSignatureError, 37 | XMLExternalEntitiesForbiddenError, 38 | SAMLResponseFailureError, 39 | SAMLAssertionExpiredError, 40 | SAMLAssertionNotYetValidError, 41 | ErrorDetails, 42 | } from "./errors"; 43 | -------------------------------------------------------------------------------- /test/contract/data/common_vulnerabilities/comments_not_allowed_in_attribute/comments_not_allowed_in_attribute.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "XML Comments Not Allowed in Attributes", 3 | "description": "Should detect and block XML comments in SAML attribute values (CVE-2017-11427)", 4 | "comment": [ 5 | "Test metadata generated from personal Okta acct", 6 | "See: CVE-2017-11427", 7 | "https://duo.com/blog/duo-finds-saml-vulnerabilities-affecting-multiple-implementations", 8 | "Stronger safety precaution after #incident-2025-03-05-node-saml-vuln - ban all comments", 9 | "To evaluate test case:", 10 | "diff contract_tests/data/v1_validate_saml_response/assets/misc/comment_in_attribute_value.formatted.xml \\", 11 | "contract_tests/data/v1_validate_saml_response/assets/okta_personal/okta_personal_sp_initiated_groups_response.formatted.xml" 12 | ], 13 | "input": { 14 | "response_xml": "!embed:base64:file://comment_in_attribute_value.xml" 15 | }, 16 | "mockTime": "2022-11-17T22:28:00Z", 17 | "shouldSucceed": false, 18 | "expectedError": "Invalid input: response contained illegal XML comments", 19 | "expectedErrorCode": "xml_validation_error" 20 | } 21 | -------------------------------------------------------------------------------- /test/contract/data/vulnerabilities/processing_instructions.xml: -------------------------------------------------------------------------------- 1 | http://test.idphttp://test.idptest123fake_signatureuser@example.com -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/multiple_responses_attack.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | http://www.test.com/issuer 5 | 6 | 7 | http://www.test.com/issuer 8 | 9 | 10 | 11 | http://www.test.com/issuer 12 | 13 | 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: ✨ OSS Feature Request 2 | description: Suggest a feature or enhancement for the open-source SAML Shield library 3 | title: "[Feature] " 4 | labels: ["enhancement", "needs-triage"] 5 | assignees: [] 6 | 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | ⚠️ This form is for OSS users only. If you're using the **SaaS-managed version**, please request features in the [Stytch Slack](https://join.slack.com/t/stytch/shared_invite/zt-2n6jvvgzm-fAyxi_kr1uqGc15UJqxiPw). 12 | 13 | - type: textarea 14 | id: problem 15 | attributes: 16 | label: What problem are you trying to solve? 17 | placeholder: It’s difficult to... 18 | validations: 19 | required: true 20 | 21 | - type: textarea 22 | id: proposal 23 | attributes: 24 | label: What would you like to see added? 25 | placeholder: I’d like the library to support... 26 | validations: 27 | required: true 28 | 29 | - type: textarea 30 | id: notes 31 | attributes: 32 | label: Anything else? 33 | placeholder: Related links, existing tools, alternatives considered, etc. 34 | validations: 35 | required: false 36 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/invalid_canonicalization_attack.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://www.test.com/issuer 4 | 5 | 6 | 7 | 8 | 9 | 10 | test 11 | 12 | 13 | test 14 | 15 | 16 | 17 | http://www.test.com/issuer 18 | 19 | -------------------------------------------------------------------------------- /test/basic.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | validateSAMLResponse, 3 | safeValidateSAMLResponse, 4 | ValidationError, 5 | } = require("../dist/index.js"); 6 | 7 | describe("SAML Shield Basic Tests", () => { 8 | test("should throw ValidationError for empty response", async () => { 9 | await expect(validateSAMLResponse({ response_xml: "" })).rejects.toThrow( 10 | "missing required field: SAMLResponse", 11 | ); 12 | }); 13 | 14 | test("should throw ValidationError for missing response_xml", async () => { 15 | await expect(validateSAMLResponse({})).rejects.toThrow( 16 | "missing required field: SAMLResponse", 17 | ); 18 | }); 19 | 20 | test("safeValidateSAMLResponse should return error object instead of throwing", async () => { 21 | const result = await safeValidateSAMLResponse({ response_xml: "" }); 22 | 23 | expect(result.valid).toBe(false); 24 | expect(result.errors).toHaveLength(1); 25 | expect(result.errors[0]).toContain("missing required field: SAMLResponse"); 26 | }); 27 | 28 | test("should detect invalid XML structure", async () => { 29 | const invalidXML = Buffer.from( 30 | "not a saml response", 31 | ).toString("base64"); 32 | 33 | await expect( 34 | validateSAMLResponse({ response_xml: invalidXML }), 35 | ).rejects.toThrow("document does not contain a SAML Response element"); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/contract/data/common_vulnerabilities/embedded_comment_in_nameid/embedded_comment_in_nameid.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Embedded XML Comment in NameID", 3 | "description": "Should detect and block embedded XML comments in SAML NameID elements (CVE-2017-11427)", 4 | "comment": [ 5 | "Test metadata generated from personal Okta acct", 6 | "Attack looks like setting victim@example.com.evil.com", 7 | "which causes a valid assertion for victim@example.com.evil.com to grant access to victim@example.com 's account", 8 | "The test case XML sets mgerber@berkeley.edu", 9 | "But the resultant nameID should still be read as mgerber", 10 | "See: CVE-2017-11427", 11 | "https://duo.com/blog/duo-finds-saml-vulnerabilities-affecting-multiple-implementations", 12 | "To evaluate test case:", 13 | "diff contract_tests/data/v1_validate_saml_response/assets/misc/comment_in_nameid.formatted.xml \\", 14 | "contract_tests/data/v1_validate_saml_response/assets/okta_personal/okta_personal_idp_initiated_response.formatted.xml" 15 | ], 16 | "input": { 17 | "response_xml": "!embed:base64:file://comment_in_nameid.xml" 18 | }, 19 | "mockTime": "2022-11-04T03:05:00Z", 20 | "shouldSucceed": false, 21 | "expectedError": "Invalid input: response contained illegal XML comments", 22 | "expectedErrorCode": "xml_validation_error" 23 | } 24 | -------------------------------------------------------------------------------- /test/contract/data/common_vulnerabilities/comments_not_allowed_in_nameid/comments_not_allowed_in_nameid.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "XML Comments Not Allowed in NameID", 3 | "description": "Should detect and block XML comments in SAML NameID elements (CVE-2017-11427)", 4 | "comment": [ 5 | "Test metadata generated from personal Okta acct", 6 | "Attack looks like setting victim@example.com.evil.com", 7 | "which causes a valid assertion for victim@example.com.evil.com to grant access to victim@example.com 's account", 8 | "The test case XML sets mgerber@berkeley.edu", 9 | "But the resultant nameID should still be read as mgerber", 10 | "See: CVE-2017-11427", 11 | "https://duo.com/blog/duo-finds-saml-vulnerabilities-affecting-multiple-implementations", 12 | "To evaluate test case:", 13 | "diff contract_tests/data/v1_validate_saml_response/assets/misc/comment_in_nameid.formatted.xml \\", 14 | "contract_tests/data/v1_validate_saml_response/assets/okta_personal/okta_personal_idp_initiated_response.formatted.xml" 15 | ], 16 | "input": { 17 | "response_xml": "!embed:base64:file://comment_in_nameid.xml" 18 | }, 19 | "mockTime": "2022-11-04T03:05:00Z", 20 | "shouldSucceed": false, 21 | "expectedError": "Invalid input: response contained illegal XML comments", 22 | "expectedErrorCode": "xml_validation_error" 23 | } 24 | -------------------------------------------------------------------------------- /test/contract/data/vulnerabilities/multiple_signedinfo.xml: -------------------------------------------------------------------------------- 1 | http://test.idphttp://test.idptest123malicious456fake_signatureuser@example.com -------------------------------------------------------------------------------- /test/contract/data/common_vulnerabilities/embedded_comment_in_attribute/embedded_comment_in_attribute.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Embedded XML Comment in Attribute Value", 3 | "description": "Should detect and block embedded XML comments in SAML attribute values (CVE-2017-11427)", 4 | "comment": [ 5 | "Test metadata generated from personal Okta acct", 6 | "Attack looks like setting ADMIN_READONLY", 7 | "which causes a valid assertion granting a role of ADMIN_READONLY to grant a role of ADMIN", 8 | "The test case XML sets GroupOne", 9 | "But the resultant attribute value should still be read as Group One", 10 | "See: CVE-2017-11427", 11 | "https://duo.com/blog/duo-finds-saml-vulnerabilities-affecting-multiple-implementations", 12 | "To evaluate test case:", 13 | "diff contract_tests/data/v1_validate_saml_response/assets/misc/comment_in_attribute_value.formatted.xml \\", 14 | "contract_tests/data/v1_validate_saml_response/assets/okta_personal/okta_personal_sp_initiated_groups_response.formatted.xml" 15 | ], 16 | "input": { 17 | "response_xml": "!embed:base64:file://comment_in_attribute_value.xml" 18 | }, 19 | "mockTime": "2022-11-17T22:28:00Z", 20 | "shouldSucceed": false, 21 | "expectedError": "Invalid input: response contained illegal XML comments", 22 | "expectedErrorCode": "xml_validation_error" 23 | } 24 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/invalid_transform_attack.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://www.test.com/issuer 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | test 14 | 15 | 16 | test 17 | 18 | 19 | 20 | http://www.test.com/issuer 21 | 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 OSS Bug Report 2 | description: Report unexpected behavior in the open-source SAML Shield library 3 | title: "[Bug] " 4 | labels: ["bug", "needs-triage"] 5 | assignees: [] 6 | 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | ⚠️ This form is for OSS users only. If you're using the **SaaS-managed version**, please request support in the [Stytch Slack](https://join.slack.com/t/stytch/shared_invite/zt-2n6jvvgzm-fAyxi_kr1uqGc15UJqxiPw). 12 | 13 | - type: input 14 | id: version 15 | attributes: 16 | label: SAML Shield Version 17 | placeholder: e.g., v1.2.3 or main 18 | validations: 19 | required: true 20 | 21 | - type: textarea 22 | id: description 23 | attributes: 24 | label: What happened? 25 | placeholder: Describe the issue and what you expected to happen. 26 | validations: 27 | required: true 28 | 29 | - type: textarea 30 | id: steps 31 | attributes: 32 | label: Steps to Reproduce 33 | placeholder: | 34 | 1. Do this... 35 | 2. Observe this... 36 | 3. Expected result was... 37 | validations: 38 | required: false 39 | 40 | - type: textarea 41 | id: logs 42 | attributes: 43 | label: Logs or Assertion (Optional) 44 | description: Share any logs or a redacted SAML assertion that helps reproduce the issue. 45 | validations: 46 | required: false 47 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/too_many_transforms_attack.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://www.test.com/issuer 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | test 16 | 17 | 18 | test 19 | 20 | 21 | 22 | http://www.test.com/issuer 23 | 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@stytch/samlshield", 3 | "version": "0.4.0", 4 | "description": "A Node.js library for linting and validating SAML responses", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "author": "SamlShield", 8 | "license": "Apache-2.0", 9 | "keywords": [ 10 | "saml", 11 | "xml", 12 | "security", 13 | "validation", 14 | "linting" 15 | ], 16 | "scripts": { 17 | "build": "tsc", 18 | "test": "jest", 19 | "lint": "eslint src --ext .ts", 20 | "format": "prettier --write .", 21 | "prepublishOnly": "yarn build", 22 | "hooks": "yarn dlx husky install", 23 | "prepare": "husky" 24 | }, 25 | "dependencies": { 26 | "@xmldom/xmldom": "^0.8.10", 27 | "xpath": "^0.0.34" 28 | }, 29 | "devDependencies": { 30 | "@types/jest": "^29.2.3", 31 | "@types/node": "^22.2.0", 32 | "@typescript-eslint/eslint-plugin": "^7.0.1", 33 | "@typescript-eslint/parser": "^7.0.1", 34 | "eslint": "^8.56.0", 35 | "eslint-config-prettier": "^9.1.0", 36 | "eslint-plugin-prettier": "^5.1.3", 37 | "husky": "^9.1.7", 38 | "jest": "^29.3.1", 39 | "prettier": "^3.2.5", 40 | "ts-jest": "^29.0.3", 41 | "typescript": "^4.8.4" 42 | }, 43 | "files": [ 44 | "dist/", 45 | "README.md" 46 | ], 47 | "engines": { 48 | "node": ">=18.0.0" 49 | }, 50 | "jest": { 51 | "preset": "ts-jest", 52 | "testEnvironment": "node", 53 | "testPathIgnorePatterns": [ 54 | "/node_modules/", 55 | "/test-dist/" 56 | ], 57 | "collectCoverageFrom": [ 58 | "src/**/*.ts", 59 | "!src/**/*.d.ts" 60 | ] 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/README.md: -------------------------------------------------------------------------------- 1 | # Security Validations - Malicious Test Cases 2 | 3 | This directory contains contract tests for malicious SAML payloads that should be blocked by specific security validation functions in the SAMLShield library. 4 | 5 | ## Test Coverage 6 | 7 | ### performStringLevelValidation 8 | 9 | - `doctype_simple_attack` - Tests blocking of DOCTYPE declarations without entities at string level 10 | 11 | ### validateElementCounts 12 | 13 | - `multiple_assertions_attack` - Tests blocking of multiple assertion elements 14 | - `forbidden_authnrequest_attack` - Tests blocking of forbidden AuthnRequest elements 15 | 16 | ### validateSAMLResponseStructure 17 | 18 | - `multiple_responses_attack` - Tests blocking of multiple response elements 19 | 20 | ### validateSignatureURI 21 | 22 | - `malformed_uri_working_base` - Tests blocking of malformed URI references 23 | - `nonexistent_id_working_base` - Tests blocking of URI references to nonexistent IDs 24 | 25 | ### validateCanonicalizationMethod 26 | 27 | - `invalid_canonicalization_attack` - Tests blocking of invalid canonicalization algorithms 28 | 29 | ### validateTransforms 30 | 31 | - `too_many_transforms_attack` - Tests blocking of excessive transform elements (>2) 32 | - `invalid_transform_attack` - Tests blocking of invalid transform algorithms 33 | 34 | ### XML Parser Validation (not performStringLevelValidation) 35 | 36 | - `doctype_entity_attack` - Tests that DOCTYPE with entities is caught by XML parser 37 | 38 | ## Purpose 39 | 40 | These tests ensure that the security validation functions correctly identify and block various types of SAML-based attacks and malicious payloads while maintaining backward compatibility with legitimate SAML responses. 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/cve_request.yml: -------------------------------------------------------------------------------- 1 | name: 🛡️ Request Coverage for Existing CVE 2 | description: Ask for coverage of a known CVE affecting SAML implementations 3 | title: "[CVE Request] " 4 | labels: ["cve-request", "security", "needs-triage"] 5 | assignees: [] 6 | 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | ⚠️ This form is for OSS users only. If you're using the **SaaS-managed version**, please request support in the [Stytch Slack](https://join.slack.com/t/stytch/shared_invite/zt-2n6jvvgzm-fAyxi_kr1uqGc15UJqxiPw). 12 | 13 | 🔒 If you're unsure whether the vulnerability is public or sensitive, please report it **privately first** by emailing [security@stytch.com](mailto:security@stytch.com). We’ll review and advise you on how to proceed, in line with our [Security Policy](https://github.com/stytchauth/samlshield/blob/main/SECURITY.md). 14 | 15 | - type: input 16 | id: cve_id 17 | attributes: 18 | label: CVE Identifier 19 | placeholder: e.g., CVE-2025-25291 20 | validations: 21 | required: true 22 | 23 | - type: textarea 24 | id: description 25 | attributes: 26 | label: Vulnerability Summary 27 | placeholder: What does this CVE allow an attacker to do? Do you have any blog posts or reference links? 28 | validations: 29 | required: true 30 | 31 | - type: input 32 | id: affected_libs 33 | attributes: 34 | label: Affected Libraries or Toolkits 35 | placeholder: e.g., python-saml, ruby-saml, node-saml 36 | validations: 37 | required: false 38 | 39 | - type: textarea 40 | id: example_payload 41 | attributes: 42 | label: Example SAML Assertion (Optional) 43 | description: If possible, provide a sample assertion that reproduces the issue (please redact sensitive info). 44 | validations: 45 | required: false 46 | -------------------------------------------------------------------------------- /examples/basic-usage.js: -------------------------------------------------------------------------------- 1 | const { 2 | validateSAMLResponse, 3 | safeValidateSAMLResponse, 4 | } = require("../dist/index.js"); 5 | 6 | // Example SAML response (base64 encoded) 7 | // This would normally come from your SAML identity provider 8 | const exampleSAMLResponse = 9 | "PHNhbWwyOlJlc3BvbnNlIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj4KPC9zYW1sMjpSZXNwb25zZT4="; 10 | 11 | async function demonstrateBasicUsage() { 12 | console.log("=== Basic SAML Validation Demo ===\n"); 13 | 14 | // Method 1: Using the throwing version 15 | console.log("1. Using validateSAMLResponse (throws on error):"); 16 | try { 17 | await validateSAMLResponse({ 18 | response_xml: exampleSAMLResponse, 19 | }); 20 | console.log("✅ SAML response is valid!"); 21 | } catch (error) { 22 | console.log("❌ SAML validation failed:", error.message); 23 | console.log(" Error code:", error.code); 24 | if (error.details) { 25 | console.log(" Details:", JSON.stringify(error.details, null, 2)); 26 | } 27 | } 28 | 29 | console.log("\n2. Using safeValidateSAMLResponse (returns result object):"); 30 | 31 | // Method 2: Using the safe version that returns a result 32 | const result = await safeValidateSAMLResponse({ 33 | response_xml: exampleSAMLResponse, 34 | }); 35 | 36 | if (result.valid) { 37 | console.log("✅ SAML response is valid!"); 38 | } else { 39 | console.log("❌ SAML validation failed"); 40 | console.log(" Errors:", result.errors); 41 | } 42 | 43 | // Example of handling different error types 44 | console.log("\n3. Demonstrating error handling:"); 45 | 46 | try { 47 | await validateSAMLResponse({ 48 | response_xml: "", // Empty response to trigger validation error 49 | }); 50 | } catch (error) { 51 | console.log("Caught expected error:", error.constructor.name); 52 | console.log("Message:", error.message); 53 | } 54 | } 55 | 56 | // Run the demo 57 | demonstrateBasicUsage().catch(console.error); 58 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Development Commands 6 | 7 | ### Building and Testing 8 | 9 | - `yarn build` - Compile TypeScript to dist/ 10 | - `yarn test` - Run all tests (unit and contract tests) 11 | - `yarn lint` - ESLint TypeScript files in src/ 12 | - `yarn format` - Format code with Prettier 13 | 14 | ### Test Development 15 | 16 | When adding tests, use the contract testing pattern: 17 | 18 | - Add XML test data to `test/contract/data/` subdirectories 19 | - Create `.test.json` files with test metadata 20 | - Use embed directives like `!embed:base64:file://filename.xml` for test data 21 | 22 | ## Architecture 23 | 24 | ### Core Components 25 | 26 | - `src/validate.ts` - Main SAML validation logic with `validateSAMLResponse()` and `safeValidateSAMLResponse()` 27 | - `src/xml.ts` - XML parsing utilities with XPath selectors and SAML namespace support 28 | - `src/errors.ts` - Comprehensive error class hierarchy for different validation failures 29 | - `src/index.ts` - Public API exports 30 | 31 | ### Security-First Design 32 | 33 | This library validates SAML responses against multiple attack vectors: 34 | 35 | - XXE (XML External Entity) attacks 36 | - XML comment injection (CVE-2017-11428 family) 37 | - Multiple SignedInfo element attacks 38 | - Processing instruction injection (always blocked) 39 | - Missing signature validation 40 | 41 | ### Contract Testing Pattern 42 | 43 | The codebase uses data-driven contract tests in `test/contract/`: 44 | 45 | - `data/valid/` - Tests that should pass 46 | - `data/error_cases/` - Structural validation failures 47 | - `data/vulnerabilities/` - Security vulnerability detection tests 48 | - `loader.ts` - Test case loading with embed directive processing 49 | - `runner.test.ts` - Test execution engine 50 | 51 | ### TypeScript Configuration 52 | 53 | - Main config: `tsconfig.json` (excludes test files) 54 | - Output: CommonJS modules to `dist/` with declarations 55 | 56 | When making changes, always run the full test suite (`yarn test`) to ensure security validations remain intact. 57 | -------------------------------------------------------------------------------- /test/contract/data/security_validations/unsigned_encrypted_assertion.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://idp.example.com/metadata.php 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | validDigestValue1234567890= 14 | 15 | 16 | someSignatureValue 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | encryptedKeyValue 29 | 30 | 31 | 32 | 33 | encryptedAssertionContent 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/hmac_signature_method.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://idp.example.com/metadata.php 4 | 5 | 6 | 7 | 8 | http://idp.example.com/metadata.php 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | validDigestValue1234567890= 19 | 20 | 21 | someSignatureValue 22 | 23 | 24 | _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 25 | 26 | 27 | 28 | 29 | 30 | 31 | http://sp.example.com/demo1/metadata.php 32 | 33 | 34 | 35 | 36 | urn:oasis:names:tc:SAML:2.0:ac:classes:Password 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /test/contract/data/common_vulnerabilities/error_no_signatures/unsigned_response.formatted.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://idp.example.com/metadata.php 4 | 5 | 6 | 7 | 8 | http://idp.example.com/metadata.php 9 | 10 | _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 11 | 12 | 13 | 14 | 15 | 16 | 17 | http://sp.example.com/demo1/metadata.php 18 | 19 | 20 | 21 | 22 | urn:oasis:names:tc:SAML:2.0:ac:classes:Password 23 | 24 | 25 | 26 | 27 | test 28 | 29 | 30 | test@example.com 31 | 32 | 33 | users 34 | examplerole1 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/digestvalue_wrapping_attack.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://idp.example.com/metadata.php 4 | 5 | 6 | 7 | 8 | http://idp.example.com/metadata.php 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | validDigestValue1234567890= 19 | 20 | 21 | maliciousDigestValue098765= 22 | someSignatureValue 23 | 24 | 25 | _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 26 | 27 | 28 | 29 | 30 | 31 | 32 | http://sp.example.com/demo1/metadata.php 33 | 34 | 35 | 36 | 37 | urn:oasis:names:tc:SAML:2.0:ac:classes:Password 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /test/contract/data/security_validations_malicious/digestvalue_location_mismatch.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://idp.example.com/metadata.php 4 | 5 | 6 | 7 | 8 | http://idp.example.com/metadata.php 9 | maliciousOutOfPlaceDigest= 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | validDigestValue1234567890= 20 | 21 | 22 | someSignatureValue 23 | 24 | 25 | _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 26 | 27 | 28 | 29 | 30 | 31 | 32 | http://sp.example.com/demo1/metadata.php 33 | 34 | 35 | 36 | 37 | urn:oasis:names:tc:SAML:2.0:ac:classes:Password 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /test/contract/data/common_vulnerabilities/error_no_signatures/unsigned_response.xml: -------------------------------------------------------------------------------- 1 | 2 | http://idp.example.com/metadata.php 3 | 4 | 5 | 6 | 7 | http://idp.example.com/metadata.php 8 | 9 | _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 10 | 11 | 12 | 13 | 14 | 15 | 16 | http://sp.example.com/demo1/metadata.php 17 | 18 | 19 | 20 | 21 | urn:oasis:names:tc:SAML:2.0:ac:classes:Password 22 | 23 | 24 | 25 | 26 | test 27 | 28 | 29 | test@example.com 30 | 31 | 32 | users 33 | examplerole1 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/errors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom error classes for SAML validation and processing 3 | */ 4 | 5 | export type ErrorDetails = Record; 6 | 7 | export class SAMLShieldError extends Error { 8 | public code: string; 9 | public details?: ErrorDetails; 10 | 11 | constructor(message: string, code: string, details?: ErrorDetails) { 12 | super(message); 13 | this.code = code; 14 | this.details = details; 15 | } 16 | } 17 | 18 | export class ValidationError extends SAMLShieldError { 19 | constructor(reason: string) { 20 | super(`Invalid input: ${reason}`, "validation_error"); 21 | } 22 | } 23 | 24 | export class XMLValidationError extends SAMLShieldError { 25 | constructor(reason: string, details?: ErrorDetails) { 26 | super(`Invalid input: ${reason}`, "xml_validation_error", { 27 | ...details, 28 | invalid_input: reason, 29 | }); 30 | } 31 | } 32 | 33 | export class XPathError extends XMLValidationError { 34 | constructor(xpath: string, expected: string, received: string) { 35 | super(`expected a ${expected} at path "${xpath}" but received ${received}`); 36 | } 37 | } 38 | 39 | export class XMLExpectedSingletonError extends XMLValidationError { 40 | constructor(xpath: string, expected: string, count: number) { 41 | super( 42 | `expected exactly one ${expected} at path "${xpath}" but received ${count}`, 43 | ); 44 | } 45 | } 46 | 47 | export class XMLExpectedOptionalSingletonError extends XMLValidationError { 48 | constructor(xpath: string, expected: string, count: number) { 49 | super( 50 | `expected at most one ${expected} at path "${xpath}" but received ${count}`, 51 | ); 52 | } 53 | } 54 | 55 | export class SAMLExpectedAtLeastOneSignatureError extends SAMLShieldError { 56 | constructor() { 57 | super( 58 | "Invalid input: one of response or assertion must be signed", 59 | "saml_assertion_not_signed", 60 | ); 61 | } 62 | } 63 | 64 | export class XMLExternalEntitiesForbiddenError extends SAMLShieldError { 65 | constructor() { 66 | super( 67 | "Invalid input: External Entities are forbidden", 68 | "xml_validation_error", 69 | { invalid_input: "External Entities are forbidden" }, 70 | ); 71 | } 72 | } 73 | 74 | export class SAMLResponseFailureError extends SAMLShieldError { 75 | constructor( 76 | response_id: unknown, 77 | saml_status_code: unknown, 78 | nested_status_codes: unknown[], 79 | ) { 80 | super( 81 | "IDP was not able to complete the login as requested", 82 | "saml_login_failed", 83 | { 84 | response_id, 85 | saml_status_code, 86 | nested_status_codes, 87 | }, 88 | ); 89 | } 90 | } 91 | 92 | export class SAMLAssertionExpiredError extends SAMLShieldError { 93 | constructor() { 94 | super( 95 | "SAML assertion expired: clocks skewed too much", 96 | "saml_assertion_expired", 97 | ); 98 | } 99 | } 100 | 101 | export class SAMLAssertionNotYetValidError extends SAMLShieldError { 102 | constructor() { 103 | super("SAML assertion not yet valid", "saml_assertion_not_yet_valid"); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /test/contract/data/security_validations/forbidden_authn_request.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://www.okta.com/exk2wpjmakQqqs7dN697 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | E3HUdwVZnUcXZHthp/+IQWwyQ6VvC45qdg8coTCn55I= 15 | 16 | 17 | SpfZBRz6JZkYivdJh3nrAIyWd3AQonlEf9hw7GK7jMi0QxIU0Fl9rTyKnsXKmGUoIQEuUle4qdkg5haV+Xcetm1mPiEsLlhm+nKOGp4usnJ6zJipi79prNOfe11RHFfcjUBXfLGWDwO3qCfwrTQYWrn8nDb2ugo1yL2WOt6mjjxccEmqd3PkI3ZJ0UCP32wv8KaGVzdrbIu36LIGzRVkGvoiyeaq/W9mfRgsGKwhtF2RBbPKZtQ0o/VWOlIPj1yRdfDXe7MAk76CuPXqmdKHUIV9rUViE8nZ1GmFKJwR6wbuvnCx+6tD1VUKBLc4AHWoyhhR8r0EWXoalhTVv3/7EA== 18 | 19 | 20 | 21 | 22 | 23 | http://evil.com 24 | 25 | 26 | http://www.okta.com/exk2wpjmakQqqs7dN697 27 | 28 | mgerber@berkeley.edu 29 | 30 | 31 | 32 | audience_entity_id 33 | 34 | 35 | 36 | 37 | urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability (Private Disclosure) 4 | 5 | If you discover an undisclosed **security vulnerability** related to this repository, its code, or any SAML-related library, **please do not file a public issue or pull request.** Instead, report it to us **privately** so we can investigate and fix it safely and responsibly. 6 | 7 | - **Email:** [security@stytch.com](mailto:security@stytch.com) 8 | - **Acknowledgment:** We aim to acknowledge your report within **2 business days**. 9 | - **Remediation:** Stytch commits to triaging, resolving, and coordinating disclosure in alignment with our [Responsible Disclosure Policy](https://stytch.com/docs/resources/security-and-trust/security). 10 | 11 | ### What to Include in Your Report 12 | 13 | To help us assess the issue quickly: 14 | 15 | - A clear description of the vulnerability and its potential impact. 16 | - Steps to reproduce or proof-of-concept code, if available. 17 | - Affected components or dependencies, including versions. 18 | - Logs, stack traces, or data samples (no personal or user data). 19 | - Whether the issue has already been disclosed elsewhere. 20 | 21 | ## Third-Party SAML Library Vulnerabilities 22 | 23 | SAMLShield exists to protect applications from vulnerabilities in **third-party SAML libraries**. We strongly encourage reports of vulnerabilities you discover in libraries such as: 24 | 25 | - `python-saml` / OneLogin SAML Toolkit 26 | - `xmlsec` bindings 27 | - Other SAML processors or middleware used in the ecosystem 28 | 29 | Please email these reports to [security@stytch.com](mailto:security@stytch.com) as described above. Clearly indicate that the issue affects a **third-party SAML library** and include reproduction details and impact assessment. We may coordinate with the upstream maintainers and/or implement mitigations within SAMLShield to protect affected users. 30 | 31 | ## Publicly Disclosed Vulnerabilities and Improvements 32 | 33 | For issues that are already public or not security-sensitive: 34 | 35 | - If a vulnerability has already been disclosed elsewhere, feel free to open an issue or PR with links to public advisories (e.g., CVEs, blog posts). 36 | - For general bug fixes, improvements, or tests, submit a pull request with documentation and references. 37 | - If unsure whether an issue is sensitive, please report it **privately first**. We’ll advise on how to proceed. 38 | 39 | ## Guidelines for Safe Security Research 40 | 41 | We appreciate responsible security research and welcome contributions from the community. Please follow these principles: 42 | 43 | - Only test against accounts or systems you own. 44 | - Do not attempt to access, modify, or destroy data belonging to others. 45 | - Avoid any denial-of-service (DoS), spam, or social engineering techniques. 46 | - Do not target Stytch infrastructure or employees. 47 | - Respect Coordinated Vulnerability Disclosure timelines. 48 | 49 | Stytch will not pursue legal action against researchers who act in good faith and follow this policy. 50 | 51 | ## Our Commitment 52 | 53 | - We will acknowledge valid reports within **2 business days**. 54 | - We aim to resolve critical issues within **30 days** or faster where possible. 55 | - We will keep you updated throughout the process. 56 | - We will credit you for your discovery (with permission) when disclosure is appropriate. 57 | - We follow **Coordinated Vulnerability Disclosure** practices and appreciate your collaboration in keeping users safe. 58 | 59 | --- 60 | 61 | Thank you for helping secure the SAMLShield project and the broader SAML ecosystem. 62 | -------------------------------------------------------------------------------- /test/contract/data/security_validations/multiple_assertions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://www.okta.com/exk2wpjmakQqqs7dN697 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | E3HUdwVZnUcXZHthp/+IQWwyQ6VvC45qdg8coTCn55I= 15 | 16 | 17 | SpfZBRz6JZkYivdJh3nrAIyWd3AQonlEf9hw7GK7jMi0QxIU0Fl9rTyKnsXKmGUoIQEuUle4qdkg5haV+Xcetm1mPiEsLlhm+nKOGp4usnJ6zJipi79prNOfe11RHFfcjUBXfLGWDwO3qCfwrTQYWrn8nDb2ugo1yL2WOt6mjjxccEmqd3PkI3ZJ0UCP32wv8KaGVzdrbIu36LIGzRVkGvoiyeaq/W9mfRgsGKwhtF2RBbPKZtQ0o/VWOlIPj1yRdfDXe7MAk76CuPXqmdKHUIV9rUViE8nZ1GmFKJwR6wbuvnCx+6tD1VUKBLc4AHWoyhhR8r0EWXoalhTVv3/7EA== 18 | 19 | 20 | 21 | 22 | 23 | http://www.okta.com/exk2wpjmakQqqs7dN697 24 | 25 | mgerber@berkeley.edu 26 | 27 | 28 | 29 | audience_entity_id 30 | 31 | 32 | 33 | 34 | urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport 35 | 36 | 37 | 38 | 39 | http://www.okta.com/exk2wpjmakQqqs7dN697 40 | 41 | janedoe@example.com 42 | 43 | 44 | -------------------------------------------------------------------------------- /test/contract/runner.test.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { safeValidateSAMLResponse, validateSAMLResponse } from "../../src"; 3 | import * as validateModule from "../../src/validate"; 4 | import { ContractTestLoader } from "./loader"; 5 | 6 | // Mock the getCurrentTime function using jest.spyOn 7 | const mockGetCurrentTime = jest.spyOn(validateModule, "getCurrentTime"); 8 | 9 | describe("SAML Shield Contract Tests", () => { 10 | // Use absolute path to test data directory 11 | const dataPath = path.resolve(__dirname, "data/"); 12 | const loader = new ContractTestLoader(dataPath); 13 | 14 | // For existing tests with old SAML data, we need to set a time within their validity window 15 | // Since test data spans Nov 4-24, 2022, use Nov 20 which should work for most test data 16 | const MOCK_TIME = new Date("2022-11-20T12:00:00.000Z").getTime(); 17 | 18 | beforeAll(() => { 19 | // For existing test data (with 2022 timestamps), set mock time to make SAML data valid 20 | // Only specific tests with mockTime will override this for timestamp validation testing 21 | mockGetCurrentTime.mockReturnValue(MOCK_TIME); 22 | }); 23 | 24 | afterAll(() => { 25 | jest.restoreAllMocks(); 26 | }); 27 | 28 | loader.loadTestCases().forEach((testCase) => { 29 | const testFn = testCase.isSkipped 30 | ? it.skip 31 | : testCase.isOnly 32 | ? it.only 33 | : it; 34 | 35 | testFn(testCase.name, async () => { 36 | const input = (await testCase.input) as any; 37 | 38 | // Mock time if specified in test case 39 | if (testCase.mockTime) { 40 | const mockTimeMs = new Date(testCase.mockTime).getTime(); 41 | mockGetCurrentTime.mockReturnValue(mockTimeMs); 42 | } 43 | 44 | const validateArgs = { 45 | response_xml: input.response_xml, 46 | }; 47 | 48 | if (testCase.shouldSucceed) { 49 | // Test should pass without throwing 50 | try { 51 | await validateSAMLResponse(validateArgs); 52 | 53 | // Also test the safe version 54 | const result = await safeValidateSAMLResponse(validateArgs); 55 | expect(result.valid).toBe(true); 56 | } catch (error) { 57 | const errorMessage = 58 | error instanceof Error ? error.message : "Unknown error"; 59 | console.error(`${testCase.debugInfo}\nUnexpected error:`, error); 60 | throw new Error( 61 | `Test case "${testCase.name}" should have succeeded but threw: ${errorMessage}`, 62 | ); 63 | } 64 | } else { 65 | // Test should fail with expected error 66 | let thrownError: any = null; 67 | 68 | try { 69 | await validateSAMLResponse(validateArgs); 70 | throw new Error( 71 | `Test case "${testCase.name}" should have failed but succeeded`, 72 | ); 73 | } catch (error) { 74 | thrownError = error; 75 | } 76 | 77 | // Test safe version returns error 78 | const result = await safeValidateSAMLResponse(validateArgs); 79 | expect(result.valid).toBe(false); 80 | expect(result.errors).toBeDefined(); 81 | expect(result.errors!.length).toBeGreaterThan(0); 82 | 83 | // Verify expected error message if specified 84 | if (testCase.expectedError && thrownError) { 85 | expect(thrownError.message).toContain(testCase.expectedError); 86 | } 87 | 88 | // Verify expected error code if specified 89 | if (testCase.expectedErrorCode && thrownError) { 90 | expect(thrownError.code).toBe(testCase.expectedErrorCode); 91 | } 92 | } 93 | 94 | // Reset time mock after each test to prevent interference 95 | if (testCase.mockTime) { 96 | mockGetCurrentTime.mockReturnValue(MOCK_TIME); 97 | } 98 | }); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /test/contract/data/security_validations/multiple_response_elements.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://www.okta.com/exk2wpjmakQqqs7dN697 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | E3HUdwVZnUcXZHthp/+IQWwyQ6VvC45qdg8coTCn55I= 15 | 16 | 17 | SpfZBRz6JZkYivdJh3nrAIyWd3AQonlEf9hw7GK7jMi0QxIU0Fl9rTyKnsXKmGUoIQEuUle4qdkg5haV+Xcetm1mPiEsLlhm+nKOGp4usnJ6zJipi79prNOfe11RHFfcjUBXfLGWDwO3qCfwrTQYWrn8nDb2ugo1yL2WOt6mjjxccEmqd3PkI3ZJ0UCP32wv8KaGVzdrbIu36LIGzRVkGvoiyeaq/W9mfRgsGKwhtF2RBbPKZtQ0o/VWOlIPj1yRdfDXe7MAk76CuPXqmdKHUIV9rUViE8nZ1GmFKJwR6wbuvnCx+6tD1VUKBLc4AHWoyhhR8r0EWXoalhTVv3/7EA== 18 | 19 | 20 | 21 | 22 | 23 | http://www.okta.com/exk2wpjmakQqqs7dN697 24 | 25 | mgerber@berkeley.edu 26 | 27 | 28 | 29 | audience_entity_id 30 | 31 | 32 | 33 | 34 | urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport 35 | 36 | 37 | 38 | 39 | http://www.okta.com/exk2wpjmakQqqs7dN697 40 | 41 | 42 | 43 | 44 | http://www.okta.com/exk2wpjmakQqqs7dN697 45 | 46 | janedoe@example.com 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /test/contract/data/vulnerabilities/xml_comment_injection.xml: -------------------------------------------------------------------------------- 1 | https://accounts.google.com/o/saml2?idpid=C02ji0uvfhttps://accounts.google.com/o/saml2?idpid=C02ji0uvfzHhpph/xtjI6HHF2J0LBFAXzTNJoEb0ndJj1ODZL87Q=nhOxTX8oYhLhhdAp5wqEJfTv+r0vJlSydIsGVNDPXB8RX9utc+Fwstf3698YSI8pWGrhn94S9mYnAT/YfwioRJH7SUE0fAyUxo+DjsYgVn0da6r3dAh9J/Fe653RGo3Qgy9X1edoZJusN5gcIL4HHPpVFvMxRs7JsPdmIFsRaE916IAtQei3KITYcNkEw7wGNHNx8RqfL7CA0P5oWLrmnq6t2IZNYHz8ZIrViUg62FmxKHdgtVlHtIMmXO2uWD/UgHN7oUHtxDXTNv0kMaDKRUuFDXtw/AwjHPqLR3hCKlTT7Ts0Y7aBT21QCrXs+mNL1mn1bXwSPKZm2WNzAAZAEg==ST=California,C=US,OU=Google For Work,CN=Google,L=Mountain View,O=Google Inc.MIIDdDCCAlygAwIBAgIGAYSgi9d6MA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJbmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dvb2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMjIxMTIyMTgxMzQ5WhcNMjcxMTIxMTgxMzQ5WjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn9dOryAKN3ejQRaO2eE64fc3gQofDk1esXp8ANnuDxfzWUXKe8gshligszaq05xNTPMJV9isb124nWsVqqPzR7z2mjg/3wR/U2vELLTYg0en/ToPXMax0+3rFM3ClEeWAiwErnTW9M482zOwOJICrdkM9JQ71QuU1Y6qYfHv0v8ZGWXOEmwuUeG3FYcgfGdTfndgbhxOYTJ8WR6cvoaO3CN/qvcy5XAZnj1UswfV+l7bZhYBjkbybajc9VTdnbqp5vE07qdbGWDEHFNkoteenr61JWLAIi8hDLMEWDXU3umibNjq+FSRxIUErDytczChuGyUpKb2B0uIlzn0sqrpywIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAlF/7umzkl90jgRI4gpO4sajw1v63hrhnu+OfXiv1T8OZuYlh9qqU+XWFwPtW19i2Vlev5DVPHHDukpjJcnVz14cnnqUmt0CrspOQm4+y4dKRimKAzof964/BIvcVIz/m7UEQiQ80EAWUgvjwa/WIl1fu2bIbS4AykJ5uw6FyqGVMrrYg38CfaW9655w/8ihSgJvj6qioBYe90SAzwTomOeV5msyawxUQYF5InCLYOejinkOlsFrhqAI71t8Uy+VpqxwtsN1G3vfDvTY1gG7ju8n1YFwvVm9Z6BKpD0hbvToEpHDFcUzUDC3qAjdvBPDRqyfAeNUBJBsYrARXNqSyrmgerber@stytchdev.comentity_idurn:oasis:names:tc:SAML:2.0:ac:classes:unspecified -------------------------------------------------------------------------------- /test/contract/data/valid/google/google_idp_initiated/idp_initiated.xml: -------------------------------------------------------------------------------- 1 | https://accounts.google.com/o/saml2?idpid=C02ji0uvfhttps://accounts.google.com/o/saml2?idpid=C02ji0uvfqWPLug0QyWTi7JIm9ricydoxnbOM3v82jtEkpeC3X3o=aK7FCgYSvLYGY77yCi+iw3hCgY6OK8+5gDDEM0EoPOnwtHTaaIPeWmZTgNojMxDr16vbzOAluFtU 2 | l5zExz64+37l9VZS5bcNHjVm1QioVWEvotndmYtOlV8ElETMW2isQAlc0GDYt/BWH6skK1gEO9da 3 | Xn5u/rKb4Jw4Xpx6ykvch9gI7BLss30A6ARMJSm3An25DajKYSf71dj4QpZpLz4rPg6gx/9UEylb 4 | niVCLYCc2u05KTreciOLUeK8xRUZTt14QrQnV1LFdpqzenZ0BlzrGJcWdltIePwvKP0wiy/rY3im 5 | MdPoMjyD+dArQ5x25Wqj6FA3tSpc8SPIv/bq1w==ST=California,C=US,OU=Google For Work,CN=Google,L=Mountain View,O=Google Inc.MIIDdDCCAlygAwIBAgIGAYSgi9d6MA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ 6 | bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv 7 | b2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMjIxMTIy 8 | MTgxMzQ5WhcNMjcxMTIxMTgxMzQ5WjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN 9 | TW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx 10 | CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 11 | MIIBCgKCAQEAn9dOryAKN3ejQRaO2eE64fc3gQofDk1esXp8ANnuDxfzWUXKe8gshligszaq05xN 12 | TPMJV9isb124nWsVqqPzR7z2mjg/3wR/U2vELLTYg0en/ToPXMax0+3rFM3ClEeWAiwErnTW9M48 13 | 2zOwOJICrdkM9JQ71QuU1Y6qYfHv0v8ZGWXOEmwuUeG3FYcgfGdTfndgbhxOYTJ8WR6cvoaO3CN/ 14 | qvcy5XAZnj1UswfV+l7bZhYBjkbybajc9VTdnbqp5vE07qdbGWDEHFNkoteenr61JWLAIi8hDLME 15 | WDXU3umibNjq+FSRxIUErDytczChuGyUpKb2B0uIlzn0sqrpywIDAQABMA0GCSqGSIb3DQEBCwUA 16 | A4IBAQAlF/7umzkl90jgRI4gpO4sajw1v63hrhnu+OfXiv1T8OZuYlh9qqU+XWFwPtW19i2Vlev5 17 | DVPHHDukpjJcnVz14cnnqUmt0CrspOQm4+y4dKRimKAzof964/BIvcVIz/m7UEQiQ80EAWUgvjwa 18 | /WIl1fu2bIbS4AykJ5uw6FyqGVMrrYg38CfaW9655w/8ihSgJvj6qioBYe90SAzwTomOeV5msyaw 19 | xUQYF5InCLYOejinkOlsFrhqAI71t8Uy+VpqxwtsN1G3vfDvTY1gG7ju8n1YFwvVm9Z6BKpD0hbv 20 | ToEpHDFcUzUDC3qAjdvBPDRqyfAeNUBJBsYrARXNqSyrmgerber@stytchdev.comtest_entity_idurn:oasis:names:tc:SAML:2.0:ac:classes:unspecified -------------------------------------------------------------------------------- /test/contract/data/valid/google/google_sp_initiated/sp_initiated.xml: -------------------------------------------------------------------------------- 1 | https://accounts.google.com/o/saml2?idpid=C02ji0uvfhttps://accounts.google.com/o/saml2?idpid=C02ji0uvfzHhpph/xtjI6HHF2J0LBFAXzTNJoEb0ndJj1ODZL87Q=nhOxTX8oYhLhhdAp5wqEJfTv+r0vJlSydIsGVNDPXB8RX9utc+Fwstf3698YSI8pWGrhn94S9mYn 2 | AT/YfwioRJH7SUE0fAyUxo+DjsYgVn0da6r3dAh9J/Fe653RGo3Qgy9X1edoZJusN5gcIL4HHPpV 3 | FvMxRs7JsPdmIFsRaE916IAtQei3KITYcNkEw7wGNHNx8RqfL7CA0P5oWLrmnq6t2IZNYHz8ZIrV 4 | iUg62FmxKHdgtVlHtIMmXO2uWD/UgHN7oUHtxDXTNv0kMaDKRUuFDXtw/AwjHPqLR3hCKlTT7Ts0 5 | Y7aBT21QCrXs+mNL1mn1bXwSPKZm2WNzAAZAEg==ST=California,C=US,OU=Google For Work,CN=Google,L=Mountain View,O=Google Inc.MIIDdDCCAlygAwIBAgIGAYSgi9d6MA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ 6 | bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv 7 | b2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMjIxMTIy 8 | MTgxMzQ5WhcNMjcxMTIxMTgxMzQ5WjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN 9 | TW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx 10 | CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 11 | MIIBCgKCAQEAn9dOryAKN3ejQRaO2eE64fc3gQofDk1esXp8ANnuDxfzWUXKe8gshligszaq05xN 12 | TPMJV9isb124nWsVqqPzR7z2mjg/3wR/U2vELLTYg0en/ToPXMax0+3rFM3ClEeWAiwErnTW9M48 13 | 2zOwOJICrdkM9JQ71QuU1Y6qYfHv0v8ZGWXOEmwuUeG3FYcgfGdTfndgbhxOYTJ8WR6cvoaO3CN/ 14 | qvcy5XAZnj1UswfV+l7bZhYBjkbybajc9VTdnbqp5vE07qdbGWDEHFNkoteenr61JWLAIi8hDLME 15 | WDXU3umibNjq+FSRxIUErDytczChuGyUpKb2B0uIlzn0sqrpywIDAQABMA0GCSqGSIb3DQEBCwUA 16 | A4IBAQAlF/7umzkl90jgRI4gpO4sajw1v63hrhnu+OfXiv1T8OZuYlh9qqU+XWFwPtW19i2Vlev5 17 | DVPHHDukpjJcnVz14cnnqUmt0CrspOQm4+y4dKRimKAzof964/BIvcVIz/m7UEQiQ80EAWUgvjwa 18 | /WIl1fu2bIbS4AykJ5uw6FyqGVMrrYg38CfaW9655w/8ihSgJvj6qioBYe90SAzwTomOeV5msyaw 19 | xUQYF5InCLYOejinkOlsFrhqAI71t8Uy+VpqxwtsN1G3vfDvTY1gG7ju8n1YFwvVm9Z6BKpD0hbv 20 | ToEpHDFcUzUDC3qAjdvBPDRqyfAeNUBJBsYrARXNqSyrmgerber@stytchdev.comentity_idurn:oasis:names:tc:SAML:2.0:ac:classes:unspecified -------------------------------------------------------------------------------- /test/contract/data/valid/google/google_sp_initiated_response_signed/sp_initiated_response_signed.xml: -------------------------------------------------------------------------------- 1 | https://accounts.google.com/o/saml2?idpid=C02ji0uvf3QHc440BMexnJCFOmNr8eW3vutb4ZRpvoHuzmz1hoH8=CVSlJl9M3uuHFkQM5KYjqaQcUx47yXTv+Ic6h3/bNZhv8aB4vlklKuarezTXnfaJVEPo5TYBM2Jd 2 | QkxkXW9yReGDTOcu2FxJrOuszgd0lZ3RxDPybXvLhKTTqcTdS9Q+Z+wYUVxKWtT43DtEhUoTxroG 3 | row+lMb8Aaq8m6LAEdpPk6vCIqoo+S1OW26uqx1Lz3xeIGT07Pkk+uqMDwNzVyWXJhESP6Hvg/HV 4 | WdXN28zxqd2lmnAcn4oMBD4rNQQ+L1caauZCutgTnzyodd6c0wvQiOuDIlTfu5ZpzsCQnIDX4Jsy 5 | KE/oWw6/FZw0dZU7ltEesBUQxK7c/gwIDDhtww==ST=California,C=US,OU=Google For Work,CN=Google,L=Mountain View,O=Google Inc.MIIDdDCCAlygAwIBAgIGAYSgi9d6MA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ 6 | bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv 7 | b2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMjIxMTIy 8 | MTgxMzQ5WhcNMjcxMTIxMTgxMzQ5WjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN 9 | TW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx 10 | CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 11 | MIIBCgKCAQEAn9dOryAKN3ejQRaO2eE64fc3gQofDk1esXp8ANnuDxfzWUXKe8gshligszaq05xN 12 | TPMJV9isb124nWsVqqPzR7z2mjg/3wR/U2vELLTYg0en/ToPXMax0+3rFM3ClEeWAiwErnTW9M48 13 | 2zOwOJICrdkM9JQ71QuU1Y6qYfHv0v8ZGWXOEmwuUeG3FYcgfGdTfndgbhxOYTJ8WR6cvoaO3CN/ 14 | qvcy5XAZnj1UswfV+l7bZhYBjkbybajc9VTdnbqp5vE07qdbGWDEHFNkoteenr61JWLAIi8hDLME 15 | WDXU3umibNjq+FSRxIUErDytczChuGyUpKb2B0uIlzn0sqrpywIDAQABMA0GCSqGSIb3DQEBCwUA 16 | A4IBAQAlF/7umzkl90jgRI4gpO4sajw1v63hrhnu+OfXiv1T8OZuYlh9qqU+XWFwPtW19i2Vlev5 17 | DVPHHDukpjJcnVz14cnnqUmt0CrspOQm4+y4dKRimKAzof964/BIvcVIz/m7UEQiQ80EAWUgvjwa 18 | /WIl1fu2bIbS4AykJ5uw6FyqGVMrrYg38CfaW9655w/8ihSgJvj6qioBYe90SAzwTomOeV5msyaw 19 | xUQYF5InCLYOejinkOlsFrhqAI71t8Uy+VpqxwtsN1G3vfDvTY1gG7ju8n1YFwvVm9Z6BKpD0hbv 20 | ToEpHDFcUzUDC3qAjdvBPDRqyfAeNUBJBsYrARXNqSyrhttps://accounts.google.com/o/saml2?idpid=C02ji0uvfmgerber@stytchdev.comentity_idurn:oasis:names:tc:SAML:2.0:ac:classes:unspecified 21 | -------------------------------------------------------------------------------- /test/contract/data/digest_vuln/poc.xml: -------------------------------------------------------------------------------- 1 | 2 | https://poc.securesaml.comhttps://poc.securesaml.com 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 9gGrXV3vDzvaWII856hziPBYAb2y2Mf1fM6cRjU+5A4= 14 | 15 | 16 | dRTHoRSyjuFtdqnw7jnafFNpK/bDgqZ/1CFMZ5cvt1hX91AmHEQ0ne/Eg4yBbI4GyBMte52EUZu3Wa8ECqWDpKSaEFWSqP6xt8ADkcCKcg3NF5AljKb2xHsta5GLvvp2PVQKvhiZMTM8sU2yvySfrprYS61xFCWctYjfsgh3jGPLNJnoFf3Si68zUr9mJt2exlVZ9ZTWJHm6lSRLS1Xmp1noGnGjrK5nCl8TMki/qA77d13ZEkO0v232d2oGD5ckzfjpA4SBW17g8Z1j+QKlfaiij/E4m0z0yATQ8ZoLVAXdY5mnS+Iw7j2zJZnqHdp29z/MiiuZdMfrg8+lIHSaxg== 17 | 18 | 19 | MIIDzzCCAregAwIBAgIUMZMb3dfDNPcYK9rYUCz6U/Y/vdwwDQYJKoZIhvcN 20 | AQELBQAwdzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMREwDwYDVQQH 21 | DAhMb2NhdGlvbjEVMBMGA1UECgwMT3JnYW5pemF0aW9uMREwDwYDVQQLDAhP 22 | cmcgVW5pdDEbMBkGA1UEAwwScG9jLnNlY3VyZXNhbWwuY29tMB4XDTI0MTEy 23 | ODA1NDYyN1oXDTM0MTEyNjA1NDYyN1owdzELMAkGA1UEBhMCVVMxDjAMBgNV 24 | BAgMBVN0YXRlMREwDwYDVQQHDAhMb2NhdGlvbjEVMBMGA1UECgwMT3JnYW5p 25 | emF0aW9uMREwDwYDVQQLDAhPcmcgVW5pdDEbMBkGA1UEAwwScG9jLnNlY3Vy 26 | ZXNhbWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArBx4 27 | nG94nZJvXMSWkkJMxWMTY5YS53MegLD/DOMgM5n5tXBRewAgFkEdL6tclvqK 28 | EP80yc5N/KSdGZrbwD5oKhw4+4+GTpRSSoleFLhSYr0DZvTMvFHMgB45SddU 29 | A3DkcI0ZSF+RExZQhMypYxNjEMkKL5EJDh7d+Xt9FCVQ1GKjVRI12jeXOvTQ 30 | TOefPaz314aFBJ0XfqP3tl08jJAWC2kOgi9vB43Xu7u//FgubRifhwcVkzFt 31 | WLdDJSm/Q3qHkV8QDb4TL54dGHdXUP8wo0msqt2WXGZ691VYrRXw8dYmthl7 32 | KeVwcBsUUbUr2jA+Ia2hxnbBTfPY2m9ZfKEBUQIDAQABo1MwUTAdBgNVHQ4E 33 | FgQUknvBAHKXFwZjDB0rSvTGi2e/7n0wHwYDVR0jBBgwFoAUknvBAHKXFwZj 34 | DB0rSvTGi2e/7n0wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC 35 | AQEAj9BFFl9jSvmR/3GipWuBAC84jEdEzLk6o8AgqZGdBABFAK3TURlQLTli 36 | Nj17zqOlr3xHBorX9iCk46IZZ5ARjjjwzQZ5mzGsMYp+LPlC+w9G1AsqwXCL 37 | 619+JQ5ORHN7kMHgQYIzkKe8FRa0NjBAl0FIwCe0DWGrbuNrQB5p5h/77TTF 38 | N+/ESjVbK0m/ubsl4tBnDqR3aq7KiBNr0e1yTF17Gg5iHc1ofINzq5i30/4v 39 | GGw0ohtr4ihg6J3hdwUIVnRknfuN3tE80jSF4e1LRojlyFoQXcg4emXq0Jn8 40 | lj6sw9dhQDq19MYaXchAuJMkWmXwt9e/CaWm7JRyuUgBcg== 41 | 42 | 43 | 44 | captured@anyuser.comhttps://poc.securesaml.com/sp/acsurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport 45 | -------------------------------------------------------------------------------- /test/contract/data/security_validations/invalid_transform_algorithm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://www.okta.com/exk2wpjmakQqqs7dN697 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | E3HUdwVZnUcXZHthp/+IQWwyQ6VvC45qdg8coTCn55I= 15 | 16 | 17 | SpfZBRz6JZkYivdJh3nrAIyWd3AQonlEf9hw7GK7jMi0QxIU0Fl9rTyKnsXKmGUoIQEuUle4qdkg5haV+Xcetm1mPiEsLlhm+nKOGp4usnJ6zJipi79prNOfe11RHFfcjUBXfLGWDwO3qCfwrTQYWrn8nDb2ugo1yL2WOt6mjjxccEmqd3PkI3ZJ0UCP32wv8KaGVzdrbIu36LIGzRVkGvoiyeaq/W9mfRgsGKwhtF2RBbPKZtQ0o/VWOlIPj1yRdfDXe7MAk76CuPXqmdKHUIV9rUViE8nZ1GmFKJwR6wbuvnCx+6tD1VUKBLc4AHWoyhhR8r0EWXoalhTVv3/7EA== 18 | 19 | 20 | MIIDqjCCApKgAwIBAgIGAYRAlYb3MA0GCSqGSIb3DQEBCwUAMIGVMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEUMBIGA1UECwwLU1NPUHJvdmlkZXIxFjAUBgNVBAMMDXRyaWFsLTkxNTkxODgxHDAaBgkqhkiG9w0BCQEWDWluZm9Ab2t0YS5jb20wHhcNMjIxMTA0MDI1OTUyWhcNMzIxMTA0MDMwMDUxWjCBlTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRYwFAYDVQQDDA10cmlhbC05MTU5MTg4MRwwGgYJKoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlauL9nTKFoBLtH5ubsKQWy6Odb7xlumZGeBglcYX8jK1G246eZT61ArbtBN3YXirHXYPeN6NGaFM35iNiyRYedSDN/nurZxcz0WJQZQwFfbTFw5AJO4xYCFoKiRRkTi1rHKyNQz0HWZQGeJmF2NT48Cj8U+gFfQyFeL20O5jbLG0pWF439GV+cR04c82SxJuOc1aEFQVT9n8HEMBnkPsj3LNoplJf7KcvrL+UEy1tmgXHRYzT6qi3+aDOsF6Sx8qdcnGPSEz6iGJBnMdXfba7AEyylZQ9M3qBCiGc6sY3xtZD8qo8y/Fit8sUklgk3S0+Bo08YwcgM/26lVSZZsQsQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBaJxyXEg92Vr8r0z5KoR4xQMkzGq5TxoQlrHk47PjF8hy3/2aUBWwWhDQUzfWN7p2rjMBoeLnBD/EUaQ+784wwDX1wUIliCJay38+vbyRV12Kiajkvx2xGM0RmCFvz+3Eeu9xGanfzrsUw0zI3HGG2Cp/KnkLcMdID163FTtzVwgIQDInY6E698fVPutxKIUEvgt02OZvIlPkziHZgTOqyGf1dFL4jY4DpbbbE/hfnRUy5LY6U23NNmq4qLX068iEvF5CXZ47yx3HwXSNF5Gg4s9ncuMx+XjhYl7hQlaI8mkH8wcIH1TAwOUzz+2wOT/NxZE//yj4PEKr6lGCUELt 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | http://www.okta.com/exk2wpjmakQqqs7dN697 29 | 30 | mgerber@berkeley.edu 31 | 32 | 33 | 34 | audience_entity_id 35 | 36 | 37 | 38 | 39 | urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /test/contract/data/security_validations/invalid_canonicalization_method.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://www.okta.com/exk2wpjmakQqqs7dN697 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | E3HUdwVZnUcXZHthp/+IQWwyQ6VvC45qdg8coTCn55I= 15 | 16 | 17 | SpfZBRz6JZkYivdJh3nrAIyWd3AQonlEf9hw7GK7jMi0QxIU0Fl9rTyKnsXKmGUoIQEuUle4qdkg5haV+Xcetm1mPiEsLlhm+nKOGp4usnJ6zJipi79prNOfe11RHFfcjUBXfLGWDwO3qCfwrTQYWrn8nDb2ugo1yL2WOt6mjjxccEmqd3PkI3ZJ0UCP32wv8KaGVzdrbIu36LIGzRVkGvoiyeaq/W9mfRgsGKwhtF2RBbPKZtQ0o/VWOlIPj1yRdfDXe7MAk76CuPXqmdKHUIV9rUViE8nZ1GmFKJwR6wbuvnCx+6tD1VUKBLc4AHWoyhhR8r0EWXoalhTVv3/7EA== 18 | 19 | 20 | MIIDqjCCApKgAwIBAgIGAYRAlYb3MA0GCSqGSIb3DQEBCwUAMIGVMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEUMBIGA1UECwwLU1NPUHJvdmlkZXIxFjAUBgNVBAMMDXRyaWFsLTkxNTkxODgxHDAaBgkqhkiG9w0BCQEWDWluZm9Ab2t0YS5jb20wHhcNMjIxMTA0MDI1OTUyWhcNMzIxMTA0MDMwMDUxWjCBlTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRYwFAYDVQQDDA10cmlhbC05MTU5MTg4MRwwGgYJKoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlauL9nTKFoBLtH5ubsKQWy6Odb7xlumZGeBglcYX8jK1G246eZT61ArbtBN3YXirHXYPeN6NGaFM35iNiyRYedSDN/nurZxcz0WJQZQwFfbTFw5AJO4xYCFoKiRRkTi1rHKyNQz0HWZQGeJmF2NT48Cj8U+gFfQyFeL20O5jbLG0pWF439GV+cR04c82SxJuOc1aEFQVT9n8HEMBnkPsj3LNoplJf7KcvrL+UEy1tmgXHRYzT6qi3+aDOsF6Sx8qdcnGPSEz6iGJBnMdXfba7AEyylZQ9M3qBCiGc6sY3xtZD8qo8y/Fit8sUklgk3S0+Bo08YwcgM/26lVSZZsQsQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBaJxyXEg92Vr8r0z5KoR4xQMkzGq5TxoQlrHk47PjF8hy3/2aUBWwWhDQUzfWN7p2rjMBoeLnBD/EUaQ+784wwDX1wUIliCJay38+vbyRV12Kiajkvx2xGM0RmCFvz+3Eeu9xGanfzrsUw0zI3HGG2Cp/KnkLcMdID163FTtzVwgIQDInY6E698fVPutxKIUEvgt02OZvIlPkziHZgTOqyGf1dFL4jY4DpbbbE/hfnRUy5LY6U23NNmq4qLX068iEvF5CXZ47yx3HwXSNF5Gg4s9ncuMx+XjhYl7hQlaI8mkH8wcIH1TAwOUzz+2wOT/NxZE//yj4PEKr6lGCUELt 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | http://www.okta.com/exk2wpjmakQqqs7dN697 29 | 30 | mgerber@berkeley.edu 31 | 32 | 33 | 34 | audience_entity_id 35 | 36 | 37 | 38 | 39 | urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /test/contract/data/security_validations/too_many_transforms.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://www.okta.com/exk2wpjmakQqqs7dN697 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | E3HUdwVZnUcXZHthp/+IQWwyQ6VvC45qdg8coTCn55I= 16 | 17 | 18 | SpfZBRz6JZkYivdJh3nrAIyWd3AQonlEf9hw7GK7jMi0QxIU0Fl9rTyKnsXKmGUoIQEuUle4qdkg5haV+Xcetm1mPiEsLlhm+nKOGp4usnJ6zJipi79prNOfe11RHFfcjUBXfLGWDwO3qCfwrTQYWrn8nDb2ugo1yL2WOt6mjjxccEmqd3PkI3ZJ0UCP32wv8KaGVzdrbIu36LIGzRVkGvoiyeaq/W9mfRgsGKwhtF2RBbPKZtQ0o/VWOlIPj1yRdfDXe7MAk76CuPXqmdKHUIV9rUViE8nZ1GmFKJwR6wbuvnCx+6tD1VUKBLc4AHWoyhhR8r0EWXoalhTVv3/7EA== 19 | 20 | 21 | MIIDqjCCApKgAwIBAgIGAYRAlYb3MA0GCSqGSIb3DQEBCwUAMIGVMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEUMBIGA1UECwwLU1NPUHJvdmlkZXIxFjAUBgNVBAMMDXRyaWFsLTkxNTkxODgxHDAaBgkqhkiG9w0BCQEWDWluZm9Ab2t0YS5jb20wHhcNMjIxMTA0MDI1OTUyWhcNMzIxMTA0MDMwMDUxWjCBlTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRYwFAYDVQQDDA10cmlhbC05MTU5MTg4MRwwGgYJKoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlauL9nTKFoBLtH5ubsKQWy6Odb7xlumZGeBglcYX8jK1G246eZT61ArbtBN3YXirHXYPeN6NGaFM35iNiyRYedSDN/nurZxcz0WJQZQwFfbTFw5AJO4xYCFoKiRRkTi1rHKyNQz0HWZQGeJmF2NT48Cj8U+gFfQyFeL20O5jbLG0pWF439GV+cR04c82SxJuOc1aEFQVT9n8HEMBnkPsj3LNoplJf7KcvrL+UEy1tmgXHRYzT6qi3+aDOsF6Sx8qdcnGPSEz6iGJBnMdXfba7AEyylZQ9M3qBCiGc6sY3xtZD8qo8y/Fit8sUklgk3S0+Bo08YwcgM/26lVSZZsQsQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBaJxyXEg92Vr8r0z5KoR4xQMkzGq5TxoQlrHk47PjF8hy3/2aUBWwWhDQUzfWN7p2rjMBoeLnBD/EUaQ+784wwDX1wUIliCJay38+vbyRV12Kiajkvx2xGM0RmCFvz+3Eeu9xGanfzrsUw0zI3HGG2Cp/KnkLcMdID163FTtzVwgIQDInY6E698fVPutxKIUEvgt02OZvIlPkziHZgTOqyGf1dFL4jY4DpbbbE/hfnRUy5LY6U23NNmq4qLX068iEvF5CXZ47yx3HwXSNF5Gg4s9ncuMx+XjhYl7hQlaI8mkH8wcIH1TAwOUzz+2wOT/NxZE//yj4PEKr6lGCUELt 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | http://www.okta.com/exk2wpjmakQqqs7dN697 30 | 31 | mgerber@berkeley.edu 32 | 33 | 34 | 35 | audience_entity_id 36 | 37 | 38 | 39 | 40 | urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /test/contract/data/security_validations/doctype_without_entity.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | ]> 5 | 6 | http://www.okta.com/exk2wpjmakQqqs7dN697 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | E3HUdwVZnUcXZHthp/+IQWwyQ6VvC45qdg8coTCn55I= 18 | 19 | 20 | SpfZBRz6JZkYivdJh3nrAIyWd3AQonlEf9hw7GK7jMi0QxIU0Fl9rTyKnsXKmGUoIQEuUle4qdkg5haV+Xcetm1mPiEsLlhm+nKOGp4usnJ6zJipi79prNOfe11RHFfcjUBXfLGWDwO3qCfwrTQYWrn8nDb2ugo1yL2WOt6mjjxccEmqd3PkI3ZJ0UCP32wv8KaGVzdrbIu36LIGzRVkGvoiyeaq/W9mfRgsGKwhtF2RBbPKZtQ0o/VWOlIPj1yRdfDXe7MAk76CuPXqmdKHUIV9rUViE8nZ1GmFKJwR6wbuvnCx+6tD1VUKBLc4AHWoyhhR8r0EWXoalhTVv3/7EA== 21 | 22 | 23 | 24 | 25 | 26 | http://www.okta.com/exk2wpjmakQqqs7dN697 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | iFA687LLXJvfU5D/kWDmC1SpUyQLBH8kavBluisxaEM= 38 | 39 | 40 | IbqhTEnjzHIcN2mSscUsYdHH0k6rVJyzomAyeiXdw3NS6GT+5bK2ddpCHFEiHEVWK0r2W1sk4QHXkiDYqEMiLVIp25XvMsM9bFHtpveJmp9aBxb4JLGn2Jh+qVX+EVUVDS+N6JEzBGv0KDKG/gwDmOpqAO3pyHvlsr0NuIcmW+tnNNTchfTC7WDMYngMSHD07eplnkf6AWMA2umosy+dKzwuXknLLtFZHHcUg2oOSJ6CdsUfpnZQpwwo6VE2Z12oByPY1bZplj/Rq+bIocwbhZMIWc1pM9vZ8DKWJkw0MdGkQqNPQKcANRjaKNeokEmpy7XKE3wdqTdScHh7Mi/KjA== 41 | 42 | 43 | johndoe@example.com 44 | 45 | 46 | 47 | 48 | 49 | 50 | http://localhost:5001 51 | 52 | 53 | 54 | 55 | urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /test/contract/data/valid/google/google_idp_initiated/idp_initiated.formatted.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://accounts.google.com/o/saml2?idpid=C02ji0uvf 4 | 5 | 6 | 7 | 8 | https://accounts.google.com/o/saml2?idpid=C02ji0uvf 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | qWPLug0QyWTi7JIm9ricydoxnbOM3v82jtEkpeC3X3o= 20 | 21 | 22 | aK7FCgYSvLYGY77yCi+iw3hCgY6OK8+5gDDEM0EoPOnwtHTaaIPeWmZTgNojMxDr16vbzOAluFtU 23 | l5zExz64+37l9VZS5bcNHjVm1QioVWEvotndmYtOlV8ElETMW2isQAlc0GDYt/BWH6skK1gEO9da 24 | Xn5u/rKb4Jw4Xpx6ykvch9gI7BLss30A6ARMJSm3An25DajKYSf71dj4QpZpLz4rPg6gx/9UEylb 25 | niVCLYCc2u05KTreciOLUeK8xRUZTt14QrQnV1LFdpqzenZ0BlzrGJcWdltIePwvKP0wiy/rY3im 26 | MdPoMjyD+dArQ5x25Wqj6FA3tSpc8SPIv/bq1w== 27 | 28 | 29 | ST=California,C=US,OU=Google For Work,CN=Google,L=Mountain View,O=Google Inc. 30 | MIIDdDCCAlygAwIBAgIGAYSgi9d6MA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ 31 | bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv 32 | b2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMjIxMTIy 33 | MTgxMzQ5WhcNMjcxMTIxMTgxMzQ5WjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN 34 | TW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx 35 | CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 36 | MIIBCgKCAQEAn9dOryAKN3ejQRaO2eE64fc3gQofDk1esXp8ANnuDxfzWUXKe8gshligszaq05xN 37 | TPMJV9isb124nWsVqqPzR7z2mjg/3wR/U2vELLTYg0en/ToPXMax0+3rFM3ClEeWAiwErnTW9M48 38 | 2zOwOJICrdkM9JQ71QuU1Y6qYfHv0v8ZGWXOEmwuUeG3FYcgfGdTfndgbhxOYTJ8WR6cvoaO3CN/ 39 | qvcy5XAZnj1UswfV+l7bZhYBjkbybajc9VTdnbqp5vE07qdbGWDEHFNkoteenr61JWLAIi8hDLME 40 | WDXU3umibNjq+FSRxIUErDytczChuGyUpKb2B0uIlzn0sqrpywIDAQABMA0GCSqGSIb3DQEBCwUA 41 | A4IBAQAlF/7umzkl90jgRI4gpO4sajw1v63hrhnu+OfXiv1T8OZuYlh9qqU+XWFwPtW19i2Vlev5 42 | DVPHHDukpjJcnVz14cnnqUmt0CrspOQm4+y4dKRimKAzof964/BIvcVIz/m7UEQiQ80EAWUgvjwa 43 | /WIl1fu2bIbS4AykJ5uw6FyqGVMrrYg38CfaW9655w/8ihSgJvj6qioBYe90SAzwTomOeV5msyaw 44 | xUQYF5InCLYOejinkOlsFrhqAI71t8Uy+VpqxwtsN1G3vfDvTY1gG7ju8n1YFwvVm9Z6BKpD0hbv 45 | ToEpHDFcUzUDC3qAjdvBPDRqyfAeNUBJBsYrARXNqSyr 46 | 47 | 48 | 49 | 50 | mgerber@stytchdev.com 51 | 52 | 53 | 54 | 55 | 56 | 57 | test_entity_id 58 | 59 | 60 | 61 | 62 | urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /test/contract/data/digest_vuln/poc.formatted.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://poc.securesaml.com 4 | 5 | 6 | 7 | 8 | https://poc.securesaml.com 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 9gGrXV3vDzvaWII856hziPBYAb2y2Mf1fM6cRjU+5A4= 21 | 22 | 23 | dRTHoRSyjuFtdqnw7jnafFNpK/bDgqZ/1CFMZ5cvt1hX91AmHEQ0ne/Eg4yBbI4GyBMte52EUZu3Wa8ECqWDpKSaEFWSqP6xt8ADkcCKcg3NF5AljKb2xHsta5GLvvp2PVQKvhiZMTM8sU2yvySfrprYS61xFCWctYjfsgh3jGPLNJnoFf3Si68zUr9mJt2exlVZ9ZTWJHm6lSRLS1Xmp1noGnGjrK5nCl8TMki/qA77d13ZEkO0v232d2oGD5ckzfjpA4SBW17g8Z1j+QKlfaiij/E4m0z0yATQ8ZoLVAXdY5mnS+Iw7j2zJZnqHdp29z/MiiuZdMfrg8+lIHSaxg== 24 | 25 | 26 | MIIDzzCCAregAwIBAgIUMZMb3dfDNPcYK9rYUCz6U/Y/vdwwDQYJKoZIhvcN 27 | AQELBQAwdzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMREwDwYDVQQH 28 | DAhMb2NhdGlvbjEVMBMGA1UECgwMT3JnYW5pemF0aW9uMREwDwYDVQQLDAhP 29 | cmcgVW5pdDEbMBkGA1UEAwwScG9jLnNlY3VyZXNhbWwuY29tMB4XDTI0MTEy 30 | ODA1NDYyN1oXDTM0MTEyNjA1NDYyN1owdzELMAkGA1UEBhMCVVMxDjAMBgNV 31 | BAgMBVN0YXRlMREwDwYDVQQHDAhMb2NhdGlvbjEVMBMGA1UECgwMT3JnYW5p 32 | emF0aW9uMREwDwYDVQQLDAhPcmcgVW5pdDEbMBkGA1UEAwwScG9jLnNlY3Vy 33 | ZXNhbWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArBx4 34 | nG94nZJvXMSWkkJMxWMTY5YS53MegLD/DOMgM5n5tXBRewAgFkEdL6tclvqK 35 | EP80yc5N/KSdGZrbwD5oKhw4+4+GTpRSSoleFLhSYr0DZvTMvFHMgB45SddU 36 | A3DkcI0ZSF+RExZQhMypYxNjEMkKL5EJDh7d+Xt9FCVQ1GKjVRI12jeXOvTQ 37 | TOefPaz314aFBJ0XfqP3tl08jJAWC2kOgi9vB43Xu7u//FgubRifhwcVkzFt 38 | WLdDJSm/Q3qHkV8QDb4TL54dGHdXUP8wo0msqt2WXGZ691VYrRXw8dYmthl7 39 | KeVwcBsUUbUr2jA+Ia2hxnbBTfPY2m9ZfKEBUQIDAQABo1MwUTAdBgNVHQ4E 40 | FgQUknvBAHKXFwZjDB0rSvTGi2e/7n0wHwYDVR0jBBgwFoAUknvBAHKXFwZj 41 | DB0rSvTGi2e/7n0wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC 42 | AQEAj9BFFl9jSvmR/3GipWuBAC84jEdEzLk6o8AgqZGdBABFAK3TURlQLTli 43 | Nj17zqOlr3xHBorX9iCk46IZZ5ARjjjwzQZ5mzGsMYp+LPlC+w9G1AsqwXCL 44 | 619+JQ5ORHN7kMHgQYIzkKe8FRa0NjBAl0FIwCe0DWGrbuNrQB5p5h/77TTF 45 | N+/ESjVbK0m/ubsl4tBnDqR3aq7KiBNr0e1yTF17Gg5iHc1ofINzq5i30/4v 46 | GGw0ohtr4ihg6J3hdwUIVnRknfuN3tE80jSF4e1LRojlyFoQXcg4emXq0Jn8 47 | lj6sw9dhQDq19MYaXchAuJMkWmXwt9e/CaWm7JRyuUgBcg== 48 | 49 | 50 | 51 | 52 | 53 | captured@anyuser.com 54 | 55 | 56 | 57 | 58 | 59 | 60 | https://poc.securesaml.com/sp/acs 61 | 62 | 63 | 64 | 65 | urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /test/contract/loader.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import path from "node:path"; 3 | 4 | const BASE64_DIRECTIVE = "!embed:base64:"; 5 | const EMBED_DIRECTIVE = "!embed:file://"; 6 | const EMBED_BASE64_DIRECTIVE = "!embed:base64:file://"; 7 | 8 | type TestCase = { 9 | only?: boolean; 10 | skip?: boolean; 11 | name?: string; 12 | description?: string; 13 | input?: Record; 14 | mockTime?: string; 15 | shouldSucceed?: boolean; 16 | expectedError?: string; 17 | expectedErrorCode?: string; 18 | }; 19 | 20 | export class ContractTestCase { 21 | public fileContents: TestCase; 22 | 23 | constructor( 24 | public testDir: string, 25 | public testFile: string, 26 | ) { 27 | this.fileContents = JSON.parse( 28 | fs.readFileSync(testFile).toString(), 29 | ) as TestCase; 30 | } 31 | 32 | get isOnly() { 33 | return !!this.fileContents.only; 34 | } 35 | 36 | get isSkipped() { 37 | return !!this.fileContents.skip; 38 | } 39 | 40 | get name() { 41 | return ( 42 | this.fileContents.name || 43 | this.testFile 44 | .replace(path.join(__dirname, "data"), "") 45 | .replace(".test.json", "") 46 | .split(path.sep) 47 | .join(" ") 48 | ); 49 | } 50 | 51 | get description() { 52 | return this.fileContents.description || this.name; 53 | } 54 | 55 | get debugInfo() { 56 | return "\t" + "[Test Case INPUT]\n" + "\tfile://" + this.testFile; 57 | } 58 | 59 | get shouldSucceed() { 60 | return this.fileContents.shouldSucceed !== false; // Default to true 61 | } 62 | 63 | get expectedError() { 64 | return this.fileContents.expectedError; 65 | } 66 | 67 | get expectedErrorCode() { 68 | return this.fileContents.expectedErrorCode; 69 | } 70 | 71 | get mockTime() { 72 | return this.fileContents.mockTime; 73 | } 74 | 75 | get input() { 76 | if (typeof this.fileContents.input !== "object") { 77 | throw Error("Missing input for " + this.testFile); 78 | } 79 | return ContractTestCase.resolveEmbeds( 80 | this.testDir, 81 | this.fileContents.input, 82 | ); 83 | } 84 | 85 | private static async resolveEmbeds( 86 | root: string, 87 | input: Array | Record | string, 88 | ): Promise { 89 | if (typeof input === "string") { 90 | if (input.startsWith(EMBED_DIRECTIVE)) { 91 | const embedLocation = path.join( 92 | root, 93 | input.replace(EMBED_DIRECTIVE, ""), 94 | ); 95 | return await fs.promises 96 | .readFile(embedLocation) 97 | .then((buf) => buf.toString()); 98 | } 99 | if (input.startsWith(EMBED_BASE64_DIRECTIVE)) { 100 | const embedLocation = path.join( 101 | root, 102 | input.replace(EMBED_BASE64_DIRECTIVE, ""), 103 | ); 104 | return await fs.promises 105 | .readFile(embedLocation) 106 | .then((buf) => buf.toString("base64")); 107 | } 108 | if (input.startsWith(BASE64_DIRECTIVE)) { 109 | return Buffer.from(input.replace(BASE64_DIRECTIVE, "")).toString( 110 | "base64", 111 | ); 112 | } 113 | return input; 114 | } 115 | 116 | if (Array.isArray(input)) { 117 | return Promise.all( 118 | input.map(ContractTestCase.resolveEmbeds.bind(null, root)), 119 | ); 120 | } 121 | 122 | if (input === null) { 123 | return null; 124 | } 125 | 126 | if (typeof input === "object") { 127 | const output: Record = {}; 128 | for (const key in input) { 129 | output[key] = await ContractTestCase.resolveEmbeds( 130 | root, 131 | input[key] as Record, 132 | ); 133 | } 134 | return output; 135 | } 136 | 137 | return input; 138 | } 139 | } 140 | 141 | export class ContractTestLoader { 142 | constructor(public root: string) {} 143 | 144 | loadTestCases() { 145 | const testCases = []; 146 | const stack = [this.root]; 147 | 148 | while (stack.length > 0) { 149 | const dirPath = stack.shift() as string; 150 | const files = fs 151 | .readdirSync(dirPath) 152 | .map((file) => path.join(dirPath, file)); 153 | for (const file of files) { 154 | const stats = fs.statSync(file); 155 | if (stats.isDirectory()) { 156 | stack.push(file); 157 | } else if (file.endsWith(".test.json")) { 158 | testCases.push(new ContractTestCase(path.dirname(file), file)); 159 | } 160 | } 161 | } 162 | return testCases; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /test/contract/data/valid/google/google_sp_initiated_response_signed/sp_initiated_response_signed.formatted.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://accounts.google.com/o/saml2?idpid=C02ji0uvf 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 3QHc440BMexnJCFOmNr8eW3vutb4ZRpvoHuzmz1hoH8= 15 | 16 | 17 | CVSlJl9M3uuHFkQM5KYjqaQcUx47yXTv+Ic6h3/bNZhv8aB4vlklKuarezTXnfaJVEPo5TYBM2Jd 18 | QkxkXW9yReGDTOcu2FxJrOuszgd0lZ3RxDPybXvLhKTTqcTdS9Q+Z+wYUVxKWtT43DtEhUoTxroG 19 | row+lMb8Aaq8m6LAEdpPk6vCIqoo+S1OW26uqx1Lz3xeIGT07Pkk+uqMDwNzVyWXJhESP6Hvg/HV 20 | WdXN28zxqd2lmnAcn4oMBD4rNQQ+L1caauZCutgTnzyodd6c0wvQiOuDIlTfu5ZpzsCQnIDX4Jsy 21 | KE/oWw6/FZw0dZU7ltEesBUQxK7c/gwIDDhtww== 22 | 23 | 24 | ST=California,C=US,OU=Google For Work,CN=Google,L=Mountain View,O=Google Inc. 25 | MIIDdDCCAlygAwIBAgIGAYSgi9d6MA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ 26 | bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv 27 | b2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMjIxMTIy 28 | MTgxMzQ5WhcNMjcxMTIxMTgxMzQ5WjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN 29 | TW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx 30 | CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 31 | MIIBCgKCAQEAn9dOryAKN3ejQRaO2eE64fc3gQofDk1esXp8ANnuDxfzWUXKe8gshligszaq05xN 32 | TPMJV9isb124nWsVqqPzR7z2mjg/3wR/U2vELLTYg0en/ToPXMax0+3rFM3ClEeWAiwErnTW9M48 33 | 2zOwOJICrdkM9JQ71QuU1Y6qYfHv0v8ZGWXOEmwuUeG3FYcgfGdTfndgbhxOYTJ8WR6cvoaO3CN/ 34 | qvcy5XAZnj1UswfV+l7bZhYBjkbybajc9VTdnbqp5vE07qdbGWDEHFNkoteenr61JWLAIi8hDLME 35 | WDXU3umibNjq+FSRxIUErDytczChuGyUpKb2B0uIlzn0sqrpywIDAQABMA0GCSqGSIb3DQEBCwUA 36 | A4IBAQAlF/7umzkl90jgRI4gpO4sajw1v63hrhnu+OfXiv1T8OZuYlh9qqU+XWFwPtW19i2Vlev5 37 | DVPHHDukpjJcnVz14cnnqUmt0CrspOQm4+y4dKRimKAzof964/BIvcVIz/m7UEQiQ80EAWUgvjwa 38 | /WIl1fu2bIbS4AykJ5uw6FyqGVMrrYg38CfaW9655w/8ihSgJvj6qioBYe90SAzwTomOeV5msyaw 39 | xUQYF5InCLYOejinkOlsFrhqAI71t8Uy+VpqxwtsN1G3vfDvTY1gG7ju8n1YFwvVm9Z6BKpD0hbv 40 | ToEpHDFcUzUDC3qAjdvBPDRqyfAeNUBJBsYrARXNqSyr 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | https://accounts.google.com/o/saml2?idpid=C02ji0uvf 49 | 50 | mgerber@stytchdev.com 51 | 52 | 53 | 54 | 55 | 56 | 57 | entity_id 58 | 59 | 60 | 61 | 62 | urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /test/contract/data/valid/google/google_sp_initiated/sp_initiated.formatted.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://accounts.google.com/o/saml2?idpid=C02ji0uvf 4 | 5 | 6 | 7 | 8 | https://accounts.google.com/o/saml2?idpid=C02ji0uvf 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | zHhpph/xtjI6HHF2J0LBFAXzTNJoEb0ndJj1ODZL87Q= 20 | 21 | 22 | nhOxTX8oYhLhhdAp5wqEJfTv+r0vJlSydIsGVNDPXB8RX9utc+Fwstf3698YSI8pWGrhn94S9mYn 23 | AT/YfwioRJH7SUE0fAyUxo+DjsYgVn0da6r3dAh9J/Fe653RGo3Qgy9X1edoZJusN5gcIL4HHPpV 24 | FvMxRs7JsPdmIFsRaE916IAtQei3KITYcNkEw7wGNHNx8RqfL7CA0P5oWLrmnq6t2IZNYHz8ZIrV 25 | iUg62FmxKHdgtVlHtIMmXO2uWD/UgHN7oUHtxDXTNv0kMaDKRUuFDXtw/AwjHPqLR3hCKlTT7Ts0 26 | Y7aBT21QCrXs+mNL1mn1bXwSPKZm2WNzAAZAEg== 27 | 28 | 29 | ST=California,C=US,OU=Google For Work,CN=Google,L=Mountain View,O=Google Inc. 30 | MIIDdDCCAlygAwIBAgIGAYSgi9d6MA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ 31 | bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv 32 | b2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMjIxMTIy 33 | MTgxMzQ5WhcNMjcxMTIxMTgxMzQ5WjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN 34 | TW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx 35 | CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 36 | MIIBCgKCAQEAn9dOryAKN3ejQRaO2eE64fc3gQofDk1esXp8ANnuDxfzWUXKe8gshligszaq05xN 37 | TPMJV9isb124nWsVqqPzR7z2mjg/3wR/U2vELLTYg0en/ToPXMax0+3rFM3ClEeWAiwErnTW9M48 38 | 2zOwOJICrdkM9JQ71QuU1Y6qYfHv0v8ZGWXOEmwuUeG3FYcgfGdTfndgbhxOYTJ8WR6cvoaO3CN/ 39 | qvcy5XAZnj1UswfV+l7bZhYBjkbybajc9VTdnbqp5vE07qdbGWDEHFNkoteenr61JWLAIi8hDLME 40 | WDXU3umibNjq+FSRxIUErDytczChuGyUpKb2B0uIlzn0sqrpywIDAQABMA0GCSqGSIb3DQEBCwUA 41 | A4IBAQAlF/7umzkl90jgRI4gpO4sajw1v63hrhnu+OfXiv1T8OZuYlh9qqU+XWFwPtW19i2Vlev5 42 | DVPHHDukpjJcnVz14cnnqUmt0CrspOQm4+y4dKRimKAzof964/BIvcVIz/m7UEQiQ80EAWUgvjwa 43 | /WIl1fu2bIbS4AykJ5uw6FyqGVMrrYg38CfaW9655w/8ihSgJvj6qioBYe90SAzwTomOeV5msyaw 44 | xUQYF5InCLYOejinkOlsFrhqAI71t8Uy+VpqxwtsN1G3vfDvTY1gG7ju8n1YFwvVm9Z6BKpD0hbv 45 | ToEpHDFcUzUDC3qAjdvBPDRqyfAeNUBJBsYrARXNqSyr 46 | 47 | 48 | 49 | 50 | mgerber@stytchdev.com 51 | 52 | 53 | 54 | 55 | 56 | 57 | entity_id 58 | 59 | 60 | 61 | 62 | urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /test/contract/data/valid/pingone/pingone_idp_initiated_only_assertion_signed/idp_initiated_only_assertion_signed.xml: -------------------------------------------------------------------------------- 1 | https://auth.pingone.com/fac598b8-a94e-483b-b602-af44aa9dbc97https://auth.pingone.com/fac598b8-a94e-483b-b602-af44aa9dbc97oq3xUqgdWJ37hgg1UcHNY9Mcs35vNV6JQKviwpDMBqHPo6JIOTcvwXYzXdzo2re561LHimG58LWvOU3veRzXHw==MrT8lLuD/Xa1sz/Aej49mHsx89FrbeNx7EZoKoy9xr0mFUKKN6zsFtD1bkxvz79Gtndwij/ob0wiP0VpZZyUtT1vO13E51qb27z8htKXthCxq3aIib9D/GxKhjIIHnZK9djq78jSl+djtPTy//sdhJNcHZOqhWop6U/e0+OUA8E8xZWohYTFi+s7MDSiZ0unnv6qqX7hvuGcw8v5QWt7wkJJQkRtF0te/WqerToftu2TwjveoffQ3i83XxAgAQwvpzTprIJuHn67bosm1lb2aGiNWn4elP1VBBcrqraCogsmEe3hFxJEAjHdw8vgUP9xaamytte0oWR//pR1silzvg==MIIDejCCAmKgAwIBAgIGAYSH+ql9MA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKDA1QaW5nIElkZW50aXR5MRYwFAYDVQQLDA1QaW5nIElkZW50aXR5MT8wPQYDVQQDDDZQaW5nT25lIFNTTyBDZXJ0aWZpY2F0ZSBmb3IgQWRtaW5pc3RyYXRvcnMgZW52aXJvbm1lbnQwHhcNMjIxMTE3MjM0NDIyWhcNMjMxMTE3MjM0NDIyWjB+MQswCQYDVQQGEwJVUzEWMBQGA1UECgwNUGluZyBJZGVudGl0eTEWMBQGA1UECwwNUGluZyBJZGVudGl0eTE/MD0GA1UEAww2UGluZ09uZSBTU08gQ2VydGlmaWNhdGUgZm9yIEFkbWluaXN0cmF0b3JzIGVudmlyb25tZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwSDhBJAU1hag1DB6qgsxHxiI8GnAt9K4opQGZOTihWL8/dggFXEJS1JHSRTtovP7H3Fhk8q61KabzA1CQ6NPkJpGgqhXWI7e5gu2lyGuOqj97GkEseF+soe+KvenzVmbn7pdfXP1xM0xalbTOm82p9Evx2PHu0dn6YqV7YdXTW4juop8ZCi1sFPecbT0iMzVSrn4zrqdBp52oKU12g7iv3arzs3TBarz3kkA9e/9dCTzx84y5uJOsUN1smFTJXBECB5esZbkw/v5EM8ff0iKL8lei/c6jMWPJuqRlgRgxgzNw2H3OgH+ZBEV1Or0m7FgDXZ87uACnReWYQFqDx3KNwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBcXotWQrXSWMNeQ4gwtagHfuJZ+SsNl9ptcFPQ4fSyZ/z1F8JD0uS15yFapBt77I7wknvD9Y/Q53PLNXYFlmx1p9k8AgsY9g8mXZdgxUnD1qF8sziPM2VK0+7B0ZOPHzaJube671o8THLoT9Zoha0Ts7ac/+sSJcrNxJvl5fRB4S5l30FQ/0gI/uBye0UIFIvUgOwqFjltJFeGu21CEFOsvjC5FEbuQ93xR+YprOrwy/c/HZpAKIJzOUJcOSxXRIQbI4/WgqTSVIFLbT5C1FVQRF+ZpN+hGXaz+21UNV1qCHZSyiAurIGj6z2/jWc1IpAw9kWOYcPmW/aC5CKDpLlqwSDhBJAU1hag1DB6qgsxHxiI8GnAt9K4opQGZOTihWL8/dggFXEJS1JHSRTtovP7H3Fhk8q61KabzA1CQ6NPkJpGgqhXWI7e5gu2lyGuOqj97GkEseF+soe+KvenzVmbn7pdfXP1xM0xalbTOm82p9Evx2PHu0dn6YqV7YdXTW4juop8ZCi1sFPecbT0iMzVSrn4zrqdBp52oKU12g7iv3arzs3TBarz3kkA9e/9dCTzx84y5uJOsUN1smFTJXBECB5esZbkw/v5EM8ff0iKL8lei/c6jMWPJuqRlgRgxgzNw2H3OgH+ZBEV1Or0m7FgDXZ87uACnReWYQFqDx3KNw==AQAB2d60c7f9-edd1-45e4-80f8-271d0d2055edtest_entityurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransporthttps://auth.pingone.com/fac598b8-a94e-483b-b602-af44aa9dbc972d60c7f9-edd1-45e4-80f8-271d0d2055ed -------------------------------------------------------------------------------- /test/contract/data/valid/pingone/pingone_idp_initiated_only_response_signed/idp_initiated_only_response_signed.xml: -------------------------------------------------------------------------------- 1 | https://auth.pingone.com/fac598b8-a94e-483b-b602-af44aa9dbc97pAfh8g7/w3Cl0UuU17ERQNi8Swuh7LCDpisN4DHULTaHO5UXvzwW/uVNA5H3rwGrcge3ZqzGUvCKrOGyrsO0OQ==kbh17qQ5hKbRS6BEp2JyHMg5xYyeIdwoN08swvuk4qpybD3S05KpfY+VFEmTnkyOgfUpFikBZdZ4buaKlivDbT1UAnFK6kheXiIO9/bsJRcFnZHafxBehYoMs2IiS2W7LxCpCt0iEFBFAMDzP8Tb/6VyxUIEl8/pH7pdG9AcGMwqR0sg6mSRI4unwq0sHJocc3Ec3qXRKP8TK733BHH6WoOZNwEsjhkjScu4gWe5z3MYV4BQROzKwdBdylEBdPKhCtSn9XoDsCtjBfeU+bamyTVO0TrAhmHFBkTFbiQjmrDb5ARUeujEDBehKSpCLuutOLuCA3z48cAjUunqtf+9QA==MIIDejCCAmKgAwIBAgIGAYSH+ql9MA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKDA1QaW5nIElkZW50aXR5MRYwFAYDVQQLDA1QaW5nIElkZW50aXR5MT8wPQYDVQQDDDZQaW5nT25lIFNTTyBDZXJ0aWZpY2F0ZSBmb3IgQWRtaW5pc3RyYXRvcnMgZW52aXJvbm1lbnQwHhcNMjIxMTE3MjM0NDIyWhcNMjMxMTE3MjM0NDIyWjB+MQswCQYDVQQGEwJVUzEWMBQGA1UECgwNUGluZyBJZGVudGl0eTEWMBQGA1UECwwNUGluZyBJZGVudGl0eTE/MD0GA1UEAww2UGluZ09uZSBTU08gQ2VydGlmaWNhdGUgZm9yIEFkbWluaXN0cmF0b3JzIGVudmlyb25tZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwSDhBJAU1hag1DB6qgsxHxiI8GnAt9K4opQGZOTihWL8/dggFXEJS1JHSRTtovP7H3Fhk8q61KabzA1CQ6NPkJpGgqhXWI7e5gu2lyGuOqj97GkEseF+soe+KvenzVmbn7pdfXP1xM0xalbTOm82p9Evx2PHu0dn6YqV7YdXTW4juop8ZCi1sFPecbT0iMzVSrn4zrqdBp52oKU12g7iv3arzs3TBarz3kkA9e/9dCTzx84y5uJOsUN1smFTJXBECB5esZbkw/v5EM8ff0iKL8lei/c6jMWPJuqRlgRgxgzNw2H3OgH+ZBEV1Or0m7FgDXZ87uACnReWYQFqDx3KNwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBcXotWQrXSWMNeQ4gwtagHfuJZ+SsNl9ptcFPQ4fSyZ/z1F8JD0uS15yFapBt77I7wknvD9Y/Q53PLNXYFlmx1p9k8AgsY9g8mXZdgxUnD1qF8sziPM2VK0+7B0ZOPHzaJube671o8THLoT9Zoha0Ts7ac/+sSJcrNxJvl5fRB4S5l30FQ/0gI/uBye0UIFIvUgOwqFjltJFeGu21CEFOsvjC5FEbuQ93xR+YprOrwy/c/HZpAKIJzOUJcOSxXRIQbI4/WgqTSVIFLbT5C1FVQRF+ZpN+hGXaz+21UNV1qCHZSyiAurIGj6z2/jWc1IpAw9kWOYcPmW/aC5CKDpLlqwSDhBJAU1hag1DB6qgsxHxiI8GnAt9K4opQGZOTihWL8/dggFXEJS1JHSRTtovP7H3Fhk8q61KabzA1CQ6NPkJpGgqhXWI7e5gu2lyGuOqj97GkEseF+soe+KvenzVmbn7pdfXP1xM0xalbTOm82p9Evx2PHu0dn6YqV7YdXTW4juop8ZCi1sFPecbT0iMzVSrn4zrqdBp52oKU12g7iv3arzs3TBarz3kkA9e/9dCTzx84y5uJOsUN1smFTJXBECB5esZbkw/v5EM8ff0iKL8lei/c6jMWPJuqRlgRgxgzNw2H3OgH+ZBEV1Or0m7FgDXZ87uACnReWYQFqDx3KNw==AQABhttps://auth.pingone.com/fac598b8-a94e-483b-b602-af44aa9dbc972d60c7f9-edd1-45e4-80f8-271d0d2055edtest_entityurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransporthttps://auth.pingone.com/fac598b8-a94e-483b-b602-af44aa9dbc972d60c7f9-edd1-45e4-80f8-271d0d2055ed -------------------------------------------------------------------------------- /test/contract/data/valid/google/google_groups_and_attributes/groups_and_attributes.xml: -------------------------------------------------------------------------------- 1 | https://accounts.google.com/o/saml2?idpid=C02ji0uvf8ELEuzd+w/IM4qjaWYqqpfUjsn1uqaZBY4SCXHxiJEA=OT85p+DCSREpKLdd6B1C2tO6ChUM0ZMzsudBG/rMK6Tigb4LivCgI6cxueHQLFQMQ0HGvsZ0GENZ 2 | qViFWtrLG4ZxAtA9xAa3am0fP8eo1jlOTG1CD0v1mpxCZFDkReBTL9iqnNwrtpQBCH9RBAdcxOrf 3 | woh/EDJvNLGZU+p9j6qk6DvtyZkrU9Wixs4nUQ+Ta2CbfGW4/osGTx4WhDrbmAE5XtlmxEBgakuF 4 | tz/jEniHjRJsGHdPJBI5WO3dfGrLYz/C5vclxQJ3VPZpA97Du7Q3oFc4nmVtBIw9L1RDKh3lI4G2 5 | aMBJNZjMFN8YqKKMp4E0P5F4KWGFYOwI/qbtBw==ST=California,C=US,OU=Google For Work,CN=Google,L=Mountain View,O=Google Inc.MIIDdDCCAlygAwIBAgIGAYSgi9d6MA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ 6 | bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv 7 | b2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMjIxMTIy 8 | MTgxMzQ5WhcNMjcxMTIxMTgxMzQ5WjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN 9 | TW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx 10 | CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 11 | MIIBCgKCAQEAn9dOryAKN3ejQRaO2eE64fc3gQofDk1esXp8ANnuDxfzWUXKe8gshligszaq05xN 12 | TPMJV9isb124nWsVqqPzR7z2mjg/3wR/U2vELLTYg0en/ToPXMax0+3rFM3ClEeWAiwErnTW9M48 13 | 2zOwOJICrdkM9JQ71QuU1Y6qYfHv0v8ZGWXOEmwuUeG3FYcgfGdTfndgbhxOYTJ8WR6cvoaO3CN/ 14 | qvcy5XAZnj1UswfV+l7bZhYBjkbybajc9VTdnbqp5vE07qdbGWDEHFNkoteenr61JWLAIi8hDLME 15 | WDXU3umibNjq+FSRxIUErDytczChuGyUpKb2B0uIlzn0sqrpywIDAQABMA0GCSqGSIb3DQEBCwUA 16 | A4IBAQAlF/7umzkl90jgRI4gpO4sajw1v63hrhnu+OfXiv1T8OZuYlh9qqU+XWFwPtW19i2Vlev5 17 | DVPHHDukpjJcnVz14cnnqUmt0CrspOQm4+y4dKRimKAzof964/BIvcVIz/m7UEQiQ80EAWUgvjwa 18 | /WIl1fu2bIbS4AykJ5uw6FyqGVMrrYg38CfaW9655w/8ihSgJvj6qioBYe90SAzwTomOeV5msyaw 19 | xUQYF5InCLYOejinkOlsFrhqAI71t8Uy+VpqxwtsN1G3vfDvTY1gG7ju8n1YFwvVm9Z6BKpD0hbv 20 | ToEpHDFcUzUDC3qAjdvBPDRqyfAeNUBJBsYrARXNqSyrhttps://accounts.google.com/o/saml2?idpid=C02ji0uvfmgerber@stytchdev.comentity_idMaxGerberGroup 1Group 2urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified -------------------------------------------------------------------------------- /test/contract/data/valid/pingone/pingone_idp_initiated_only_response_signed/idp_initiated_only_response_signed.formatted.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://auth.pingone.com/fac598b8-a94e-483b-b602-af44aa9dbc97 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | pAfh8g7/w3Cl0UuU17ERQNi8Swuh7LCDpisN4DHULTaHO5UXvzwW/uVNA5H3rwGrcge3ZqzGUvCKrOGyrsO0OQ== 15 | 16 | 17 | kbh17qQ5hKbRS6BEp2JyHMg5xYyeIdwoN08swvuk4qpybD3S05KpfY+VFEmTnkyOgfUpFikBZdZ4buaKlivDbT1UAnFK6kheXiIO9/bsJRcFnZHafxBehYoMs2IiS2W7LxCpCt0iEFBFAMDzP8Tb/6VyxUIEl8/pH7pdG9AcGMwqR0sg6mSRI4unwq0sHJocc3Ec3qXRKP8TK733BHH6WoOZNwEsjhkjScu4gWe5z3MYV4BQROzKwdBdylEBdPKhCtSn9XoDsCtjBfeU+bamyTVO0TrAhmHFBkTFbiQjmrDb5ARUeujEDBehKSpCLuutOLuCA3z48cAjUunqtf+9QA== 18 | 19 | 20 | MIIDejCCAmKgAwIBAgIGAYSH+ql9MA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKDA1QaW5nIElkZW50aXR5MRYwFAYDVQQLDA1QaW5nIElkZW50aXR5MT8wPQYDVQQDDDZQaW5nT25lIFNTTyBDZXJ0aWZpY2F0ZSBmb3IgQWRtaW5pc3RyYXRvcnMgZW52aXJvbm1lbnQwHhcNMjIxMTE3MjM0NDIyWhcNMjMxMTE3MjM0NDIyWjB+MQswCQYDVQQGEwJVUzEWMBQGA1UECgwNUGluZyBJZGVudGl0eTEWMBQGA1UECwwNUGluZyBJZGVudGl0eTE/MD0GA1UEAww2UGluZ09uZSBTU08gQ2VydGlmaWNhdGUgZm9yIEFkbWluaXN0cmF0b3JzIGVudmlyb25tZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwSDhBJAU1hag1DB6qgsxHxiI8GnAt9K4opQGZOTihWL8/dggFXEJS1JHSRTtovP7H3Fhk8q61KabzA1CQ6NPkJpGgqhXWI7e5gu2lyGuOqj97GkEseF+soe+KvenzVmbn7pdfXP1xM0xalbTOm82p9Evx2PHu0dn6YqV7YdXTW4juop8ZCi1sFPecbT0iMzVSrn4zrqdBp52oKU12g7iv3arzs3TBarz3kkA9e/9dCTzx84y5uJOsUN1smFTJXBECB5esZbkw/v5EM8ff0iKL8lei/c6jMWPJuqRlgRgxgzNw2H3OgH+ZBEV1Or0m7FgDXZ87uACnReWYQFqDx3KNwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBcXotWQrXSWMNeQ4gwtagHfuJZ+SsNl9ptcFPQ4fSyZ/z1F8JD0uS15yFapBt77I7wknvD9Y/Q53PLNXYFlmx1p9k8AgsY9g8mXZdgxUnD1qF8sziPM2VK0+7B0ZOPHzaJube671o8THLoT9Zoha0Ts7ac/+sSJcrNxJvl5fRB4S5l30FQ/0gI/uBye0UIFIvUgOwqFjltJFeGu21CEFOsvjC5FEbuQ93xR+YprOrwy/c/HZpAKIJzOUJcOSxXRIQbI4/WgqTSVIFLbT5C1FVQRF+ZpN+hGXaz+21UNV1qCHZSyiAurIGj6z2/jWc1IpAw9kWOYcPmW/aC5CKDpLlq 21 | 22 | 23 | 24 | wSDhBJAU1hag1DB6qgsxHxiI8GnAt9K4opQGZOTihWL8/dggFXEJS1JHSRTtovP7H3Fhk8q61KabzA1CQ6NPkJpGgqhXWI7e5gu2lyGuOqj97GkEseF+soe+KvenzVmbn7pdfXP1xM0xalbTOm82p9Evx2PHu0dn6YqV7YdXTW4juop8ZCi1sFPecbT0iMzVSrn4zrqdBp52oKU12g7iv3arzs3TBarz3kkA9e/9dCTzx84y5uJOsUN1smFTJXBECB5esZbkw/v5EM8ff0iKL8lei/c6jMWPJuqRlgRgxgzNw2H3OgH+ZBEV1Or0m7FgDXZ87uACnReWYQFqDx3KNw== 25 | AQAB 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | https://auth.pingone.com/fac598b8-a94e-483b-b602-af44aa9dbc97 35 | 36 | 2d60c7f9-edd1-45e4-80f8-271d0d2055ed 37 | 38 | 39 | 40 | 41 | 42 | 43 | test_entity 44 | 45 | 46 | 47 | 48 | urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport 49 | https://auth.pingone.com/fac598b8-a94e-483b-b602-af44aa9dbc97 50 | 51 | 52 | 53 | 54 | 2d60c7f9-edd1-45e4-80f8-271d0d2055ed 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /test/contract/data/valid/pingone/pingone_idp_initiated_only_assertion_signed/idp_initiated_only_assertion_signed.formatted.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://auth.pingone.com/fac598b8-a94e-483b-b602-af44aa9dbc97 4 | 5 | 6 | 7 | 8 | https://auth.pingone.com/fac598b8-a94e-483b-b602-af44aa9dbc97 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | oq3xUqgdWJ37hgg1UcHNY9Mcs35vNV6JQKviwpDMBqHPo6JIOTcvwXYzXdzo2re561LHimG58LWvOU3veRzXHw== 20 | 21 | 22 | MrT8lLuD/Xa1sz/Aej49mHsx89FrbeNx7EZoKoy9xr0mFUKKN6zsFtD1bkxvz79Gtndwij/ob0wiP0VpZZyUtT1vO13E51qb27z8htKXthCxq3aIib9D/GxKhjIIHnZK9djq78jSl+djtPTy//sdhJNcHZOqhWop6U/e0+OUA8E8xZWohYTFi+s7MDSiZ0unnv6qqX7hvuGcw8v5QWt7wkJJQkRtF0te/WqerToftu2TwjveoffQ3i83XxAgAQwvpzTprIJuHn67bosm1lb2aGiNWn4elP1VBBcrqraCogsmEe3hFxJEAjHdw8vgUP9xaamytte0oWR//pR1silzvg== 23 | 24 | 25 | MIIDejCCAmKgAwIBAgIGAYSH+ql9MA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKDA1QaW5nIElkZW50aXR5MRYwFAYDVQQLDA1QaW5nIElkZW50aXR5MT8wPQYDVQQDDDZQaW5nT25lIFNTTyBDZXJ0aWZpY2F0ZSBmb3IgQWRtaW5pc3RyYXRvcnMgZW52aXJvbm1lbnQwHhcNMjIxMTE3MjM0NDIyWhcNMjMxMTE3MjM0NDIyWjB+MQswCQYDVQQGEwJVUzEWMBQGA1UECgwNUGluZyBJZGVudGl0eTEWMBQGA1UECwwNUGluZyBJZGVudGl0eTE/MD0GA1UEAww2UGluZ09uZSBTU08gQ2VydGlmaWNhdGUgZm9yIEFkbWluaXN0cmF0b3JzIGVudmlyb25tZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwSDhBJAU1hag1DB6qgsxHxiI8GnAt9K4opQGZOTihWL8/dggFXEJS1JHSRTtovP7H3Fhk8q61KabzA1CQ6NPkJpGgqhXWI7e5gu2lyGuOqj97GkEseF+soe+KvenzVmbn7pdfXP1xM0xalbTOm82p9Evx2PHu0dn6YqV7YdXTW4juop8ZCi1sFPecbT0iMzVSrn4zrqdBp52oKU12g7iv3arzs3TBarz3kkA9e/9dCTzx84y5uJOsUN1smFTJXBECB5esZbkw/v5EM8ff0iKL8lei/c6jMWPJuqRlgRgxgzNw2H3OgH+ZBEV1Or0m7FgDXZ87uACnReWYQFqDx3KNwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBcXotWQrXSWMNeQ4gwtagHfuJZ+SsNl9ptcFPQ4fSyZ/z1F8JD0uS15yFapBt77I7wknvD9Y/Q53PLNXYFlmx1p9k8AgsY9g8mXZdgxUnD1qF8sziPM2VK0+7B0ZOPHzaJube671o8THLoT9Zoha0Ts7ac/+sSJcrNxJvl5fRB4S5l30FQ/0gI/uBye0UIFIvUgOwqFjltJFeGu21CEFOsvjC5FEbuQ93xR+YprOrwy/c/HZpAKIJzOUJcOSxXRIQbI4/WgqTSVIFLbT5C1FVQRF+ZpN+hGXaz+21UNV1qCHZSyiAurIGj6z2/jWc1IpAw9kWOYcPmW/aC5CKDpLlq 26 | 27 | 28 | 29 | wSDhBJAU1hag1DB6qgsxHxiI8GnAt9K4opQGZOTihWL8/dggFXEJS1JHSRTtovP7H3Fhk8q61KabzA1CQ6NPkJpGgqhXWI7e5gu2lyGuOqj97GkEseF+soe+KvenzVmbn7pdfXP1xM0xalbTOm82p9Evx2PHu0dn6YqV7YdXTW4juop8ZCi1sFPecbT0iMzVSrn4zrqdBp52oKU12g7iv3arzs3TBarz3kkA9e/9dCTzx84y5uJOsUN1smFTJXBECB5esZbkw/v5EM8ff0iKL8lei/c6jMWPJuqRlgRgxgzNw2H3OgH+ZBEV1Or0m7FgDXZ87uACnReWYQFqDx3KNw== 30 | AQAB 31 | 32 | 33 | 34 | 35 | 36 | 2d60c7f9-edd1-45e4-80f8-271d0d2055ed 37 | 38 | 39 | 40 | 41 | 42 | 43 | test_entity 44 | 45 | 46 | 47 | 48 | urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport 49 | https://auth.pingone.com/fac598b8-a94e-483b-b602-af44aa9dbc97 50 | 51 | 52 | 53 | 54 | 2d60c7f9-edd1-45e4-80f8-271d0d2055ed 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /CONTRACT_TESTING.md: -------------------------------------------------------------------------------- 1 | # Contract Testing in SAML Shield 2 | 3 | This document explains the contract testing pattern used in SAML Shield 4 | 5 | ## Overview 6 | 7 | Contract testing provides comprehensive, data-driven test coverage by using JSON test case definitions that specify inputs, expected outputs, and test metadata. This approach allows for: 8 | 9 | - **Systematic testing** of various SAML scenarios and edge cases 10 | - **Easy addition** of new test cases without code changes 11 | - **Clear separation** between test data and test execution logic 12 | - **Reproducible results** with embedded test data 13 | 14 | ## Test Structure 15 | 16 | ### Test Case Format 17 | 18 | Each test case is defined in a `.test.json` file with the following structure: 19 | 20 | ```json 21 | { 22 | "name": "Descriptive test name", 23 | "description": "Detailed description of what this test validates", 24 | "input": { 25 | "response_xml": "!embed:base64:file://saml_response.xml" 26 | }, 27 | "mockTime": "2022-11-20T12:00:00.000Z", 28 | "shouldSucceed": true, 29 | "expectedError": "Expected error message (for failing tests)", 30 | "expectedErrorCode": "EXPECTED_ERROR_CODE", 31 | "only": false, 32 | "skip": false 33 | } 34 | ``` 35 | 36 | ### Field Descriptions 37 | 38 | - `name`: Human-readable test name (optional, defaults to file path) 39 | - `description`: Detailed description of the test case 40 | - `input`: Input parameters passed to the validation function 41 | - `mockTime`: ISO 8601 timestamp to mock current time for timestamp validation tests 42 | - `shouldSucceed`: Whether the test should pass (true) or fail (false) 43 | - `expectedError`: Substring that should appear in error message (for failing tests) 44 | - `expectedErrorCode`: Expected error code for failing tests 45 | - `only`: Run only this test (for debugging) 46 | - `skip`: Skip this test 47 | 48 | ### Embed Directives 49 | 50 | Test cases support special directives for embedding external content: 51 | 52 | - `!embed:file://path.xml` - Embed file contents as string 53 | - `!embed:base64:file://path.xml` - Embed file contents as base64 54 | - `!embed:base64:string_content` - Encode inline string as base64 55 | 56 | Example: 57 | 58 | ```json 59 | { 60 | "input": { 61 | "response_xml": "!embed:base64:file://valid_saml_response.xml" 62 | } 63 | } 64 | ``` 65 | 66 | ## Directory Structure 67 | 68 | ``` 69 | test/contract/ 70 | ├── data/ 71 | │ ├── valid/ 72 | │ │ ├── valid_saml_response.test.json 73 | │ │ └── sp_initiated.xml 74 | │ ├── error_cases/ 75 | │ │ ├── empty_response.test.json 76 | │ │ ├── invalid_xml.test.json 77 | │ │ ├── missing_signature.test.json 78 | │ │ └── missing_signature.xml 79 | │ └── vulnerabilities/ 80 | │ ├── xml_comment_injection.test.json 81 | │ ├── xml_comment_injection.xml 82 | │ ├── multiple_signedinfo.test.json 83 | │ └── multiple_signedinfo.xml 84 | ├── loader.ts 85 | └── runner.test.ts 86 | ``` 87 | 88 | ## Test Categories 89 | 90 | ### Valid Cases 91 | 92 | Test cases that should pass validation: 93 | 94 | - Properly signed SAML responses 95 | - Responses with correct structure 96 | - Responses without security vulnerabilities 97 | 98 | ### Error Cases 99 | 100 | Test cases that should fail due to structural issues: 101 | 102 | - Empty or missing response data 103 | - Invalid XML structure 104 | - Missing required signatures 105 | - Malformed SAML responses 106 | 107 | ### Vulnerabilities 108 | 109 | Test cases that should fail due to security vulnerabilities: 110 | 111 | - XML comment injection (CVE-2017-11428 family) 112 | - Multiple SignedInfo elements 113 | - Processing instruction injection 114 | - External entity references 115 | - Timestamp validation (replay attack prevention) 116 | 117 | ## Running Contract Tests 118 | 119 | ```bash 120 | # Run all tests 121 | yarn test 122 | ``` 123 | 124 | ## Adding New Test Cases 125 | 126 | 1. **Create test data**: Add XML files to the appropriate subdirectory 127 | 2. **Create test definition**: Add a `.test.json` file with test metadata 128 | 3. **Add mockTime if needed**: For SAML XML with validity timestamps, add `mockTime` field set to a time within the XML's validity window to prevent timestamp validation failures 129 | 4. **Run tests**: Use `yarn test` to validate 130 | 131 | Example new test case: 132 | 133 | ```json 134 | { 135 | "name": "New Security Vulnerability", 136 | "description": "Should detect and block responses with XXE attacks", 137 | "input": { 138 | "response_xml": "!embed:base64:file://xxe_attack.xml" 139 | }, 140 | "shouldSucceed": false, 141 | "expectedError": "External Entities are forbidden", 142 | "expectedErrorCode": "XML_VALIDATION_ERROR" 143 | } 144 | ``` 145 | 146 | ## Benefits 147 | 148 | 1. **Comprehensive Coverage**: Easy to add test cases for new vulnerabilities and edge cases 149 | 2. **Maintainability**: Test logic is separated from test data 150 | 3. **Documentation**: Test cases serve as documentation of expected behavior 151 | 4. **Regression Prevention**: Ensures new changes don't break existing security protections 152 | 5. **Real-world Data**: Uses actual SAML responses from various identity providers 153 | 154 | ## Security Focus 155 | 156 | The contract tests particularly focus on: 157 | 158 | - **XML parsing vulnerabilities** (XXE, billion laughs, quadratic blowup) 159 | - **Signature validation bypasses** (comment injection, multiple SignedInfo) 160 | - **Canonicalization attacks** (processing instructions - always blocked) 161 | - **Structural validation** (missing elements, invalid formats) 162 | 163 | This comprehensive testing approach ensures that SAML Shield maintains the same security-first approach as the original Stytch implementation. 164 | -------------------------------------------------------------------------------- /test/contract/data/valid/google/google_groups_and_attributes/groups_and_attributes.formatted.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://accounts.google.com/o/saml2?idpid=C02ji0uvf 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 8ELEuzd+w/IM4qjaWYqqpfUjsn1uqaZBY4SCXHxiJEA= 15 | 16 | 17 | OT85p+DCSREpKLdd6B1C2tO6ChUM0ZMzsudBG/rMK6Tigb4LivCgI6cxueHQLFQMQ0HGvsZ0GENZ 18 | qViFWtrLG4ZxAtA9xAa3am0fP8eo1jlOTG1CD0v1mpxCZFDkReBTL9iqnNwrtpQBCH9RBAdcxOrf 19 | woh/EDJvNLGZU+p9j6qk6DvtyZkrU9Wixs4nUQ+Ta2CbfGW4/osGTx4WhDrbmAE5XtlmxEBgakuF 20 | tz/jEniHjRJsGHdPJBI5WO3dfGrLYz/C5vclxQJ3VPZpA97Du7Q3oFc4nmVtBIw9L1RDKh3lI4G2 21 | aMBJNZjMFN8YqKKMp4E0P5F4KWGFYOwI/qbtBw== 22 | 23 | 24 | ST=California,C=US,OU=Google For Work,CN=Google,L=Mountain View,O=Google Inc. 25 | MIIDdDCCAlygAwIBAgIGAYSgi9d6MA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ 26 | bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv 27 | b2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMjIxMTIy 28 | MTgxMzQ5WhcNMjcxMTIxMTgxMzQ5WjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN 29 | TW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx 30 | CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 31 | MIIBCgKCAQEAn9dOryAKN3ejQRaO2eE64fc3gQofDk1esXp8ANnuDxfzWUXKe8gshligszaq05xN 32 | TPMJV9isb124nWsVqqPzR7z2mjg/3wR/U2vELLTYg0en/ToPXMax0+3rFM3ClEeWAiwErnTW9M48 33 | 2zOwOJICrdkM9JQ71QuU1Y6qYfHv0v8ZGWXOEmwuUeG3FYcgfGdTfndgbhxOYTJ8WR6cvoaO3CN/ 34 | qvcy5XAZnj1UswfV+l7bZhYBjkbybajc9VTdnbqp5vE07qdbGWDEHFNkoteenr61JWLAIi8hDLME 35 | WDXU3umibNjq+FSRxIUErDytczChuGyUpKb2B0uIlzn0sqrpywIDAQABMA0GCSqGSIb3DQEBCwUA 36 | A4IBAQAlF/7umzkl90jgRI4gpO4sajw1v63hrhnu+OfXiv1T8OZuYlh9qqU+XWFwPtW19i2Vlev5 37 | DVPHHDukpjJcnVz14cnnqUmt0CrspOQm4+y4dKRimKAzof964/BIvcVIz/m7UEQiQ80EAWUgvjwa 38 | /WIl1fu2bIbS4AykJ5uw6FyqGVMrrYg38CfaW9655w/8ihSgJvj6qioBYe90SAzwTomOeV5msyaw 39 | xUQYF5InCLYOejinkOlsFrhqAI71t8Uy+VpqxwtsN1G3vfDvTY1gG7ju8n1YFwvVm9Z6BKpD0hbv 40 | ToEpHDFcUzUDC3qAjdvBPDRqyfAeNUBJBsYrARXNqSyr 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | https://accounts.google.com/o/saml2?idpid=C02ji0uvf 49 | 50 | mgerber@stytchdev.com 51 | 52 | 53 | 54 | 55 | 56 | 57 | entity_id 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | Max 66 | 67 | 68 | Gerber 69 | 70 | 71 | Group 1 72 | Group 2 73 | 74 | 75 | 76 | 77 | urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /test/contract/data/valid/keycloak_idp_initiated/keycloak_idp_initiated_response.xml: -------------------------------------------------------------------------------- 1 | http://localhost:8080/realms/myrealmlkcEQEMb5jywL/70+L0mnaxytUI7dngzCXtZ0EV6bQ4=lddbzkUXS2LblEb0mLPZigwh9blRPy68s7w9fOkBV6K569Q+PEY4a5NzGD5rQ96R5IZ0EGYXdL+7 2 | Ibj3mNP75x7u0I84nkUaTRrJjcyU3lVQV+GtXdqv4NfDWJmhW/Gpy87v6R6UD2e7j9lYQIoQ6XJd 3 | s8gN8UzbY0laO1cLSUtgyfkXxpqbErDJPsY+P3CIBKm2TbpHzZZ+dH/pRK5ToEitrmKBf7O1TgrD 4 | lPTFSO2MRjGpsioogBpFLHNgw60xpUfyB3DV0N3EIs0OJw9jW6QyJ6ZCLIbEymjAPq2EouGf2Huf 5 | kd6P/5CD12iOMzdgBE9PE1cR1yy5DxALgJc4fA==UGT_pMZHHOUEDAOs9ZNAO-sbGTuccrAYe_J_bB3Wvu0MIICnTCCAYUCBgGEoHzJsjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTIy 6 | MTEyMjE3NTU0M1oXDTMyMTEyMjE3NTcyM1owEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZI 7 | hvcNAQEBBQADggEPADCCAQoCggEBAKx5yPzqGd+b+AZNRI07Q20NUtmK3ra1nqj2RanRJu+QsyKM 8 | IcKZAl9Etm5smwBhmH7e+II9aI2PpTtICEDRiI3n3QEONWL9ye6tbZIcR/8Ks2OTQa4EQsSTm++O 9 | BjnEn8tir5S4CgWEBlYkLg0TMNMr+6qIa9p239ttQtdllQ4OH04AiewxJSlQiPrBTmMe/5IWzu6F 10 | aILExs+Bq3BApP/lIrMfCLm6oOdNSjRT4xa3K9cC+zvddvlqUMzEqgzrMGUq2C/u6J5HhDrgDa6V 11 | CH21nVX5CzfXijN2OF/DDvBJoOFpqAtLjrwTx6DYGIYRKjpEygwi8FwAgSu+T4mQj48CAwEAATAN 12 | BgkqhkiG9w0BAQsFAAOCAQEAb2jagOdRskn68bry9MLfhcI1oLusaNaVY26uVV6H1qpsm0C+7X8G 13 | UQV8Dt8+IEYUNnbSqfQ9NqpUy+tPTqjuoagkBxpnBvy3TMcyKa1YF6xHP951KQ9fsTnqBH4bDncB 14 | pVK4cAvTbHl4ZUNV5o2+IKLVjxTAwfammw9gLzK2nr86jNlhflAl4IZX5M1vVtiFvAFPDachHVjd 15 | m06VHzYOi17yXCIDpKhzXZ7WK0AEG41XxoBDs2qRGSJPtKJiW8LDHrPfk0et9F1y0pvRlbf4IhVN 16 | kUdTSUNoBXp2LDvnrtJku6fRRxO2v04ZDhskW2k6bfPA55Dzyqdoo0b3TJMSpw==rHnI/OoZ35v4Bk1EjTtDbQ1S2YretrWeqPZFqdEm75CzIowhwpkCX0S2bmybAGGYft74gj1ojY+l 17 | O0gIQNGIjefdAQ41Yv3J7q1tkhxH/wqzY5NBrgRCxJOb744GOcSfy2KvlLgKBYQGViQuDRMw0yv7 18 | qohr2nbf221C12WVDg4fTgCJ7DElKVCI+sFOYx7/khbO7oVogsTGz4GrcECk/+Uisx8Iubqg501K 19 | NFPjFrcr1wL7O912+WpQzMSqDOswZSrYL+7onkeEOuANrpUIfbWdVfkLN9eKM3Y4X8MO8Emg4Wmo 20 | C0uOvBPHoNgYhhEqOkTKDCLwXACBK75PiZCPjw==AQABhttp://localhost:8080/realms/myrealmmyusertest-samlurn:oasis:names:tc:SAML:2.0:ac:classes:unspecifiedoffline_accessuma_authorizationview-profiledefault-roles-myrealmmanage-accountmanage-account-links --------------------------------------------------------------------------------