TECH PLAY

Lisp

イベント

該当するコンテンツが見つかりませんでした

マガジン

該当するコンテンツが見つかりませんでした

技術ブログ

はじめまして、2026年5月からUzabaseで働いております。清水と申します。 自分のジョインしたチームでは、関数型言語であるClojureを使って開発しています。関数型言語での開発をきっかけに、関数型プログラミングの良さについて興味を持ったので、今回社内発表をしました。 関数型デザイン【委託】 - 達人出版会 なっとく!関数型プログラミング【PDF版】 | SEshop| 翔泳社の本・電子書籍通販サイト https://www.goodreads.com/book/show/39996759-a-philosophy-of-software-design 🎓 学生の方へ | 1day イン…
電通 総研 クロス イノベーション 本部の山下です。2025年11月-12月にかけて開催されたKiroの Hackathon イベントであるKiroweenに参加しましたので、そのレポートをお送りします。 このイベントはKiroを使ってアプリケーションを開発することを目的とした ハッカソン イベントです。 作るもののテーマがハロウィンをモチーフにしたイベントになっています。 参加要件など 以下のような参加要件になっていました。 実際の詳細は 公式サイト をご覧ください。 基本的にKiroを使ってアプリケーション開発をすればよいのですが、テーマが指定されているのが特徴です。 Resurrection: お気に入りの技術を復活させる Frankenstein: 複数の技術を組み合わせてアプリを作る Skeleton Crew: ス ケルト ンを作成し、それから複数のアプリを作る Costume Contest: 洗練された不気味なデザインのアプリを作る といったテーマのようです(日本語訳は筆者による)。 自分はResurrectionを選びました。参加するにあたりテーマ選定にかなり悩んだのですが、知人から自分が普段 Common Lisp を使っていて、それは十分に古い技術なのではという指摘を受けて、確かにその通りだなということで決めました。 作ったもの Kabotanというアプリケーションを実装しました。Kabotanは Common Lisp を使って作った、HTMXとLLMを組み合わせたアプリケーションです。ハロウィンにちなんだ機能を提供していて、質問に答えたり、ハロウィンに関する文章を生成したりすることができます。 なぜ Common Lisp を採用したかというと、古い技術と見なされており、テーマのResurrectionにも合っているためです。一方で自分は普段それなりに Common Lisp を使っているので少しでも Common Lisp の良さを知ってもらえたらと思い選びました。 Kabotanは以下のURLで公開しています。 https://github.com/dentsusoken/kabotan/ Kabotanは以下のような アーキテクチャ になっています。 フロントエンド: HTMX + Tailwind CSS バックエンド: Common Lisp (clack + hunchentoot) LLM: llama.cppを利用したローカルモデル(gpt- oss -120bなどを想定) モダンなアプリケーションではフロントエンドにReactやVue.jsなどの JavaScript フレームワーク を使うことが多いですが、今回はシステムの大部分を Common Lisp で実装したかったため、HTMXを採用しました。 フロントエンドにHTMXを使うことでフロントエンドの JavaScript コードを最小限に抑え、アプリケーションの大部分を Common Lisp で実装することができました。 実際の画面の例を以下に示します。 特に各 コンポーネント 間のやり取りではServer Sent Event(SSE)を利用して、LLMからの応答をリアルタイムに受け取れるようにしています。これにより、ユーザはLLMが応答を生成している間も進捗を確認でき、より インタラクティブ な体験が可能となっています。 個人的には Common Lisp でも現代的なアプリケーションの実装は十分に可能ということを示せたのではないかと思います。 ちなみに、 Common Lisp を含む Lisp 系の言語は括弧が多いことで有名です。慣れるとS式は読みやすいのですがなれないと苦労するかもしれません。例えばKabotanのindex.htmlを返す部分は以下のようなコードになっています。 ( defun serve-index ( env ) "Serve the main index.html page. The Lack session middleware automatically handles session cookies, so we don't need to manually set them here." ( declare ( ignore env )) ( let (( html ( uiop:read-file-string "public/index.html" :external-format :utf-8 ))) `( 200 ( :content-type "text/html; charset=utf-8" ) ( ,html ) ) )) Lisp 系言語ではこのS式と呼ばれる (関数名 引数1 引数2 ... 引数N) というような記法でプログラム自体を記述します。このデータもプログラム本体も全てこのS式で表現することで、非常に強力なマクロを作れたりするのが特徴となっています。 実装するうえで苦労したところ Common Lisp をKiroで利用するにあたって苦労した点、工夫した点がいくつかありました。 Common Lisp をKiroで利用するための整備 まず、 Common Lisp をKiroが利用できるようにするための整備です。例えば、 Common Lisp には標準でデバッガが実装されており、エラー発生時などには自動的にデバッガが起動します。 Common Lisp で広く使われている開発環境のSLIMEではこれを便利に利用することができます。しかし、この機能はKiroなどのAIにとっては対話的な操作が必要になってしまいAIの操作を阻害してしまいます。 また、ASDF(Another System Definition Facility)という Common Lisp の デファクトスタンダード なビルド管理システムがあります。これも事前に定義を行っておきひな形のアプリケーションが動作するような状態まで整備を行いました。その上で、makeを利用して常にデバッガを起動しないオプションを付けて起動するようにし、Kiroからもmake経由で実行するような形にしました。 最終的には以下のような Makefile のエントリとなりました。 --disable-debugger を実行時に引数で渡し、ASDFを使ってKabotanをビルド、実行する形になっています( ql:quickload がASDFを内部で呼ぶ仕組みになっています)。 ROS = ros LISP_IMPL = sbcl SYSTEM = kabotan TEST_SYSTEM = kabotan-test run: $(ROS) -L $(LISP_IMPL) run -- \ --disable-debugger \ --eval '(ql:quickload :$(SYSTEM))' \ --eval '(uiop:quit (kabotan:main))' Server Sent Eventへの対応 Kiroでのアプリケーション開発において、Server Sent Event(SSE)に対応させるのに苦労しました。SSEはサーバからクライアントへリアルタイムにデータを送信するための技術であり、LLMの応答をリアルタイムに受け取るために必要でした。 ブラウザ-Kabotan間のSSE対応 Common Lisp のWebフレームワークであるclackやhunchentootは直接このSSEをサポートしておらず、独自に実装する必要がありました。これはclackのソケットを直接操作する機能を利用して、SSEに対応させることができました。 Kabotan-llama.cpp間のSSE対応 llma.cppのサーバにとってKabotanはSSEのクライアントとして振る舞う必要があります。 これも Common Lisp のHTTPクライアントライブラリのdexadorを利用して独自に実装する必要がありました。dexadorは通信に利用しているソケットを扱うことができ、これを操作することでSSEに対応させることができました。 その他苦労した点 HTMX周りはKiroに色々指示を出さないとうまく対応できないことがあり苦労しました。HTMXはフロントエンドの JavaScript コードを減らすことができる利点がありますが、Kiroにその利点を理解してもらうのが難しい場合があり、何も指示を行わないとフロントエンドの JavaScript でほとんどの実装を行ってしまい、HTMXの利点がない構成になってしまうことがありました。 またLLMを利用するアプリケーションはテストに時間がかかってしまいます。 そしてKiroはコマンドの応答待ち時間が最大で20分になっていますが、稀にこれを超えてしまうことがありました。こうなってしまうと、Kiroはテストを途中で打ち切ったり問題がないのに問題があると判定して編集作業を行おうとしたり、逆に問題があるのに問題ないと判断してしまったりすることがあり、開発効率が低下することがありました。 Kiroの使い方について Hackathon 全体を通じてどのようにKiroを活用したのかについても紹介します。 Kiroを使ううえで重要だと感じたポイントは以下のとおりです。 Spec、Steeringの活用 Hookの活用 テストの工夫 特に、SpecとSteeringの使い分けは重要だと感じました。 Specという名前を見るとSpec側に詳細な仕様を書くべきだと考えがちですが、実際にはSteering側に詳細な仕様を書く方が効果的でした。例えば、 アーキテクチャ に関する指示、設計上の選択といったものはSteeringに記載し、実装が進むにつれて状況が変わるたびにSteeringはプロジェクトの実際の状況を表すように更新する必要がありました。 そして、Specは実際の小さな作業を行うために必要な最小限の仕様に留めておく方が効果的でした。基本的な動作の概要を伝えて、Design.mdを作成してもらい、Task.mdを生成してもらうようにしました。つまり、Specは スクラム 開発などでいうところの「ユーザーストーリー」に近い役割を果たし、Steeringが「詳細な要件定義書」や「設計書」に近い役割を果たす形です。 これらを前提に置き、詳細な設計などはVibe CodingでKiroと相談しながら進め随時Steeringを更新したり、簡単なバグ修正などは直接修正したりして進めました。一定規模を超える作業になりそうな場合はSpecを作成して対応してもらい、 リファクタリング などの作業もSpecとして作成して随時実施するようにしました。 以下は開発時のKiroの画面の様子です。Agent Steeringに色々設計上を指定しておき、作業ごとにSpecを作り開発していきました。 また、Hookも積極的に活用しました。Hookを使うことで、Kiroが生成したコードに対して自動的に追加の処理を行うことができます。Kabotanでは lisp ファイルが更新されたときに自動的にテストが実行されるようにHookを設定しました。Hookは便利なのですが、TaskとしてKiroが実行してしまうためHookを実行している間新しいタスクの着手が出来ないという欠点もあります。つまり、タスクが完了したとKiroが報告してくるので次のタスクを実行しようとするが、Hookが動作している間は新しいタスクに着手できないということです。しかもKiroは現在実行中のタスクを一望するインタフェースが分かりづらい位置にあるので最初は苦労しました。 以下のUIで実行中のタスクなどが確認できます。クリックして初めて詳細が分かるようになっています。常に表示されていると便利なのですが今後是非改善してほしいですね。 テストの書き方も簡単な 単体テスト であればKiroに生成してもらうようにして、実際の動作を確認するような総合テストについては細かく指示を出してKiroに生成してもらうようにしました。総合テストでは受け入れのためのテストを作るような指示を出し、それをこまめに実行するような運用を行いました。これは最終的な動作だけはちゃんと確認したいという意図でした。 Kiroが良くなっていた点 Kiroが発表されてから時間が経過しており、その間にKiro自体も改善されていました。今回の Hackathon を通じて特に良くなっていたと感じた点は以下のとおりです。 利用できるモデルが増え、特にClaude Sonnet 4.5が利用できるようになりました。これにより、生成されるコードの品質が向上しています。また、利用中にKiroがGA(General Availability)になりQ Developer CLI がKiro CLI になったという変化もありました。これに合わせてアカウント管理などもKiro側で行うことが可能になり、より使いやすくなっていました。特に上限に達した場合にも追加で課金を行うことで利用が可能になるのはとても便利になった点です。Q Developerを試していたころは上限に達すると利用できなくなってしまい、開発が中断されてしまうことがありました。新規アカウントをその都度発行するという手段もあるのですが、会社のアカウントで利用している場合は難しい場合もあるので、追加課金で対応できるのは便利です。 また、プロパティベースのテストが生成できるようになりました。以前は 単体テスト などの具体的な値を使ったテストが中心でしたが、今回はプロパティベースのテストを生成するように指示を出すことで、より広範囲な動作確認が可能になります。受入テストなどでは特に有効だと感じました。 まとめ Kiroの Hackathon イベントであるKiroweenに参加し、 Common Lisp を使ったHTMX+LLMアプリケーションであるKabotanを開発しました。Kiroを活用することで、効率的に開発を進めることができ、 Common Lisp でも近代的なアプリケーションの実装が可能であることを示せたと感じています。 またKiroは言語の限定なく利用できるということが 公式ドキュメント で記載されています。 Common Lisp でも問題なく対応出来ました。採用する機会が少ない言語も含めて色々な言語でアプリケーション開発可能であることも確認できました。 Kiro自体も改善されており、より使いやすくなっていました。今後もKiroを活用して様々なアプリケーション開発に挑戦していきたいと考えています。 以上、Kiroween参加レポートでした。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @yamashita.tsuyoshi レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )
本記事は「 Property-Based Testing Caught a Security Bug I Never Would Have Found 」を翻訳したものです。 ターゲット型ランダムテストが実際のセキュリティ脆弱性を発見したとき セキュリティ脆弱性は、私たちがテストしようと思わないコードの隅に隠れていることがよくあります。正常系テストを書き、想像できるいくつかの境界値ケースをテストしますが、考えもしない入力についてはどうでしょうか? LLM がデフォルトでこれらのシナリオを処理していると仮定することが多いですが、LLM が生成したコードも人間が書いたコードと同様にバグや脆弱性を含む可能性があります。ユーザーがアプリケーションに悪意のある文字列を入力したらどうなるでしょうか? これは、Kiro の 最新の GA 機能 を使用して AI でチャットアプリケーション用のストレージサービスを構築するテストを行ったときに起こったことです。 仕様駆動開発(SDD)ワークフロー に従って、Kiro は要件を慎重に定義し、テスト可能なプロパティを抽出し、API キーの保存と取得のための一見単純なコードを実装しました。実装は堅実に見えました。コードレビューでも承認されたでしょう。従来の単体テストも通過したでしょう。 しかし、プロパティベーステストの 75 回目の反復で、予期しないことが起こりました。ラウンドトリップケースのプロパティテスト全体が失敗したのです。単純な保存と取得操作であるはずが、代わりに JavaScript プロトタイプの誤った処理を露呈しました。これは、早期に欠陥を排除するよう注意しないと、将来的にセキュリティ問題につながる可能性があるバグです。 この投稿では、プロパティベーステスト(PBT)が人間の直感や従来のテスト手法では見逃されたであろうセキュリティバグをどのように発見したかのストーリーを紹介します。以下について説明します。 Kiro が定義した仕様とプロパティ 重大な欠陥を含んでいた一見無害な実装 PBT の入力空間の体系的な探索が脆弱性をどのように発見したか 脆弱性に対処する修正 これが安全なソフトウェア構築にとってなぜ重要なのか これは単なる理論的な演習ではありません。自動テスト技術が、セキュリティ研究者を夜も眠れなくするエッジケースを、本番環境に到達する前に発見できることの実例です。 背景 一部の顧客とアプリケーションの構築に取り組み、仕様のプロンプトを検討する際、Kiro はユーザーデータをブラウザの localStorage に保存するチャットアプリケーション用のストレージシステムを実装していました。主要な機能の一つは、異なる LLM プロバイダー(OpenAI、Anthropic など)の API キーを保存することでした。ユーザーはプロバイダー名をキーとして API キーを保存できます。このオブジェクトは以下のような API を持ちます。 storageService.saveApiKey("openai", "sk-abc123..."); storageService.saveApiKey("anthropic", "sk-ant-xyz..."); Kiro は SDD に従って以下の要件を策定しました。 ### 要件 6 **ユーザーストーリー:** ユーザーとして、異なる LLM プロバイダーの API キーを設定したい。そうすることで、自分のアカウントを使用してコストを管理できる。 #### 受け入れ基準 1. ユーザーが設定を開いたとき、チャットアプリケーションは各 LLM プロバイダーの API キー入力フィールドを表示する 2. ユーザーが API キーを保存したとき、チャットアプリケーションはそれをローカルストレージに安全に保存する 3. API キーが無効または欠落している場合、チャットアプリケーションは明確なエラーメッセージを表示し、メッセージ送信を防ぐ 4. チャットアプリケーションはセキュリティのため UI で API キー値をマスクする 5. ユーザーが API キーを削除したとき、チャットアプリケーションはその LLM プロバイダーを無効にする 受け入れ基準 2 について詳しく見てみましょう。Kiro はこれを重要な正確性プロパティとして選択しました。 **プロパティ 19: API キーストレージのラウンドトリップ** *任意の* プロバイダーに保存された API キーについて、ストレージから取得すると同じキー値が返される。 **検証対象: 要件 6.2** Kiro はこれを「ラウンドトリップ」プロパティと呼んでいます。ラウンドトリップは正確性プロパティの一般的な形で、任意の値から始めて、一連の操作を実行し、同じ値で終わるものです。この場合、任意の文字列値 provider と key から始めて以下を行いました。 ストレージの provider の下に key を保存 provider に関連付けられた値を取得 そして、取得した値は key と等しくなければなりません。これが真でない場合(異なる値を取得したり、例外が発生したりする場合)、明らかに実装に何か問題があります。この仕様は素晴らしく見えるので、承認して Kiro に API を実装してもらいます。 LLM は API の一部として以下のコードを生成しました。 /** * 特定のプロバイダーの API キーを保存 */ saveApiKey(provider: string, apiKey: string): void { try { const apiKeys = this.loadAllApiKeys(); apiKeys[provider] = apiKey; localStorage.setItem( StorageService.API_KEYS_KEY, JSON.stringify(apiKeys) ); } catch (error) { if (error instanceof Error && error.name === 'QuotaExceededError') { throw new Error('ストレージクォータを超過しました。API キーを保存できません。'); } throw error; } } その後、Kiro はプロパティベーステストを使用してこのコードをテストし、期待するプロパティが実際に成り立つという証拠を収集しました。プロパティ 19 をチェックするために、Kiro は TypeScript 用の fast-check ライブラリを使用して以下のテストを書きました。 describe('プロパティ 19: API キーストレージのラウンドトリップ', () => { /** * 機能: llm-chat-app, プロパティ 19: API キーストレージのラウンドトリップ * 検証対象: 要件 6.2 * * プロバイダーに保存された任意の API キーについて、ストレージから取得すると * 同じキー値が返される。 */ it('保存と読み込みサイクルを通じて API キーを保持する', () => { fc.assert( fc.property( fc.string({ minLength: 1, maxLength: 100 }), // プロバイダー名 fc.string({ minLength: 10, maxLength: 200 }), // API キー (provider, apiKey) => { // 各プロパティテスト実行前に localStorage をクリア global.localStorage.clear(); // API キーを保存 storageService.saveApiKey(provider, apiKey); // 読み込み直す const loaded = storageService.loadApiKey(provider); // 元の値と一致することを確認 expect(loaded).toBe(apiKey); } ), { numRuns: 100 } ); }); Kiro がこのテストを実行すると、試行 #75 で失敗が発生しました!Kiro は失敗を Shurinking し、以下の反例を報告しました。プロバイダー "__proto__" と API キー " " 。 何が起こっているのか? プロパティベーステストはプロバイダー名にランダムな文字列を生成し、75 回のテスト実行後、プロバイダー名として文字列 "__proto__" を生成しました。これにより、以下の反例でテストが失敗しました。 反例: ["__proto__"," "] プロバイダー名 __proto__ で API キーを保存してから読み込もうとすると、奇妙なことが起こり、期待した値を取得できません。Kiro は Shurinking を使用して最小反例を提示して問題を特定し、問題から余分な詳細を取り除くのに役立ちます。この場合、apiKey 文字列をジェネレーターで許可される最小の文字列(スペースのみを含む)に Shurinking します。これは、問題が値ではなく、奇妙なキーが問題を引き起こしていることを示しています。JavaScript に詳しい方なら、このエラーはすぐに目に付くでしょうが、そうでない方は読み続けてください。 これは JavaScript がオブジェクトシステムを実装する方法の特徴です。より伝統的なオブジェクト指向プログラミング言語(Java、Python、SmallTalk など)は、クラスの概念を使用します。各クラスは、オブジェクトの構築方法を記述し、異なるオブジェクト間の継承関係を記述するコードベースの静的メンバーです。JavaScript は「プロトタイプ」と呼ばれる代替アプローチを使用します。プロトタイプベースのオブジェクトシステムでは、クラスは存在しません。代わりに、すべてのオブジェクトには、コードとデータを継承すべき親オブジェクトを指すプロトタイプと呼ばれる特別なフィールドが含まれています。これにより、継承関係を動的に設定できます。JavaScript では、このプロトタイプは __proto__ フィールドに存在します。フィールドを文字列に設定しようとしたとき、JavaScript エンジンはこれを拒否し、元のプロトタイプをそのまま保持しました。これにより、プロパティテストの第 2 ステップで provider を検索したときに、元のプロトタイプ(空のオブジェクト)を取得することになります。 プロトタイプへの書き込みが例のように無害というわけではありません。 provider と apiKey は攻撃者の制御下にあるため、攻撃者が apiKey に文字列以外の値を取得する方法を見つけた場合、プロトタイプに値を注入でき、オブジェクトのプロパティからのさらなる読み取りが攻撃者制御の値を返す可能性があります。 これは悪用可能でしょうか?いいえ。 apiKeys オブジェクトは十分に長く存在せず、シリアル化後すぐに解放され、 JSON.stringify は __proto__ フィールドをスキップすることを知っています。また、グローバルプロトタイプを変更するのではなく、 apiKeys のプロトタイプのみを上書きしています。しかし、コードのリファクタリングにより、この悪用不可能な脆弱性をより広範囲な影響を与える可能性のあるものに変える新しいコードパスが導入される可能性があります。プロパティベーステストが提供するテスト力は、これを即座に捕捉して、コードベースにおいて微妙な不正確さや難しいエッジケースが増えるのを防ぐのに役立ちます。 Kiro はこれをどのようにテストしたのか? プロバイダー名 __proto__ で API キーを保存してから読み込もうとしたとき、保存した API キーの代わりに空のオブジェクト {} を取得しました。なぜこれが起こったのでしょうか?内部で何が起こったかについてもう少し背景を理解しましょう。 PBT の利点の1つと言われているのはバイアスです。単体テストでは、テストを書いた人(モデルまたは人間)がエッジケースを考慮しようとしましたが、自分自身の内部バイアスによって制限されています。同じ(モデル/人)が実装を書いたので、実装中に考えなかったエッジケースを思いつくのは困難だと考えるのが妥当です。この場合、プロパティベーステストを使用することで、テストフレームワークを作った人たちの集合知が使えます。この場合、一般的なバグタイプの体系的知識“をプロセスに注入しています。( __proto__ は、fast-check コミュニティの作者によって PBT ジェネレーターにエンコードされた一般的なバグ文字列の一つです)をテストプロセスに注入しています。 続行する前に注意すべき点は、PBT コードに { numRuns: 100 } があることです。これは、ジェネレーターがバグを見つけようとする 100 回の反復があることを意味します。Kiro はこれをデフォルトにしていますが、プログラムに求める信頼レベルに応じて、この値を上げたり下げたりできます。時にはもっと必要ですが、実装のテストに少し時間がかかるため、100 回以上の入力テストを実行するパフォーマンスが開発ライフサイクルのその段階ではまだ価値がない場合もあります。良い点は、必要に応じていつでもこれを上げたり下げたりできることです。 修正 Kiro は MITRE の高効果緩和戦略 に基づいて 2 つの防御策を実装しました。 1. 安全な保存( saveApiKey 内) // プロトタイプ汚染を避けるため null プロトタイプオブジェクトを作成 const safeApiKeys = Object.create(null); Object.assign(safeApiKeys, apiKeys); safeApiKeys[provider] = apiKey; Object.create(null) で作成されたオブジェクトにはプロトタイプチェーンがないため、 __proto__ は単なる通常のプロパティになります。 2. 安全な取得( loadApiKey 内) // hasOwnProperty を使用してキーを安全にチェック return Object.prototype.hasOwnProperty.call(apiKeys, provider) ? apiKeys[provider] : null; より大きな視点 このストーリーは、Kiro が SDD の一部としてプロパティベーステストを使用する理由を示しています: プロパティは要件に直結 – 「任意のプロバイダー名について、ラウンドトリップする」というプロパティは、要件をそのまま変換したものです。 ランダム生成は予期しないエッジケースを発見 – 人間と LLM は、テストする入力についてバイアスを持っています。ランダム生成はテストケースを徹底的に追い込みます 実行可能な仕様 – プロパティは実行できる仕様です。「コードは何をすべきか」(要件)と「コードは実際にそれを動かすのか」(テスト)の間のギャップを埋めます。 タイトなフィードバックループ – プロパティが失敗すると、デバッグを容易にする最小限の反例を取得します。Kiro はこれを使用してコードを修正し、迅速な反復サイクルを作成できます。 このバグは Kiro での実際の開発中に発見されました。プロパティベーステストは、以下の方法では発見が非常に困難だったであろうセキュリティ弱点をキャッチしました。 手動コードレビュー 手動で選んだ例を使った従来の単体テスト 統合テスト

動画

該当するコンテンツが見つかりませんでした

書籍

ハッカーと作家

発売日:

Bookmark Icon

ハッカーと作家

Applied AI

発売日:

Bookmark Icon

Applied AI