{"record":{"id":"3ae6cc3b2f7554df","repo":"Tencent/WeKnora","slug":"app-secret-is-required","errorCode":null,"errorMessage":"app_secret is required","messagePattern":"app_secret is required","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/application/service/weknoracloud.go","lineNumber":42,"sourceCode":"\trepo interfaces.ModelRepository,\n\ttenantRepo interfaces.TenantRepository,\n) interfaces.WeKnoraCloudService {\n\treturn &weKnoraCloudService{\n\t\ttenantRepo: tenantRepo,\n\t}\n}\n\nfunc IsWeKnoraCloudDocReaderAddr(addr string) bool {\n\treturn strings.TrimSuffix(strings.TrimSpace(addr), \"/\") == strings.TrimRight(provider.WeKnoraCloudBaseURL, \"/\")+\"/api/v1/doc/reader\"\n}\n\n// SaveCredentials 仅保存 APPID/APPSECRET 凭证，不自动创建模型\nfunc (s *weKnoraCloudService) SaveCredentials(ctx context.Context, appID, appSecret string) error {\n\tif appID == \"\" {\n\t\treturn fmt.Errorf(\"app_id is required\")\n\t}\n\tif appSecret == \"\" {\n\t\treturn fmt.Errorf(\"app_secret is required\")\n\t}\n\n\tif err := s.verifyCredentials(ctx, appID, appSecret); err != nil {\n\t\treturn fmt.Errorf(\"credential verification failed: %w\", err)\n\t}\n\n\ttenantID := types.MustTenantIDFromContext(ctx)\n\treturn s.updateTenantCredentials(ctx, tenantID, appID, appSecret)\n}\n\n// verifyCredentials 向 WeKnoraCloud /api/v1/health 发送带签名头的 GET。\n//\n// 注意：health 一般为探活接口，远端常不校验 APPID/SECRET 或签名；HTTP 200 通常只表示\n// 「网关/服务可达」，不能严格证明凭证有效。若需强校验，应改为调用必须鉴权的业务接口。\nfunc (s *weKnoraCloudService) verifyCredentials(ctx context.Context, appID, appSecret string) error {\n\tbaseURL := strings.TrimRight(provider.WeKnoraCloudBaseURL, \"/\")\n\thealthURL := baseURL + \"/api/v1/health\"\n","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/application/service/weknoracloud.go#L24-L60","documentation":"SaveCredentials (WeKnoraCloud) rejects an empty appSecret. Only the credential presence check — APPID and APPSECRET are both mandatory before the credentials are stored or verified against the cloud health endpoint.","triggerScenarios":"Calling SaveCredentials(ctx, validAppID, \"\") — blank secret field in the settings form, or unbound/typo'd JSON field so the secret deserializes empty.","commonSituations":"Secrets manager returns empty during startup and the value is passed through unchecked; user pastes only the APPID; secret trimming removes a whitespace-only value; partial credential update flows that intentionally omit the secret.","solutions":["Supply the APPSECRET from the WeKnoraCloud console","Check the form/handler didn't drop the secret field","Return a client-side validation error before calling SaveCredentials"],"exampleFix":"// before\nsecret := os.Getenv(\"WEKNORA_APP_SECRET\") // unset -> \"\"\nsvc.SaveCredentials(ctx, appID, secret)\n// after\nsecret := os.Getenv(\"WEKNORA_APP_SECRET\")\nif secret == \"\" { return errors.New(\"WEKNORA_APP_SECRET is not configured\") }\nsvc.SaveCredentials(ctx, appID, secret)","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(appSecret) == \"\" {\n\treturn errors.New(\"app_secret must be provided before saving credentials\")\n}","typeGuard":null,"tryCatchPattern":"if err := svc.SaveCredentials(ctx, appID, appSecret); err != nil {\n\tif strings.Contains(err.Error(), \"app_secret is required\") {\n\t\treturn fieldError(\"app_secret\", err)\n\t}\n\treturn err\n}","preventionTips":["Assert the secret env var is non-empty during startup health checks.","Never pass potentially-empty secrets straight from Getenv; wrap with a checked getter.","Support partial updates by reusing the stored secret instead of blank input.","Trim pasted secrets to strip accidental whitespace/newlines."],"tags":["validation","weknora-cloud","credentials","input-validation"],"backgroundTag":"missing-required-argument","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}