Burn-Link ·

プレビューが出るチャットにワンタイムリンクを貼ったあと、閲覧回数がすでに消費されているとは限らない理由

チャンネルにカードが出たことと、受け手が暗号文を読んだことは別です。Slack、Discord、Chatwork、Teams はプレビューを描くために、先にページへ HTTP GET を送ります。この取得が読むのはタイトルと短い説明がほとんどで、「開いて見る」は押さず、アドレスバーの # 以降の鍵も運びません。以下では「カードが出た」と「閲覧を1回消費した」を分け、その場で照合できる手順を書きます。ワンタイムリンク作成ページの操作説明ではありません。

プレビュー取得を照合 その場で確認できる 登録不要ですぐ使える
00 / 目次

まずカードが取る層を分ける

本記事が答えるのは「プレビューが出るチャットに貼ったあと、閲覧回数がすでに消費されているとは限らない理由」だけです。ワンタイムリンクのフォーム説明でも、「# がサーバーログに入らない理由」の層でもありません。

01 / 取得

リンクプレビューが取る

カードは、送信ボタンを押した前後の数秒で出ます。動いているのはチャット製品のサーバーであり、受け手の指ではありません。

https://…/s.html?id=…#… を Slack、Discord、Chatwork、Microsoft Teams に貼ると、入力欄やメッセージの下に小さなカードが出ることがあります。タイトル、一文の説明、ときどきアイコンです。このカードを「リンクはすでに開かれた」と読む人は多いですが、順序は逆です。チャット製品は先にそのアドレスへ HTTP GET を送り、返ってきた HTML から og:titleog:description、あるいは普通の <title> を抜き、それからカードを描きます。受け手はその時点で、まだ中に入っていないことがほとんどです。

Slack はこの動きを公式に書いています。既定では、利用者や Slack アプリが送った、完全な URL を含むメッセージはリンクを展開します。メディアも展開できます。説明は Slack ドキュメント《Unfurling links in messages》 にあります。リンク展開専用のボットは自分を Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots) と名乗ります。同じページの Slack Robots は、ページをできるだけ少なく取り(HTTP Range を使う)、目的は oEmbed、Twitter Card、Open Graph タグだと書いています。タグが画像・動画・音声を指していれば、そのファイルを妥当性確認のために取りにいきます。応答はおよそ 30 分キャッシュされます。文書は「ページ上の JavaScript を実行する」とも「利用者の代わりにボタンを押す」とも書いていません。

ブラウザがこの GET を出すとき、# 以降はリクエスト行に入りません。IETF の RFC 3986 §3.5 は fragment をクライアント側の識別子とし、サーバーが URI を処理する材料にはしません。プレビュー用ボットがチャット欄に見えている文字列をそのままコピーしても、UsePwd に届くリクエストはたいてい s.html?id={番号} までです。鍵がアクセスログに入らない理由は、既存記事URL の # fragment がサーバーログに出ない理由にあります。本記事が補うのは次の層です。鍵を持たないこの GET は、「1回読んだ」になるのか。

先に一文

プレビューで問うべきなのは「チャット製品がこの URL を見たか」ではありません。「この GET が暗号文取得の API まで進んだか」です。カードが出たことは、タイトルが読まれた証明です。暗号文がまだあるかは、別の照合です。

02 / 層分け

最初の GET が閲覧にならない理由

「誰が暗号文を取るか」と「誰が閲覧ページを開いたか」を同じリクエストに縛ると、プレビュー用ボットが唯一の閲覧を使い切ります。層を分けたあと、この二つは同じ動作ではありません。

一度限りのリンクの中には、プレビュー対象そのものの URL に本文を載せているものがあります。ボットが一度 GET すると、サーバーは本文を返して記録を消します。受け手があとから開くと、「すでに読まれた」または 404 です。この失敗はよくあります。Slack が「悪意で秘密を焼いた」のではなく、RFC では安全でキャッシュ可能なはずの GET を、「暗号文を破棄する動作」に使った実装だからです。

UsePwd のワンタイムリンクは三つの段を分けます。作成時、現在のタブが Web Crypto で AES-256-GCM により暗号化します。平文の上限は 32 KB です。アップロードする欄は暗号文、有効期限、閲覧回数だけです。有効期限は 1 時間、24 時間、7 日、または「閲覧後のみ破棄」です。閲覧回数は 1–10、既定は 1 です。リンクの形は s.html?id={id}#{key} です。作成ページも閲覧ページも登録不要ですぐ使えます。サーバーが一時保管するのは暗号文だけで、平文は見えず、利用者ごとに取り戻すアカウントもありません。

受け手が閲覧ページを開くと、ページは先に /api/secrets/{id}/status へ、暗号文がまだあるかを尋ねます。返るのは activeburnedexpired だけで、閲覧回数は増えず、記録も消しません。「開いて見る」を押したときだけ、ブラウザは /api/secrets/{id} を呼んで暗号文を取り、# 以降の鍵でこの端末が復号します。作成時に決めた回数に達すると、サーバーは暗号文を削除します。再取得は 410 です。プレビュー用ボットは閲覧ページの HTML で止まり、よくある場合は鍵もなく、そのボタンも押しません。

閲覧ページの GET
s.html?id=番号

静的な閲覧ページを返します。タイトルは「ワンタイムリンクを開く」で、平文はありません。プレビューカードはたいていこの層で止まります。

状態照会
/secrets/{id}/status

まだある、破棄済み、期限切れ、だけを答えます。何度尋ねても回数は増えません。

暗号文取得
/secrets/{id}

ここで初めて 1 回の閲覧になります。既定は 1 回のあと削除です。閲覧ページでは「開いて見る」に対応します。

閲覧ページには、その場で照合できる一文もあります。ボタンを押すと「閲覧回数を1回消費することがあり」ます。スクリプトがなければ復号できず、ページは <noscript> でそれを説明しています。プレビュー用ボットが HTML だけを読み、スクリプトを実行しなければ、状態照会すら出しません。内蔵ブラウザがスクリプトを実行したとしても、先に進むのは「暗号文はまだあります」までです。暗号文を取るには、まだボタンが必要です。

03 / ボット

どのボットが来て、何を持っていくか

カードを描けるソフトは多いです。持っていくのはページの見出しであり、# 以降の鍵でも、復号後の平文でもありません。

Slack のリンク展開ボット、Discord の埋め込みカード用の取得、Chatwork や Teams が URL を一枚のカードにまとめるサーバー側リクエストは、同じ種類です。先にページを GET し、HTML からタイトルと説明を探します。Slack はさらに、robots.txt を従来のクローラ規則としては守らない、と書いています。リンク展開は「リンクを貼った人の代わりに動く」のであり、サイトを辿って歩きまわる収集ではない、という理由です。閲覧ページに noindex を付けても、検索エンジンの登録は止められても、チャンネルのカードは止められません。

UsePwd の閲覧ページは一時的な暗号文の場面であり、noindex, nofollow を付け、サイトマップにも入れません。ページタイトルは「ワンタイムリンクを開く」に固定し、説明は復号が現在のタブで完了することと、鍵が # の後ろにあることだけを書きます。プレビューカードが生成できたとき、チャンネルに出るのはこの共通文です。書き込んだパスワード、トークン、チケット原文は出ません。これは「サーバーログに鍵があるか」と同時に成り立ちます。カードは、誰かが閲覧ページを GET した証明です。ログに fragment は、それでもありません。

Slack は、同じ URL の展開結果をおよそ 30 分キャッシュすると書いています。短い時間に同じテストリンクを何度もチャンネルへ貼っても、二度目の取得は見えないことがあります。「ボットが来たか」を照合するなら、カードを何度更新したかではなく、自分のアクセスログに Slackbot-LinkExpanding のような User-Agent があるかを見てください。s.html への GET がログにあっても、「/api/secrets/{id} が呼ばれた」とは言えません。

閲覧ページの HTML 01 プレビューがよく取る層です。タイトルは共通文で、平文も # の鍵もありません。
状態 API 02 ページのスクリプトが動いたときだけ尋ねます。尋ねても回数は減りません。
暗号文取得 API 03 「開いて見る」に対応します。普通のカード取得はこの層まで行きません。
チャット履歴の完全な URL 04 別のコピーです。回数が残っていても、リンクが受け手の頭の中だけにあることにはなりません。
04 / 減るとき

本当に回数が減るとき

「必ずしも減らない」は、「永遠に減らない」ではありません。「開いて見る」まで届くアクセスだけが、暗号文取得の層に入ります。

いちばん多い消費者は、受け手本人です。完全なリンクを開き、「暗号文はまだあります」を見て、「開いて見る」を押します。既定の回数は 1 です。この一回が成功したあと、サーバーは暗号文を削除します。同じリンクを再度開くと、破棄済みと出ます。作成時に回数を 2 以上にすると、上限に達するまで暗号文は残ります。期限が来ても削除されます。既定は 24 時間で、1 時間または 7 日にもできます。「閲覧後のみ破棄」を選ぶと、時計ではなく回数だけで消します。

二種類目は、スクリプトを実行し、クリックまで真似する環境です。一部のメールセキュリティゲートウェイやリンクサンドボックスは、隔離ブラウザで URL を開き、ページ上の主ボタンまで押すことがあります。それは「カードを一枚描く」ではなく、人と同じ、もう一度の完全な訪問です。サンドボックスが「開いて見る」を押すと回数は減り、受け手はそのあと破棄済みを見ます。この層はゲートウェイ自身の報告で照合してください。チャットのカードから逆算はできません。

三種類目は、暗号文取得の API アドレスそのものを送り出すことです。閲覧ページのアドレスは s.html?id= です。回数を消費するのは /api/secrets/{id} です。普通の利用者は後者をコピーしません。誰かが API の URL をプレビュー付きチャンネルへ貼り、その製品が最初の GET を暗号文取得と同じに扱うと、プレビューの段階で回数が減ります。UsePwd の共有形はこのアドレスではありません。作成ページがコピーするのは、# 付きの閲覧ページリンクです。

逆に読まない

プレビューが回数を減らさなかったことは、秘密が受け手のところにだけある、という意味ではありません。チャット製品は完全な URL を保存し、ワークスペースの管理者は記録を書き出せることがあります。回数が制限するのは、サーバー上の暗号文の寿命です。チャンネルに残ったアドレスを、誰がまた開けるかではありません。

05 / 照合

その場でプレビューを照合する

目標は「世界中のチャット製品が暗号文を読まない」ことの証明ではありません。カードが出たあと、暗号文取得の API がまだ呼ばれていないことを示すことです。

  1. 01
    本番の受け渡しに使わないテスト文を用意する

    使っている API キーやログインパスワードは書かないでください。たとえばワンタイムリンクを開き、PreviewCard-20260905 を入れ、回数は既定の 1、期限は 24 時間のままにします。ページは登録不要ですぐ使えます。以下の手順はプレビューと API の照合だけであり、この文字列を同僚への正式な鍵にはしません。

  2. 02
    完全なリンクを控え、先に自分で閲覧ページを開く

    生成後は s.html?id=# の二段が見えます。同じリンクを、いまのブラウザで開きます。ページは「暗号文はまだあります」で止まり、「開いて見る」が出ます。先にボタンは押さないでください。F12 でネットワークパネルを開き、/api/secrets/{id}/status へのリクエストが見えるはずです。この時点では、/status が付かない /api/secrets/{id} への暗号文取得は出ていてはなりません。

  3. 03
    同じリンクを、プレビューが出るテストチャンネルへ貼る

    自分だけの Slack チャンネル、Discord の DM、Chatwork のテストルームを使います。貼ったあと、カードが出るまで待ちます。カードのタイトルは「ワンタイムリンクを開く」に近く、PreviewCard-20260905 は出てはなりません。閲覧ページを再読み込みするか、ボタンを押していない別ウィンドウで同じリンクを開き直すと、まだ「暗号文はまだあります」で止まるはずです。ここで「破棄済み」なら、カードそのものが閲覧なのではなく、別のアクセスが暗号文取得まで進んだ、という意味です。

  4. 04
    必要なときだけ、アクセスログの User-Agent を見る

    サイトのアクセスログが見られるなら、プレビュー段階で多いのは /ja/s.html?id=… への GET で、User-Agent に Slackbot-LinkExpanding が含まれることがあります。同じ時刻に /api/secrets/{id} の成功した取得は、出ていてはなりません。Slack は同じ URL をおよそ 30 分キャッシュするため、短い時間の再貼り付けは二度目の取得を生まないことがあります。

  5. 05
    最後に「開いて見る」を押し、回数が減ったことを確認する

    ここでボタンを押します。ネットワークパネルに暗号文取得が出て、ページはテスト原文を表示します。閉じてから同じリンクを開くと、破棄済みと出ます。この段は、「ボタンのあと」と「カードのあと」が同じ状態ではないことを照合するためです。測り終わったらリンクは捨ててください。本番の受け手には回さないでください。

新しい鍵を別の人へ渡すだけなら、平文をチャンネルへ貼って「カードを一枚試す」ことはしないでください。テスト文と本番の文は分けます。本番の受け渡しは、それでもワンタイムリンクです。環境変数が流出したあとの二巡目は、クラウドの環境変数が流出したあと、新しい鍵をチャットに送ってはいけない理由にあります。あちらは「API キーそのものを、検索できる記録へ貼るな」です。本記事は「リンクを貼ったあと、プレビュー取得がどの層で止まるか」です。

06 / 境界

回数が残っても、チャットに残るもの

「カードが暗号文を焼かなかった」を「このリンクはチャンネルから消えた」と読むと、同じように照合できる境界をいくつか落とします。

チャット製品が保存するのは、貼り出したアドレス全体です。履歴を検索できる人、ワークスペースを書き出せる人、バックアップを開ける人は、閲覧ページをまた開けます。回数が残っているあいだ、あとから開いた人も「開いて見る」を押して平文を読めます。回数が尽きたあと、記録に残るのは破棄済みのリンクと、当時のカードタイトルです。管理者の書き出しは、サーバー上ですでに消えた暗号文は戻せません。ただし「当時のチャンネルに完全な URL が出た」ことは消せません。

スクリーンショットと転送は、別のコピーです。閲覧成功後の平文を切り取る、リンクを別のプレビュー付きグループへ転送する、すると境界は「この一回の GET」から「次のホップが誰を通るか」へ移ります。ワンタイムリンクが制限するのは、サーバー上の暗号文の寿命と回数です。受け手が画面を撮るかは止めません。UsePwd にアカウントもパスワード保管庫もなく、なくしたリンクを利用者ごとに取り戻すこともできません。長く残す、複数端末で同期する、なら専用のパスワードマネージャーを使ってください。立場の説明は概要にあります。

ファイルは三本目の道です。1 件 5 GB までの本機バックアップはファイル暗号化へ行き、.lock / .enc を出し、ファイルは既定ではアップロードしません。パスフレーズ自体はパスワード生成で 6–128 文字のあいだで作り、パスワード強度チェックで強さを見ます。これらのページも本記事と同じく、登録不要ですぐ使えます。「プレビューが回数を減らさなかった」を「どのチャンネルへ貼っても安全」とは読まないでください。チャンネルの記録と、プレビュー取得は、同じ層ではありません。

07 / 質問

プレビューのあとよく聞かれること

次の四件は本記事の境界だけに答えます。作成ページのボタン説明は繰り返しません。

カードが出たら、もう読まれたのですか 01 たいてい違います。カードは閲覧ページが GET されたことだけを示します。回数は「開いて見る」を押したあとです。
LINE や Chatwork のプレビューはスクリプトを実行しますか 02 カードを作るサーバー側取得は、たいてい HTML だけを読みます。相手が内蔵ブラウザに入ってからスクリプトが動き、暗号文を取るにはさらにボタンが必要です。
# 以降の鍵はプレビュー用ボットに見えますか 03 HTTP リクエストは fragment を運びません。ボットが URL を取りにいくとき、たいてい疑問符後ろの番号までです。
作成と閲覧に登録は必要ですか 04 不要です。登録不要ですぐ使えます。上部バー右側は言語切替だけで、ログインやパスワード保管庫の入口はありません。
08 / 次の一歩

読み終えたらプレビューカードを照合する

記事が答えるのは「プレビューのあと、回数がすでに消費されているとは限らない理由」です。本番ではないテストリンクをその場で作り、テストチャンネルへ貼るなら、ワンタイムリンクを開けばよく、先に登録する必要はありません。