<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://aruma256.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://aruma256.dev/" rel="alternate" type="text/html" /><updated>2026-06-15T11:46:33+09:00</updated><id>https://aruma256.dev/feed.xml</id><title type="html">arumaのブログ</title><subtitle>arumaのブログです</subtitle><author><name>aruma256</name></author><entry><title type="html">sqldef + MySQL 8 で CHECK ～ IN (1要素) と書いたら ALTER が出続けた</title><link href="https://aruma256.dev/blog/2026/06/13/sqldef-mysql8-check-in-single.html" rel="alternate" type="text/html" title="sqldef + MySQL 8 で CHECK ～ IN (1要素) と書いたら ALTER が出続けた" /><published>2026-06-13T00:00:00+09:00</published><updated>2026-06-13T00:00:00+09:00</updated><id>https://aruma256.dev/blog/2026/06/13/sqldef-mysql8-check-in-single</id><content type="html" xml:base="https://aruma256.dev/blog/2026/06/13/sqldef-mysql8-check-in-single.html"><![CDATA[<p>sqldef + MySQL 8 の環境にて、schema.sql に <code class="language-plaintext highlighter-rouge">CHECK (priority IN ('normal'))</code> と書いたところ、2回目以降の実行でも毎回 <code class="language-plaintext highlighter-rouge">DROP CHECK</code> / <code class="language-plaintext highlighter-rouge">ADD CONSTRAINT</code> の ALTER が出続けるという現象に遭遇しました。</p>

<h2 id="再現">再現</h2>

<p>MySQL 8.4.8、mysqldef v3.11.3 で確認しました。</p>

<p>schema.sql:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">tasks</span> <span class="p">(</span>
  <span class="n">id</span> <span class="nb">BIGINT</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="n">AUTO_INCREMENT</span><span class="p">,</span>
  <span class="n">priority</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">50</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
  <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">id</span><span class="p">),</span>
  <span class="k">CONSTRAINT</span> <span class="n">chk_tasks_priority</span>
    <span class="k">CHECK</span> <span class="p">(</span><span class="n">priority</span> <span class="k">IN</span> <span class="p">(</span><span class="s1">'normal'</span><span class="p">))</span>
<span class="p">);</span>
</code></pre></div></div>

<p>これを <code class="language-plaintext highlighter-rouge">mysqldef</code> で適用したあと、もう一度同じ schema.sql で実行すると、毎回 ALTER が出続けます。</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">ALTER</span> <span class="k">TABLE</span> <span class="nv">`tasks`</span> <span class="k">DROP</span> <span class="k">CHECK</span> <span class="nv">`chk_tasks_priority`</span><span class="p">;</span>
<span class="k">ALTER</span> <span class="k">TABLE</span> <span class="nv">`tasks`</span> <span class="k">ADD</span> <span class="k">CONSTRAINT</span> <span class="nv">`chk_tasks_priority`</span> <span class="k">CHECK</span> <span class="p">(</span><span class="n">priority</span> <span class="k">in</span> <span class="p">(</span><span class="s1">'normal'</span><span class="p">));</span>
</code></pre></div></div>

<p>何度実行しても収束せず、CIで「diff なし」を期待していると常に落ちる、という状態になります。</p>

<h2 id="show-create-table-してみる">SHOW CREATE TABLE してみる</h2>

<p>差分を出されている DB 側の状態が気になり、 <code class="language-plaintext highlighter-rouge">SHOW CREATE TABLE</code> で実体を確認してみました。</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="nv">`tasks`</span> <span class="p">(</span>
  <span class="p">...</span>
  <span class="k">CONSTRAINT</span> <span class="nv">`chk_tasks_priority`</span> <span class="k">CHECK</span> <span class="p">((</span><span class="nv">`priority`</span> <span class="o">=</span> <span class="n">_utf8mb4</span><span class="s1">'normal'</span><span class="p">))</span>
<span class="p">)</span>
</code></pre></div></div>

<p>schema.sql に書いた <code class="language-plaintext highlighter-rouge">IN ('normal')</code> が、 DB 側では <code class="language-plaintext highlighter-rouge">= 'normal'</code> になっています。</p>

<h2 id="原因mysql-は-check-式を正規化して保存する">原因：MySQL は CHECK 式を正規化して保存する</h2>

<p>調べてみると、 MySQL 8 は CHECK 制約の式をパースしたあと、<strong>正規化した形で保存</strong>するようです。1要素の <code class="language-plaintext highlighter-rouge">IN (...)</code> は等価な <code class="language-plaintext highlighter-rouge">=</code> に書き換えられます。</p>

<p>sqldef は schema.sql の状態と DB の現状を比較して差分の DDL を生成します。今回のケースでは、</p>

<ul>
  <li>schema.sql 側: <code class="language-plaintext highlighter-rouge">IN ('normal')</code></li>
  <li>DB 側: <code class="language-plaintext highlighter-rouge">= 'normal'</code></li>
</ul>

<p>が一致しないと判定され、毎回 <code class="language-plaintext highlighter-rouge">DROP</code> → <code class="language-plaintext highlighter-rouge">ADD</code> の ALTER が生成されます。<code class="language-plaintext highlighter-rouge">ADD</code> 後に DB 側がまた <code class="language-plaintext highlighter-rouge">=</code> に正規化されるので、いくら適用しても収束しません。</p>

<h2 id="回避策">回避策</h2>

<p>CHECK 制約を書くときに、要素数で書き分けます。</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- 1要素なら =</span>
<span class="k">CHECK</span> <span class="p">(</span><span class="n">priority</span> <span class="o">=</span> <span class="s1">'normal'</span><span class="p">)</span>

<span class="c1">-- 2要素以上なら IN</span>
<span class="k">CHECK</span> <span class="p">(</span><span class="n">priority</span> <span class="k">IN</span> <span class="p">(</span><span class="s1">'normal'</span><span class="p">,</span> <span class="s1">'high'</span><span class="p">))</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">= 'normal'</code> で書けば DB 側の表現と一致し、 <code class="language-plaintext highlighter-rouge">mysqldef</code> は <code class="language-plaintext highlighter-rouge">Nothing is modified</code> を返して冪等になります。</p>

<p>2要素以上の <code class="language-plaintext highlighter-rouge">IN (...)</code> は MySQL も内部表現を <code class="language-plaintext highlighter-rouge">IN</code> のまま保存するため、こちらも冪等です。</p>

<h2 id="さいごに">さいごに</h2>

<p>宣言型のスキーマ管理ツールは「目標 = 現状」の同値判定が肝です。その判定は DB エンジン側の式の正規化に左右されるため、 schema.sql に書いた SQL がそのまま DB に保存される保証はありません。</p>

<p>何かおかしいなと思ったら <code class="language-plaintext highlighter-rouge">SHOW CREATE TABLE</code> で実体を確認する、というのを安全装置として覚えておこうと思いました。</p>]]></content><author><name>aruma256</name></author><category term="blog" /><category term="MySQL" /><category term="sqldef" /><summary type="html"><![CDATA[sqldef + MySQL 8 の環境にて、schema.sql に CHECK (priority IN ('normal')) と書いたところ、2回目以降の実行でも毎回 DROP CHECK / ADD CONSTRAINT の ALTER が出続けるという現象に遭遇しました。]]></summary></entry><entry><title type="html">flaky testの原因は、無関係なファイルの1行にあった</title><link href="https://aruma256.dev/blog/2026/04/15/flaky-test-investigation.html" rel="alternate" type="text/html" title="flaky testの原因は、無関係なファイルの1行にあった" /><published>2026-04-15T00:00:00+09:00</published><updated>2026-04-15T00:00:00+09:00</updated><id>https://aruma256.dev/blog/2026/04/15/flaky-test-investigation</id><content type="html" xml:base="https://aruma256.dev/blog/2026/04/15/flaky-test-investigation.html"><![CDATA[<p><a href="https://tech.pepabo.com/2026/04/15/flaky-test-investigation/">会社のテックブログで記事を書きました。</a> <a href="https://tech.pepabo.com/2026/04/15/flaky-test-investigation/">https://tech.pepabo.com/2026/04/15/flaky-test-investigation/</a></p>]]></content><author><name>aruma256</name></author><category term="blog" /><category term="test" /><summary type="html"><![CDATA[会社のテックブログで記事を書きました。 https://tech.pepabo.com/2026/04/15/flaky-test-investigation/]]></summary></entry><entry><title type="html">SUZURIで遭遇したflaky testの事例集</title><link href="https://aruma256.dev/blog/2026/04/14/flaky-tests.html" rel="alternate" type="text/html" title="SUZURIで遭遇したflaky testの事例集" /><published>2026-04-14T00:00:00+09:00</published><updated>2026-04-14T00:00:00+09:00</updated><id>https://aruma256.dev/blog/2026/04/14/flaky-tests</id><content type="html" xml:base="https://aruma256.dev/blog/2026/04/14/flaky-tests.html"><![CDATA[<p><a href="https://tech.pepabo.com/2026/04/14/flaky-tests/">会社のテックブログで記事を書きました。</a> <a href="https://tech.pepabo.com/2026/04/14/flaky-tests/">https://tech.pepabo.com/2026/04/14/flaky-tests/</a></p>]]></content><author><name>aruma256</name></author><category term="blog" /><category term="test" /><summary type="html"><![CDATA[会社のテックブログで記事を書きました。 https://tech.pepabo.com/2026/04/14/flaky-tests/]]></summary></entry><entry><title type="html">Self-hosting n8n から Hono + BullMQ に移行した</title><link href="https://aruma256.dev/blog/2026/02/23/n8n-to-hono-bullmq.html" rel="alternate" type="text/html" title="Self-hosting n8n から Hono + BullMQ に移行した" /><published>2026-02-23T00:00:00+09:00</published><updated>2026-02-23T00:00:00+09:00</updated><id>https://aruma256.dev/blog/2026/02/23/n8n-to-hono-bullmq</id><content type="html" xml:base="https://aruma256.dev/blog/2026/02/23/n8n-to-hono-bullmq.html"><![CDATA[<p>以前 <a href="/blog/2024/09/16/n8n.html">n8n の self-hosting のすすめ</a> という記事を書きましたが、最近は n8n をやめて Hono + BullMQ の構成に移行しました。<br />
その経緯と所感をまとめます。</p>

<h2 id="移行先の構成">移行先の構成</h2>

<p><a href="https://hono.dev/">Hono</a> と <a href="https://bullmq.io/">BullMQ</a> を組み合わせ、TypeScript で実装しています。
BullMQ の Job Scheduler で cron スケジュール実行、Hono で Webhook の受け付けを行っています。</p>

<p><a href="/blog/2024/09/16/n8n.html">前回の記事</a>と同様に Synology NAS 上で動かしており、app コンテナと Redis コンテナを Docker Compose で立ち上げています。</p>

<p>現在動いているワークフローは、</p>

<ul>
  <li>毎日12時にRSSフィードから新着記事の情報を取得し、AI要約とリンクを通知</li>
  <li>毎日7時に天気予報を取得・加工し、AIによる外干しプラを付加して通知</li>
  <li>睡眠トラッキングアプリ（Sleep as Android）のイベントを Webhook で受け取り、寝室の照明（SwitchBot）の自動制御や通知</li>
</ul>

<p>などがあります。
いずれも、n8n ワークフローからの移行です。</p>

<h2 id="なぜ移行したか">なぜ移行したか</h2>

<p>AIコーディングツールの進化で「コードを書く」コストが劇的に下がったためです。</p>

<p>元記事では以下のように書いていました。</p>

<blockquote>
  <p>コード生成（プロンプトを組み立て、コードを生成させ、動作を検証し、実行環境を整えてデプロイする）する方法よりも早く目的の処理を実現できました。</p>
</blockquote>

<p>2024年9月時点ではこれが実感でした。<br />
しかし2026年2月現在、Claude Code に簡潔な依頼を投げるだけで、公式ライブラリを探してドキュメントを読み、実装、テスト、デプロイ、資料更新までを一貫してやってくれるようになりました。<br />
「コードを書く」側のコストが大きく下がったことで、n8n を使う動機が薄れました。</p>

<h2 id="組み込みノードの難しさ">組み込みノードの難しさ</h2>

<p>n8n は500を超える組み込みの integrations を持っており、そのおかげでローコードで様々なワークフローを実現できます。</p>

<p>ただ、実際に使ったのはそのうちのごくわずかでした。
また、Claude API をはじめ未対応のサービスも多く、対応していてもメジャーではない機能や新しく追加されたばかりの機能には対応が追いついていません。
特にAI関連サービスは変化が速いため、組み込みノードの恩恵を受けにくい領域でした。</p>

<p>そうなると、各社の API を調べ、Edit ノードや Code ノードを組み合わせて HTTP Request ノードで API を直接叩き、レスポンスを再び基本的なノードで加工し…、という流れになりがちで、フローの構築に時間と手間がかかります。<br />
それよりは、サービス公式のライブラリを入れてコードで書いた方がずっと早い、と感じるようになりました。</p>

<h2 id="gui-ワークフローと-ai-の相性">GUI ワークフローと AI の相性</h2>

<p>GUI ベースのワークフロー構築やデバッグは、AI に任せにくいと感じました。<br />
n8n-mcp のような試みもありますが、普通の TypeScript を書かせて、普通にテストして、というサイクルの方が楽でスムーズでした。</p>

<p>n8n の Code ノード内にコードを書くこともできますが、実ファイルのコードならテストも書けますし、差分管理もしやすいです。</p>

<h2 id="妻でも編集できるのその後">「妻でも編集できる」のその後</h2>

<p>n8n 導入時は、コーディングが不慣れな妻でも GUI でワークフローを編集できることがメリットの1つでした。</p>

<p>しかし今や、妻も Claude Code を使って開発するようになりました。<br />
AI も、人も、成長しました。</p>

<h2 id="チューニング">チューニング</h2>

<p>Redis コンテナをデフォルト設定のまま起動したところ、ほぼ待機状態のシステムにしてはそれなりの CPU 使用率がありました。<br />
Redis の <code class="language-plaintext highlighter-rouge">hz</code> パラメータ（バックグラウンドタスクの実行頻度）を <code class="language-plaintext highlighter-rouge">1</code> に設定したところ、app コンテナと同程度の低い水準に落ち着きました。</p>

<p>以下の画像は、左半分が before で、停止、再起動（スパイク部分）を経て、after でCPU使用率の低下を確認するまでの様子です。<br />
<img src="/assets/2026/2026-02-23-n8n-to-hono-bullmq/redis-cpu.png" alt="Redis の hz 設定変更前後の CPU 使用率" /></p>

<p>メモリ使用量は n8n の約350MBに対し、新構成では50MB以下になりました。</p>

<h2 id="さいごに">さいごに</h2>

<p>n8n は視覚的なわかりやすさや実行履歴の管理など、優れた点が多いツールです。<br />
しかし今では、個人的な「動けば十分」なワークフローの作成・メンテナンス程度なら、AIに全て任せられるようになりGUIのワークフローエディタは不要になりました。</p>

<p>元記事の最後では</p>

<blockquote>
  <p>n8nを超えるとしたら、もっとAIが活用されるようになるのかなと思います。
フローの構築や編集も、人力ではなくAIに任せられるようになると嬉しいですね。</p>
</blockquote>

<p>と書いていましたが、その通りになりました。</p>

<p>2027年末あたりには、全て AI Agent に置き換わっているかもしれません。</p>]]></content><author><name>aruma256</name></author><category term="blog" /><category term="apps" /><summary type="html"><![CDATA[以前 n8n の self-hosting のすすめ という記事を書きましたが、最近は n8n をやめて Hono + BullMQ の構成に移行しました。 その経緯と所感をまとめます。]]></summary></entry><entry><title type="html">2026年2月前半の Daily AlpacaHack 記録</title><link href="https://aruma256.dev/blog/2026/02/18/daily-alpacahack-202602.html" rel="alternate" type="text/html" title="2026年2月前半の Daily AlpacaHack 記録" /><published>2026-02-18T00:00:00+09:00</published><updated>2026-02-18T00:00:00+09:00</updated><id>https://aruma256.dev/blog/2026/02/18/daily-alpacahack-202602</id><content type="html" xml:base="https://aruma256.dev/blog/2026/02/18/daily-alpacahack-202602.html"><![CDATA[<p>Daily AlpacaHack でCTFデビューし、最近ハマっています。<br />
2/1～2/15 の問題を解いたので、解法メモを残しておきます。前半と言いつつ、勢い余って15日分になりました。<br />
どれも「自力で解ける」～「AIに相談しつつ解ける」程度で、CTF初心者にちょうどいい、楽しめる難易度でした。</p>

<h2 id="2026-02-01-camelidae">2026-02-01: Camelidae</h2>

<p>問題文に FLAG がありました。</p>

<h2 id="2026-02-02-substance">2026-02-02: Substance</h2>

<p>tar.gz の中には、2つの大きい数が書かれた output.txt と、chal.py が入っていました。
chal.py に登場するメソッドを見ておきます。</p>

<ul>
  <li><a href="https://docs.python.org/ja/3.14/library/stdtypes.html#int.from_bytes"><code class="language-plaintext highlighter-rouge">int.from_bytes</code></a>: bytesを受け取り整数表現を返す</li>
  <li><a href="https://docs.python.org/ja/3.14/library/random.html#random.randint"><code class="language-plaintext highlighter-rouge">random.randint(a, b)</code></a>: 範囲 [a, b] の整数をランダムに返す</li>
</ul>

<p>chal.py は <code class="language-plaintext highlighter-rouge">flag * a * b * c</code> と <code class="language-plaintext highlighter-rouge">flag * d * e * f</code> （a～fは2～2026のランダムな整数）を出力するようです。<br />
実際は <code class="language-plaintext highlighter-rouge">A * flag</code> と <code class="language-plaintext highlighter-rouge">B * flag</code> と考えてよいでしょう。</p>

<p>とりあえず、最大公約数から <code class="language-plaintext highlighter-rouge">N * flag</code> （<code class="language-plaintext highlighter-rouge">N = gcd(A, B)</code>）を求めてみると、 31748696846809458845893315116545812553950394766538362807608704739811018 でした。<br />
このまま <code class="language-plaintext highlighter-rouge">.to_bytes(32)</code> しても正しくdecodeできないため、 <code class="language-plaintext highlighter-rouge">N &gt; 1</code> と推測できます。<br />
2で1回、3で2回割ってから <code class="language-plaintext highlighter-rouge">.to_bytes(32)</code> すると正しくdecodeできるようになり、FLAGが得られました。</p>

<h2 id="2026-02-03-read-a-binary">2026-02-03: read-a-binary</h2>

<p>配布ファイルはバイナリファイル1つでした。<br />
リバースエンジニアリングの問題のようです。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>./read-a-binary 
Input <span class="o">&gt;</span> abc
Incorrect...
</code></pre></div></div>

<p>バイナリ解析は初心者なので、AIに聞きながら進めることにします。<br />
まずバイナリ内の文字列を抽出する方法を聞いてみると、 <code class="language-plaintext highlighter-rouge">strings</code> を提案されました。<br />
<code class="language-plaintext highlighter-rouge">strings read-a-binary | less</code> で</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Correct! The flag is %s</code></li>
  <li><code class="language-plaintext highlighter-rouge">Incorrect...</code></li>
</ul>

<p>などの文字列が含まれていましたが、 Alpaca… は見つかりませんでした。</p>

<p>もう一つの提案、 <code class="language-plaintext highlighter-rouge">xxd</code> を眺めてみると…</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.........H.E.H..
H..f...H........
......0...A..1..
.l..2...p..3...a
..4...c..5...a..
6...{..7...D..8.
..e..9...c..:...
…
</code></pre></div></div>

<p>A l p a c a … が見えました。7 bytes おきに FLAG が分割されているようです。<br />
ということで、文字列結合して <code class="language-plaintext highlighter-rouge">s[::7]</code> で、やたら長い FLAG を入手できました。</p>

<p>ところで… FLAG によると、デコンパイラを使うとよかったようです。なるほど…</p>

<h2 id="2026-02-04-magic-engine">2026-02-04: Magic Engine</h2>

<p>nginx の default.conf によると、このサーバーは複数の hostname を兼任しており、hostname <code class="language-plaintext highlighter-rouge">admin.alpaca.secret</code> の <code class="language-plaintext highlighter-rouge">/</code> が FLAG を返してくれるようです。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-H</span> <span class="s2">"Host: admin.alpaca.secret"</span> （対象サーバー）
</code></pre></div></div>

<p>で、FLAG を入手できました。</p>

<h2 id="2026-02-05-rre_time_limiter">2026-02-05: RRe_Time_Limiter</h2>

<p>flag を 349以下の各素数で割ったときの余りから、flagを割り出す問題です。</p>

<p>「2 で割ったときの余りが 1 になる数」は、1, 3, 5, … です。<br />
この数列を昇順で見つつ、「3 で割ったときの余りが 2」も満たすところで止まれば、2条件を満たす数（ここでは 5）が得られます。<br />
次は 5 + (2*3)n の数列を昇順に… と全ての条件を満たす数を得られるまで操作を続けることで、FLAGを得られます。</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">primes</span> <span class="o">=</span> <span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="err">…</span><span class="p">]</span>
<span class="n">outs</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="err">…</span><span class="p">]</span> 

<span class="n">c</span> <span class="o">=</span> <span class="mi">1</span>
<span class="n">ans</span> <span class="o">=</span> <span class="mi">0</span>

<span class="k">while</span> <span class="n">primes</span><span class="p">:</span>
    <span class="n">p</span> <span class="o">=</span> <span class="n">primes</span><span class="p">.</span><span class="n">pop</span><span class="p">()</span>
    <span class="n">o</span> <span class="o">=</span> <span class="n">outs</span><span class="p">.</span><span class="n">pop</span><span class="p">()</span>
    <span class="k">while</span> <span class="n">ans</span> <span class="o">%</span> <span class="n">p</span> <span class="o">!=</span> <span class="n">o</span><span class="p">:</span>
        <span class="n">ans</span> <span class="o">+=</span> <span class="n">c</span>
    <span class="n">c</span> <span class="o">*=</span> <span class="n">p</span>

<span class="k">print</span><span class="p">(</span><span class="n">ans</span><span class="p">.</span><span class="n">to_bytes</span><span class="p">(</span><span class="mi">43</span><span class="p">))</span>
</code></pre></div></div>

<h2 id="2026-02-06-the-world">2026-02-06: The World</h2>

<p>サーバー側の時刻と100nsレベルで一致する入力を送ることでクリアできるようです。<br />
大量のリクエストを送れば…とも考えましたが、入力を色々試す中でサニタイズが甘いことに気が付きました。
FLAG と入力し、エラーメッセージから FLAG を得られました。</p>

<h2 id="2026-02-07-misdirection">2026-02-07: misdirection</h2>

<p><code class="language-plaintext highlighter-rouge">strings</code> でバイナリを読むと、ダミーのフラグと、reverseされた真のフラグがありました。</p>

<h2 id="2026-02-08-compressor">2026-02-08: Compressor</h2>

<p>テキストを送ると、 <code class="language-plaintext highlighter-rouge">FLAG + input</code> を <code class="language-plaintext highlighter-rouge">zlib.compress</code> した際のサイズが返されます。<br />
様々な入力を与えて、「うまく圧縮される」文字列が見つかった場合、それが FLAG と共通する部分を多く持つと推測できます。</p>

<p>フラグは <code class="language-plaintext highlighter-rouge">Alpaca{</code> で始まることがわかっているので、 <code class="language-plaintext highlighter-rouge">known + 1文字</code> を総当りし、最も圧縮された1文字を結合し新たな <code class="language-plaintext highlighter-rouge">known</code> として次の1文字を総当り…していけば特定できそうと思いました。</p>

<p>しかし、やってみると同率一位の候補が多数出現して詰まってしまいました。<br />
試行錯誤した結果、 <code class="language-plaintext highlighter-rouge">(known + 1文字) * 4</code> を入力とすることで20文字程度を当て、複数候補が現れたところでrepeatをやめることで最後まで特定できました。</p>

<h2 id="2026-02-09-plz-login">2026-02-09: Plz Login</h2>

<p>ログイン画面が表示され、username: <code class="language-plaintext highlighter-rouge">admin</code> で正しいパスワードを入れると FLAG が表示されるという問題です。</p>

<p>username/password の一致判定部分が怪しいかと思いましたが突破口が見つからず。<br />
curlで様々なリクエストを試行していたところで「0文字のusernameを渡すと <code class="language-plaintext highlighter-rouge">username[0]</code> で IndexError が発生し、トレースが表示される」ことを発見し、トレースにif文の判定ロジックが含まれていたため、そこで password が判明しました。</p>

<p>例: <code class="language-plaintext highlighter-rouge">curl -X POST http://localhost:3000/login -d "username=&amp;password="</code></p>

<p>あとは通常のログイン画面に “admin” と判明したpasswordを入力するだけです。</p>

<p><code class="language-plaintext highlighter-rouge">debug=True</code> で実行されていたことが根本原因でした。</p>

<h2 id="2026-02-10-alpaca-llama-ranch">2026-02-10: Alpaca-Llama Ranch</h2>

<p>セグフォを起こせば、シェルを起動できるようです。</p>

<p><code class="language-plaintext highlighter-rouge">unsigned alpaca, llama</code> （種類別の合計数）と、それぞれ各個体のID情報を格納する配列（長さ0x40）に対して入力できますが、 <code class="language-plaintext highlighter-rouge">alpaca+llama &gt; 0x40</code> のバリデーションがあります。</p>

<p>ここで、 <code class="language-plaintext highlighter-rouge">alpaca, llama</code> 単体でのバリデーションが無いことに注目すると、オーバーフローを狙えそうです。<br />
結果として <code class="language-plaintext highlighter-rouge">alpaca+llama &lt;= 0x40</code> となるように 4294967295, 2 などを選び、あとは書き込みまくることでシェルを起動し、フラグを入手できました。</p>

<h2 id="2026-02-11-destructuring">2026-02-11: destructuring</h2>

<p>複雑な構造の要求に合うJSONを渡せば FLAG を貰えます。</p>

<p>ソースコード内のフォーマット部分を文字列として、以下のような文字列置換をすることで正しいJSONを生成することができました。</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">re</span>

<span class="k">def</span> <span class="nf">transform</span><span class="p">(</span><span class="n">s</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="n">s</span> <span class="o">=</span> <span class="n">s</span><span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="s">'_'</span><span class="p">,</span> <span class="s">''</span><span class="p">)</span>
    <span class="n">s</span> <span class="o">=</span> <span class="n">re</span><span class="p">.</span><span class="n">sub</span><span class="p">(</span><span class="sa">r</span><span class="s">'([a-zA-Z]):'</span><span class="p">,</span> <span class="sa">r</span><span class="s">'"\1":'</span><span class="p">,</span> <span class="n">s</span><span class="p">)</span>
    <span class="n">s</span> <span class="o">=</span> <span class="n">s</span><span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="s">'[,'</span><span class="p">,</span> <span class="s">'[0,'</span><span class="p">)</span>
    <span class="n">s</span> <span class="o">=</span> <span class="n">s</span><span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="s">' ,'</span><span class="p">,</span> <span class="s">'0,'</span><span class="p">)</span>
    <span class="n">s</span> <span class="o">=</span> <span class="n">s</span><span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="s">', ]'</span><span class="p">,</span> <span class="s">',0]'</span><span class="p">)</span>
    <span class="n">s</span> <span class="o">=</span> <span class="n">s</span><span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="s">' '</span><span class="p">,</span> <span class="s">''</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">s</span>
</code></pre></div></div>

<p>なお、当初は読みやすくスペースを入れたり使わないvalueには <code class="language-plaintext highlighter-rouge">null</code> を入れたりしていたのですが、今回は nc で手動入力していた関係で入力が長すぎると（おそらく 4096 bytes）入力がカットされてしまったため、密なJSONとなるよう調整しました。</p>

<h2 id="2026-02-12-aaaaaaaaeeeeeeeessssssss">2026-02-12: AAAAAAAAEEEEEEEESSSSSSSS</h2>

<p>FLAG の各1文字を8連続に引き伸ばし、それをランダムな鍵で MODE_ECB でAES暗号化した値が返されます。
また、インタラクティブな仕組みがあり、任意の入力値を同じ鍵で暗号化した値も返されます。</p>

<p>MODE_ECB （ブロック長 16 bytes）であること、1文字が8連続（8 bytes）になっていることを考えると、2文字の組み合わせ（約3000通り）とその暗号化hexの対応を導けば、元の FLAG がわかります。</p>

<p>これを実行するスクリプトを用意し実行することで、FLAG を入手できました。</p>

<h2 id="2026-02-13-nc-magic">2026-02-13: nc magic</h2>

<p>（実は最初に解いた問題のため、初歩的な部分から書いています。）</p>

<p>タイトルや問題文の netcat / nc を調べると、TCP/UDPの接続を手軽に扱えるコマンドラインツールとのことでした。ポートスキャンやリッスンも対応、便利…。</p>

<p>tar.gz に含まれるファイルを読むと、ネットワーク越しに server.py に入出力できるようです。 server.py は print されたランダムな hex string をその場で渡すだけで FLAG をくれるようですが、実際にやってみるとうまく行きませんでした。<br />
server.py の内容をよく読むと</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="n">sys</span><span class="p">.</span><span class="n">stdin</span><span class="p">.</span><span class="nb">buffer</span><span class="p">.</span><span class="n">readline</span><span class="p">()</span> <span class="o">==</span> <span class="n">secret</span><span class="p">.</span><span class="n">encode</span><span class="p">():</span>
</code></pre></div></div>

<p>となっており、単にコピペして渡すだけでは改行が問題となるようです。<br />
実際にソースコードの <code class="language-plaintext highlighter-rouge">secret</code> の末尾に <code class="language-plaintext highlighter-rouge">"\n"</code> を追加してみると、手入力でも FLAG が返ってくることを確認できます。</p>

<p>整理すると、受け取ったテキストに含まれるコードを、末尾に改行を含めずに送信する必要があるということになります。</p>

<p>Ruby で実現できそうだったので、</p>

<p><code class="language-plaintext highlighter-rouge">docker run --rm -it --network host ruby irb</code> で</p>

<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">require</span> <span class="s1">'socket'</span>

<span class="no">HOST</span> <span class="o">=</span> <span class="s1">'127.0.0.1'</span> <span class="c1"># ターゲットによって変える</span>
<span class="no">PORT</span> <span class="o">=</span> <span class="mi">1337</span> <span class="c1"># ターゲットによって変える</span>

<span class="n">s</span> <span class="o">=</span> <span class="no">TCPSocket</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="no">HOST</span><span class="p">,</span> <span class="no">PORT</span><span class="p">)</span>
<span class="n">line</span> <span class="o">=</span> <span class="n">s</span><span class="p">.</span><span class="nf">gets</span>
<span class="n">secret</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="nf">split</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span>
<span class="n">s</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="n">secret</span><span class="p">)</span>
<span class="n">s</span><span class="p">.</span><span class="nf">close_write</span>
<span class="n">s</span><span class="p">.</span><span class="nf">read</span>
</code></pre></div></div>

<p>を実行し、FLAG を取れることを確認できました。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>irb(main):012&gt; s.read
irb(main):013&gt; 
=&gt; "Alpaca{REDACTED}\n"
</code></pre></div></div>

<p>同様の手順で本番サーバーから FLAG を入手すると、クリアです。</p>

<h2 id="2026-02-14-simple-rop">2026-02-14: simple ROP</h2>

<p><code class="language-plaintext highlighter-rouge">win</code> 関数を適切な引数とともに呼び出せば FLAG を入手できるようです。</p>

<p><code class="language-plaintext highlighter-rouge">gets</code> という見慣れない関数が出てきたので調べると、脆弱な実装になりやすいため廃止されたとのことでした。なるほど。<br />
<code class="language-plaintext highlighter-rouge">-fno-stack-protector</code> も調べ、オーバーフローによるリターンアドレスの書き換えを防止する（書き換えられたことを検知する）仕組みであることを理解しました。なるほどなるほど。<br />
様々な改善や仕組みで守られているんだなぁ…。</p>

<p>今回はこれらのおかげで簡単にリターンアドレスの書き換えができます。</p>

<p>また、「ROP gadgets」として rdi, rsi, rdx のセットが用意されているので、これらで引数の値をセットしてから <code class="language-plaintext highlighter-rouge">win</code> を呼べばよさそうです。</p>

<p><code class="language-plaintext highlighter-rouge">buffer</code> + RBP を埋め、値をセットしつつ最後に <code class="language-plaintext highlighter-rouge">win</code> を呼ぶような入力をすることで、FLAG を入手できました。</p>

<h2 id="2026-02-15-you-are-being-redirected">2026-02-15: You are being redirected</h2>

<p>リダイレクト機能を持つ web サービスと、そこへブラウザ（puppeteer）でアクセスする bot サービスがあり、bot のアクセス時のクッキーを奪取するという問題でした。</p>

<p>リダイレクト処理の流れを追ってみると</p>

<ol>
  <li>URLパラメータの <code class="language-plaintext highlighter-rouge">to</code> の値を抽出する</li>
  <li>その <code class="language-plaintext highlighter-rouge">toLowerCase()</code> が “data”, “javascript”, “blob” のいずれも含んでいないことをチェック</li>
  <li><code class="language-plaintext highlighter-rouge">new URL(dest, base)</code> で URL オブジェクトを作る</li>
  <li><code class="language-plaintext highlighter-rouge">window.location.replace(url.href);</code></li>
</ol>

<p>となっていました。<br />
クッキーの内容を外部に投げるようなXSSを実行できれば良さそうです。</p>

<p>URL parser の仕様を調べていくと、<a href="https://url.spec.whatwg.org/#concept-basic-url-parser">The basic URL parser</a> はTAB文字を除去するようでした。<br />
これを利用し、<code class="language-plaintext highlighter-rouge">java(tab)script:（クッキーを取得し用意した外部サーバーに送るスクリプト）</code> を実行させることで、FLAGを入手できました。</p>

<p>バリデーションの考慮漏れ（というかJSの知らない挙動）、怖い。</p>

<h2 id="さいごに">さいごに</h2>

<p>Daily AlpacaHack、楽しいし勉強になるし最高ですね。</p>]]></content><author><name>aruma256</name></author><category term="blog" /><category term="CTF" /><summary type="html"><![CDATA[Daily AlpacaHack でCTFデビューし、最近ハマっています。 2/1～2/15 の問題を解いたので、解法メモを残しておきます。前半と言いつつ、勢い余って15日分になりました。 どれも「自力で解ける」～「AIに相談しつつ解ける」程度で、CTF初心者にちょうどいい、楽しめる難易度でした。]]></summary></entry><entry><title type="html">二重書き込みで実現した、新機能リリースのための無停止DBスキーマ移行</title><link href="https://aruma256.dev/blog/2025/12/26/db-migration-for-variation.html" rel="alternate" type="text/html" title="二重書き込みで実現した、新機能リリースのための無停止DBスキーマ移行" /><published>2025-12-26T00:00:00+09:00</published><updated>2025-12-26T00:00:00+09:00</updated><id>https://aruma256.dev/blog/2025/12/26/db-migration-for-variation</id><content type="html" xml:base="https://aruma256.dev/blog/2025/12/26/db-migration-for-variation.html"><![CDATA[<p><a href="https://tech.pepabo.com/2025/12/26/db-migration-for-variation/">会社のテックブログで記事を書きました。</a> <a href="https://tech.pepabo.com/2025/12/26/db-migration-for-variation/">https://tech.pepabo.com/2025/12/26/db-migration-for-variation/</a></p>]]></content><author><name>aruma256</name></author><category term="blog" /><category term="DB" /><summary type="html"><![CDATA[会社のテックブログで記事を書きました。 https://tech.pepabo.com/2025/12/26/db-migration-for-variation/]]></summary></entry><entry><title type="html">ruby/gem_rbs_collection に初めてコントリビュートするまで</title><link href="https://aruma256.dev/blog/2025/12/11/gem-rbs-collection-contribution.html" rel="alternate" type="text/html" title="ruby/gem_rbs_collection に初めてコントリビュートするまで" /><published>2025-12-11T00:00:00+09:00</published><updated>2025-12-11T00:00:00+09:00</updated><id>https://aruma256.dev/blog/2025/12/11/gem-rbs-collection-contribution</id><content type="html" xml:base="https://aruma256.dev/blog/2025/12/11/gem-rbs-collection-contribution.html"><![CDATA[<p>担当しているRailsプロジェクトに<a href="https://github.com/soutaro/rbs-inline">rbs-inline</a>と<a href="https://github.com/soutaro/steep">steep</a>による型付け・型検査を導入しました。<br />
検出された型エラーの調査・修正を行う中で、<a href="https://github.com/ruby/gem_rbs_collection">ruby/gem_rbs_collection</a> 側の追加・修正が必要な箇所をいくつか見つけたので、初めて gem_rbs_collection にコントリビュートしてみました。</p>

<p>この記事は、<a href="https://adventar.org/calendars/12190">GMOペパボ エンジニア Advent Calendar 2025 🎄会場</a> の11日目です。</p>

<h2 id="gem_rbs_collection-とは">gem_rbs_collection とは</h2>

<p><a href="https://github.com/ruby/gem_rbs_collection">gem_rbs_collection</a> は、RBSを同梱していないgemのための型定義を集めた、コミュニティ管理のリポジトリです。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rbs collection init
rbs collection <span class="nb">install</span>
</code></pre></div></div>

<p>で簡単に導入でき、steepなどの型チェッカーと組み合わせて利用できます。</p>

<h2 id="作成したpr">作成したPR</h2>

<p>これまで合計3つのPRを出しました。</p>

<h3 id="pr-871-graphqlexecutionerrorinitialize-の型定義追加">PR #871: GraphQL::ExecutionError#initialize の型定義追加</h3>

<p><a href="https://github.com/ruby/gem_rbs_collection/pull/871">graphql: Add types for GraphQL::ExecutionError#initialize #871</a></p>

<p>初めてのPRです。 graphql-ruby gemの <code class="language-plaintext highlighter-rouge">GraphQL::ExecutionError#initialize</code> メソッドは <code class="language-plaintext highlighter-rouge">ast_node</code> 、 <code class="language-plaintext highlighter-rouge">options</code> 、 <code class="language-plaintext highlighter-rouge">extensions</code> などの引数を受け取れますが、そもそもメソッドの型定義がなかったためにこれらの引数を渡すと型エラーが発生する状態でした。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>test.rb:61:62: [error] Unexpected keyword argument
│ Diagnostic ID: Ruby::UnexpectedKeywordArgument
│
└       raise GraphQL::ExecutionError.new("Test error message", ast_node: nil, extensions: { "code" =&gt; "TEST_ERROR" })
                                                                ~~~~~~~~

test.rb:61:77: [error] Unexpected keyword argument
│ Diagnostic ID: Ruby::UnexpectedKeywordArgument
│
└       raise GraphQL::ExecutionError.new("Test error message", ast_node: nil, extensions: { "code" =&gt; "TEST_ERROR" })
                                                                               ~~~~~~~~~~
</code></pre></div></div>

<p>（これはテストコードをもとに当時の状況を再現したものです）</p>

<ul>
  <li>gem の api-doc <a href="https://graphql-ruby.org/api-doc/1.12.0/GraphQL/ExecutionError.html#initialize-instance_method">https://graphql-ruby.org/api-doc/1.12.0/GraphQL/ExecutionError.html#initialize-instance_method</a></li>
  <li>gem のソースコード <a href="https://github.com/rmosolgo/graphql-ruby/blob/v1.12.0/lib/graphql/execution_error.rb">https://github.com/rmosolgo/graphql-ruby/blob/v1.12.0/lib/graphql/execution_error.rb</a></li>
</ul>

<p>などを確認しつつ、以下のような型定義を追加しました。</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gi">+ def initialize: (String message, ?ast_node: untyped, ?extensions: Hash[untyped, untyped]?) -&gt; void
</span></code></pre></div></div>

<p>通常の開発であれば単体テストなどで動作を保証できますが、型定義ファイル自体はそもそも実行するものではないため実行時の挙動に基づいたテストはできません。<br />
代わりに gem_rbs_collection では、まずそのgemを利用した際のコードを用意し、それを型定義ファイルに基づいた型検査がpassすることをテストします。</p>

<p>テストとして用意した実際のコードが以下です。</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">error_field</span>
  <span class="k">raise</span> <span class="no">GraphQL</span><span class="o">::</span><span class="no">ExecutionError</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="s2">"Test error message"</span><span class="p">,</span> <span class="ss">ast_node: </span><span class="kp">nil</span><span class="p">,</span> <span class="ss">extensions: </span><span class="p">{</span> <span class="s2">"code"</span> <span class="o">=&gt;</span> <span class="s2">"TEST_ERROR"</span> <span class="p">})</span>
<span class="k">end</span>
</code></pre></div></div>

<p>このコードをテストに追加すると型検査が落ちるようになるので、型定義を修正し、再び型検査が通るようになることを確認しました。つまり、TDDのサイクルで進められます。</p>

<p>ちなみにgem_rbs_collectionは<a href="https://www.timedia.co.jp/tech/20240513-tech/">レビュー無しでもマージできる運用</a>になっていますが、初めてで心配だったので念のためレビューを依頼し、approveを頂いてからマージしました。元々はこのPRに <code class="language-plaintext highlighter-rouge">options</code> 引数も含めていたのですが、レビューにて deprecated であることを指摘頂き修正しました。（ありがとうございました！）</p>

<h3 id="pr-887-rqrcodeqrcodeinitialize-の型定義修正">PR #887: RQRCode::QRCode#initialize の型定義修正</h3>

<p><a href="https://github.com/ruby/gem_rbs_collection/pull/887">rqrcode: Fix type definition for RQRCode::QRCode#initialize #887</a></p>

<p>rqrcode gemの <code class="language-plaintext highlighter-rouge">RQRCode::QRCode#initialize</code> は<a href="https://github.com/whomwah/rqrcode/blob/e789be339b6f833a01bc4c84e3d444e4699c3765/README.md#advanced-options">gemのREADMEにもあるように</a>オプションを受け取れますが、これが型検査にてエラーとなっていました。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>test.rb:6:45: [error] Unexpected keyword argument
│ Diagnostic ID: Ruby::UnexpectedKeywordArgument
│
└ qr = RQRCode::QRCode.new("https://kyan.com", level: :h)
                                               ~~~~~
</code></pre></div></div>

<p>gem の実装に合わせた型定義に修正しました。</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gd">- def initialize: (String) -&gt; void
</span><span class="gi">+ def initialize: (String, *untyped) -&gt; void
</span></code></pre></div></div>

<h3 id="pr-933-graphqlschemaresolvernull-メソッド署名の追加">PR #933: GraphQL::Schema::Resolver.null メソッド署名の追加</h3>

<p><a href="https://github.com/ruby/gem_rbs_collection/pull/933">graphql: Add GraphQL::Schema::Resolver.null method signature #933</a></p>

<p>graphql-ruby gemの <code class="language-plaintext highlighter-rouge">GraphQL::Schema::Resolver.null</code> クラスメソッドの型定義がなかったため、追加しました。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>test.rb:76:4: [error] Type `singleton(::Mutations::Ping)` does not have method `null`
│ Diagnostic ID: Ruby::NoMethod
│
└     null true
      ~~~~
</code></pre></div></div>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gi">+ def self.null: (?bool allow_null) -&gt; untyped
</span></code></pre></div></div>

<h2 id="注意すべき点">注意すべき点</h2>

<p>gem_rbs_collection では、gem ごとに対象とするバージョンが定められています。<br />
PRを作り始める前に、そのメソッドや引数が追加・変更されたバージョンと、型定義の対象バージョンをチェックしておくとよいでしょう。</p>

<h2 id="おわりに">おわりに</h2>

<p>gem側の型エラーはその場でパッチを用意して暫定対応することもできますが、以前 RubyKaigi 2022 の発表 <a href="https://rubykaigi.org/2022/presentations/fugakkbn.html">Types teaches success, what will we do?</a> で流れを聞いていたので、この機会にコントリビュートしてみることにしました。</p>

<p>実際にやってみると、ハードルは思ったより低かったです。型定義の不足や誤りを見つけたら、暫定対応だけでなくコントリビュートも選択肢に入れてみてください。</p>]]></content><author><name>aruma256</name></author><category term="blog" /><category term="Ruby" /><category term="RBS" /><category term="OSS" /><summary type="html"><![CDATA[担当しているRailsプロジェクトにrbs-inlineとsteepによる型付け・型検査を導入しました。 検出された型エラーの調査・修正を行う中で、ruby/gem_rbs_collection 側の追加・修正が必要な箇所をいくつか見つけたので、初めて gem_rbs_collection にコントリビュートしてみました。]]></summary></entry><entry><title type="html">Daily AlpacaHack でCTFデビューした - day1～3 writeups</title><link href="https://aruma256.dev/blog/2025/12/09/daily-alpacahack.html" rel="alternate" type="text/html" title="Daily AlpacaHack でCTFデビューした - day1～3 writeups" /><published>2025-12-09T00:00:00+09:00</published><updated>2025-12-09T00:00:00+09:00</updated><id>https://aruma256.dev/blog/2025/12/09/daily-alpacahack</id><content type="html" xml:base="https://aruma256.dev/blog/2025/12/09/daily-alpacahack.html"><![CDATA[<p><a href="https://alpacahack.com/daily">Daily AlpacaHack</a> は、2025年12月1日から始まった毎日1問出題される初心者向けの常設CTFです。<br />
CTF（ほぼ）初挑戦の私が取り組んだ記録、解説と、その過程で得た気づきをまとめます。</p>

<p>2025/12/01～2025/12/03 の3問の内容が含まれます。</p>

<p>この記事は、<a href="https://adventar.org/calendars/12190">GMOペパボ エンジニア Advent Calendar 2025 🎄会場</a> の9日目です。</p>

<h2 id="day1-alpacahack-2100">Day1: AlpacaHack 2100</h2>

<p><a href="https://alpacahack.com/daily/challenges/alpacahack-2100">https://alpacahack.com/daily/challenges/alpacahack-2100</a></p>

<blockquote>
  <p>フラグは Daily AlpacaHack の 2100年1月 のカレンダーにあるパカ</p>
</blockquote>

<p>とのことです（親切なアルパカだ）。</p>

<p>カレンダーのUIを見てみると、「前の月」「次の月」ボタンのみがあります。素直に2100年まで進めるには、およそ12回*74年分の「次の月」ボタンを押す必要があり、なかなか大変そうです。<br />
とりあえず数回「次の月」ボタンを押してみると、URLのクエリパラメータが変化することに気づきました。</p>

<p><code class="language-plaintext highlighter-rouge">?month=2026-01</code><br />
<code class="language-plaintext highlighter-rouge">?month=2026-02</code><br />
<code class="language-plaintext highlighter-rouge">?month=2026-03</code><br />
…</p>

<p>ということは、ここを直接 <code class="language-plaintext highlighter-rouge">?month=2100-01</code> に書き換えれば良さそうです。<br />
実際にアクセスし、無事FLAGを入手できました。</p>

<h3 id="感想や気づき">感想や気づき</h3>

<p>フロントのパラメータは信用するな、という基本的なセキュリティの教訓を改めて思い出しました。<br />
この件は単にカレンダーの表示月なので問題は無さそうですが、パラメータを操作されても機密性が保たれるように設計することが重要ですね。</p>

<p>（2100年の運営さんは、どうするんだろう…）</p>

<h2 id="day2-a-fact-of-ctf">Day2: a fact of CTF</h2>

<p><a href="https://alpacahack.com/daily/challenges/a-fact-of-CTF">https://alpacahack.com/daily/challenges/a-fact-of-CTF</a></p>

<p>問題を開くと tar.gz ファイルが置いてありました。適当にubuntuコンテナを立ち上げて展開してみると、 <code class="language-plaintext highlighter-rouge">chall.py</code>, <code class="language-plaintext highlighter-rouge">output.txt</code> の2つのファイルが出てきました。<br />
<code class="language-plaintext highlighter-rouge">chall.py</code> は、環境変数 <code class="language-plaintext highlighter-rouge">FLAG</code> を決まった手続きで変換して標準出力するスクリプトのようです。<br />
<code class="language-plaintext highlighter-rouge">output.txt</code> には <code class="language-plaintext highlighter-rouge">0x</code> から始まる16進数の文字列が書かれていました。この内容は、特定の <code class="language-plaintext highlighter-rouge">FLAG</code> を <code class="language-plaintext highlighter-rouge">chall.py</code> に渡した際の出力結果ではないかと推測しました。</p>

<p><code class="language-plaintext highlighter-rouge">chall.py</code> の決まった手続きとは、具体的には「<code class="language-plaintext highlighter-rouge">昇順でn番目の素数 ** n番目の文字のコードポイント</code> の総積を求め、16進数表現に変換」のようです。確かに <code class="language-plaintext highlighter-rouge">output.txt</code> の末尾は0がいくつか連続していて、 2 * 5 が含まれていそうな雰囲気です。<br />
ということは、素因数分解して「昇順でn番目の素数がいくつ含まれるか」から、n番目の文字のコードポイントを逆算できそうです。<br />
（ここで改めて問題を見ると <code class="language-plaintext highlighter-rouge">Crypto</code> とあり、暗号解読系の問題のようでした。なるほど。）</p>

<p>シンプルな処理なので、久しぶりにAIサポート無しの温かみのある手打ちコーディングをしてみました。使い捨てコードなので、動けばヨシ！</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">primes</span> <span class="o">=</span> <span class="p">[</span><span class="mi">2</span><span class="p">]</span>

<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">300</span><span class="p">):</span>
    <span class="k">if</span> <span class="nb">any</span><span class="p">(</span><span class="n">i</span> <span class="o">%</span> <span class="n">prime</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">for</span> <span class="n">prime</span> <span class="ow">in</span> <span class="n">primes</span><span class="p">):</span>
        <span class="k">continue</span>
    <span class="n">primes</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">i</span><span class="p">)</span>

<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">'output.txt'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
    <span class="n">txt</span> <span class="o">=</span> <span class="n">f</span><span class="p">.</span><span class="n">read</span><span class="p">()</span>

<span class="n">num</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">txt</span><span class="p">,</span> <span class="mi">16</span><span class="p">)</span>

<span class="n">decoded</span> <span class="o">=</span> <span class="p">[]</span>

<span class="k">for</span> <span class="n">prime</span> <span class="ow">in</span> <span class="n">primes</span><span class="p">:</span>
    <span class="n">counter</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">while</span> <span class="n">num</span> <span class="o">%</span> <span class="n">prime</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
        <span class="n">num</span> <span class="o">//=</span> <span class="n">prime</span>
        <span class="n">counter</span> <span class="o">+=</span> <span class="mi">1</span>
    <span class="k">if</span> <span class="n">counter</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">:</span>
        <span class="n">decoded</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="nb">chr</span><span class="p">(</span><span class="n">counter</span><span class="p">))</span>

<span class="k">print</span><span class="p">(</span><span class="o">*</span><span class="n">decoded</span><span class="p">,</span> <span class="n">sep</span><span class="o">=</span><span class="s">''</span><span class="p">)</span>
</code></pre></div></div>

<p>これを実行し、FLAGを入手できました。</p>

<h3 id="感想や気づき-1">感想や気づき</h3>

<p>競プロのおかげで素数にビビらずに済みました。推理・暗号解読ゲームのような感覚で楽しく解けました。<br />
逆操作で容易に復号できる暗号では手順が流出すると一気に解読されてしまうことを改めて認識しました。</p>

<h2 id="day3-emojify">Day3: Emojify</h2>

<p><a href="https://alpacahack.com/daily/challenges/emojify">https://alpacahack.com/daily/challenges/emojify</a></p>

<p>tar.gz ファイルとIPv4のURLがありました。tar.gz の中には、Dockerfile、compose.yaml、frontend/backend/secret ディレクトリが入っていました。3つのサービスをdocker composeで立ち上げる構成のようです。<br />
立ち上げて <code class="language-plaintext highlighter-rouge">/</code> にアクセスすると、以下のような画面が表示されました。</p>

<p><img src="/assets/2025/2025-12-09-daily-alpacahack/emojify.png" alt="emojifyの画面" /></p>

<p>各要素について詳しく見ていきます。</p>

<ul>
  <li>frontend は、<code class="language-plaintext highlighter-rouge">/</code> に対し <code class="language-plaintext highlighter-rouge">index.html</code> を、 <code class="language-plaintext highlighter-rouge">/api</code> に対しては <code class="language-plaintext highlighter-rouge">waf</code> メソッド（怪しすぎる）を通した上で backend に問い合わせてレスポンスを返すようです。</li>
  <li>backend は、受け取ったパラメータを <code class="language-plaintext highlighter-rouge">node-emoji</code> に問い合わせて返すのみのシンプルなAPIサーバーです。node-emoji が見当たらない…と思ったら、package.json の依存関係にあり、 https://www.npmjs.com/package/node-emoji のようでした。</li>
  <li>secret は <code class="language-plaintext highlighter-rouge">/flag</code> エンドポイントを持つサーバーです。おそらく、機密情報を持つ内部サーバーのような設定でしょう。</li>
</ul>

<p>通常のアクセスでは frontend と backend しか登場しないので、どちらかから secret にアクセスさせる必要があります。backend には隙が無さそうだったので、frontend から secret へのアクセスを目指すことにしました。</p>

<p>手始めに <code class="language-plaintext highlighter-rouge">http://localhost:3000/api?path=/emoji/pizza</code> を叩いてみると、🍕が返ってきました。<br />
frontend は <code class="language-plaintext highlighter-rouge">waf</code> メソッドを通した後に <code class="language-plaintext highlighter-rouge">new URL(path, "http://backend:3000")</code> によってURLを生成しているようです。今回は <code class="language-plaintext highlighter-rouge">path</code> = <code class="language-plaintext highlighter-rouge">"/emoji/pizza"</code> なので、<code class="language-plaintext highlighter-rouge">http://backend:3000/emoji/pizza</code> というURLが組み立てられたことになります。</p>

<p><a href="https://nodejs.org/docs/v25.2.1/api/url.html#new-urlinput-base">node v25.2.1 の <code class="language-plaintext highlighter-rouge">new URL(input[, base])</code> のドキュメント</a>を見てみると、</p>

<blockquote>
  <p>If <code class="language-plaintext highlighter-rouge">input</code> is relative, then <code class="language-plaintext highlighter-rouge">base</code> is required. If <code class="language-plaintext highlighter-rouge">input</code> is absolute, the <code class="language-plaintext highlighter-rouge">base</code> is ignored.</p>
</blockquote>

<p>とありました。つまり、 <code class="language-plaintext highlighter-rouge">path</code> に絶対URLを渡せば、 <code class="language-plaintext highlighter-rouge">base</code> は無視され、任意のURLにアクセスさせることができそうです。<br />
しかし、 <code class="language-plaintext highlighter-rouge">http://localhost:3000/api?path=http://secret:1337/flag</code> を叩いてみると、<code class="language-plaintext highlighter-rouge">Invalid 1</code> というレスポンスが返ってきました。</p>

<p><code class="language-plaintext highlighter-rouge">waf</code> メソッドは</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">path</code> がstring以外なら、 <code class="language-plaintext highlighter-rouge">Invalid types</code> 例外</li>
  <li><code class="language-plaintext highlighter-rouge">path</code> が <code class="language-plaintext highlighter-rouge">/</code> から始まっていないなら、 <code class="language-plaintext highlighter-rouge">Invalid 1</code> 例外</li>
  <li><code class="language-plaintext highlighter-rouge">path</code> に <code class="language-plaintext highlighter-rouge">emoji</code> が含まれていないなら、 <code class="language-plaintext highlighter-rouge">Invalid 2</code> 例外</li>
</ul>

<p>というチェックを行っていました。<br />
整理すると、「<code class="language-plaintext highlighter-rouge">new URL(path, "http://backend:3000")</code> を <code class="language-plaintext highlighter-rouge">http://secret:1337/flag</code> とするために <code class="language-plaintext highlighter-rouge">path</code> に絶対URLを渡したいが、<code class="language-plaintext highlighter-rouge">path</code> は <code class="language-plaintext highlighter-rouge">/</code> で始まる必要がある」という状況です。</p>

<p>URLの仕様でこれを実現できそうなものを探すため <a href="https://developer.mozilla.org/ja/docs/Learn_web_development/Howto/Web_mechanics/What_is_a_URL">https://developer.mozilla.org/ja/docs/Learn_web_development/Howto/Web_mechanics/What_is_a_URL</a> を見てみると、絶対パスではありませんがスキーム相対URL （例: <code class="language-plaintext highlighter-rouge">//example.com/path</code>）というものがあることがわかりました。これは、絶対URLからプロトコルのみを取り除いた形式です。</p>

<p>手元で <code class="language-plaintext highlighter-rouge">new URL("//secret:1337/flag", "http://backend:3000")</code> を試すと <code class="language-plaintext highlighter-rouge">http://secret:1337/flag</code> となり、狙い通りに動作することが確認できました。</p>

<p>これを利用し、<code class="language-plaintext highlighter-rouge">emoji</code> を含むというチェックの対策も入れたURL <code class="language-plaintext highlighter-rouge">http://localhost:3000/api?path=//secret:1337/flag?emoji</code> を叩いてみると、<code class="language-plaintext highlighter-rouge">Alpaca{REDACTED}</code> という secret にセットされたダミーのFLAGが返ってきました。</p>

<p>あとはこれを提供されるサーバー用にして叩くだけです。</p>

<h3 id="感想や気づき-2">感想や気づき</h3>

<p>分類としてはサーバーサイドリクエストフォージェリとなりそうですね。<br />
一見問題なさそうな入力値検証でも、仕様の見落としによってあっさりと突破され漏洩してしまうことを体感できました。</p>

<p>今回のケースであれば、</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">/emoji/</code> までは固定し、以降を動的に組み立てる</li>
  <li>アクセス可能なホスト名やIPアドレスを許可リストで管理する</li>
  <li><code class="language-plaintext highlighter-rouge">secret</code> のアクセス元を限定、あるいは認証する</li>
</ul>

<p>などしていれば、同じ攻撃は成立しなかったでしょう。<br />
防御は多層的に行うことが重要だと改めて感じました。</p>

<h2 id="まとめ">まとめ</h2>

<p>初心者向けとはいえ、パラメータ操作、暗号解読、Web（SSRF）と幅広いジャンルを体験でき、普段の開発でも意識すべきセキュリティの観点を学び直す良い機会になりました。<br />
何より、謎解き感覚で楽しく取り組めたのが良かったです。引き続き挑戦していきます。</p>]]></content><author><name>aruma256</name></author><category term="blog" /><category term="CTF" /><summary type="html"><![CDATA[Daily AlpacaHack は、2025年12月1日から始まった毎日1問出題される初心者向けの常設CTFです。 CTF（ほぼ）初挑戦の私が取り組んだ記録、解説と、その過程で得た気づきをまとめます。]]></summary></entry><entry><title type="html">Fisher–Yatesシャッフルのやさしい解説</title><link href="https://aruma256.dev/blog/2025/03/13/fisher-yates-shuffle.html" rel="alternate" type="text/html" title="Fisher–Yatesシャッフルのやさしい解説" /><published>2025-03-13T00:00:00+09:00</published><updated>2025-03-13T00:00:00+09:00</updated><id>https://aruma256.dev/blog/2025/03/13/fisher-yates-shuffle</id><content type="html" xml:base="https://aruma256.dev/blog/2025/03/13/fisher-yates-shuffle.html"><![CDATA[<p>配列のシャッフルに有用な Fisher–Yates shuffle と呼ばれるアルゴリズムをわかりやすく解説することを目指した記事です。</p>

<h1 id="fisheryates-shuffle-アルゴリズム">Fisher–Yates shuffle アルゴリズム</h1>

<p>Fisher–Yates shuffle は<br />
「<code class="language-plaintext highlighter-rouge">i</code> を <code class="language-plaintext highlighter-rouge">N-1</code>, <code class="language-plaintext highlighter-rouge">N-2</code>, …, <code class="language-plaintext highlighter-rouge">1</code> まで変化させながら、<code class="language-plaintext highlighter-rouge">0</code> 以上 <code class="language-plaintext highlighter-rouge">i</code> 以下の整数をランダムに選択し(この整数を <code class="language-plaintext highlighter-rouge">j</code> とする) <code class="language-plaintext highlighter-rouge">i</code> 番目と <code class="language-plaintext highlighter-rouge">j</code> 番目の要素を入れ替える操作を繰り返す」というアルゴリズム…<br />
と言われても、この説明だけではイメージが湧きにくいと思います。</p>

<p>JavaScript で実装すると以下のようになりますが、</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">shuffle</span><span class="p">(</span><span class="nx">array</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// i を N-1, N-2, ..., 1 までループ</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="nx">array</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span><span class="o">--</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// 0 以上 i 以下の整数をランダムに選択し（この整数を j とする）</span>
        <span class="kd">const</span> <span class="nx">j</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="p">(</span><span class="nx">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">));</span>
        <span class="c1">// i 番目と j 番目の要素を入れ替える</span>
        <span class="p">[</span><span class="nx">array</span><span class="p">[</span><span class="nx">i</span><span class="p">],</span> <span class="nx">array</span><span class="p">[</span><span class="nx">j</span><span class="p">]]</span> <span class="o">=</span> <span class="p">[</span><span class="nx">array</span><span class="p">[</span><span class="nx">j</span><span class="p">],</span> <span class="nx">array</span><span class="p">[</span><span class="nx">i</span><span class="p">]];</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nx">array</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<ul>
  <li>もっとシンプルに、乱数を紐づけてソートではダメなの？</li>
  <li>たった1回のループで本当にシャッフルできているの？</li>
</ul>

<p>など、気になります。</p>

<h1 id="乱数を紐づけてソートする方法との比較">乱数を紐づけてソートする方法との比較</h1>

<p>例えば以下の配列の要素をシャッフルしたいとします。</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">]</span>
</code></pre></div></div>

<p>この配列の各要素に乱数を紐づけて</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[[</span><span class="mi">1</span><span class="p">,</span> <span class="mf">0.83</span><span class="p">],</span>
 <span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mf">0.37</span><span class="p">],</span>
 <span class="p">[</span><span class="mi">3</span><span class="p">,</span> <span class="mf">0.45</span><span class="p">],</span>
 <span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mf">0.13</span><span class="p">],</span>
 <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mf">0.52</span><span class="p">]]</span>
</code></pre></div></div>

<p>乱数の方でソートし</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[[</span><span class="mi">4</span><span class="p">,</span> <span class="mf">0.13</span><span class="p">],</span>
 <span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mf">0.37</span><span class="p">],</span>
 <span class="p">[</span><span class="mi">3</span><span class="p">,</span> <span class="mf">0.45</span><span class="p">],</span>
 <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mf">0.52</span><span class="p">],</span>
 <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mf">0.83</span><span class="p">]]</span>
</code></pre></div></div>

<p>最後に乱数を削除すれば</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">1</span><span class="p">]</span>
</code></pre></div></div>

<p>シャッフルされた配列が得られます。</p>

<p>単純明快な手法ですし、ソート処理もメジャーな言語ならサポートされているので実装も楽そうです。<br />
これでよさそうな気がしますが、実は改善できる点があります。</p>

<h2 id="シャッフルに必要となるリソースを比較する">シャッフルに必要となるリソースを比較する</h2>

<p>同じ結果を得られるなら、メモリ使用量が少ない方が嬉しいでしょう。</p>

<p>乱数を紐づけてソートする方法では、紐づけた乱数の分のメモリが追加で必要になります。<br />
一方、<strong>Fisher–Yates アルゴリズムの場合、もとの配列の中で入れ替え処理を行うため、追加のメモリはほとんど必要ありません。</strong></p>

<p>また、同じ結果を得られるなら、計算時間は短い方が嬉しいです。</p>

<p>少数のシャッフルなら方法によらず瞬時に計算が終わるので、ここではシャッフル対象の配列はある程度大きいものとして、対象の配列の長さ <code class="language-plaintext highlighter-rouge">N</code> が大きくなると計算時間がどのような伸び方をするかを比較してみます。<br />
（例えば <code class="language-plaintext highlighter-rouge">N</code> が2倍、3倍、4倍…になったとき、計算時間も2倍、3倍、4倍…で済む実装と、4倍、9倍、16倍…のように膨れ上がっていく実装の2択なら、前者を選びたいですよね。）<br />
乱数を紐づけてソートする方法では、基本的に伸び方は <code class="language-plaintext highlighter-rouge">N*log(N)</code> と表現されます。<code class="language-plaintext highlighter-rouge">N</code> が2倍、3倍、4倍…になったとき、計算時間は2倍、3倍、4倍…よりも大きくなってしまうということです。<br />
<strong>一方、Fisher–Yates の伸び方は <code class="language-plaintext highlighter-rouge">N</code> と表現されます。つまり、2倍、3倍、4倍…で済むのです。</strong><br />
（詳細は時間計算量について調べてみてください）</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>メモリ使用量の増加</th>
      <th>計算時間の伸び方</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>乱数を紐づけてソート</td>
      <td>❌ 乱数値 <code class="language-plaintext highlighter-rouge">N</code> 個分の追加メモリが必要</td>
      <td>❌ <code class="language-plaintext highlighter-rouge">N*log(N)</code> で伸びていく</td>
    </tr>
    <tr>
      <td>Fisher-Yates shuffle</td>
      <td>✅ わずか</td>
      <td>✅ <code class="language-plaintext highlighter-rouge">N</code> で済む</td>
    </tr>
  </tbody>
</table>

<p>Fisher–Yates shuffle は、メモリ使用量の面でも計算時間の面でも優れていることがわかります。</p>

<h1 id="正しくシャッフルできているのか">正しくシャッフルできているのか</h1>

<p>いくら効率が良くても、例えば <strong>「最初に前の方にあった要素が前に来やすい」というような偏りがあると困ります。</strong><br />
偏りをなくすにはどうすればよいでしょうか。</p>

<h2 id="シャッフルの基本">シャッフルの基本</h2>

<p>「元の配列をどう並べ替えるか？」と考えると難しくなってしまいますが、シャッフルで実現したいことは最終的にランダムな順序にすることであり、最初の順序は気にする必要がありません。</p>

<p>そこで、</p>

<ol>
  <li>一旦、元の配列の要素を1つの大きな袋に入れてしまう</li>
  <li>袋からランダムに1つ取り出し、最後尾に追加する という操作を繰り返す</li>
</ol>

<p>と考えてみましょう。</p>

<video muted="" playsinline="" controls="" style="max-width: 100%; width: 600px; height: auto; display: block; margin: 0 auto;">
  <source src="/assets/2025/2025-03-13-fisher-yates-shuffle/bag-shuffle.mp4" type="video/mp4" />
</video>

<p>初期状態の並び順に無関係で、シャッフル後の順序も偏りがないシャッフル方法ができました。<br />
これと同じことをコードで実現すればよいのです。</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">shuffle</span><span class="p">(</span><span class="nx">array</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// まずは袋に入れる</span>
    <span class="kd">const</span> <span class="nx">bag</span> <span class="o">=</span> <span class="nx">array</span><span class="p">.</span><span class="nx">slice</span><span class="p">();</span>
    <span class="kd">const</span> <span class="nx">shuffled</span> <span class="o">=</span> <span class="p">[];</span>

    <span class="k">while</span> <span class="p">(</span><span class="nx">bag</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// 袋からランダムの1つ取り出し</span>
        <span class="kd">const</span> <span class="nx">randomIndex</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="nx">bag</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span>
        <span class="kd">const</span> <span class="nx">selectedElement</span> <span class="o">=</span> <span class="nx">bag</span><span class="p">.</span><span class="nx">splice</span><span class="p">(</span><span class="nx">randomIndex</span><span class="p">,</span> <span class="mi">1</span><span class="p">)[</span><span class="mi">0</span><span class="p">];</span>
        <span class="c1">// 取り出した要素を最後尾に追加する</span>
        <span class="nx">shuffled</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">selectedElement</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nx">shuffled</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="in-place-での実装">in-place での実装</h2>

<p>上記の実装では、シャッフルされた配列を新しく作成しています。<br />
配列を新規作成しないような実装に変更するにはどうすればよいでしょうか。</p>

<p>もう一度、先程のシャッフルのイメージを思い出してみましょう。</p>

<video muted="" playsinline="" controls="" style="max-width: 100%; width: 600px; height: auto; display: block; margin: 0 auto;">
  <source src="/assets/2025/2025-03-13-fisher-yates-shuffle/bag-shuffle.mp4" type="video/mp4" />
</video>

<p>ここで、袋の中身の順序は無関係という性質を利用し、<strong>配列のうち未シャッフル部分を袋として利用</strong>しましょう。<br />
つまり、<strong>「袋から取り出し、最後尾に追加する」を「袋を1つ分縮小させる。この時、はみ出た要素が最後尾に追加されたことにする」</strong>と考えます。<br />
このとき、「ランダムに1つ取り出す」は「ランダムに1つ選び、はみ出ることになる位置に移動させてから、袋を縮小させる」となります。</p>

<video muted="" playsinline="" controls="" style="max-width: 100%; width: 600px; height: auto; display: block; margin: 0 auto;">
  <source src="/assets/2025/2025-03-13-fisher-yates-shuffle/in-place-shuffle.mp4" type="video/mp4" />
</video>

<p>これを、実装しやすいように前後反転すると、最初の Fisher–Yates shuffle の実装になります。</p>]]></content><author><name>aruma256</name></author><category term="blog" /><category term="algorithm" /><summary type="html"><![CDATA[配列のシャッフルに有用な Fisher–Yates shuffle と呼ばれるアルゴリズムをわかりやすく解説することを目指した記事です。]]></summary></entry><entry><title type="html">GraphQLに入門した</title><link href="https://aruma256.dev/blog/2024/12/12/graphql.html" rel="alternate" type="text/html" title="GraphQLに入門した" /><published>2024-12-12T00:00:00+09:00</published><updated>2024-12-12T00:00:00+09:00</updated><id>https://aruma256.dev/blog/2024/12/12/graphql</id><content type="html" xml:base="https://aruma256.dev/blog/2024/12/12/graphql.html"><![CDATA[<p>仕事でGraphQLを使うようになったので、入門した感想や学びをまとめます。<br />
この記事は <a href="https://adventar.org/calendars/10317">GMOペパボ エンジニア Advent Calendar 2024</a> の12日目です。</p>

<h1 id="graphqlとの出会い">GraphQLとの出会い</h1>

<p>2024年9月に、EC事業部からSUZURI事業部へ異動しました。
SUZURIではGraphQLが採用されており、これまで聞いたことしかなかったGraphQLを初めて使うことになりました。<br />
これまでほぼRESTで生きてきたので、なかなか衝撃的でした。「クライアントが好き勝手に複雑な構造のクエリを要求して、ちゃんとその構造の通りに、一発でデータを返してくれる…？そんなことある…？」となりました。</p>

<p>実際に学び、触れていく中で、そのシンプルで無駄のない仕組みに惹かれていきました。
また、競技プログラミングなどでグラフ関連のアルゴリズムを学んでいた経験があったため、データをグラフとして扱う考え方も面白く感じたポイントでした。</p>

<h1 id="graphqlのおもしろポイント">GraphQLのおもしろポイント</h1>

<p>RESTはリソースとそれらの操作という考え方ですが、GraphQLではクライアントが必要なデータのみを指定して取得できる仕組みを提供しています。<strong>本当に必要な情報を過不足なく取得できるのは、通信の効率性という面で大きな利点</strong>といえるでしょう。<br />
また、リソースをグラフとして捉えるアプローチは、GraphQLのスキーマにモデル間の関係をそのまま活かせる点が魅力です。ActiveRecordを使用しているプロジェクトでは、既存のモデリングを自然な形でAPIに反映できます。</p>

<h1 id="開発時の注意点">開発時の注意点</h1>

<p>アクセスが制限されるリソースに対して適切な制限を適用することが重要です。
もちろんこれはGraphQLに限ったことではありませんが、GraphQLでは入口を塞いでも回り道でアクセスされてしまうことに注意が必要です。<br />
例えば、ルートの user フィールドにのみアクセス制限を適用している場合、</p>

<div class="language-graphql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">query</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">user</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">address</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>のようなクエリには認証エラーを返せますが、</p>

<div class="language-graphql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">query</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">post</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">author</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="n">address</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>のように間接的にアクセスすると、個人情報を取得できてしまうかもしれません。<br />
（これについては、<a href="https://graphql-ruby.org/authorization/overview">GraphQL RubyのAuthorizationのドキュメント</a>でより詳しくRESTとGraphQLの違いが説明されています。）</p>

<p>入口ではなく、リソースそのものにアクセス制限を適用することで、このような問題を防げます。
<a href="https://graphql-ruby.org/authorization/authorization">GraphQL Rubyなら、<code class="language-plaintext highlighter-rouge">authorized?</code> などが利用できます。</a></p>

<p>パフォーマンス面では、（これもGraphQLに限ったことではありませんが）N+1に注意する必要があります。
DataLoaderなど、効率化するための仕組みを活用しましょう。</p>

<h1 id="学習方法とおすすめリソース">学習方法とおすすめリソース</h1>

<p>同僚におすすめされた<a href="https://www.oreilly.co.jp/books/9784873118932/">「初めてのGraphQL ―Webサービスを作って学ぶ新世代API」（O’Reilly Japan）</a>が参考になりました。
背景・歴史（第一章）やグラフ理論（第二章）などから始まっていて、GraphQLの本質的な部分から理解を深めることができ、「なぜGraphQLなのか」という根本的な疑問に答えてくれる内容でした。</p>

<p><a href="https://graphql.org/learn/">GraphQL公式サイトのLearnセクション</a>もコンパクトにまとまっていて、とても参考になります。
インタラクティブに試せるサンプルも用意されているので、実際に触って感覚を掴みたい方におすすめです。
ベストプラクティスについてもまとめられています。</p>

<h1 id="最後に">最後に</h1>

<p>GraphQLは「必要な情報を過不足なく取得する」という考え方を実現した、シンプルながら強力なアプローチです。適切に使いこなすことで、クライアントの要求に柔軟に応えつつ効率的な通信を実現できるでしょう。<br />
また今回は触れませんでしたが、データの更新もGraphQLで行えますし、subscriptionを使えばリアルタイム向けの機能も実現できるところも面白いです。</p>

<p>ただし、アクセス制御やパフォーマンスなど、実装時に注意すべきポイントもあります。これらに適切に対応することで、GraphQLの利点を最大限に活かすことができるでしょう。</p>

<p>まだGraphQLを使ったことがない方、ぜひ一度触ってみてください。</p>]]></content><author><name>aruma256</name></author><category term="blog" /><category term="graphql" /><summary type="html"><![CDATA[仕事でGraphQLを使うようになったので、入門した感想や学びをまとめます。 この記事は GMOペパボ エンジニア Advent Calendar 2024 の12日目です。]]></summary></entry></feed>