Skip to content

feat: http 请求 tags 与 crypto.rsaEncrypt - #44

Merged
HarlonWang merged 3 commits into
mainfrom
feat/http-tags-rsa
Oct 28, 2026
Merged

HarlonWang merged 3 commits into
mainfrom
feat/http-tags-rsa

Conversation

@HarlonWang

@HarlonWang HarlonWang commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

注册流程所需框架能力的第二部分(第一部分 #43 已合;PageHost 随导航条目存活另起 PR,合齐后发 0.14.0)。

改动

项 内容
http tags HttpOptions.tags?: Record<string, string>:页面写给宿主通道的备注({ captchaScene: "signup" }),任何通道都不发上网;值非字符串 → E_INVALID。Kotlin HttpRequest 多 tags(默认空,源码兼容;构造函数二进制签名变了,宿主需重编)。default / KtorChannel 忽略,不进 manifest
crypto.rsaEncrypt J3,crypto.rsaEncrypt(text, { publicKey }):PKCS#1 v1.5,X.509 SPKI 公钥 base64 进、密文 base64 出。Android Cipher("RSA/ECB/PKCS1Padding");iOS SecKeyCreateEncryptedData,框架从 X.509 取出 PKCS#1 交给系统。超长(密钥字节数 − 11)/ 公钥非法在公共代码里报 E_INVALID,两端一致。名字进框架保留名单

不做(记 roadmap):KtorChannel 把 tags 转成 ktor attributes(现有宿主都自写通道);OAEP / 哈希 / 对称加密;原拟的 host 保留包名已撤——原生页结果走 host.call,只跳不要结果走 deep link。

文档

  • native-api §6:tags 与「通道是身份边界」最佳实践(凭证在通道里消费,页面只见会话状态)
  • native-api §16:crypto
  • roadmap D 组三条

验证

  • pnpm 全套(native 新增 tags / crypto 用例)
  • :compose:testAndroidHostTest 64 个通过:新增 CryptoTest(解析、换行、拒绝裸 PKCS#1 与非法 DER、245 / 246 字节边界、UTF-8 计字节、随机填充)、RsaRoundTripTest(私钥解密回明文)、PageHost 经桥的 tags 透传 / 非字符串拒绝、加密 / 坏公钥
  • :compose:iosSimulatorArm64Test(本地;CI 不跑 iOS):CryptoTest + iOS 版 RsaRoundTripTest(SecKeyCreateDecryptedData 解回明文)全过
  • apiCheck、iOS 编译、sample 构建;Android 模拟器 form 页「Encrypt password」得到 344 字符密文

🤖 Generated with Claude Code

Summary by Sourcery

Add host-channel HTTP metadata and cross-platform RSA encryption support for registration flows.

New Features:

  • Add HTTP request tags that carry string metadata to custom host channels without being sent over the network.
  • Add cross-platform RSA PKCS#1 v1.5 public-key encryption through the native crypto API.
  • Expose the new tags and encryption capabilities in the TypeScript native package and sample form.

Enhancements:

  • Validate RSA public keys and plaintext size consistently across Android and iOS, returning E_INVALID for invalid inputs.
  • Document channel-bound tag handling, credential isolation guidance, and RSA encryption behavior.

Documentation:

  • Document HTTP tags and the crypto.rsaEncrypt API in the native API guide and record related roadmap items.

Tests:

  • Add native, bridge, Android, and iOS coverage for HTTP tag propagation, input validation, RSA encryption, and private-key round trips.

PingPong 注册试点的第二批框架能力:

- http tags:页面写给宿主通道的备注(如极验场景),任何通道都不发上网;值只能是字符串,否则 E_INVALID;
  HttpRequest 多 tags(默认空,源码兼容);default / KtorChannel 忽略,不进 manifest
- crypto.rsaEncrypt:RSA PKCS#1 v1.5 公钥加密,X.509 公钥 base64 进、密文 base64 出;
  Android Cipher、iOS SecKeyCreateEncryptedData(框架从 X.509 取出 PKCS#1 交给 iOS);
  长度与公钥在公共代码里检查,两端报错一致;名字进框架保留名单
- 测试:公共解析 / 长度 / 随机填充,Android 与 iOS 模拟器各用私钥解密往返,PageHost 经桥的 tags 与加密
- 文档:native-api §6 tags 与「通道是身份边界」最佳实践,新增 §16 crypto;roadmap 记推迟项
  (KtorChannel 转 attributes、其余算法;原拟 host 保留包名已撤,原生页结果走 host.call)
- sample form 页加「Encrypt password」演示

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

本 PR 为 HTTP 请求增加不会上网的宿主通道 tags,并新增 Android/iOS 原生 RSA PKCS#1 v1.5 加密能力;实现包含统一输入校验、桥接/API 暴露、跨平台回环测试及相应文档和示例更新。

Sequence diagram for HTTP request tags reaching the host channel

sequenceDiagram
    actor Page
    participant PageHost
    participant AppChannel
    participant Network
    Page->>PageHost: http.request(tags)
    PageHost->>PageHost: Validate tags are string values
    PageHost->>AppChannel: request(HttpRequest with tags)
    AppChannel->>AppChannel: Consume tags for channel-specific handling
    AppChannel->>Network: Send HTTP request without tags
    Network-->>AppChannel: HttpResponse
    AppChannel-->>PageHost: HttpResponse
    PageHost-->>Page: Resolve response
Loading

Sequence diagram for native RSA encryption through the bridge

sequenceDiagram
    actor Page
    participant PageHost
    participant CommonCrypto
    participant PlatformCrypto
    Page->>PageHost: crypto.rsaEncrypt(text, publicKey)
    PageHost->>CommonCrypto: rsaEncrypt(text, publicKey)
    CommonCrypto->>CommonCrypto: Parse X.509 SPKI and validate length
    CommonCrypto->>PlatformCrypto: platformRsaEncrypt(UTF8 bytes, key)
    PlatformCrypto->>PlatformCrypto: Encrypt with RSA PKCS1Padding
    PlatformCrypto-->>CommonCrypto: Ciphertext bytes
    CommonCrypto-->>PageHost: Base64 ciphertext
    PageHost-->>Page: Resolve ciphertext
Loading

Flow diagram for RSA input validation and encryption

flowchart TD
    Start[crypto.rsaEncrypt] --> Parse[Parse Base64 X.509 SPKI public key]
    Parse --> ValidKey{Valid RSA key?}
    ValidKey -- No --> Invalid[E_INVALID]
    ValidKey -- Yes --> Length[Encode text as UTF-8 and check key bytes minus 11]
    Length --> ValidLength{Within limit?}
    ValidLength -- No --> Invalid
    ValidLength -- Yes --> Encrypt[Platform RSA PKCS#1 v1.5 encryption]
    Encrypt --> Output[Return ciphertext as Base64]
Loading

File-Level Changes

Change Details Files
扩展 HTTP 请求模型以携带仅供宿主通道消费的 tags,并在桥接层统一校验其为字符串映射。
  • 新增 TypeScript HttpOptions.tags 并透传至 http.request。
  • 为 Kotlin HttpRequest 增加默认空 tags,保持源码调用兼容但改变构造函数二进制签名。
  • PageHost 拒绝非对象或非字符串 tag;default/KtorChannel 不上网、不进入 manifest。
  • 补充 native、PageHost 和通道透传测试。
compose/src/commonMain/kotlin/app/tinyui/HostInterfaces.kt
compose/src/commonMain/kotlin/app/tinyui/PageHost.kt
compose/src/commonTest/kotlin/app/tinyui/PageHostTest.kt
packages/native/src/http.ts
packages/native/test/native.test.ts
docs/native-api.md
新增跨平台 crypto.rsaEncrypt 框架能力,使用 RSA PKCS#1 v1.5 对 UTF-8 文本加密并返回 base64。
  • 加入 public API、框架名称白名单及 crypto 文档/roadmap 条目。
  • 在公共代码中解析并校验 X.509 SPKI RSA 公钥、计算 UTF-8 字节长度上限并统一报告 E_INVALID。
  • Android 使用 RSA Cipher,iOS 将 SPKI 提取为 PKCS#1 后使用 SecKey 加密。
  • 增加随机填充、DER/PEM 换行、边界长度、桥接调用及 Android/iOS 私钥解密回环测试。
compose/src/commonMain/kotlin/app/tinyui/Crypto.kt
compose/src/androidMain/kotlin/app/tinyui/Crypto.android.kt
compose/src/iosMain/kotlin/app/tinyui/Crypto.ios.kt
compose/src/commonMain/kotlin/app/tinyui/PageHost.kt
packages/native/src/crypto.ts
packages/native/src/index.ts
compose/src/commonTest/kotlin/app/tinyui/CryptoTest.kt
compose/src/androidHostTest/kotlin/app/tinyui/RsaRoundTripTest.kt
compose/src/iosTest/kotlin/app/tinyui/RsaRoundTripTest.kt
packages/native/test/native.test.ts
同步公开接口、运行时生成代码、示例页面及设计文档,使新能力可被页面使用并明确安全边界。
  • 更新 klib API 与 RuntimeBytecode 产物。
  • 在 sample 表单中展示密码加密调用。
  • 记录 tags 的通道身份边界、凭证消费原则及 RSA 约束。
compose/api/compose.klib.api
compose/src/commonMain/kotlin/app/tinyui/generated/RuntimeBytecode.kt
sample/js-extra/src/pages/form.tsx
docs/native-api.md
docs/roadmap.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ed7391d2-f89c-4ff4-92e0-a9639c7ec90e
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codeseerbot codeseerbot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeSeer 审查

结论:批准(附 1 条建议)

摘要

本次改动为 HTTP 请求增加了仅供宿主通道消费的 tags,并新增通过桥接调用的 crypto.rsaEncrypt,同时补充了 Android/iOS 实现、测试和文档。tags 的对象和值类型校验及透传路径完整。主要风险在 RSA 公钥的公共解析校验不够严格,Android 侧对后续解析失败的错误码与 iOS/文档约定不一致。

整个 PR 至 4beae7d · gpt-5.6-terra

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 7 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="compose/src/androidMain/kotlin/app/tinyui/Crypto.android.kt" line_range="9" />
<code_context>
+
+internal actual fun platformRsaEncrypt(plain: ByteArray, key: RsaPublicKey): ByteArray {
+    val publicKey = KeyFactory.getInstance("RSA").generatePublic(X509EncodedKeySpec(key.spki))
+    return Cipher.getInstance("RSA/ECB/PKCS1Padding").run {
+        init(Cipher.ENCRYPT_MODE, publicKey)
+        doFinal(plain)
</code_context>
<issue_to_address>
**Legacy padding enables oracle attacks**

When the server decrypts these ciphertexts and an attacker can distinguish valid-padding responses from invalid-padding responses, a network attacker who can submit ciphertexts to a registration endpoint that reveals whether PKCS#1 decoding succeeded can use adaptive Bleichenbacher queries to recover plaintext from captured ciphertexts, exposing the encrypted password.

Use RSA-OAEP with SHA-256 on both platforms and the server, or otherwise ensure the decryption endpoint cannot distinguish padding failures.

Also at `compose/src/iosMain/kotlin/app/tinyui/Crypto.ios.kt:39`.
</issue_to_address>

### Comment 2
<location path="compose/src/commonMain/kotlin/app/tinyui/Crypto.kt" line_range="23" />
<code_context>
+            if (bits.isEmpty() || bits[0] != 0.toByte()) invalid("the public key's BIT STRING is malformed")
+            val pkcs1 = bits.copyOfRange(1, bits.size)
+            val modulus = Der(pkcs1).sequence().next(0x02).dropWhile { it == 0.toByte() }
+            if (modulus.size < 64) invalid("the public key is too small")
+            return RsaPublicKey(der, pkcs1, modulus.size)
+        }
</code_context>
<issue_to_address>
**Weak keys expose encrypted passwords**

When the server supplies a 512-bit or similarly weak RSA key, `RsaPublicKey.parse` accepts the weak modulus and `rsaEncrypt` encrypts passwords to it, so an attacker who captures the ciphertext can factor the modulus and recover the password.

Reject RSA moduli below an adequate security minimum.
</issue_to_address>

### Comment 3
<location path="compose/src/iosMain/kotlin/app/tinyui/Crypto.ios.kt" line_range="58" />
<code_context>
+
+@OptIn(ExperimentalForeignApi::class)
+private fun cfData(bytes: ByteArray): CFDataRef =
+    bytes.usePinned { CFDataCreate(null, it.addressOf(0).reinterpret(), bytes.size.toLong()) }!!
</code_context>
<issue_to_address>
**Empty text fails on iOS**

When the caller encrypts an empty string on iOS, `cfData` calls `addressOf(0)` on the empty plaintext array and throws before `SecKeyCreateEncryptedData` runs, so the bridge returns `E_NET` instead of ciphertext; in the `rsaEncrypt` path, this happens before the cleanup `try`, leaving `keyData` and `attributes` unreleased.

Make `cfData` create zero-length `CFData` without calling `addressOf(0)`, so encryption proceeds and the existing cleanup runs.

Also at `compose/src/iosMain/kotlin/app/tinyui/Crypto.ios.kt:35-36`.
</issue_to_address>

### Comment 4
<location path="compose/src/commonMain/kotlin/app/tinyui/Crypto.kt" line_range="22" />
<code_context>
+            val bits = spki.next(0x03)
+            if (bits.isEmpty() || bits[0] != 0.toByte()) invalid("the public key's BIT STRING is malformed")
+            val pkcs1 = bits.copyOfRange(1, bits.size)
+            val modulus = Der(pkcs1).sequence().next(0x02).dropWhile { it == 0.toByte() }
+            if (modulus.size < 64) invalid("the public key is too small")
+            return RsaPublicKey(der, pkcs1, modulus.size)
</code_context>
<issue_to_address>
**Malformed keys return inconsistent errors**

When a DER key has a sufficiently large modulus but an incomplete or otherwise invalid RSA structure, `RsaPublicKey.parse` reads the modulus without validating the rest of the PKCS#1 sequence, so Android's `KeyFactory` rejects the key and `PageHost.settle` reports `E_NET`, while iOS reports `E_INVALID`.

Validate the complete RSA public-key structure during parsing and reject malformed keys as `E_INVALID`.

Also at `compose/src/androidMain/kotlin/app/tinyui/Crypto.android.kt:8-12`, `compose/src/iosMain/kotlin/app/tinyui/Crypto.ios.kt:37`.
</issue_to_address>

### Comment 5
<location path="compose/src/commonMain/kotlin/app/tinyui/PageHost.kt" line_range="362" />
<code_context>
                 if (name == "ui.confirm") confirmed.toString() else ""
             }
+            "crypto.rsaEncrypt" -> settle(cbId) {
+                val text = a.str("text") ?: throw HostException("E_INVALID", "crypto.rsaEncrypt needs text")
+                val key = a.str("publicKey") ?: throw HostException("E_INVALID", "crypto.rsaEncrypt needs a publicKey")
+                // off the engine's lane: RSA is cheap but not free on a slow phone
</code_context>
<issue_to_address>
**Non-string text is encrypted**

When a caller sends a numeric or boolean `text` value through the bridge, `a.str("text")` uses `contentOrNull` without checking `JsonPrimitive.isString`, so numeric or boolean inputs are converted to their textual form and encrypted instead of being rejected; callers receive ciphertext for data different from their requested string.

Require the `text` field to be a string JSON primitive before encrypting it.
</issue_to_address>

### Comment 6
<location path="compose/src/commonMain/kotlin/app/tinyui/HostInterfaces.kt" line_range="72" />
<code_context>
+    val headers: Map<String, String>,
+    val body: String?,
+    val timeoutMs: Long?,
+    val tags: Map<String, String> = emptyMap(),
+)

</code_context>
<issue_to_address>
**Java callers lose the old constructor**

When a Java host constructs `HttpRequest` using the previous five-argument constructor, kotlin's default argument does not generate a five-argument Java overload, so the Java call no longer compiles.

Preserve a five-argument Java constructor overload, for example with `@JvmOverloads`.
</issue_to_address>

### Comment 7
<location path="compose/src/commonMain/kotlin/app/tinyui/PageHost.kt" line_range="403" />
<code_context>
             body = body,
             timeoutMs = a["timeout"]?.jsonPrimitive?.longOrNull,
+            tags = when (val t = a["tags"]) {
+                null, JsonNull -> emptyMap()
+                is JsonObject -> t.mapValues { (k, v) -> (v as? JsonPrimitive)?.takeIf { it.isString }?.content ?: throw HostException("E_INVALID", "http tag \"$k\" must be a string") }
+                else -> throw HostException("E_INVALID", "http tags must be an object of strings")
</code_context>
<issue_to_address>
**Null tags bypass validation**

When a caller supplies `tags: null`, the `JsonNull` branch treats an explicitly null `tags` field as an omitted field, so the request reaches the channel with empty tags instead of rejecting the invalid input with `E_INVALID`.

Treat only a missing `tags` field as empty and reject `JsonNull` as an invalid tags value.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 7 findings to address first, and the new API encrypts caller-provided text with RSA PKCS#1 v1.5; if callers rely on it to protect credentials and the padding or key handling proves unsafe, ciphertext already sent to a backend cannot be recalled by reverting. The tags are only passed to the host channel and do not themselves go on the wire.

Blocking findings: compose/src/androidMain/kotlin/app/tinyui/Crypto.android.kt:9, compose/src/commonMain/kotlin/app/tinyui/Crypto.kt:23, compose/src/iosMain/kotlin/app/tinyui/Crypto.ios.kt:58, compose/src/commonMain/kotlin/app/tinyui/Crypto.kt:22, compose/src/commonMain/kotlin/app/tinyui/PageHost.kt:362, and 2 more


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

- 公钥 DER 逐层读完(无尾随字节、必须有指数、算法参数只能是 NULL 或省略),不少于 1024 位;
  Android KeyFactory 仍拒绝时也报 E_INVALID,两端错误码一致
- iOS:空字符串不再因 addressOf(0) 抛错,空文本照常加密
- text / publicKey 只收字符串,数字布尔不再被转成文本加密
- HttpRequest 构造函数加 @jvmoverloads,Java 宿主的五参构造仍可用
- native-api §16 写明公钥要求与 PKCS#1 v1.5 的解密端风险(服务端勿区分填充错误,后端支持后改 OAEP)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@codeseerbot codeseerbot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeSeer 审查

结论:批准(附 1 条建议)

上轮意见:1 条,已处理 1 条,待处理 0 条

摘要

本次增量补齐了 RSA 公钥解析的容器消费、指数存在性校验和 Android 端的 InvalidKeySpecException 映射,上一轮关于损坏 SPKI 落入 E_NET 的问题已修复。也为 HttpRequest 添加了带默认值的 tags 构造参数,并收紧了 crypto.rsaEncrypt 的入参字符串校验。剩余风险是最小 RSA 密钥长度按字节数而非实际有效位数判断,少于 1024 位的特制密钥仍可能通过。

增量 4beae7d..99dd617 · gpt-5.6-terra

rsa.done()
if (exponent.isEmpty()) invalid("the public key's exponent is zero")
// 512 and 768 bits have been factored; 1024 stays for servers that still use it
if (modulus.size * 8 < MIN_BITS) invalid("the public key has ${modulus.size * 8} bits; at least $MIN_BITS are required")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[建议] modulus.size * 8 只是编码占用的整字节位数,不是模数的实际位数。例如首字节为 0x7f 的 1023 位模数在去掉符号用的前导零后仍有 128 字节,会通过这里的 1024 位下限并交给平台。应依据首个非零字节的有效位数计算实际 bit length 后再与 MIN_BITS 比较;modulusBytes 可继续用于计算 PKCS#1 的明文上限。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@codeseerbot codeseerbot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeSeer 审查

结论:批准(附 2 条建议)

上轮意见:1 条,已处理 0 条,待处理 1 条

  • compose/src/commonMain/kotlin/app/tinyui/Crypto.kt:37 [建议] modulus.size * 8 只是编码占用的整字节位数,不是模数的实际位数。例如首字节为 0x7f 的 1023 位模数在去掉符号…

摘要

本次增量为 HttpRequest 增加了 tags,并将 crypto.rsaEncrypt 加入框架保留能力列表。HttpRequest 构造函数同时移除了 @JvmOverloads,这会使已有 Java 宿主的五参数构造调用无法重新编译,与 PR 所述的源码兼容不符。

增量 99dd617..ac0c41d · gpt-5.6-terra

/** One request of J3 `http.request`; [body] is the text to send, its `Content-Type` among [headers]. */
class HttpRequest(val method: String, val url: String, val headers: Map<String, String>, val body: String?, val timeoutMs: Long?)
/** [tags]: the page's notes for this channel (`captchaScene`), never sent; the built-in channels ignore them (docs/native-api.md §6). */
class HttpRequest(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[建议] 移除 @JvmOverloads 后,JVM 上不再生成原有的五参数构造函数;已有 Java 宿主若调用 new HttpRequest(method, url, headers, body, timeoutMs) 会直接编译失败。tags 已有默认值,保留 @JvmOverloads 即可继续提供该重载,并与 PR 声明的源码兼容保持一致。

@HarlonWang
HarlonWang merged commit 087f539 into main Oct 28, 2026
5 checks passed
@HarlonWang
HarlonWang deleted the feat/http-tags-rsa branch October 28, 2026 09:06
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.

1 participant