account_mode1_protection.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. package service
  2. import (
  3. "context"
  4. "fmt"
  5. "maps"
  6. "strings"
  7. "time"
  8. infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
  9. "github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint"
  10. )
  11. const mode1PolicyVersion = 3
  12. func mode1Marker(a *Account) map[string]any {
  13. if a == nil {
  14. return nil
  15. }
  16. m, _ := a.Extra[AntiDegradeMarkerExtraKey].(map[string]any)
  17. return m
  18. }
  19. func mode1Int(v any) int {
  20. switch n := v.(type) {
  21. case int:
  22. return n
  23. case int64:
  24. return int(n)
  25. case float64:
  26. if n == float64(int(n)) {
  27. return int(n)
  28. }
  29. }
  30. return 0
  31. }
  32. func isMode1ProtectionEnabled(a *Account) bool {
  33. m := mode1Marker(a)
  34. version := mode1Int(m["policy_version"])
  35. return m["enabled"] == true && m["mode"] == string(AntiDegradeMode1) && (version == 2 || version == mode1PolicyVersion)
  36. }
  37. // isLegacyProtectionEnabled identifies the explicitly selected sub2初代
  38. // strategy. Legacy is intentionally separate from mode1 v3: it keeps the
  39. // original session fingerprint + nodejs24 TLS behavior and must not be
  40. // subjected to mode1-v3 integrity checks.
  41. func isLegacyProtectionEnabled(a *Account) bool {
  42. m := mode1Marker(a)
  43. return m != nil && m["enabled"] == true && m["mode"] == string(AntiDegradeModeLegacy)
  44. }
  45. func isMode1ProtectionRequested(a *Account) bool {
  46. m := mode1Marker(a)
  47. _, versionPresent := m["policy_version"]
  48. // A versioned marker with a missing/unknown mode must not silently bypass
  49. // integrity checks. Mode 2 and the explicitly unversioned `legacy` policy
  50. // own their separate transports.
  51. return m["enabled"] != false && m["mode"] != string(AntiDegradeMode2) && versionPresent
  52. }
  53. func hasMode1ManagedUpdates(updates map[string]any) bool {
  54. for _, key := range mode1ManagedExtraKeys {
  55. if _, exists := updates[key]; exists {
  56. return true
  57. }
  58. }
  59. return false
  60. }
  61. func (a *Account) IsMode1ProtectionEnabled() bool { return isMode1ProtectionEnabled(a) }
  62. // Mode1EffectiveConcurrency uses the editable account ceiling for every strategy.
  63. func (a *Account) Mode1EffectiveConcurrency() int {
  64. if a == nil {
  65. return 0
  66. }
  67. if !a.AntiDegradationEnabled() || a.Concurrency > 0 {
  68. return a.Concurrency
  69. }
  70. return AntiDegradeConcurrencyCap
  71. }
  72. func mode1ConfigurationIssues(a *Account) []string {
  73. issues := []string{}
  74. if !isOpenAIOAuthLike(a) || a.IsShadow() {
  75. return []string{"模式一仅支持 OpenAI OAuth / Setup Token 账号"}
  76. }
  77. if _, ok := codexFingerprintSeed(a.Extra); !ok {
  78. issues = append(issues, "账号持久身份种子缺失或无效")
  79. }
  80. if a.GetCodexFingerprintMode() != codexFingerprintDevice {
  81. issues = append(issues, "模式一必须保留独立会话,仅固定设备身份")
  82. }
  83. if a.Extra["proxy_mode"] == "random" {
  84. issues = append(issues, "随机代理与模式一固定出口配置冲突")
  85. }
  86. if cap := mode1Int(mode1Marker(a)["max_concurrency"]); cap < 1 {
  87. issues = append(issues, "保护并发上限无效,请还原后重新配置")
  88. }
  89. return issues
  90. }
  91. // v3 uses the proven standard transport. v2 remains readable and retains its
  92. // explicitly configured template while enabled globally; TLS is not a claim
  93. // about model quality. Invalid identity/policy state still fails explicitly.
  94. func resolveMode1TLSProfile(a *Account) (*tlsfingerprint.Profile, error) {
  95. if !isMode1ProtectionRequested(a) {
  96. if err := validateRegisteredProtection(a); err != nil {
  97. return nil, err
  98. }
  99. }
  100. if isLegacyProtectionEnabled(a) && (isOpenAIOAuthLike(a) || isAnthropicOAuthLike(a)) {
  101. if p := tlsfingerprint.BuiltinProfile("nodejs24"); p != nil {
  102. return p, nil
  103. }
  104. return nil, fmt.Errorf("legacy TLS profile unavailable")
  105. }
  106. if !isMode1ProtectionRequested(a) {
  107. // Diagnostic profiles can opt into a built-in TLS profile without
  108. // claiming mode1-v3 integrity semantics.
  109. if a != nil && a.AntiDegradationEnabled() {
  110. if raw, ok := a.Extra["tls_fingerprint_builtin"].(string); ok && raw != "" {
  111. if p := tlsfingerprint.BuiltinProfile(raw); p != nil {
  112. return p, nil
  113. }
  114. return nil, fmt.Errorf("TLS profile unavailable: %s", raw)
  115. }
  116. }
  117. return nil, nil
  118. }
  119. if !isMode1ProtectionEnabled(a) {
  120. return nil, infraerrors.BadRequest("MODE1_CONFIGURATION_INVALID", "模式一配置标记或版本无效,请检查配置")
  121. }
  122. if issues := mode1ConfigurationIssues(a); len(issues) > 0 {
  123. return nil, infraerrors.BadRequest("MODE1_CONFIGURATION_INVALID", strings.Join(issues, "; "))
  124. }
  125. if mode1Int(mode1Marker(a)["policy_version"]) == mode1PolicyVersion || !a.IsTLSFingerprintEnabled() {
  126. return nil, nil
  127. }
  128. p := tlsfingerprint.BuiltinProfile("nodejs24")
  129. if p == nil {
  130. return nil, fmt.Errorf("mode1 TLS profile unavailable")
  131. }
  132. return p, nil
  133. }
  134. func previewMode1(a *Account) AntiDegradePreview {
  135. out := AntiDegradePreview{Changes: []AntiDegradeChange{}, Issues: []string{}, TLSProfile: "standard"}
  136. if a == nil {
  137. out.Reason = "account not found"
  138. return out
  139. }
  140. out.AccountID = a.ID
  141. out.Enabled = antiDegradeEnabled(a)
  142. if out.Enabled {
  143. out.ActiveMode = string(antiDegradeMode(a))
  144. out.PolicyVersion = mode1Int(mode1Marker(a)["policy_version"])
  145. if out.PolicyVersion == 2 && a.IsTLSFingerprintEnabled() {
  146. out.TLSProfile = "nodejs24"
  147. }
  148. }
  149. _, out.IdentityReady = codexFingerprintSeed(a.Extra)
  150. if !isOpenAIOAuthLike(a) || a.IsShadow() {
  151. out.Reason = "模式一仅支持独立 OpenAI OAuth / Setup Token 账号"
  152. return out
  153. }
  154. if a.Extra["proxy_mode"] == "random" {
  155. out.Reason = "请先为账号选择固定代理或直连,再启用模式一"
  156. return out
  157. }
  158. if out.Enabled && !isMode1ProtectionEnabled(a) {
  159. if antiDegradeMode(a) == AntiDegradeMode1 {
  160. // A malformed/legacy marker without an explicit alternate mode is
  161. // still treated as enabled but not eligible for an implicit rewrite.
  162. out.Reason = "当前模式一配置无效,请先还原后重新配置"
  163. return out
  164. }
  165. // A different strategy is active. Allow the explicit mode-1 button to
  166. // perform a safe switch (the service restores the current snapshot before
  167. // applying mode 1) instead of reporting an unexplained hard stop.
  168. out.Eligible = true
  169. out.Reason = "可切换到兼容保护 v3;应用前会先还原当前策略快照"
  170. out.Changes = append(out.Changes, AntiDegradeChange{Key: "strategy", From: string(antiDegradeMode(a)), To: string(AntiDegradeMode1), Note: "安全切换策略"})
  171. return out
  172. }
  173. if isMode1ProtectionEnabled(a) && out.PolicyVersion == mode1PolicyVersion {
  174. out.Issues = mode1ConfigurationIssues(a)
  175. if len(out.Issues) == 0 {
  176. out.Reason = "已启用兼容保护 v3:稳定设备身份、会话隔离、并发上限和请求完整性检查;不保证上游模型质量"
  177. } else {
  178. out.Reason = "配置异常,请还原后重新配置"
  179. }
  180. return out
  181. }
  182. if isMode1ProtectionEnabled(a) && antiDegradePrev(a) == nil {
  183. out.Reason = "v2 原始配置快照缺失,无法安全升级;请先修复账号快照"
  184. return out
  185. }
  186. out.Eligible = true
  187. if out.PolicyVersion == 2 {
  188. out.Reason = "可升级至兼容保护 v3:改用初代标准传输,保留身份种子与原始还原快照"
  189. out.Changes = append(out.Changes, AntiDegradeChange{Key: "policy_version", From: 2, To: mode1PolicyVersion, Note: "显式升级;还原恢复首次启用前的配置"})
  190. }
  191. for _, ch := range []AntiDegradeChange{
  192. {Key: "extra.codex_fingerprint_mode", From: string(a.GetCodexFingerprintMode()), To: "device", Note: "稳定设备身份,保留不同对话的 session/thread"},
  193. {Key: "transport", From: a.Extra["tls_fingerprint_builtin"], To: "standard", Note: "沿用初代标准传输,不强制实验性 TLS ClientHello"},
  194. {Key: "concurrency", From: a.Concurrency, To: mode1InitialLimit(a), Note: "运行时持续执行上限,已有更低上限保留"},
  195. } {
  196. out.Changes = append(out.Changes, ch)
  197. }
  198. if !out.IdentityReady {
  199. out.Changes = append(out.Changes, AntiDegradeChange{Key: "identity", To: "生成账号独立随机 UUID 种子并持久保存"})
  200. }
  201. return out
  202. }
  203. func mode1InitialLimit(a *Account) int {
  204. if isMode1ProtectionEnabled(a) {
  205. return a.Mode1EffectiveConcurrency()
  206. }
  207. if a.Concurrency > 0 {
  208. return a.Concurrency
  209. }
  210. return AntiDegradeConcurrencyCap
  211. }
  212. var mode1ManagedExtraKeys = []string{AntiDegradeMarkerExtraKey, "codex_fingerprint_mode", "enable_tls_fingerprint", "tls_fingerprint_builtin", "tls_fingerprint_profile_id", "proxy_mode"}
  213. type mode1ManagedWriteKey struct{}
  214. func (s *AntiDegradeService) applyMode1(ctx context.Context, a *Account) (*Account, error) {
  215. p := previewMode1(a)
  216. if isMode1ProtectionEnabled(a) && p.PolicyVersion == mode1PolicyVersion && len(p.Issues) == 0 {
  217. return a, nil
  218. }
  219. if !p.Eligible {
  220. return nil, infraerrors.BadRequest("MODE1_NOT_ELIGIBLE", p.Reason)
  221. }
  222. extra := maps.Clone(a.Extra)
  223. if extra == nil {
  224. extra = map[string]any{}
  225. }
  226. prev := map[string]any{"concurrency": a.Concurrency}
  227. for _, key := range mode1ManagedExtraKeys {
  228. if key != AntiDegradeMarkerExtraKey {
  229. prev[key] = extra[key]
  230. }
  231. }
  232. if isMode1ProtectionEnabled(a) {
  233. prev = maps.Clone(antiDegradePrev(a))
  234. }
  235. extra["codex_fingerprint_mode"] = "device"
  236. extra["enable_tls_fingerprint"] = false
  237. delete(extra, "tls_fingerprint_builtin")
  238. delete(extra, "tls_fingerprint_profile_id")
  239. cap := mode1InitialLimit(a)
  240. extra[AntiDegradeMarkerExtraKey] = map[string]any{"enabled": true, "mode": "mode1", "policy_version": mode1PolicyVersion, "max_concurrency": cap, "applied_at": time.Now().UTC().Format(time.RFC3339), "prev": prev}
  241. extra[AntiDegradationExtraKey] = true
  242. extra[ProtectionScopeExtraKey] = "codex_v3"
  243. return s.admin.UpdateAccount(context.WithValue(ctx, mode1ManagedWriteKey{}, true), a.ID, &UpdateAccountInput{Extra: extra, Concurrency: &cap})
  244. }
  245. // Preserve server-managed protection fields during ordinary modal saves. The
  246. // explicit apply/revert endpoints are the sole owners of policy transitions.
  247. func preserveMode1ManagedExtra(ctx context.Context, a *Account, incoming map[string]any) map[string]any {
  248. return PreserveAccountProtection(ctx, a, incoming)
  249. }