Skip to content

anyconnect: treat an empty CSD wait response as scan completion - #2

Merged
nekohasekai merged 1 commit into
SagerNet:mainfrom
mukhanov:fix/csd-wait-empty
Nov 3, 2026
Merged

nekohasekai merged 1 commit into
SagerNet:mainfrom
mukhanov:fix/csd-wait-empty

Conversation

@mukhanov

Copy link
Copy Markdown

Problem

With a Cisco ASA head-end that answers the hostscan wait endpoint
(/+CSCOE+/sdesktop/wait.html) with HTTP 200 and an empty body after the
CSD report has been accepted (scan.xml → <hostscan><status>TOKEN_SUCCESS</status></hostscan>),
authentication always fails with:

ERROR endpoint/openconnect[oc-mts]: client terminated: CSD wait endpoint returned neither XML nor an HTML refresh page

The report is stored server-side (subsequent aggregate-auth requests still
carry host-scan), but the client aborts, so CSD-protected tunnel groups are
unusable even with a working csd.wrapper_path script.

Upstream behavior

openconnect (auth.c, cstp_obtain_cookie, Step 4) treats an empty wait-page
body as completion and proceeds to re-request the auth form:

while (1) {
    result = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
    if (result <= 0)          /* empty body: buflen 0 -> break, proceed */
        break;
    result = check_response_type(vpninfo, form_buf);
    if (result <= 0)
        break;
    sleep(1);
}

Observed with the real client: the empty-body case is the normal "scan
finished" answer on this ASA; HTML-refresh pages only appear while scanning
is still in progress.

Fix

Treat a whitespace-only 200 body as scan completion (patch attached:

csd-wait-empty.patch):

 		trimmedBody := bytes.TrimSpace(response.body)
+		if len(trimmedBody) == 0 {
+			// Upstream openconnect treats an empty wait response as scan
+			// completion (auth.c: result <= 0 -> break, then the auth
+			// form is re-requested). Some ASAs answer 200 with an empty
+			// body once the CSD report was accepted via scan.xml.
+			return nil
+		}
 		if bytes.HasPrefix(trimmedBody, []byte("<?xml")) {

Environment

  • sing-box 1.14.3 (release) and 1.15.0-alpha.12 — both affected
  • sing-openconnect v0.1.1-0.20260929151226-29757281a247
  • Head-end: Cisco ASAv (AnyConnect), tunnel group with CSD/hostscan enforced
  • Verified end-to-end with a csd-wrapper posting a valid report: openconnect
    9.21 CLI passes the same flow (empty wait → proceeds → SSO form), the
    sing-openconnect port aborts.

@mukhanov

Copy link
Copy Markdown
Author

Added a follow-up commit: the same runHostScan completion path also wiped hostScan.Token, so authentication replies built afterwards omitted <host-scan-token>. Against a Cisco ASA this surfaces as error 13 "Cisco Secure Desktop not installed on the client" right after an SSO token is accepted (and matches reports of forms hanging after submission). The new commit keeps Token and only clears the per-scan state, matching upstream openconnect which keeps echoing the token.

@nekohasekai
nekohasekai merged commit d47571a into SagerNet:main Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants