├── 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
--------------------------------------------------------------------------------