{"record":{"id":"bffe9a13d013db79","repo":"zaproxy/zaproxy","slug":"unexpected-state-state","errorCode":null,"errorMessage":"Unexpected state: {state}","messagePattern":"Unexpected state: (.+?)","errorType":"exception","errorClass":"AuthenticationException","httpStatus":null,"severity":"error","filePath":"zap/src/main/java/org/zaproxy/zap/network/ZapNTLMScheme.java","lineNumber":167,"sourceCode":"        }\n        String response = null;\n        if (this.state == State.FAILED) {\n            throw new AuthenticationException(\"NTLM authentication failed\");\n        } else if (this.state == State.CHALLENGE_RECEIVED) {\n            response = this.engine.generateType1Msg(\n                    ntcredentials.getDomain(),\n                    ntcredentials.getHost());\n            this.state = State.MSG_TYPE1_GENERATED;\n        } else if (this.state == State.MSG_TYPE2_RECEVIED) {\n            response = this.engine.generateType3Msg(\n                    ntcredentials.getUserName(),\n                    ntcredentials.getPassword(),\n                    ntcredentials.getDomain(),\n                    ntcredentials.getHost(),\n                    this.challenge);\n            this.state = State.MSG_TYPE3_GENERATED;\n        } else {\n            throw new AuthenticationException(\"Unexpected state: \" + this.state);\n        }\n        return \"NTLM \" + response;\n    }\n\n    @Override\n    public boolean isComplete() {\n        return this.state == State.MSG_TYPE3_GENERATED || this.state == State.FAILED;\n    }\n\n    @Deprecated\n    @Override\n    public String getID() {\n        return null;\n    }\n\n    @Deprecated\n    @Override\n    public String authenticate(Credentials credentials, String method, String uri) throws AuthenticationException {","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/zaproxy/zaproxy/blob/9d1970a436b1b189bfb588fc88864c80d9baf6a5/zap/src/main/java/org/zaproxy/zap/network/ZapNTLMScheme.java#L149-L185","documentation":"ZapNTLMScheme.authenticate() throws this AuthenticationException when the NTLM state machine is in a state that cannot produce an NTLM message. authenticate() only handles CHALLENGE_RECEIVED (generate Type 1 msg) and MSG_TYPE2_RECEVIED (generate Type 3 msg); reaching it in UNINITIATED, MSG_TYPE1_GENERATED, MSG_TYPE2_GENERATED, or MSG_TYPE3_GENERATED state falls into the else branch and throws. This happens when the HTTP client calls authenticate() out of order relative to processChallenge(), typically because authenticate() was invoked before any server challenge was processed or after authentication already completed.","triggerScenarios":"Calling authenticate() when the scheme state is UNINITIATED (no processChallenge() call yet), already MSG_TYPE3_GENERATED (auth finished and authenticate() called again), MSG_TYPE1_GENERATED, or MSG_TYPE_GENERATED — i.e., calling authenticate() twice without a new challenge, or calling it before the server sent an NTLM challenge.","commonSituations":"Reusing a cached HttpClient/AuthScheme instance across requests so the state machine carries over a completed handshake; a misconfigured proxy or custom auth flow that calls authenticate() manually without processing the server's WWW-Authenticate: NTLM challenge first; intercepting/retrying requests and re-invoking the same scheme object.","solutions":["Create a fresh ZapNTLMScheme (or call its constructor/reset path) for each authentication handshake instead of reusing an instance whose state machine already advanced.","Ensure the standard HttpClient auth flow is used so processChallenge() is invoked with the server's NTLM challenge before authenticate() is called.","Do not call authenticate() again after a successful Type 3 message; if the server re-challenges, the HttpClient should process the new challenge on the scheme instance.","Log/inspect this.state at the call site to determine which out-of-sequence call is hitting the else branch."],"exampleFix":"// before: reusing a scheme instance across requests\nZapNTLMScheme scheme = new ZapNTLMScheme();\nscheme.processChallenge(\"NTLM\");\nString h1 = scheme.authenticate(creds, method);\nString h2 = scheme.authenticate(creds, method); // throws Unexpected state: MSG_TYPE1_GENERATED\n\n// after: fresh scheme per handshake\nString h2 = new ZapNTLMScheme().withChallengeProcessed(); // or recreate and processChallenge() before authenticate()","handlingStrategy":"try-catch","validationCode":"if (scheme.isComplete()) {\n    scheme = new ZapNTLMScheme(); // reset state machine before re-authenticating\n}\n// ensure a challenge was processed:\n// authenticate() only valid in CHALLENGE_RECEIVED or MSG_TYPE2_RECEVIED states","typeGuard":null,"tryCatchPattern":"try {\n    String header = scheme.authenticate(credentials, method);\n} catch (AuthenticationException e) {\n    // e.g. \"Unexpected state: ...\" — recreate scheme and restart handshake\n    scheme = new ZapNTLMScheme();\n    // retry the request so processChallenge() runs first\n}","preventionTips":["Never reuse a ZapNTLMScheme instance across authentication handshakes; let the HttpClient create/manage it per auth scope.","Always call processChallenge() with the server's NTLM challenge before authenticate().","Do not call authenticate() a second time after a Type 3 message was generated; start a fresh handshake instead.","When manually driving NTLM, track the state yourself and only call authenticate() twice (Type 1 then Type 3)."],"tags":["http","ntlm","authentication","state-machine","java"],"backgroundTag":"ntlm-auth-state-error","analyzedSha":"9d1970a436b1b189bfb588fc88864c80d9baf6a5","analyzedAt":"2026-09-05T19:26:59.356Z","contentChangedAt":"2026-09-05T19:26:59.356Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}