OneTimeSecret v0.26.19 REST API 实战指南
OneTimeSecret v0.26.19 REST API 实战:v1/v2/v3 三代接口与账户 API 全景、Basic/Token/会话 CSRF 三种鉴权实测、一次性秘密创建/两步揭示/焚毁全链路回显、防枚举统一 404 设计、脚本化集成十大坑位清单。
OneTimeSecret v0.26.19 REST API 实战指南
OneTimeSecret v0.26.19 的 API 是「三代并存」的格局:v1(legacy,表单编码)、v2(stable,JSON)、v3(guest 匿名面),外加一组不带版本前缀的账户接口(/api/account/*)。三套面的鉴权规则、载荷格式、错误形态都不一样,混用是脚本集成翻车的主因。本文在运行中的 v0.26.19 实例上把三类接口全链路实测一遍——创建、读取、二次读取、口令保护、焚毁、揭示、状态查询、Token 签发——所有回显逐字照抄实测输出,并沉淀成一份坑位清单。
一、环境信息
| 项目 | 值 |
|---|---|
| OneTimeSecret 版本 | v0.26.19(/api/v2/version 实测返回 ["0","26","19"]) |
| 实例地址 | http://192.168.146.131:8132(部署过程见本专栏 B1/B2 篇) |
| 测试账号 | admin@ots.local / q1w2e3r4(colonel 角色) |
| API Token | 账户页签发的 52 位小写字母数字串(下文以 <apitoken> 占位) |
| 数据存储 | Valkey(Redis 协议兼容) |
产品与配置背景见《OneTimeSecret v0.26.19 介绍与选型指南》与《OneTimeSecret v0.26.19 配置参数详解》。
二、API 全景:三代并存
| 接口面 | 前缀 | 载荷格式 | 鉴权 | 定位 |
|---|---|---|---|---|
| v1(legacy) | /api/v1/* | 表单编码(key=value) | 匿名可用;带 Basic 则归属到账号 | 老集成、curl 友好,官方保留兼容 |
| v2(stable) | /api/v2/* | JSON,且创建类载荷必须嵌套在 "secret" 键下 | Basic(用户名=邮箱,密码=API Token) | 现代主推面 |
| v3(guest) | /api/v3/guest/* | JSON(嵌套同 v2) | 匿名,开关控制在配置 | 受控匿名面 |
| 账户 API | /api/account/* | 表单/JSON | 仅会话(Basic/Token 均被拒) | Token 签发、改密码等账户自管理 |
三个探测端点可以当健康检查用(均匿名可访问,实测回显):
$ curl -s http://192.168.146.131:8132/api/v1/status
{"status":"nominal","locale":"en"} [HTTP:200]
$ curl -s -u "admin@ots.local:<apitoken>" http://192.168.146.131:8132/api/v2/status
{"success":true,"status":"nominal","locale":"en"} [HTTP:200]
$ curl -s -u "admin@ots.local:<apitoken>" http://192.168.146.131:8132/api/v2/version
{"success":true,"version":["0","26","19"],"locale":"en"} [HTTP:200]
三、鉴权模型实测
3.1 Basic:现代接口的正确姿势
v2 面用 HTTP Basic,用户名是注册邮箱、密码是 API Token(不是登录密码):
$ curl -s -u "admin@ots.local:<apitoken>" http://192.168.146.131:8132/api/v2/status
{"success":true,"status":"nominal","locale":"en"}
v1 面同样接受 Basic:匿名分享响应里 "custid":"anon",带同一组 Basic 后创建的同型请求返回 "custid":"admin@ots.local"——归属账号变了,秘密会挂到账户名下(回执与配额 accordingly)。
3.2 Bearer:直接拒绝
拿 Token 当 Bearer 用(很多现代 API 的习惯)会被明确打回,实测回显:
$ curl -s -w ' [HTTP:%{http_code}]' -X POST http://192.168.146.131:8132/api/v2/secret/conceal \
-H 'Authorization: Bearer <apitoken>' -H 'Content-Type: application/json' \
-d '{"secret":{"secret":"x","ttl":60}}'
{"error":"Authentication Required","message":"[AUTH_TYPE_INVALID] Invalid authorization type","timestamp":1793007805,"code":"api_key_invalid","code_scope":"credential"} [HTTP:401]
错误体里 code_scope: "credential" 说明是凭据形态不被接受(不是凭据内容错误)。
3.3 会话与 CSRF:账户 API 的唯一通道
/api/account/* 只认浏览器会话。用 Basic 调它,实测被拒:
$ curl -s -w '\n[HTTP:%{http_code}]' -X POST http://192.168.146.131:8132/api/account/apitoken -u "admin@ots.local:<apitoken>"
{"error":"Authentication Required","message":"[SESSION_NOT_AUTHENTICATED] Not authenticated","timestamp":1793007869,"code":"not_authenticated","code_scope":"customer_session"} [HTTP:401]
注意 code_scope 从 credential 换成了 customer_session——两套 401 的语义可以靠这个字段区分。正确的 Token 签发流程是浏览器会话流:登录后服务端在响应头下发 x-csrf-token(登录成功会轮换新值),后续账户 API 请求带上 X-CSRF-Token 请求头与会话 Cookie。实测按此流程完成过签发与注销:POST /api/account/apitoken 返回 {"record":{"apitoken":"…","active":true}} 形态的 JSON,POST /api/account/apitoken/destroy 注销指定 Token;POST /api/account/change-password 同属这一会话面。脚本化运维如果需要批量签 Token,现实的做法是用浏览器登录一次拿会话与 CSRF 头,或者固定使用首个 Token 不轻易轮换。
顺带排除一个误区:v2 面下没有账户接口——POST /api/v2/account/apitoken 实测返回:
{"error":"Not Found","error_type":"NotFound"} [HTTP:404]
四、标识符体系:两把钥匙别混用
一次分享会产生两个 64 位标识符(8 位短前缀 + 全长):
- secret_key / secret_identifier:秘密本体的取件钥匙,给「读秘密」的端点用;
- metadata_key / identifier(receipt):回执标识,给「看状态/焚毁」的端点用。
v1 的创建响应同时返回两把(见第四节回显);v2 的创建响应把它们分装在 record.receipt.identifier 与 record.secret.identifier 下。混用的实测代价:把 secret 标识符塞给回执端点,得到 404 且错误信息里回显了你传的标识符:
$ curl -s -w ' [HTTP:%{http_code}]' -u "admin@ots.local:<apitoken>" \
http://192.168.146.131:8132/api/v2/receipt/<secret_identifier>
{"error":"identifier: 0iz7shgc5f6r2e37669x9awxdzruyzee6x7e2od1oz0tp7c8g9rl6pkg29cjih","error_type":"RecordNotFound","request_id":"2uetupmidt7t7"} [HTTP:404]
换成正确的 receipt 标识符后同一端点正常返回(见 5.5 节)。
五、v1 实战:一次性秘密全生命周期
5.1 创建与两次读取
$ curl -s -X POST http://192.168.146.131:8132/api/v1/share \
-d 'secret=D5-api-echo-2026&ttl=3600'
{"custid":"anon","metadata_key":"fwl0pumap9sr8bda6globmhoewkouq4klqxqjunc0prtbu72uzcytumqdyvweb","secret_key":"eihc8wk9ul20g8as6hmr3j0dzcqh0j2srv1985n947nl1k3x0vejn820sfv0x8","ttl":3600,"metadata_ttl":7200,"secret_ttl":3600,"metadata_url":"http://192.168.146.131:8132/receipt/fwl0pumap9sr8bda6globmhoewkouq4klqxqjunc0prtbu72uzcytumqdyvweb","state":"new","updated":1793007740,"created":1793007740,"recipient":[],"share_domain":"","passphrase_required":false,"identifier":"fwl0pumap9sr8bda6globmhoewkouq4klqxqjunc0prtbu72uzcytumqdyvweb","secret_identifier":"eihc8wk9ul20g8as6hmr3j0dzcqh0j2srv1985n947nl1k3x0vejn820sfv0x8","has_passphrase":false,"recipients":[],"receipt_ttl":7200,"receipt_url":"http://192.168.146.131:8132/receipt/fwl0pumap9sr8bda6globmhoewkouq4klqxqjunc0prtbu72uzcytumqdyvweb"}
第一次读取(返回明文并立即销毁):
$ curl -s -w ' [HTTP:%{http_code}]' -X POST http://192.168.146.131:8132/api/v1/secret/eihc8wk9ul20g8as6hmr3j0dzcqh0j2srv1985n947nl1k3x0vejn820sfv0x8
{"value":"D5-api-echo-2026","secret_key":"eihc8wk9ul20g8as6hmr3j0dzcqh0j2srv1985n947nl1k3x0vejn820sfv0x8","share_domain":"http://192.168.146.131:8132"} [HTTP:200]
第二次读取(一次性语义生效):
$ curl -s -w ' [HTTP:%{http_code}]' -X POST http://192.168.146.131:8132/api/v1/secret/eihc8wk9ul20g8as6hmr3j0dzcqh0j2srv1985n947nl1k3x0vejn820sfv0x8
{"secret_key":"eihc8wk9ul20g8as6hmr3j0dzcqh0j2srv1985n947nl1k3x0vejn820sfv0x8","message":"Unknown secret"} [HTTP:404]
留意匿名创建的 TTL 细节:请求 ttl=3600,响应 secret_ttl: 3600 而 metadata_ttl: 7200——回执比秘密多活一倍,创建者来得及事后查状态;登录用户的 v2 创建里两者相等(见 5.1)。匿名 TTL 上限(请求 30 天被钳到 7 天)的实测与参数解释见《配置参数详解》第三节。
5.2 口令保护与防枚举设计
创建时加 passphrase=link-2026,三种读取姿势的实测回显:
# 不带口令
{"secret_key":"0zxuk13j9dv6d2vuq2bnmp44ytb1u0pgn4ivp5arr2ugzif6o0354giaj8a49i","message":"Unknown secret"} [HTTP:404]
# 口令错误
{"secret_key":"0zxuk13j9dv6d2vuq2bnmp44ytb1u0pgn4ivp5arr2ugzif6o0354giaj8a49i","message":"Unknown secret"} [HTTP:404]
# 口令正确
{"value":"D5-pass-echo","secret_key":"0zxuk13j9dv6d2vuq2bnmp44ytb1u0pgn4ivp5arr2ugzif6o0354giaj8a49i","share_domain":"http://192.168.146.131:8132"} [HTTP:200]
三个失败态(口令缺失/口令错误/秘密不存在)全部返回同一个 Unknown secret 404——接口层不区分「不存在」和「口令不对」,杜绝用错误响应差异枚举口令或探测秘密是否存在。脚本重试逻辑不要试图从 404 文案里区分场景,没得分。
5.3 焚毁:对未读秘密有效
焚毁端点挂在回执标识下:POST /api/v1/receipt/<metadata_key>/burn。对尚未被读取的秘密实测:
$ curl -s -w ' [HTTP:%{http_code}]' -X POST http://192.168.146.131:8132/api/v1/receipt/2hd2x1gxe3348zedoqt80uovj2f8ejhuixqdahyxc64m2h1jdu50ayzsp9v0xd/burn
{"state":{"custid":"anon","metadata_key":"2hd2x1gxe3348zedoqt80uovj2f8ejhuixqdahyxc64m2h1jdu50ayzsp9v0xd","secret_key":"","ttl":3600,"metadata_ttl":7200,"secret_ttl":null,"metadata_url":"http://192.168.146.131:8132/receipt/2hd2x1gxe3348zedoqt80uovj2f8ejhuixqdahyxc64m2h1jdu50ayzsp9v0xd","state":"burned","updated":1793007791,"created":1793007791,"recipient":[],"share_domain":"","identifier":"2hd2x1gxe3348zedoqt80uovj2f8ejhuixqdahyxc64m2h1jdu50ayzsp9v0xd","secret_identifier":"","recipients":[],"receipt_ttl":7200,"receipt_url":"http://192.168.146.131:8132/receipt/2hd2x1gxe3348zedoqt80uovj2f8ejhuixqdahyxc64m2h1jdu50ayzsp9v0xd"},"secret_shortkey":"gb46fznv"} [HTTP:200]
响应里 state: "burned"、secret_key 与 secret_identifier 被清空成空串、只留 8 位 secret_shortkey 作对账凭据。焚毁后再读,同样得到 Unknown secret 404。
两个边界:先读后焚(读已销毁)时同一端点返回 {"secret_key":"<你传的标识符>","message":"Unknown secret"} [HTTP:404]——焚毁动作本身也是一次性的;对已过期秘密焚毁同理。所以「焚毁兜底」脚本要在读到 200 之前就准备好 metadata_key。
六、v2 实战:现代接口
6.1 载荷嵌套规则与 conceal
v2 创建类请求的 JSON 必须嵌套在 "secret" 键下,扁平写法直接被格式校验打回:
$ curl -s -X POST http://192.168.146.131:8132/api/v2/secret/conceal -u "admin@ots.local:<apitoken>" \
-H 'Content-Type: application/json' -d '{"secret":"C1-ttl-clamp-verify-2026","ttl":2592000}'
{"error":"Incorrect payload format","request_id":"3r93fjy5ypct0"}
正确写法(登录用户请求 30 天 TTL,实测原样生效——匿名上限不约束认证用户):
$ curl -s -X POST http://192.168.146.131:8132/api/v2/secret/conceal \
-u "admin@ots.local:<apitoken>" -H 'Content-Type: application/json' \
-d '{"secret":{"secret":"D5-reveal-me-2026","ttl":3600}}'
{"success":true,"record":{"receipt":{...,"identifier":"63qqltyvc04ovbeno6ocnbfoomryr4g8es9o5cf12hy9fyizbldg1evowf6m65",...,"state":"new","secret_shortid":"0iz7shgc","secret_identifier":"0iz7shgc5f6r2e37669x9awxdzruyzee6x7e2od1oz0tp7c8g9rl6pkg29cjih","secret_ttl":3600,"metadata_ttl":3600,"receipt_ttl":3600,"lifespan":3600,...,"is_burned":false,"is_expired":false,"is_orphaned":false,"is_destroyed":false,"has_passphrase":false,"kind":"conceal","source":"standard"},"metadata":{...同构...},"secret":{"identifier":"0iz7shgc5f6r2e37669x9awxdzruyzee6x7e2od1oz0tp7c8g9rl6pkg29cjih","key":"0iz7shgc5f6r2e37669x9awxdzruyzee6x7e2od1oz0tp7c8g9rl6pkg29cjih","shortid":"0iz7shgc","state":"new","lifespan":3600,"secret_ttl":3600,"has_passphrase":false,"verification":false,"display_type":"text",...}},"details":{"kind":"conceal","recipient":[],"recipient_safe":[]}}
(响应较长,上为节选,字段名与数值逐字保留。)响应是三联结构:record.receipt(回执)、record.metadata(与 receipt 同构的元数据)、record.secret(秘密本体),外加 details.kind 标注动作类型。
6.2 generate:服务端生成密码
$ curl -s -w ' [HTTP:%{http_code}]' -X POST http://192.168.146.131:8132/api/v2/secret/generate \
-u "admin@ots.local:<apitoken>" -H 'Content-Type: application/json' -d '{"secret":{"ttl":3600}}'
{"success":true,"record":{...,"secret":{"identifier":"8hvf1ylxlqdyvejj3rm8dlc0ww3ma7k8o3fej3j9c0l57jvgoywydnyy9pm6pq",...}},"details":{"kind":"generate","recipient":[],"recipient_safe":[]}} [HTTP:200]
结构同 conceal,details.kind 变为 "generate";生成的密码本体同样要用揭示流程取(回执页只展示 60 秒,由配置 generated_value_display_ttl 控制)。生成器的长度与字符集参数见配置篇 3.4。
6.3 reveal:两步确认才吐明文
这是 v2 与 v1 最大的行为差异——揭示是两步流。第一步不带确认参数,只回确认页元数据(实测两种写法都停在第一步):
$ curl -s -w ' [HTTP:%{http_code}]' -X POST http://192.168.146.131:8132/api/v2/secret/0iz7shgc.../reveal -u "admin@ots.local:<apitoken>"
{"record":{...,"state":"new",...},"details":{"continue":false,"is_owner":true,"show_secret":false,"display_lines":4,"one_liner":null}} [HTTP:200]
# 带 JSON 体但嵌套在 "secret" 键下——同样停在第一步
{"record":{...,"state":"new",...},"details":{"continue":false,"is_owner":true,"show_secret":false,"display_lines":4,"one_liner":null}} [HTTP:200]
第二步:continue 必须是扁平键(实测嵌套写法无效),响应带出明文并销毁:
$ curl -s -w ' [HTTP:%{http_code}]' -X POST http://192.168.146.131:8132/api/v2/secret/0iz7shgc5f6r2e37669x9awxdzruyzee6x7e2od1oz0tp7c8g9rl6pkg29cjih/reveal \
-u "admin@ots.local:<apitoken>" -H 'Content-Type: application/json' -d '{"continue":true}'
{"record":{"identifier":"0iz7shgc5f6r2e37669x9awxdzruyzee6x7e2od1oz0tp7c8g9rl6pkg29cjih","key":"0iz7shgc5f6r2e37669x9awxdzruyzee6x7e2od1oz0tp7c8g9rl6pkg29cjih","shortid":"0iz7shgc","state":"revealed","lifespan":3600,"secret_ttl":3600,"has_passphrase":false,"verification":false,"display_type":"text","created":1793007800.58156,"updated":1793007800.58156,"is_previewed":false,"is_revealed":true,"is_viewed":false,"is_received":true,"secret_value":"D5-reveal-me-2026"},"details":{"continue":true,"is_owner":true,"show_secret":true,"display_lines":4,"one_liner":true}} [HTTP:200]
state 从 new 翻转为 revealed,is_received: true,明文在 secret_value。此后再调同一端点,记录已销毁:
{"error_type":"RecordNotFound","request_id":"2fm2th4fucguh"} [HTTP:404]
口令保护的秘密在第一步后需要带 passphrase 再确认(错误口令同样拿不到明文),两步机制对防误触和防爬虫预取都有效——写自动化脚本时别把第一次 200 当成拿到密码,判断 details.show_secret 是否为 true。
6.4 status:不泄露存在性的状态查询
活跃秘密(200,含剩余秒数):
$ curl -s -w ' [HTTP:%{http_code}]' -u "admin@ots.local:<apitoken>" \
http://192.168.146.131:8132/api/v2/secret/0iz7shgc.../status
{"record":{"identifier":"0iz7shgc...","key":"0iz7shgc...","shortid":"0iz7shgc","state":"new","lifespan":3600,"secret_ttl":3600,"has_passphrase":false,"verification":false,"display_type":"text","created":1793007800.58156,"updated":1793007800.58156,"is_previewed":false,"is_revealed":false,"is_viewed":false,"is_received":false},"details":{"current_expiration":3600}} [HTTP:200]
已销毁与从未存在的标识符返回完全相同的响应——状态端点同样不泄露存在性:
$ curl -s -u "admin@ots.local:<apitoken>" http://192.168.146.131:8132/api/v2/secret/<已销毁标识符>/status
{"record":{"state":"unknown"}} [HTTP:200]
$ curl -s -u "admin@ots.local:<apitoken>" http://192.168.146.131:8132/api/v2/secret/aaaa.../status
{"record":{"state":"unknown"}} [HTTP:200]
注意与 reveal 的差异:reveal 打在已销毁记录上返回 404 RecordNotFound,status 永远 200 + state: unknown。监控脚本想区分「秘密还活着/已消亡」,用 record.state 字段而不是 HTTP 状态码。
6.5 receipt:回执全量
用 receipt 标识符查(第三节混用示例的正确姿势):
$ curl -s -w ' [HTTP:%{http_code}]' -u "admin@ots.local:<apitoken>" \
http://192.168.146.131:8132/api/v2/receipt/63qqltyvc04ovbeno6ocnbfoomryr4g8es9o5cf12hy9fyizbldg1evowf6m65
{"record":{...,"state":"revealed","secret_shortid":"0iz7shgc","secret_ttl":3600,"metadata_ttl":3600,"receipt_ttl":3600,"lifespan":3600,...,"is_viewed":true,"is_received":true,"is_revealed":true,"is_burned":false,"is_expired":false,"is_orphaned":false,"is_destroyed":true,"has_passphrase":false,"kind":"conceal","source":"standard","secret_state":null,"natural_expiration":"60 minutes","expiration":1793011400,"expiration_in_seconds":3600,"share_path":null,"burn_path":"receipt/63qqltyv.../burn","receipt_path":"receipt/63qqltyv...","metadata_path":"receipt/63qqltyv...","share_url":null,"receipt_url":"http://192.168.146.131:8132/receipt/63qqltyv...","burn_url":"http://192.168.146.131:8132/receipt/63qqltyv.../burn"},"details":{"type":"record","display_lines":4,"no_cache":true,"secret_realttl":null,"view_count":1,"first_access":1793007800.632271,"last_access":1793007800.632271,"has_passphrase":false,"can_decrypt":false,"secret_value":null,"show_secret":false,"show_secret_link":false,"show_receipt_link":false,"show_receipt":true,"show_recipients":false}} [HTTP:200]
(节选,字段逐字保留。)这张回执是揭示后打的:is_destroyed: true、is_revealed: true、view_count: 1,还给出 natural_expiration: "60 minutes"、焚毁路径 burn_path 与 burn_url——做「分享后跟踪」面板的数据源就是它。
6.6 语言与元信息
$ curl -s -u "admin@ots.local:<apitoken>" http://192.168.146.131:8132/api/v2/supported-locales
{"success":true,"locales":["en"],"default_locale":"en","locale":"en"}
官方分发物实际只带英文资产(配置里的 30 语言是候选集),解释见配置篇第十节。
七、v3 guest:受控匿名面
/api/v3/guest/* 是给匿名调用方保留的现代格式面,载荷嵌套规则与 v2 相同,无需任何鉴权:
$ curl -s -w ' [HTTP:%{http_code}]' -X POST http://192.168.146.131:8132/api/v3/guest/secret/conceal \
-H 'Content-Type: application/json' -d '{"secret":{"secret":"D5-guest-echo","ttl":3600}}'
{"success":true,"record":{"receipt":{...,"owner_id":null,"state":"new","secret_identifier":"f3u2zp0n4w8j6vg3iwgr3cy93czyyenyegydka37xpg0rd376ki631i9xe9m4e","secret_ttl":3600,...},"secret":{"identifier":"f3u2zp0n4w8j6vg3iwgr3cy93czyyenyegydka37xpg0rd376ki631i9xe9m4e",...}},"details":{"kind":"conceal","recipient":[],"recipient_safe":[]}} [HTTP:200]
(节选。)与认证创建的差异:owner_id: null(无归属账号),且响应里没有 metadata 块——匿名 guest 只拿到 receipt 与 secret 两个记录。这类端点由配置 site.interface.api.guest_routes.*(环境变量 API_GUEST_ROUTES_ENABLED 及 API_GUEST_CONCEAL/GENERATE/REVEAL/BURN/SHOW/RECEIPT)逐项开关,默认全开;不需要匿名 API 的内网部署建议关掉总闸,参数见配置篇 2.3。
八、账户与 Token 管理
| 端点 | 方法 | 鉴权 | 作用 |
|---|---|---|---|
/api/account/apitoken | POST | 会话 + CSRF | 签发新 API Token,返回 {"record":{"apitoken":"…","active":true}} |
/api/account/apitoken/destroy | POST | 会话 + CSRF | 注销指定 Token |
/api/account/change-password | POST | 会话 + CSRF | 修改登录密码 |
三者都在无版本前缀的 /api/account 面下(第二节实测:Basic 调用 401 SESSION_NOT_AUTHENTICATED,v2 前缀下的同名路径 404)。会话流的 CSRF 机制来自配置 site.middleware.authenticity_token(默认开启):登录响应头下发 x-csrf-token,后续写请求回填 X-CSRF-Token 头。Token 是唯一能用于 Basic 的 API 凭据,泄露后应立即 destroy 再签发——每账号多 Token 并存,按 active 字段区分。
九、脚本化集成坑位清单
十项实测沉淀,按踩中概率排序:
- Bearer 无效:v2 只认 Basic(邮箱 + API Token),
Authorization: Bearer得到AUTH_TYPE_INVALID401(第二节回显); - v2 载荷必须嵌套:
{"secret": "x"}打回Incorrect payload format,要写{"secret": {"secret": "x", ...}};reveal 的continue却是扁平键——嵌套规则因端点而异,别想当然统一; - 两把标识符:读秘密用 secret_key,查状态/焚毁用 metadata_key,塞错得一模一样的 404(第三节回显);
- v2 reveal 是两步流:第一个 200 不带明文,
details.show_secret为 false;必须再 POST{"continue":true}才吐secret_value; - 防枚举统一 404:v1 读取与 v2 status 对「不存在/口令错/已销毁」返回同型响应,脚本别试图从错误体区分场景;
- 匿名 TTL 静默钳制:v1 匿名请求超长 TTL 不报错、响应里
ttl直接变小(2592000 → 604800 实测),读回执别用请求参数而要用响应体; - 匿名回执寿命是秘密的两倍:匿名 v1 创建
metadata_ttl=2×secret_ttl(7200/3600 实测),登录用户两者相等; - 焚毁也是一次性:已读/已焚的秘密再调 burn 得到
Unknown secret404,兜底脚本要容忍; - 账户 API 三个「不」:不吃 Basic、不吃 Bearer、不在
/api/v2/前缀下——只走会话 + CSRF; - 限流在暗处:匿名创建受 500 次/小时的 IP 限流(/24 网段共享桶,v0.26.11 起),触发后锁 1 小时,错误处理里给限流留退避。
十、排障交叉引用
- 秘密创建与部署层的坑(会话 Cookie、注册参数名、TTL 钳制)见《OneTimeSecret v0.26.19 Docker Compose 部署指南》排障实录;
- TTL 上限、口令长度、内容长度等参数语义见《OneTimeSecret v0.26.19 配置参数详解》;
- 源码部署与 systemd 见《Debian 12 安装 OneTimeSecret v0.26.19 指南》。
十一、参考链接
- OneTimeSecret 官方仓库:https://github.com/onetimesecret/onetimesecret
- 官方 API 文档:https://docs.onetimesecret.com/
- v0.26.19 发布页:https://github.com/onetimesecret/onetimesecret/releases/tag/v0.26.19
- 本专栏系列:《OneTimeSecret v0.26.19 介绍与选型指南》《Debian 12 安装 OneTimeSecret v0.26.19 指南》《OneTimeSecret v0.26.19 Docker Compose 部署指南》《OneTimeSecret v0.26.19 配置参数详解》