プレビューが出るチャットにワンタイムリンクを貼ったあと、閲覧回数がすでに消費されているとは限らない理由
チャンネルにカードが出たことと、受け手が暗号文を読んだことは別です。Slack、Discord、Chatwork、Teams はプレビューを描くために、先にページへ HTTP GET を送ります。この取得が読むのはタイトルと短い説明がほとんどで、「開いて見る」は押さず、アドレスバーの # 以降の鍵も運びません。以下では「カードが出た」と「閲覧を1回消費した」を分け、その場で照合できる手順を書きます。ワンタイムリンク作成ページの操作説明ではありません。
まずカードが取る層を分ける
本記事が答えるのは「プレビューが出るチャットに貼ったあと、閲覧回数がすでに消費されているとは限らない理由」だけです。ワンタイムリンクのフォーム説明でも、「# がサーバーログに入らない理由」の層でもありません。
リンクプレビューが取る層
カードは、送信ボタンを押した前後の数秒で出ます。動いているのはチャット製品のサーバーであり、受け手の指ではありません。
https://…/s.html?id=…#… を Slack、Discord、Chatwork、Microsoft Teams に貼ると、入力欄やメッセージの下に小さなカードが出ることがあります。タイトル、一文の説明、ときどきアイコンです。このカードを「リンクはすでに開かれた」と読む人は多いですが、順序は逆です。チャット製品は先にそのアドレスへ HTTP GET を送り、返ってきた HTML から og:title、og: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 まで進んだか」です。カードが出たことは、タイトルが読まれた証明です。暗号文がまだあるかは、別の照合です。
最初の 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 へ、暗号文がまだあるかを尋ねます。返るのは active、burned、expired だけで、閲覧回数は増えず、記録も消しません。「開いて見る」を押したときだけ、ブラウザは /api/secrets/{id} を呼んで暗号文を取り、# 以降の鍵でこの端末が復号します。作成時に決めた回数に達すると、サーバーは暗号文を削除します。再取得は 410 です。プレビュー用ボットは閲覧ページの HTML で止まり、よくある場合は鍵もなく、そのボタンも押しません。
静的な閲覧ページを返します。タイトルは「ワンタイムリンクを開く」で、平文はありません。プレビューカードはたいていこの層で止まります。
まだある、破棄済み、期限切れ、だけを答えます。何度尋ねても回数は増えません。
ここで初めて 1 回の閲覧になります。既定は 1 回のあと削除です。閲覧ページでは「開いて見る」に対応します。
閲覧ページには、その場で照合できる一文もあります。ボタンを押すと「閲覧回数を1回消費することがあり」ます。スクリプトがなければ復号できず、ページは <noscript> でそれを説明しています。プレビュー用ボットが HTML だけを読み、スクリプトを実行しなければ、状態照会すら出しません。内蔵ブラウザがスクリプトを実行したとしても、先に進むのは「暗号文はまだあります」までです。暗号文を取るには、まだボタンが必要です。
どのボットが来て、何を持っていくか
カードを描けるソフトは多いです。持っていくのはページの見出しであり、# 以降の鍵でも、復号後の平文でもありません。
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} が呼ばれた」とは言えません。
本当に回数が減るとき
「必ずしも減らない」は、「永遠に減らない」ではありません。「開いて見る」まで届くアクセスだけが、暗号文取得の層に入ります。
いちばん多い消費者は、受け手本人です。完全なリンクを開き、「暗号文はまだあります」を見て、「開いて見る」を押します。既定の回数は 1 です。この一回が成功したあと、サーバーは暗号文を削除します。同じリンクを再度開くと、破棄済みと出ます。作成時に回数を 2 以上にすると、上限に達するまで暗号文は残ります。期限が来ても削除されます。既定は 24 時間で、1 時間または 7 日にもできます。「閲覧後のみ破棄」を選ぶと、時計ではなく回数だけで消します。
二種類目は、スクリプトを実行し、クリックまで真似する環境です。一部のメールセキュリティゲートウェイやリンクサンドボックスは、隔離ブラウザで URL を開き、ページ上の主ボタンまで押すことがあります。それは「カードを一枚描く」ではなく、人と同じ、もう一度の完全な訪問です。サンドボックスが「開いて見る」を押すと回数は減り、受け手はそのあと破棄済みを見ます。この層はゲートウェイ自身の報告で照合してください。チャットのカードから逆算はできません。
三種類目は、暗号文取得の API アドレスそのものを送り出すことです。閲覧ページのアドレスは s.html?id= です。回数を消費するのは /api/secrets/{id} です。普通の利用者は後者をコピーしません。誰かが API の URL をプレビュー付きチャンネルへ貼り、その製品が最初の GET を暗号文取得と同じに扱うと、プレビューの段階で回数が減ります。UsePwd の共有形はこのアドレスではありません。作成ページがコピーするのは、# 付きの閲覧ページリンクです。
プレビューが回数を減らさなかったことは、秘密が受け手のところにだけある、という意味ではありません。チャット製品は完全な URL を保存し、ワークスペースの管理者は記録を書き出せることがあります。回数が制限するのは、サーバー上の暗号文の寿命です。チャンネルに残ったアドレスを、誰がまた開けるかではありません。
その場でプレビューを照合する
目標は「世界中のチャット製品が暗号文を読まない」ことの証明ではありません。カードが出たあと、暗号文取得の API がまだ呼ばれていないことを示すことです。
-
01
本番の受け渡しに使わないテスト文を用意する
使っている API キーやログインパスワードは書かないでください。たとえばワンタイムリンクを開き、
PreviewCard-20260905を入れ、回数は既定の 1、期限は 24 時間のままにします。ページは登録不要ですぐ使えます。以下の手順はプレビューと API の照合だけであり、この文字列を同僚への正式な鍵にはしません。 -
02
完全なリンクを控え、先に自分で閲覧ページを開く
生成後は
s.html?id=と#の二段が見えます。同じリンクを、いまのブラウザで開きます。ページは「暗号文はまだあります」で止まり、「開いて見る」が出ます。先にボタンは押さないでください。F12 でネットワークパネルを開き、/api/secrets/{id}/statusへのリクエストが見えるはずです。この時点では、/statusが付かない/api/secrets/{id}への暗号文取得は出ていてはなりません。 -
03
同じリンクを、プレビューが出るテストチャンネルへ貼る
自分だけの Slack チャンネル、Discord の DM、Chatwork のテストルームを使います。貼ったあと、カードが出るまで待ちます。カードのタイトルは「ワンタイムリンクを開く」に近く、
PreviewCard-20260905は出てはなりません。閲覧ページを再読み込みするか、ボタンを押していない別ウィンドウで同じリンクを開き直すと、まだ「暗号文はまだあります」で止まるはずです。ここで「破棄済み」なら、カードそのものが閲覧なのではなく、別のアクセスが暗号文取得まで進んだ、という意味です。 -
04
必要なときだけ、アクセスログの User-Agent を見る
サイトのアクセスログが見られるなら、プレビュー段階で多いのは
/ja/s.html?id=…への GET で、User-Agent にSlackbot-LinkExpandingが含まれることがあります。同じ時刻に/api/secrets/{id}の成功した取得は、出ていてはなりません。Slack は同じ URL をおよそ 30 分キャッシュするため、短い時間の再貼り付けは二度目の取得を生まないことがあります。 -
05
最後に「開いて見る」を押し、回数が減ったことを確認する
ここでボタンを押します。ネットワークパネルに暗号文取得が出て、ページはテスト原文を表示します。閉じてから同じリンクを開くと、破棄済みと出ます。この段は、「ボタンのあと」と「カードのあと」が同じ状態ではないことを照合するためです。測り終わったらリンクは捨ててください。本番の受け手には回さないでください。
新しい鍵を別の人へ渡すだけなら、平文をチャンネルへ貼って「カードを一枚試す」ことはしないでください。テスト文と本番の文は分けます。本番の受け渡しは、それでもワンタイムリンクです。環境変数が流出したあとの二巡目は、クラウドの環境変数が流出したあと、新しい鍵をチャットに送ってはいけない理由にあります。あちらは「API キーそのものを、検索できる記録へ貼るな」です。本記事は「リンクを貼ったあと、プレビュー取得がどの層で止まるか」です。
回数が残っても、チャットに残るもの
「カードが暗号文を焼かなかった」を「このリンクはチャンネルから消えた」と読むと、同じように照合できる境界をいくつか落とします。
チャット製品が保存するのは、貼り出したアドレス全体です。履歴を検索できる人、ワークスペースを書き出せる人、バックアップを開ける人は、閲覧ページをまた開けます。回数が残っているあいだ、あとから開いた人も「開いて見る」を押して平文を読めます。回数が尽きたあと、記録に残るのは破棄済みのリンクと、当時のカードタイトルです。管理者の書き出しは、サーバー上ですでに消えた暗号文は戻せません。ただし「当時のチャンネルに完全な URL が出た」ことは消せません。
スクリーンショットと転送は、別のコピーです。閲覧成功後の平文を切り取る、リンクを別のプレビュー付きグループへ転送する、すると境界は「この一回の GET」から「次のホップが誰を通るか」へ移ります。ワンタイムリンクが制限するのは、サーバー上の暗号文の寿命と回数です。受け手が画面を撮るかは止めません。UsePwd にアカウントもパスワード保管庫もなく、なくしたリンクを利用者ごとに取り戻すこともできません。長く残す、複数端末で同期する、なら専用のパスワードマネージャーを使ってください。立場の説明は概要にあります。
ファイルは三本目の道です。1 件 5 GB までの本機バックアップはファイル暗号化へ行き、.lock / .enc を出し、ファイルは既定ではアップロードしません。パスフレーズ自体はパスワード生成で 6–128 文字のあいだで作り、パスワード強度チェックで強さを見ます。これらのページも本記事と同じく、登録不要ですぐ使えます。「プレビューが回数を減らさなかった」を「どのチャンネルへ貼っても安全」とは読まないでください。チャンネルの記録と、プレビュー取得は、同じ層ではありません。
プレビューのあとよく聞かれること
次の四件は本記事の境界だけに答えます。作成ページのボタン説明は繰り返しません。
読み終えたらプレビューカードを照合する
記事が答えるのは「プレビューのあと、回数がすでに消費されているとは限らない理由」です。本番ではないテストリンクをその場で作り、テストチャンネルへ貼るなら、ワンタイムリンクを開けばよく、先に登録する必要はありません。