{
  "name": "Open Autofill Safety Corpus",
  "version": "1.0.0",
  "license": "AGPL-3.0-or-later",
  "licenseUrl": "https://spdx.org/licenses/AGPL-3.0-or-later.html",
  "limitations": [
    "All markup is synthetic and deliberately small; it is not copied from vendor pages.",
    "The corpus does not exercise a live browser, browser-extension permissions, cross-browser behavior, network requests, form submission, or real secrets.",
    "Passing the corpus is not a security audit, a compatibility guarantee, or evidence of a measured false-positive rate.",
    "Version 1 does not cover cross-origin frames, closed shadow roots, localization heuristics, visual layout, or hostile page scripts."
  ],
  "fixtures": [
    {
      "id": "password-only-login",
      "title": "Password-only step in a multi-step login",
      "category": "password-only-login",
      "provenance": "synthetic",
      "phases": [
        {
          "id": "01-current-password-step",
          "description": "A current-password field remains a login target when the username was collected on an earlier page.",
          "document": {
            "language": "en",
            "otpCodeLength": 6,
            "url": "https://synthetic.invalid/sign-in/challenge",
            "bodyHtml": "\n              <main>\n                <h1>Welcome back</h1>\n                <form id=\"password-step\">\n                  <input\n                    id=\"login-password\"\n                    type=\"password\"\n                    autocomplete=\"current-password\"\n                  />\n                  <button type=\"submit\">Continue</button>\n                </form>\n              </main>\n            "
          },
          "expected": {
            "passwordKind": "login",
            "storedPasswordTargetId": "login-password",
            "otpKind": "none",
            "otpTargetIds": []
          }
        }
      ]
    },
    {
      "id": "signup-no-fill",
      "title": "Signup fields are not stored-password targets",
      "category": "signup-no-fill",
      "provenance": "synthetic",
      "phases": [
        {
          "id": "01-new-password-pair",
          "description": "Two explicit new-password fields must not receive an existing stored password.",
          "document": {
            "language": "en",
            "otpCodeLength": 6,
            "url": "https://synthetic.invalid/join",
            "bodyHtml": "\n              <form id=\"signup-form\">\n                <input id=\"signup-email\" type=\"email\" autocomplete=\"username\" />\n                <input id=\"signup-password\" type=\"password\" autocomplete=\"new-password\" />\n                <input id=\"signup-password-confirmation\" type=\"password\" autocomplete=\"new-password\" />\n                <button type=\"submit\">Create account</button>\n              </form>\n            "
          },
          "expected": {
            "passwordKind": "signup",
            "storedPasswordTargetId": null,
            "otpKind": "none",
            "otpTargetIds": []
          }
        }
      ]
    },
    {
      "id": "change-password-no-fill",
      "title": "Change-password forms are not login autofill targets",
      "category": "change-password-no-fill",
      "provenance": "synthetic",
      "phases": [
        {
          "id": "01-current-and-new-passwords",
          "description": "A settings form can identify the existing-password field without authorizing automatic credential fill.",
          "document": {
            "language": "en",
            "otpCodeLength": 6,
            "url": "https://synthetic.invalid/account/security",
            "bodyHtml": "\n              <main>\n                <h1>Change password</h1>\n                <form id=\"change-password-form\">\n                  <input id=\"old-password\" type=\"password\" autocomplete=\"current-password\" />\n                  <input id=\"new-password\" type=\"password\" autocomplete=\"new-password\" />\n                  <input id=\"new-password-confirmation\" type=\"password\" autocomplete=\"new-password\" />\n                  <button type=\"submit\">Update password</button>\n                </form>\n              </main>\n            "
          },
          "expected": {
            "passwordKind": "change-password",
            "storedPasswordTargetId": null,
            "otpKind": "none",
            "otpTargetIds": []
          }
        }
      ]
    },
    {
      "id": "otp-versus-code-traps",
      "title": "OTP fields are separated from recovery and payment codes",
      "category": "otp-versus-code-traps",
      "provenance": "synthetic",
      "phases": [
        {
          "id": "01-single-otp",
          "description": "An explicit one-time-code field is a TOTP target.",
          "document": {
            "language": "en",
            "otpCodeLength": 6,
            "url": "https://synthetic.invalid/sign-in/verify",
            "bodyHtml": "\n              <form id=\"verification-form\">\n                <label for=\"verification-code\">Authentication code</label>\n                <input\n                  id=\"verification-code\"\n                  name=\"mfa_code\"\n                  type=\"text\"\n                  inputmode=\"numeric\"\n                  maxlength=\"6\"\n                  autocomplete=\"one-time-code\"\n                />\n              </form>\n            "
          },
          "expected": {
            "passwordKind": "none",
            "storedPasswordTargetId": null,
            "otpKind": "single",
            "otpTargetIds": [
              "verification-code"
            ]
          }
        },
        {
          "id": "02-segmented-otp",
          "description": "A corroborated six-box verification widget is an ordered TOTP target.",
          "document": {
            "language": "en",
            "otpCodeLength": 6,
            "url": "https://synthetic.invalid/sign-in/verify-boxes",
            "bodyHtml": "\n              <div id=\"otp-code-widget\">\n                <input id=\"otp-1\" class=\"otp-box\" type=\"text\" inputmode=\"numeric\" maxlength=\"1\" aria-label=\"Digit 1\" />\n                <input id=\"otp-2\" class=\"otp-box\" type=\"text\" inputmode=\"numeric\" maxlength=\"1\" aria-label=\"Digit 2\" />\n                <input id=\"otp-3\" class=\"otp-box\" type=\"text\" inputmode=\"numeric\" maxlength=\"1\" aria-label=\"Digit 3\" />\n                <input id=\"otp-4\" class=\"otp-box\" type=\"text\" inputmode=\"numeric\" maxlength=\"1\" aria-label=\"Digit 4\" />\n                <input id=\"otp-5\" class=\"otp-box\" type=\"text\" inputmode=\"numeric\" maxlength=\"1\" aria-label=\"Digit 5\" />\n                <input id=\"otp-6\" class=\"otp-box\" type=\"text\" inputmode=\"numeric\" maxlength=\"1\" aria-label=\"Digit 6\" />\n              </div>\n            "
          },
          "expected": {
            "passwordKind": "none",
            "storedPasswordTargetId": null,
            "otpKind": "segmented",
            "otpTargetIds": [
              "otp-1",
              "otp-2",
              "otp-3",
              "otp-4",
              "otp-5",
              "otp-6"
            ]
          }
        },
        {
          "id": "03-recovery-code-trap",
          "description": "A recovery-code field stays excluded even when misleading markup advertises one-time-code.",
          "document": {
            "language": "en",
            "otpCodeLength": 6,
            "url": "https://synthetic.invalid/account/recovery",
            "bodyHtml": "\n              <form id=\"recovery-form\">\n                <label for=\"recovery-code\">Recovery code</label>\n                <input\n                  id=\"recovery-code\"\n                  name=\"recovery_code\"\n                  type=\"text\"\n                  inputmode=\"numeric\"\n                  maxlength=\"10\"\n                  autocomplete=\"one-time-code\"\n                />\n              </form>\n            "
          },
          "expected": {
            "passwordKind": "none",
            "storedPasswordTargetId": null,
            "otpKind": "none",
            "otpTargetIds": []
          }
        },
        {
          "id": "04-card-security-code-trap",
          "description": "A card verification value is payment data, not a one-time authentication code.",
          "document": {
            "language": "en",
            "otpCodeLength": 6,
            "url": "https://synthetic.invalid/checkout",
            "bodyHtml": "\n              <form id=\"payment-form\">\n                <label for=\"card-security-code\">Card security code</label>\n                <input\n                  id=\"card-security-code\"\n                  name=\"cvv\"\n                  type=\"text\"\n                  inputmode=\"numeric\"\n                  maxlength=\"3\"\n                  autocomplete=\"cc-csc\"\n                />\n              </form>\n            "
          },
          "expected": {
            "passwordKind": "none",
            "storedPasswordTargetId": null,
            "otpKind": "none",
            "otpTargetIds": []
          }
        }
      ]
    },
    {
      "id": "ambiguity",
      "title": "Ambiguous targets cause abstention",
      "category": "ambiguity",
      "provenance": "synthetic",
      "phases": [
        {
          "id": "01-unannotated-password-form",
          "description": "An unannotated password form without decisive context is not treated as a login target.",
          "document": {
            "language": "en",
            "otpCodeLength": 6,
            "url": "https://synthetic.invalid/access",
            "bodyHtml": "\n              <form id=\"ambiguous-password-form\">\n                <input id=\"account-alias\" type=\"text\" name=\"account-alias\" />\n                <input id=\"ambiguous-password\" type=\"password\" />\n                <button type=\"submit\">Proceed</button>\n              </form>\n            "
          },
          "expected": {
            "passwordKind": "unknown",
            "storedPasswordTargetId": null,
            "otpKind": "none",
            "otpTargetIds": []
          }
        },
        {
          "id": "02-two-equally-plausible-otp-fields",
          "description": "Two equally scored OTP candidates are not resolved by DOM order.",
          "document": {
            "language": "en",
            "otpCodeLength": 6,
            "url": "https://synthetic.invalid/verify-choice",
            "bodyHtml": "\n              <form id=\"ambiguous-code-form\">\n                <input id=\"verification-left\" name=\"mfa_code_left\" type=\"text\" inputmode=\"numeric\" maxlength=\"6\" />\n                <input id=\"verification-right\" name=\"mfa_code_right\" type=\"text\" inputmode=\"numeric\" maxlength=\"6\" />\n              </form>\n            "
          },
          "expected": {
            "passwordKind": "none",
            "storedPasswordTargetId": null,
            "otpKind": "none",
            "otpTargetIds": []
          }
        }
      ]
    },
    {
      "id": "dynamic-replacement",
      "title": "Multi-step authentication replaces its inputs",
      "category": "dynamic-replacement",
      "provenance": "synthetic",
      "phases": [
        {
          "id": "01-identifier-step",
          "description": "The first DOM has an identifier and no secret target.",
          "document": {
            "language": "en",
            "otpCodeLength": 6,
            "url": "https://synthetic.invalid/session/identifier",
            "bodyHtml": "\n              <form id=\"session-step\">\n                <input id=\"session-identifier\" type=\"email\" autocomplete=\"username\" />\n                <button type=\"submit\">Continue</button>\n              </form>\n            "
          },
          "expected": {
            "passwordKind": "none",
            "storedPasswordTargetId": null,
            "otpKind": "none",
            "otpTargetIds": []
          }
        },
        {
          "id": "02-password-step",
          "description": "After replacement, the newly queried current-password node is the login target.",
          "document": {
            "language": "en",
            "otpCodeLength": 6,
            "url": "https://synthetic.invalid/session/password",
            "bodyHtml": "\n              <form id=\"session-step\">\n                <input id=\"session-password\" type=\"password\" autocomplete=\"current-password\" />\n                <button type=\"submit\">Continue</button>\n              </form>\n            "
          },
          "expected": {
            "passwordKind": "login",
            "storedPasswordTargetId": "session-password",
            "otpKind": "none",
            "otpTargetIds": []
          }
        },
        {
          "id": "03-otp-step",
          "description": "A second replacement removes the password target and introduces a fresh OTP target.",
          "document": {
            "language": "en",
            "otpCodeLength": 6,
            "url": "https://synthetic.invalid/session/otp",
            "bodyHtml": "\n              <form id=\"session-step\">\n                <input\n                  id=\"session-otp\"\n                  name=\"otp_code\"\n                  type=\"text\"\n                  inputmode=\"numeric\"\n                  maxlength=\"6\"\n                  autocomplete=\"one-time-code\"\n                />\n                <button type=\"submit\">Verify</button>\n              </form>\n            "
          },
          "expected": {
            "passwordKind": "none",
            "storedPasswordTargetId": null,
            "otpKind": "single",
            "otpTargetIds": [
              "session-otp"
            ]
          }
        }
      ]
    }
  ]
}
