{"record":{"id":"57c35b818f2b9ee6","repo":"siyuan-note/siyuan","slug":"invalid-import-token","errorCode":null,"errorMessage":"invalid import token","messagePattern":"invalid import token","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/api/import.go","lineNumber":258,"sourceCode":"\t\treturn\n\t}\n\tfor {\n\t\ttoken = gulu.Rand.String(32)\n\t\t_, statErr := os.Stat(stagedSYImportPath(token))\n\t\tif os.IsNotExist(statErr) {\n\t\t\tbreak\n\t\t}\n\t\tif statErr != nil {\n\t\t\treturn \"\", statErr\n\t\t}\n\t}\n\terr = os.Rename(srcPath, stagedSYImportPath(token))\n\treturn\n}\n\nfunc claimStagedSYImport(token string) (path string, err error) {\n\tif !isValidSYImportToken(token) {\n\t\treturn \"\", errors.New(\"invalid import token\")\n\t}\n\tstagedSYImportLock.Lock()\n\tdefer stagedSYImportLock.Unlock()\n\tcleanupStagedSYImports()\n\tsrcPath := stagedSYImportPath(token)\n\tif _, err = os.Stat(srcPath); err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\terr = errors.New(\"import task not found or expired\")\n\t\t}\n\t\treturn \"\", err\n\t}\n\tpath = filepath.Join(stagedSYImportDir(), token+\"-importing.zip\")\n\terr = os.Rename(srcPath, path)\n\treturn\n}\n\nfunc cleanupStagedSYImports() {\n\tentries, err := os.ReadDir(stagedSYImportDir())","sourceCodeStart":240,"sourceCodeEnd":276,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/api/import.go#L240-L276","documentation":"Returned by claimStagedSYImport (import.go:258) when the provided SY-import token fails isValidSYImportToken: it must be exactly 32 characters of ASCII letters/digits ([A-Za-z0-9]). The two-phase .sy import (stage then claim) hands back an opaque token from stageSYImport; a malformed, truncated, or tampered token is refused before any filesystem rename occurs, to prevent path-injection or stale-token abuse.","triggerScenarios":"Calling the claim/finalize step of the SY import flow with a token that is not the 32-char alphanumeric string returned by stageSYImport (import.go:235). Passing a URL-decoded, base64, or hand-typed token. A frontend bug that truncated/whitespace-padded the token before the claim request.","commonSituations":"Frontend stores the token and accidentally trims/encodes it. A second claim attempt using a derived or remembered token. Replay across kernel restarts with an old token format. Manual testing with a placeholder like 'test' or 'token'.","solutions":["Use the exact token string returned by the stage step (stageSYImport) — pass it through verbatim, without trimming or re-encoding.","Validate client-side that the token is 32 chars of [A-Za-z0-9] before issuing the claim request.","If the token was lost or corrupted, re-stage the import to obtain a fresh token."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Validate the token matches the exact format the kernel expects\nfunction isValidSYImportToken(t) {\n  return typeof t === 'string' && /^[A-Za-z0-9]{32}$/.test(t);\n}\nif (!isValidSYImportToken(token)) throw new Error('malformed import token');","typeGuard":"function isSYImportToken(t: unknown): t is string {\n  return typeof t === 'string' && /^[A-Za-z0-9]{32}$/.test(t);\n}","tryCatchPattern":"try { await claimImport(token); }\ncatch (e) { if (/invalid import token/.test(e.msg)) { token = await stageImport(data); await claimImport(token); } else throw e; }","preventionTips":["Pass the stage-returned token verbatim; never trim/encode it.","Validate the 32-char alphanumeric shape client-side before claiming.","Treat the token as opaque — don't reconstruct it from parts."],"tags":["import","validation","security","kernel"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}