
プログラミング
イベント
マガジン
技術ブログ
クレジットカードシステムのテストを担当すると、「正常に決済できることは確認したものの、ほかに何をテストすればよいのかわからない」と悩むケースは少なくありません。 クレジットカード決済は金銭を扱うため、一般的な入力フォーム以上に、 テスト漏れが売上や顧客対応へ大きな影響を与えやすい機能 です。 正常なカードで購入できるかだけではなく、カード利用拒否、本人認証の失敗、通信エラー、取消、返金、二重決済といった状況まで想定する必要があります。 さらに、画面上では決済に成功していても、注文データや管理画面のステータスが更新されていなければ、システム全体として正常とはいえません。 重要なのは、思いついたテスト項目を増やしていくのではなく、 決済開始から決済後の処理までを一連のフローとして分解し、それぞれに正常系と異常系を当てはめること です。 テスト用の環境やカードを活用すれば、実際の請求を発生させることなく、決済成功や各種エラーを再現できるサービスもあります。 そこで今回は、クレジットカードシステムで確認したいテスト観点を、 決済フロー・基本機能・外部連携・セキュリティ・リリース前確認 の順番で整理しました! テスト設計やレビュー時の観点整理に役立てながら、担当システムに必要なテストケースへ落とし込んでいきましょう。 import haihaiInquiryFormClient from "https://form-gw.hm-f.jp/js/haihai.inquiry_form.client.js";haihaiInquiryFormClient.create({baseURL: "https://form-gw.hm-f.jp",formUUID: "927d2c4e-f06c-45b1-bd36-0240e55ccf72",}) ▼テストの種類について詳しい内容はこちら▼ 【保存版】テストの目的別タイプ一覧 まず押さえたい!クレジットカードシステムのテスト範囲 クレジットカードシステムをテストするときは、カード情報を入力する画面だけではなく、 決済に関連するシステム全体をテスト対象として捉えること が重要です。 ECサイトやWebサービスでは、自社システムだけで決済処理が完結するとは限らず、決済代行サービスやカード会社など、複数のシステムをまたいで処理が進みます。 そのため、画面上で「購入が完了しました」と表示されたことだけを確認しても、十分なテストとはいえません。 決済結果が注文データへ反映されているか、管理画面のステータスが正しいか、金額にずれがないかなど、内部処理まで確認する必要があります。 特に重要なのが、 画面上の状態・決済サービス側の状態・自社データの状態が一致しているか という観点です。 最初にテスト対象となる処理やステータスを整理しておけば、正常系だけに偏らず、異常系や決済後処理まで含めたテストケースを作りやすくなります。 決済画面だけでなく「一連の処理」をテストしよう! クレジットカード決済では、カード番号を入力して決済ボタンを押した瞬間だけではなく、 注文開始から決済結果の反映までの一連の処理 を確認します。 ECサイトであれば、商品選択、注文情報の作成、カード情報の入力、本人認証、決済処理、決済結果の取得、注文状態の更新、完了画面の表示といった流れが代表的です。 さらに、決済完了メールや管理画面への反映、在庫の更新などが連動している場合は、それらもテスト範囲に含めます。 API(Application Programming Interface)を利用して決済サービスと連携している場合は、リクエスト内容だけではなく、返却された結果が自社システムへ正しく反映されるかを確認することも欠かせません。 たとえば、決済サービスでは成功しているにもかかわらず、注文データが「未決済」のままであれば、後続の出荷や顧客対応で問題が発生します。 利用者から見える画面とシステム内部の状態をセットで確認すること が、決済システムのテストで重要な基本姿勢です。 テスト前に「決済状態と業務フロー」を整理しよう! 具体的なテストケースを作成する前に、システム内で扱われる 決済ステータスと業務フローを一覧化 しておくと、テスト漏れを減らしやすくなります。 決済システムでは単純な「成功」「失敗」だけではなく、処理前、認証中、与信済み、売上済み、取消済み、返金済みなど、複数の状態を扱う場合があります。 オーソリと呼ばれる与信処理の後に売上を確定するシステムでは、与信成功後に売上処理へ進めるか、取消した場合に状態が戻るかといった確認も必要です。 特に注意したいのが、 自社の注文状態と決済サービス側の状態が食い違うケース です。 「決済は成功しているが注文登録に失敗した」「注文データは作成されたが決済は失敗した」などの状態をあらかじめ想定すると、障害時の復旧方法まで確認できます。 都度購入だけでなく、予約販売や定期購入などがある場合は、業務フローごとに「処理×状態×結果」を整理してからテストケースへ落とし込むと効率的です。 ここを押さえれば漏れにくい!クレジットカード決済の基本テスト項目 クレジットカード決済の基本テストは、 正常系・入力エラー・決済失敗・決済後処理 の4つに分けると整理しやすくなります。 最初に正常系で基本的な決済フローが成立することを確認し、その後にカード情報の誤入力や利用拒否などの異常系を追加していくのが基本的な進め方です。 また、購入できることだけを確認してテストを終えるのではなく、取消や返金まで含めて確認する必要があります。 特に決済システムでは、正常系よりも異常発生時の処理で不具合が見つかることが少なくありません。 テストケースを作成するときは、単にケース数を増やすのではなく、 どのような条件で決済結果やシステム状態が変化するのか を基準に整理します。 カード情報、金額、認証結果、決済結果などの条件を組み合わせることで、実運用に近いテスト設計につながります。 まずは正常系!問題なく購入できる流れを確認しよう! 正常系では、利用可能なカードを使用して、 注文開始から決済完了まで問題なく進められること を確認します。 カード番号、有効期限、セキュリティコードなどを正しく入力し、想定した決済結果が返ってくるかを確認しましょう。 Visa、Mastercard、JCBなど複数のカードブランドに対応している場合は、契約内容や実装仕様を踏まえて必要なブランドをテスト対象に含めます。 金額についても、商品価格だけではなく、送料、税、割引、クーポンなどを含めた 最終的な請求金額が注文金額と一致しているか を確認することが重要です。 決済成功後は、注文データや決済ステータス、管理画面、在庫、メール通知など、関連する処理まで正しく反映されているかを確認します。 一つの画面で「成功」と表示されたことだけを合格条件とせず、 決済完了後のシステム状態まで含めて正常系と考えること がポイントです。 入力チェックを網羅!カード情報の異常・境界値を確認しよう! カード情報入力画面では、正しい値だけではなく、 未入力・形式不正・境界値などを使った入力チェック を行います。 カード番号では、未入力、桁数不足、桁数超過、数字以外の入力など、画面仕様で想定されているケースを確認します。 有効期限では、過去の日付、存在しない月、未入力などに対して適切なエラーが表示されるかを確認します。 セキュリティコードやカード名義を扱う場合も、必須チェックや文字数、利用可能な文字種などを仕様書と照らし合わせます。 入力値だけでなく、決済ボタンの連打、ブラウザの戻る操作、再読み込みなど、 通常とは異なる画面操作 を組み合わせることも重要です。 なお、入力形式のエラーと、正しい形式で送信した後にカード会社や決済サービスから返される利用拒否は別の異常系になるため、混同せずテストケースを分けて設計します。 決済失敗も再現!カード拒否や処理エラーを確認しよう! クレジットカードシステムでは、正常に決済できるケースと同じくらい、 決済できなかったときの挙動 が重要です。 テスト環境で再現できる場合は、カード利用拒否、有効期限切れ、利用可能額不足、処理エラーなど、提供されている失敗パターンを確認します。 決済サービスによっては、特定のテストカードや入力条件によって異なるレスポンスを再現できる仕組みがあります。 ただし、再現可能なエラーの種類やテストカードの仕様はサービスごとに異なるため、使用中の決済サービスのテスト仕様に合わせてケースを設計する必要があります。 決済失敗後には、不要な請求が発生していないか、注文状態が正しいか、再度決済できるかなども確認しましょう。 エラーメッセージについては、内部的なエラーコードをそのまま表示するのではなく、 利用者が次に何をすればよいか判断できる内容になっているか という視点も重要です。 決済後も重要!取消・返金・売上処理まで確認しよう! クレジットカードシステムのテストでは、商品を購入できた時点で終了せず、 決済後に発生する取消や返金までテスト します。 決済取消を実行した場合は、決済サービス側だけではなく、自社システムの注文状態や管理画面にも結果が正しく反映されているかを確認します。 返金機能がある場合は、全額返金に加え、システムが対応しているのであれば一部返金も確認します。 同じ取引に対して取消や返金を複数回実行した際に、二重で処理されないことも重要なチェックポイントです。 また、決済サービスによって取消・返金できる期間や処理条件が定められている場合があるため、 サービス側の制約と自社システムの制御が一致しているか も確認する必要があります。 購入処理だけでなく、注文キャンセルや返品といった実際の業務フローまで想定することで、本番運用で発生しやすい問題を事前に見つけやすくなります。 本番障害を防ごう!外部連携・3Dセキュア・セキュリティのテスト クレジットカード決済は外部サービスとの連携が多いため、通常の画面テストだけでは確認できない障害パターンがあります。 特に注意したいのが、 API通信の失敗やタイムアウトによって決済結果が不明確になるケース です。 さらに、ECサイトではEMV 3-Dセキュアによる本人認証が重要となっており、認証成功だけでなく認証失敗やキャンセルなどの分岐もテストする必要があります。 現在のEC加盟店では、EMV 3-Dセキュアの導入だけでなく、Webサイトの脆弱性対策や不正ログイン対策なども重要なセキュリティ施策として位置づけられています。 また、カード情報を保存・処理・送信するシステムでは、PCI DSS(Payment Card Industry Data Security Standard)を踏まえたデータ保護も検討しなければなりません。 機能面で決済できることと、安全な決済システムであることは別のため、 外部障害・本人認証・情報保護をそれぞれ独立したテスト観点として持つこと が重要です。 通信エラーに強くする!API・タイムアウト時の動きを確認しよう! 外部の決済サービスとAPIで連携しているシステムでは、正常なレスポンスだけでなく、 エラーや通信障害が発生した場合の挙動 を確認します。 決済APIがエラーを返した場合に処理を適切に中断できるか、利用者へ必要な案内を表示できるかを確認しましょう。 特に注意したいのが、決済リクエストを送信した後にタイムアウトし、自社システム側では結果を受け取れないケースです。 この状態で単純に再送すると、最初の決済が実際には成功していた場合に二重決済へつながる可能性があります。 決済ボタンの連打、画面の再読み込み、通信途中の画面離脱などについても、 同じ取引が重複して実行されない仕組みになっているか を確認することが重要です。 Webhookなどの非同期通知を利用している場合は、通知が遅れる、重複する、届かないといった状況も含め、決済状態を最終的に正しく確定できるかまでテストします。 3Dセキュアも忘れずに!本人認証の分岐を確認しよう! ECサイトの決済では、EMV 3-Dセキュアによる カード利用者の本人認証を含めたフロー をテストする必要があります。 現在はEC加盟店における不正利用対策としてEMV 3-Dセキュアの導入が重要な位置づけとなっており、認証機能を含めた動作確認は欠かせません。 正常に認証して決済が完了するケースだけでなく、追加認証が必要になる場合や、認証に失敗した場合も確認します。 利用者が認証をキャンセルした場合、認証中にタイムアウトした場合、ブラウザの戻る操作を行った場合などもテスト対象です。 本人認証画面から自社サイトへ戻った際には、 注文状態と決済状態が正しい組み合わせになっているか を必ず確認します。 認証画面自体が正常に表示されることだけを確認するのではなく、認証前から認証後までを一つの決済フローとしてテストすることが重要です。 カード情報を守れている?セキュリティ観点も確認しよう! クレジットカードシステムでは、決済機能が正常に動くことに加えて、 カード情報を安全に取り扱えているか という観点が欠かせません。 まず確認したいのが、カード番号やセキュリティコードなどの機密性が高い情報を、不要な場所へ保存していないかという点です。 アプリケーションログ、エラーログ、アクセスログ、分析ツールなどへカード情報が意図せず出力されていないかも確認します。 PCI DSSは、カード会員データを保存・処理・送信する事業体などを対象として、決済情報を保護するための技術面・運用面の基本要件を定めています。 EC加盟店では、決済部分だけを見るのではなく、Webサイトやシステムの脆弱性、不正ログインなどを含めた対策も重要です。 「機能テストに合格したから安全」と判断せず、機能品質とセキュリティ品質を別々に確認すること が安全な決済システムにつながります。 実務ですぐ使える!テスト環境の準備からリリース判定までの進め方 クレジットカードシステムのテストを効率よく進めるには、テストケースを作る前に 利用できるテスト環境と再現可能な決済結果を確認すること が大切です。 決済サービスによって、テストカードで再現できる成功・失敗パターンや、テスト環境と本番環境の違いは異なります。 そのため、一般的なテスト項目をそのまま当てはめるのではなく、自社システムの仕様と利用している決済サービスの仕様を組み合わせてテストケースを作成します。 すべての条件を総当たりでテストするとケース数が膨大になるため、障害が発生した場合の影響と発生可能性を考えて優先順位を付けることも重要です。 特に決済不可、二重決済、金額誤り、注文状態との不整合などは、事業への影響が大きいため重点的に確認します。 最終的には、 重要なリスクを十分にカバーした状態でリリース判断できるテスト設計 を目指します。 まずテスト環境を確認!テストカードで安全に再現しよう! テストを始める前に、利用している決済サービスが提供している テスト環境やサンドボックスの仕様 を確認しましょう。 決済サービスによっては、実際の請求を発生させずに、決済成功や決済失敗などを再現できる専用のテストカードが用意されています。 正常決済だけでなく、利用拒否や認証失敗などを再現できれば、異常系のテストも安全に実施できます。 ただし、テストカードの番号や再現できる結果はサービスごとに異なるため、別の決済サービス向けに公開されているテスト情報を流用しないよう注意が必要です。 また、テスト環境で確認できる機能と、本番環境でなければ最終確認できない機能をあらかじめ切り分けておきます。 テスト環境は本番環境そのものではない という前提を持ち、環境差分や本番切り替え後に確認すべき項目までテスト計画へ含めることが重要です。 「画面×決済状態×外部連携」でテストケースを整理しよう! テストケースを作る際は、画面ごとに項目を並べるだけでなく、 「画面」「決済状態」「外部連携」の3つの軸 で整理すると抜け漏れを発見しやすくなります。 処理の流れとしては、入力、本人認証、決済処理、結果反映、取消・返金といった単位に分けます。 そこへ正常系、異常系、境界値、通信障害、セキュリティといった観点を組み合わせれば、必要なケースを体系的に洗い出せます。 さらに、カード状態、購入金額、本人認証結果、APIの応答結果など、結果を変化させる条件を整理します。 ただし、すべての組み合わせを機械的に実行すると膨大な工数が必要になるため、 障害時の影響度と発生可能性を考慮して優先順位を付けること が重要です。 レビューではテストケースの総数だけを見るのではなく、二重決済や状態不整合など、事業への影響が大きいリスクを十分にカバーできているかを確認しましょう。 リリース前に最終確認!重大障害につながるケースを優先しよう! リリース直前は、テスト項目を最初からすべて繰り返すのではなく、 障害発生時の影響が大きい機能から優先して最終確認 します。 代表的なのは、決済できない、二重で請求される、注文金額と決済金額が異なる、決済結果と注文状態が一致しないといったケースです。 タイムアウト、通信断、決済ボタンの連打、ブラウザの再読み込みなど、通常操作から外れた状況も重点的に再確認します。 EMV 3-Dセキュアを利用するシステムでは、本人認証を含む実際の決済フローと、テスト環境との違いを把握しておくことも大切です。 さらに、テストモードの解除、APIキーや接続先の切り替えなど、 テスト用設定が本番環境へ残っていないか もリリースチェックに含めます。 万が一の障害に備えて、取引を特定できるID、必要なログ、監視方法、決済サービスへの問い合わせ手順まで確認しておけば、問題発生後の調査や復旧も進めやすくなります。 まとめ|チェックリスト化して「漏れなく安全にリリースできる状態」をつくろう! クレジットカードシステムのテストでは、正常に購入できることだけではなく、 入力エラー、決済失敗、取消・返金、外部連携、本人認証、セキュリティまで一連の流れとして確認すること が重要です。 特に決済システムでは、利用者が目にする画面だけでなく、自社の注文データや決済サービス側のステータスが一致しているかを確認する必要があります。 テストケースは「画面」「決済状態」「外部連携」の軸で整理し、正常系・異常系・境界値を組み合わせると抜け漏れを減らしやすくなります。 テスト環境やテストカードも活用しながら、決済成功だけではなく、失敗や通信障害など本番で起こり得る状況を再現しておきましょう。 すべてを同じ優先度で確認するのではなく、二重決済、金額誤り、決済状態の不整合など、 事業や利用者への影響が大きいリスクを優先すること がポイントです。 整理したテスト観点を自社システム用のチェックリストとして残しておけば、今後の機能改修や決済サービス変更でも再利用でき、テスト設計の効率化と品質向上につなげられます。 QA業務効率化ならPractiTest テスト管理の効率化 についてお悩みではありませんか?そんなときはテスト資産の一元管理をすることで 工数を20%削減できる 総合テスト管理ツール「 PractiTest 」がおすすめです! PractiTest (プラクティテスト) に関する お問い合わせ トライアルアカウントお申し込みや、製品デモの依頼、 機能についての問い合わせなどお気軽にお問い合わせください。 お問い合わせ この記事の監修 Dr.T。テストエンジニア。 PractiTestエバンジェリスト。 大学卒業後、外車純正Navi開発のテストエンジニアとしてキャリアをスタート。DTVチューナ開発会社、第三者検証会社等、数々のプロダクトの検証業務に従事。 2017年株式会社モンテカンポへ入社し、マネージメント業務の傍ら、自らもテストエンジニアとしテストコンサルやPractiTestの導入サポートなどを担当している。 記事制作: 川上サトシ (マーケター、合同会社ぎあはーと代表)
なぜC++/JS間のメモリ管理は難しいのか 皆様、はじめまして。グローバルプロダクト開発本部 グローバルシステム部 技術推進ブロックの村木と申します。私たちのチームでは、OpenCVによる画像処理やニューラルネットワークによる画像認識を含む大規模なC++資産を、EmscriptenでWebAssemblyにコンパイルし、ブラウザへ移植するプロジェクトに取り組んでいます。 その中で私にとって難所となったのが、メモリ管理の理解でした。Emscriptenを使用する開発では、メモリの考え方が異なる2つの言語、C++とJavaScriptをまたいだ処理を実装することがあります。C++ではオブジェクトの寿命がコンストラクタとデストラクタによるRAIIで制御されるのに対し、JavaScriptではオブジェクトの寿命をGC(ガベージコレクタ)が管理し、通常、開発者が解放を意識することはありません。 このメモリモデルの違いに加え、C++のオブジェクトをJavaScript側へ渡すとき、その寿命や所有権に関する情報は引き継がれないことを、しっかりと理解しておく必要があります。 JavaScript側から見ると、C++のオブジェクトは単なる数値(メモリ上のアドレス)に過ぎず、この「数値」には、「誰が確保したのか」「いつまで有効なのか」「誰が解放すべきか」といった所有権の情報は含まれていません。また、JSのGCはC++側のメモリ領域を認識できません。 そのため、Wasmの境界を越えるオブジェクトについては、所有者と有効期間、解放の責任を明確にしたうえで実装する必要があります。この点を理解しておくことが、Emscriptenにおける開発の重要なポイントでした。 Emscriptenが提供するembindを使えば、C++のクラスや std::vector をJavaScriptのオブジェクトに近い形で扱えます。ただし、その抽象化されたAPIの背後で、オブジェクトの受け渡しと寿命管理がどのように行われているのかを理解しておく必要があります。 本記事では、まずWasmのメモリモデルの基礎を概観し、その上でembindによる実装を詳しく解説します。 目次 なぜC++/JS間のメモリ管理は難しいのか 目次 Wasmのメモリモデル C++オブジェクトの実体 境界を越えると寿命の情報が失われる embindは何を隠し、何を隠さないのか value_objectはコピー class_はハンドル typed_memory_viewはビュー おわりに Wasmのメモリモデル WebAssembly(Wasm)のメモリモデルは非常にシンプルです。Wasmモジュールは、 Linear Memory(線形メモリ) と呼ばれる、単一の連続したバイト列を自身のメモリ空間として持ちます。この線形メモリは、JavaScript側からは WebAssembly.Memory オブジェクトの buffer として見えます(型は ArrayBuffer )。 C++コードがWasmにコンパイルされると、グローバル変数やスタック領域、そして malloc / new によって確保されるヒープ領域は、すべてこの線形メモリの中に配置されます。したがって、C++オブジェクトの「実体」とは、この線形メモリ上の特定のアドレスから始まる領域にほかなりません。JavaScriptの視点で言い換えれば、C++のオブジェクトは、巨大な ArrayBuffer (= WebAssembly.Memory が内部に保持する一本の配列)の中に、バイト列として格納されているのです。 まずはこのことを、簡単なコードで確認していきます。 C++側で「指定されたアドレスから始まるバイト列の合計値を計算する」関数を実装します。 // C++側:ptrは線形メモリ内のオフセット(アドレス) extern "C" int sum_bytes ( const uint8_t * ptr, int len) { int total = 0 ; for ( int i = 0 ; i < len; i++) total += ptr[i]; return total; } JavaScript側では、Emscriptenが提供する _malloc で線形メモリ上の領域を確保し、 HEAPU8 を通じて値を書き込みます。このとき、関数に渡す ptr は単なる数値(アドレス)です。 const bytes = new Uint8Array ([ 10 , 20 , 30 ]) ; // 1. 線形メモリ上に3バイト確保 const ptr = Module . _malloc ( bytes . length ) ; // 2. JSから線形メモリへ書き込む Module . HEAPU8 . set ( bytes , ptr ) ; // 3. C++側で同じメモリ領域を読み取って合計を計算 const total = Module . _sum_bytes ( ptr , bytes . length ) ; // 4. メモリを解放 Module . _free ( ptr ) ; console . log ( total ) ; // 60 ptr はC++から見れば「ポインタ」ですが、JSから見れば HEAPU8 の「添字(number)」に過ぎません。 HEAPU8[ptr] と記述することで、C++側がポインタを通じてアクセスするのと、全く同じメモリ領域をJS側でも参照できます。 C++オブジェクトの実体 EmscriptenでコンパイルされたC++コードにおいて、 new や std::vector などによる動的なメモリ確保は、すべてWasmの線形メモリ内(のヒープ領域)に対して行われます。JS側から呼び出す _malloc も、同じヒープ領域からメモリを切り出します。 C++オブジェクトの「実体」とは、linear memory上の特定のアドレスから始まる、数バイトから数メガバイトの領域のことです。例えば、以下のクラスを考えます。 class ByteBuffer { public : explicit ByteBuffer ( size_t n) : data_ (n, 0 ) {} size_t size () const { return data_. size (); } private : std :: vector < uint8_t > data_; // 中身はヒープ上の別領域を指す }; new ByteBuffer(1024) を実行すると、まず ByteBuffer 本体(vectorのポインタやサイズ情報など)がヒープに確保され、その内部のvectorがさらに1024バイトをヒープに確保します。これらはいずれもlinear memory内に存在します。 C++の中で完結している限り、この寿命管理は言語仕様によって保証されています。スコープを抜ければスタック上のオブジェクトは破棄され、 std::vector が確保したメモリも自動的に解放されます。 delete を呼び出せばデストラクタが連鎖し、メモリは安全に回収されます。つまり、 RAII(Resource Acquisition Is Initialization)とデストラクタの仕組みが、メモリの確保と解放の対応を担保している のです。 しかし、この保証が有効なのは「C++のライフタイム管理が届く範囲にある」間だけです。オブジェクトへの参照がJS側に渡ると、この前提は崩れます。 境界を越えると寿命の情報が失われる JavaScriptでは、どこからも参照されなくなったオブジェクトはGC(ガベージコレクション)によって自動的に回収されます。しかし、GCが寿命を管理できるのは「JSヒープ上にあるJSの値」だけです。C++オブジェクトの実体は、前述のとおり線形メモリ(巨大な ArrayBuffer )の一領域にすぎず、GCから見れば「生存している1つの大きな箱」の内側でしかありません。その箱の内部のどこが有効で、どこが解放済みなのかを、GCは関知しないのです。 そのため、C++オブジェクトのアドレスを保持していたJS変数が使われなくなり、GCに回収されたとしても、線形メモリ上の実体は確保されたまま残り続けます。C++のデストラクタは呼ばれず、メモリはリークします。 逆のケースも同様で、C++側で free を呼んでメモリを解放しても、JS変数が握っているアドレス(ただの数値)はそのまま残ります。すでに無効になった領域を指し続けるその数値を使えば、解放済みメモリを読み書きする「Use-after-free」を引き起こします。 いずれもC++/JS間の境界を越えた際に、「このアドレスはByteBufferを指し、所有権はこちらにある」という型と所有権のメタ情報が単なる数値に変換されて失われます。その結果、「誰がいつ解放するのか」という所有権の所在が型システムからもランタイムからも見えなくなり、問題が発生します。 embindは何を隠し、何を隠さないのか これまで、WebAssemblyとJavaScript間でデータをやり取りするために、以下の低レイヤーな手順が必要であることを確認しました。 _malloc でメモリを確保する HEAPU8.set 等を介して手動でデータをコピーする 確保したメモリのアドレスを数値として管理する embindは、こうした低レイヤーの「配管」処理を宣言的な記述に置き換え、JavaScriptとC++の間のバインディング実装を省力化可能な、Emscriptenの強力な機能です。 embindを有効にするには、ビルド時に -lembind オプションを付与します。利用にあたっては、C++側で EMSCRIPTEN_BINDINGS ブロックを記述し、公開したい関数やクラスを明示します。 #include <emscripten/bind.h> using namespace emscripten; // 構造体:単純な値のセット struct Point { float x; float y; }; Point midpoint ( const Point& a, const Point& b) { return Point{(a.x + b.x) / 2 , (a.y + b.y) / 2 }; } // クラス:状態を持つオブジェクト class ByteBuffer { public : explicit ByteBuffer ( size_t n) : data_ (n, 0 ) {} size_t size () const { return data_. size (); } void fill ( uint8_t v) { std :: fill (data_. begin (), data_. end (), v); } private : std :: vector < uint8_t > data_; }; EMSCRIPTEN_BINDINGS (demo) { // 構造体をJSのプレーンなオブジェクトとしてマッピング value_object<Point>( "Point" ) . field ( "x" , &Point::x) . field ( "y" , &Point::y); function ( "midpoint" , &midpoint); // クラスをJSのクラスとしてマッピング class_<ByteBuffer>( "ByteBuffer" ) .constructor< size_t >() . function ( "size" , &ByteBuffer::size) . function ( "fill" , &ByteBuffer::fill); // 型登録(JSから操作可能にする) register_vector< float >( "VectorFloat" ); } この定義により、JavaScript側からは、あたかもネイティブなオブジェクトを扱うかのように、C++側の機能を利用できます。 // JS側からの利用例 const m = Module . midpoint ({ x : 0 , y : 0 } , { x : 10 , y : 4 }) ; console . log ( m . x , m . y ) ; // 5 2 const buf = new Module . ByteBuffer ( 1024 ) ; buf . fill ( 0xff ) ; console . log ( buf . size ()) ; // 1024 embindを利用することで、 _malloc による手動メモリ確保や HEAPU8 の直接操作といった低レベルな記述は隠蔽され、コードも簡潔になります。 しかし私は、前節で述べた低レイヤーにおける処理の理解は重要だと考えています。私自身について言えば、前節で見た線形メモリの挙動を一度自分の手で確かめることで、ようやくembindの宣言的な記法を使いこなせるようになったからです。また、いつハンドルを delete() すべきか、なぜ確保したはずのビューが壊れることがあるのかといった疑問にも、筋道を立てて考えられるようになりました。 以降では、embindを使ってJS/C++間でデータを受け渡す手法を見ていきます。この観点で整理すると、本記事が扱う代表的なパターンは、 コピー 、 ハンドル 、 ゼロコピーのビュー の3つに分けられます。 value_object はコピー value_object<Point> として公開した構造体は、JS/C++の境界を越えるたびに、JSのプレーンなオブジェクトへ詰め替えられます。逆方向も同じです。そのため、返ってきた m はC++側の実体への参照ではなく、独立した複製です。たとえば m.x = 99 と書き換えてもC++側には影響しません。使い終わったあとは、通常のJSオブジェクトと同じようにGCによって回収されます。 つまり、 value_object は、所有権を意識せずに扱える一方で、JS/C++の境界を越えるたびにコピーが発生する仕組みです。 この仕組みは、座標や計測値のような小さな値のまとまりを扱うのに向いています。一方で、大きな配列を内包する構造体を毎フレーム往復させるような用途には向きません。 また、 value_object は純粋な値の束であることが前提です。フィールドに生ポインタや所有権を持つリソースを含める設計は避けるべきです。コピーされた先で、そのポインタが何を指しているのか、誰が解放するのかという情報が失われ、単なるアドレス値だけが残ってしまうからです。 class_ はハンドル new Module.ByteBuffer(1024) を実行すると、C++のヒープ、すなわちlinear memory内に ByteBuffer の実体が new されます。JS側に返ってくるのは、その実体を指すハンドルを内蔵した薄いラッパーオブジェクトです。 第1部で見たとおり、C++オブジェクトの実体はlinear memory上にあります。JSの変数が保持しているのは、その実体そのものではなく、実体を指すハンドルです。 buf.fill(0xff) を呼ぶたびに、呼び出しはJSからWasm上の同じC++実体に届きます。ここではコピーは起きません。 その代わり、 実体の解放はJS側が引き受け、明示的に delete() を実行する 必要があります。 register_vector で公開した std::vector も、基本的には class_ と同じ系統です。返ってくるのは素のJS配列ではなく、 .size() 、 .get(i) 、 .set(i, x) 、 .push_back(x) などを持つハンドルです。 この仕組みのため、要素アクセスのたびにJSからC++側への呼び出しが発生します。よって、巨大な配列を .get(i) のループで一要素ずつ走査すると、JS/C++間呼び出しのコストがその回数分だけ積み上がります。また、ハンドル自体には明示的な破棄が必要です。 要素そのものはコピーによって安全にやり取りされますが、ハンドルの後始末はJS側が担う必要があります。次に見る typed_memory_view と対比すると、 register_vector は「中身の扱いは安全寄りだが、破棄の責務は残る配列」と位置づけられます。 typed_memory_view はビュー typed_memory_view が提供するのは、ゼロコピーのアクセスです。C++側の連続領域を一切複製せず、JSの TypedArray としてそのまま覗かせます。たとえば、カメラ画像のようなRGBAバッファを返す例を考えてみます。 #include <emscripten/val.h> class FrameBuffer { public : FrameBuffer ( int w, int h) : pixels_ (w * h * 4 , 0 ) {} // 内部バッファを指すビューを返す。コピーは発生しない emscripten::val pixels () { return emscripten:: val ( emscripten:: typed_memory_view (pixels_. size (), pixels_. data ())); } private : std :: vector < uint8_t > pixels_; // RGBA }; const frame = new Module . FrameBuffer ( 640 , 480 ) ; const view: Uint8Array = frame . pixels () ; // linear memoryを直接指す窓 view [ 0 ] = 255 ; // C++側のpixels_[0]が直接書き換わる この view の正体は、第1部で見た HEAPU8 の部分ビューです。linear memory上の pixels_.data() から始まる領域を、JSの Uint8Array として直接見ているだけです。コピーが発生しないため、メガバイト級のバッファでもほぼ一瞬でJS側へ「渡す」ことができます。 ただし、ビューはあくまで「ビュー」なため、その有効期限は、元のバッファの寿命に完全に支配されます。具体的には、次のいずれかが起きた時点でビューは無効になります。 元のオブジェクト、つまり frame が破棄された → viewは解放済み領域を指すことになる 内部の std::vector が再確保された → viewは古いアドレスを指したままになる 線形メモリが成長した 第1部で予告したとおり、 ArrayBuffer が差し替わり、古いビューはdetachされる。成長は別の場所の malloc をきっかけに起こることもあるため、自分のコードがそのバッファに何もしていなくても、ビューが無効になる可能性がある。 したがって typed_memory_view の鉄則は「 取得したら即座に読み書きし、持ち越さない 」です。保持して使用し続けたいデータは、 view.slice() などでその場でJS側へコピーし、切り離しておく必要があります。 3つの渡し方をまとめると、次のように整理できます。 コピー( value_object )は、所有権を考えなくてよい代わりにコピーコストを払う。 ハンドル( class_ / register_vector )は、コピーを避けられる代わりに、JS側が解放の責務を負う。 ビュー( typed_memory_view )は高速だが、元バッファの寿命と完全に結びつく。 おわりに 本記事では、まずWasmの線形メモリという土台を確認し、その上でembindが提供する3つのデータの渡し方を見てきました。 embindの宣言的なAPIは、 _malloc や HEAPU8 といった低レイヤーを隠蔽してくれます。しかし所有権と寿命の責務は、プログラマが十分に認識し、管理する必要があります。本記事では、低レイヤーの挙動から順を追って解説することで、embindを実装に採用した場合も、そのAPIの背後で所有権と寿命がどう扱われているかを見通せるようになることを目指しました。 同じようにEmscriptenと向き合う方にとって、本記事がメモリ管理を筋道立てて考えるための一助となれば幸いです。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
本ブログは 2026 年 7 月 30 日に公開された AWS Blog “ Extend Amazon Inspector SBOM Generator with Plugins ” を翻訳したものです。 Amazon Inspector は、 Amazon Web Services (AWS) のワークロードを継続的にスキャンしてソフトウェアの脆弱性を検出する、自動化された脆弱性管理サービスです。Amazon Inspector の脆弱性管理機能は、 Amazon Inspector SBOM Generator (inspector-sbomgen) と呼ばれる資産インベントリエンジンによって支えられています。これはスタンドアロンのコマンドラインツールで、コンテナイメージ、ディレクトリ、アーカイブ、ローカルシステム、コンパイル済みバイナリなどから ソフトウェア部品表 (SBOM) を生成します。過去 2 年間で、AWS は inspector-sbomgen のカバレッジを数十のプログラミング言語エコシステム、オペレーティングシステム、広く導入されているアプリケーションへと拡大してきました。 今回、inspector-sbomgen を利用するビルダー向けの新機能として、独自のカスタムパッケージコレクターを記述できる プラグインシステム を発表します。ソースコードのコンパイルや公式リリースを待つ必要はなく、すぐに使い始めることができます。 inspector-sbomgen の最新バージョンは、 Amazon Inspector ユーザーガイド からダウンロードできます。 この記事では、inspector-sbomgen プラグインシステムでできること、これを構築した理由、そして数分で最初のプラグインを書く方法を紹介します。あわせて、プラグインが生成したパッケージコンポーネントを Amazon Inspector の脆弱性スキャンと統合する方法や、セキュリティが強化された予測可能なプラグイン動作を実現するプラグインの安全性モデルについても解説します。 プラグインシステムを構築した理由 ソフトウェアのエコシステムは動的です。新しい言語パッケージマネージャー、ロックファイル形式、エンドユーザーアプリケーションが絶えずリリースされ、その多くは迅速に採用されます。中にはセキュリティの検証がほとんど行われないまま使われるものもあります。その結果、セキュリティチームには可視性のギャップが残ります。つまり、SBOM ツールがまだ認識できないソフトウェアが本番ワークロードで動いているという状態です。お客様からは、こうしたエコシステムの多くを直接インベントリ化したいという要望をいただいてきました。最近まで、それを実現する唯一の方法は、機能リクエストを出して inspector-sbomgen チームがエコシステムに対応し、新しいリリースをデプロイするのを待つことでした。 inspector-sbomgen プラグインシステムは、この状況を変えます。プラグインを使うと、次のことができます。 inspector-sbomgen が標準では対応していないエコシステムへの対応 – 新しいオープンソースエコシステム、ニッチまたは変化の速いパッケージ形式、組織独自のツールなど、inspector-sbomgen を変更することなくインベントリ化できます エコシステム検出の迅速なプロトタイピング – 開発者にも AI コーディングアシスタントにも扱いやすいプラグインシステムを設計しました。プラグインは Lua で記述され、実行時にロードされるため、Go ツールチェーンもコンパイルも不要です。組み込みのテストハーネスを使ってプラグインを繰り返し改善し、すぐに結果を確認できます 安定した基盤の上での構築 – プラグイン API はアーティファクトの種類による違いを抽象化するため、検出ロジックを一度書くだけで、コンテナイメージ、アーカイブ、ローカルシステムなどでシームレスに動作します。また、プラグインは sbomgen の内部構造から分離されているため、コアツールでリグレッションが発生した場合の影響範囲も小さく抑えられます 実際、私たち自身もこのプラグインシステムを内部で活用し、新しいエコシステムのカバレッジを以前より速く提供できるようになりました。 1.13 リリース では、Apache Tomcat、NGINX、MySQL、Redis、WordPress、OpenSSH ツールチェーンなど、これまで Go で実装されていた 20 以上のエコシステムが、プラグインとして sbomgen バイナリに組み込まれています。同じリリースでは、Apache Cassandra、Apache Struts、Conda、Swift パッケージ、AI エージェントコレクター (Amazon Q Developer、Kiro CLI、Claude Code、GitHub Copilot、Ollama) など、10 を超える新しいエコシステムもプラグインとして追加されました。 inspector-sbomgen プラグインの仕組み sbomgen プラグインは 2 段階のパイプラインで動作します。 検出 (discovery) – アーティファクトのファイルシステムをスキャンし、インストール済みパッケージのメタデータを含むファイルを特定します 収集 (collection) – 検出された各ファイルを開き、ファイルの内容を解析して、結果を SBOM にパブリッシュします 内部では、イベントバスが検出プラグインと収集プラグインをつないでいます。検出プラグインは検出したファイルの一覧をイベントとしてパブリッシュし、1 つ以上の収集プラグインがそのイベントをサブスクライブして、パッケージ収集をトリガーします。開発者にとっては、これは オブザーバーパターン としておなじみの動作でしょう。 この分離により、1 つの検出プラグインが複数のコレクターにデータを供給できます。例えば、あるコレクターはパッケージメタデータを抽出し、別のコレクターはシークレットをスキャンし、さらに別のコレクターはポリシーをチェックする、といった構成が可能です。各収集プラグインは、計算コストの高いアーティファクトファイルシステムの再走査を行うことなく、同じファイルリストを利用できます。 5 分で書ける最初のプラグイン inspector-sbomgen を使えば、プラグイン環境を簡単にセットアップできます。 plugin new コマンドで sbomgen に新しいプラグインワークスペースを作成させ、 --with-example フラグを指定すると、すぐに実行できる検出プラグインと収集プラグインのペアがワークスペースに用意されます。 inspector-sbomgen plugin new --with-example 上記のコマンドを実行すると、プラグイン名と、プラグインワークスペースを格納するディレクトリの入力を求められます。カスタム値を指定することも、デフォルト値をそのまま使うこともできます。 Plugin name (identifies the software ecosystem your plugin will inventory, e.g. debian-dpkg, rhel-rpm, python-pip, cmake) [my-custom-ecosystem]: <enter> Project directory [my-sbomgen-plugins]: <enter> Created plugin "my-custom-ecosystem" in my-sbomgen-plugins/ なお、対応するコマンドラインインターフェイス (CLI) 引数でプラグイン名とディレクトリを指定すれば、対話形式のプロンプトをスキップできます。 inspector-sbomgen plugin new \ --with-example \ --name my-custom-ecosystem \ --path my-sbomgen-plugins プラグインワークスペースを作成すると、inspector-sbomgen は次のステップを案内する画面を表示します。開発者や AI コーディングアシスタントに対して、変更が必要なソースファイルや関連ドキュメントの場所を示してくれます。 Next steps: Get started: 1. Open plugin folder in a code editor (VS Code recommended) 2. Add test files that your plugin will discover and parse (e.g., config files, lockfiles, binaries, etc.): my-sbomgen-plugins/discovery/cross-platform/extra-ecosystems/my-custom-ecosystem/_testdata/ Develop: 3. Edit discovery: my-sbomgen-plugins/discovery/cross-platform/extra-ecosystems/my-custom-ecosystem/init.lua 4. Edit collection: my-sbomgen-plugins/collection/cross-platform/extra-ecosystems/my-custom-ecosystem/init.lua Test: 5. Write unit tests: my-sbomgen-plugins/discovery/cross-platform/extra-ecosystems/my-custom-ecosystem/init_test.lua 6. Run unit tests: inspector-sbomgen plugin test --path my-sbomgen-plugins Deploy: 7. Distribute your plugin directory wherever you run inspector-sbomgen: inspector-sbomgen <arguments> --plugin-dir /path/to/my-sbomgen-plugins Example: inspector-sbomgen container --image alpine:latest -o /tmp/sbom.json --plugin-dir /path/to/my-sbomgen-plugins For code completion, install the VS Code Lua language server extension: https://luals.github.io/#vscode-install For more information: - Plugin guide: my-sbomgen-plugins/docs/sbomgen-plugin-developer-guide.md - Testing guide: my-sbomgen-plugins/docs/sbomgen-plugin-testing-guide.md - API reference: my-sbomgen-plugins/docs/sbomgen-plugin-api-reference.md - Documentation: https://docs.aws.amazon.com/inspector/latest/user/sbom-generator.html プラグインワークスペースができたので、その中身を詳しく見てみましょう。 tree my-sbomgen-plugins ├── AGENTS.md ├── collection │ └── cross-platform │ └── extra-ecosystems │ └── my-custom-ecosystem │ └── init.lua ├── discovery │ └── cross-platform │ └── extra-ecosystems │ └── my-custom-ecosystem │ ├── _testdata │ │ ├── empty │ │ └── example.lock │ ├── init_test.lua │ └── init.lua ├── docs │ ├── sbomgen-plugin-api-reference.md │ ├── sbomgen-plugin-developer-guide.md │ └── sbomgen-plugin-testing-guide.md ├── library │ └── sbomgen.lua └── README.md スキャフォールディングされたプロジェクトには、動作する検出プラグインと収集プラグインのペア、 _testdata/ 配下のテストフィクスチャを使ってパスするユニットテスト、統合開発環境 (IDE) 連携用の .vscode/settings.json 、開発者ドキュメントのローカルコピーが含まれています。 スキャフォールディングは、人間と AI コーディングアシスタントの両方が読みやすいように、意図的に簡潔で完結した内容になっています。各ファイルには、それぞれの関数の役割と、プラグイン作成者が記述すべき箇所を説明する明確なコメントが付いています。 プラグインをテストするには、まずパッケージロックファイルやコンパイル済みバイナリなど、スキャン対象となるものが必要です。サンプルプラグインは、次の内容を持つ架空の example.lock をインベントリ化します。 my-package-alpha==1.0.0 my-package-beta==2.3.1 my-package-gamma==0.9.5 付属の検出プラグインは、アーティファクトのファイルシステム内で example.lock のインスタンスを探す方法を知っています。 -- my-custom-ecosystem discovery plugin -- Discovers example.lock files in the artifact file list. function discover() return sbomgen.find_files_by_name({"example.lock"}) end そして、付属の収集プラグインは、 example.lock の内容を解析し、パッケージ情報を出力 SBOM にパブリッシュする方法を知っています。 -- my-custom-ecosystem collection plugin -- Parses example.lock files and extracts package name and version. function collect(file_path) local content = sbomgen.read_file(file_path) if content == nil then return end for line in content:gmatch("[^\n]+") do local name, ver = line:match("^(.+)==(.+)$") if name and ver then sbomgen.push_package({ name = name, version = ver, purl_type = "generic", namespace = "my-custom-ecosystem", component_type = sbomgen.component_types.APPLICATION, }) end end end テストの実行 プラグインにはテストフレームワークが組み込まれているため、実際のアーティファクトをスキャンする前にロジックを検証できます。テストは Lua で記述し、プラグインと同じ場所の init_test.lua に配置して、 _testdata/ 内のフィクスチャデータを参照します。 function test_discovers_packages() local result = testing.scan_directory("_testdata") testing.assert_equals(3, #result.findings) testing.assert_equals("my-package-alpha", result.findings[1].name) testing.assert_equals("1.0.0", result.findings[1].version) end function test_no_findings_for_empty_directory() local result = testing.scan_directory("_testdata/empty") testing.assert_equals(0, #result.findings) end 次のコマンドでテストを実行します。 inspector-sbomgen plugin test --path my-sbomgen-plugins -v === RUN my-custom-ecosystem/discovery/init_test/test_discovers_packages --- PASS: my-custom-ecosystem/discovery/init_test/test_discovers_packages (0.04s) === RUN my-custom-ecosystem/discovery/init_test/test_no_findings_for_empty_directory --- PASS: my-custom-ecosystem/discovery/init_test/test_no_findings_for_empty_directory (0.04s) ok 2 tests passed これは、私たちが設計し得た最も短い開発ループです。Go ツールチェーンも、再ビルドも、コンテナの起動も不要です。テストを書き、実行し、繰り返し改善するだけです。 実際のアーティファクトのスキャン プラグインが結果を生成するには、プラグインが探すファイルを含むアーティファクトを inspector-sbomgen に与える必要があります。サンプルプラグインの場合、 example.lock ファイルを含む任意のディレクトリが対象になります。先ほど生成したフィクスチャがちょうど良い題材です。 inspector-sbomgen directory \ --plugin-dir ./my-sbomgen-plugins \ --path ./my-sbomgen-plugins/discovery/cross-platform/extra-ecosystems/my-custom-ecosystem/_testdata \ -o sbom.json --plugin-dir フラグは、Lua プラグインの読み込み元を inspector-sbomgen に伝えます。生成される SBOM には、 example.lock 内の 3 つのパッケージそれぞれに対応する CycloneDX コンポーネントが含まれます。例を以下に示します。 { "bom-ref": "comp-2", "type": "application", "name": "my-package-alpha", "version": "1.0.0", "scope": "optional", "purl": "pkg:generic/my-sbomgen-plugin/my-package-alpha@1.0.0", "properties": [ { "name": "amazon:inspector:sbom_generator:source_path", "value": "./my-sbomgen-plugins/example.lock" } ] } プラグインが生成するすべてのコンポーネントには、収集元のファイルを記録する amazon:inspector:sbom_generator:source_path プロパティが付いています。そのため、コンポーネントを生成元のアーティファクトまで常にたどることができます。 Amazon Inspector による脆弱性スキャン プラグインが生成したパッケージ情報は、他のコンポーネントと同等の正式な SBOM コンポーネントとして扱われます。Amazon Inspector を含め、CycloneDX SBOM を読み取るあらゆる下流のツールで利用できます。SBOM を Amazon Inspector に送信して脆弱性分析を行うには、 --scan-sbom フラグを追加します (有効な AWS アカウントが必要です)。 inspector-sbomgen directory \ --path ./my-sbomgen-plugins/discovery/cross-platform/extra-ecosystems/my-custom-ecosystem/_testdata \ --plugin-dir ./my-sbomgen-plugins \ --scan-sbom \ --aws-profile your_profile \ --aws-region your_region \ -o /tmp/sbom.json まったく新しいエコシステムに対応する際の重要な注意点 : プラグイン作成者は任意のエコシステムをインベントリ化できますが、Amazon Inspector が脆弱性を報告できるのは、アドバイザリが存在するコンポーネントに限られます。アドバイザリフィードにまだ含まれていないエコシステムのコンポーネントを Amazon Inspector に渡すと、Amazon Inspector は Component skipped: no supported rules found (コンポーネントはスキップされました: サポートされるルールが見つかりません) というプロパティ付きでコンポーネントを返します。以下に例を示します。 { "bom-ref": "comp-1", "name": "my-package-alpha", "properties": [ { "name": "amazon:inspector:sbom_scanner:path", "value": "my-sbomgen-plugins/discovery/cross-platform/extra-ecosystems/my-custom-ecosystem/_testdata/example.lock" }, { "name": "amazon:inspector:sbom_scanner:info", "value": "Component skipped: no supported rules found." } ], "purl": "pkg:generic/my-custom-ecosystem/my-package-alpha@1.0.0", "type": "application", "version": "1.0.0" } これはエラーではなく、想定どおりの動作です。SBOM は正しく生成され、コンポーネントは引き続き追跡され、 source_path によってどのファイルから生成されたかを正確に把握できます。Amazon Inspector がそのエコシステムのアドバイザリカバレッジを追加すれば、プラグインを一切変更することなく、同じ SBOM から脆弱性の検出結果が生成されるようになります。Amazon Inspector がすでにサポートしているエコシステムについては、プラグインが生成したコンポーネントは組み込みスキャナーが生成したコンポーネントと区別なく扱われます。 ファーストクラスの IDE サポート AWS は、プラグインを書くときの生産性と効率を重視しています。オートコンプリートのようなモダンな便利機能なしで Lua を書くのは快適とは言えません。そのため、 plugin new コマンドでスキャフォールディングされたすべてのプラグインプロジェクトには、 library/sbomgen.lua 定義ファイルと、それを VS Code の Lua Language Server 拡張機能に自動的に接続する .vscode/settings.json が付属します。 コード補完と IDE サポートを利用するには、まず sumneko.lua 拡張機能をインストールし、VS Code でプラグインプロジェクトを開きます。これにより、すべての sbomgen.* 関数で次の機能が使えるようになります。 型情報付きのパラメータヒント ホバー時のドキュメント表示 定数のオートコンプリート ( sbomgen.component_types.* 、 sbomgen.groups.* 、 sbomgen.platform.* ) 関数呼び出しの型チェック push_package() に必須フィールドが欠けている場合のインライン警告 この定義ファイルのおかげで、AI コーディングアシスタントによるプラグイン開発もうまく機能します。型情報とドキュメントがツールで読み取れる形式で埋め込まれているため、アシスタントは、素の Lua で記述する場合に比べてはるかに少ない人手の確認で正しいプラグインコードを生成できます。 安全な基盤 プラグインは inspector-sbomgen と同じプロセス内で実際のコードを実行するため、そのコードが安定し、セキュリティが強化された状態を保てるように実行環境を設計しました。すべての Lua プラグインは隔離されたサンドボックス内で実行されます。各 Lua 仮想マシン (VM) は、安全な操作のみが許可されるように、Lua 標準ライブラリの制限されたサブセットにのみアクセスできます。 ファイルシステムへの直接アクセスの禁止 – Lua の io ライブラリはロードされません。すべてのファイル操作は sbomgen.* 関数を経由して sbomgen の内部処理にルーティングされるため、ディスク上のディレクトリ、コンテナイメージ、圧縮アーカイブ、マウントされたボリュームのいずれをスキャンする場合でも、プラグインは同じように動作します サブプロセスの実行や環境の変更の禁止 – Lua の os ライブラリはブロックされているため、プラグインはプロセスの起動、環境変数の変更、アーティファクト外のファイルへのアクセスができません VM のイントロスペクションの禁止 – Lua の debug ライブラリはブロックされています 無制限なコードロードの禁止 – dofile 、 loadfile 、 loadstring は削除されています。 require() は利用できますが、プラグイン自身のディレクトリツリーに制限されているため、プラグインは自身のヘルパーモジュールを共有できる一方、他のプラグインやシステムパスからコードをロードすることはできません プラグインが未処理の Lua エラーを発生させた場合、inspector-sbomgen は警告をログに記録し、次のファイルまたはプラグインの処理を続行します。1 つの不具合のあるプラグインが他のプラグインの実行を妨げることはありません。また、プラグインが inspector-sbomgen の組み込みパッケージコレクターを上書きすることもありません。すべてのプラグインは一意の名前を宣言する必要があり、カスタムプラグインが公式の組み込みプラグインですでに使われている名前を使用した場合、そのカスタムプラグインは警告付きでスキップされます。組み込みプラグインが常に優先されるため、カスタムプラグインがツール自身の検出動作をひそかに置き換えたり隠したりすることはできません。 次のステップ 今すぐ独自のプラグインの構築を始めるには、次の手順に従ってください。 Amazon Inspector ユーザーガイド から最新の inspector-sbomgen をインストールします inspector-sbomgen plugin new --with-example を実行し、プロンプトに従います inspector-sbomgen plugin test --path ./my-sbomgen-plugins -v を実行し、サンプルテストがパスすることを確認します サンプルのロジックを、独自のエコシステム向けの検出ロジックに置き換えます すべての関数、定数、コマンドについては、以下の完全なリファレンスドキュメントで詳しく説明しています。 Lua プラグイン開発者ガイド : プラグインの概念、ディレクトリ構造、ライフサイクル Lua プラグインテストガイド : テストフレームワークのリファレンスとフィクスチャの規約 Lua プラグイン API リファレンス : sbomgen.* API の完全なカタログ まとめ 組織独自のロックファイル形式への対応の追加、新しいオープンソースエコシステム向け検出のプロトタイピング、あるいは自作スキャナーから組織全体で大規模に運用できる仕組みへの置き換えなど、どのような用途であっても、このプラグインシステムは、アイデアから動作する SBOM までの道のりをできる限り短くするように設計されています。皆さんがこれを使って何を作るのか、とても楽しみにしています。 この記事に関するご質問がある場合は、 AWS サポートにお問い合わせください 。 Michael Long Michael は AWS の Amazon Inspector 担当 Senior Security Researcher です。Amazon Inspector SBOM Generator と Amazon Inspector for GitHub Actions の研究開発を率いています。AWS 入社前は、MITRE ATT&CK チームで principal adversary emulation engineer を務めていました。また、U.S. Army (米国陸軍) で約 10 年間、軍事情報およびサイバー作戦に従事しました。 Charlie Bacon Charlie は AWS の Amazon Inspector 担当 Head of Security Engineering and Research です。Amazon Inspector や他の Amazon Security の脆弱性管理ツールを支える脆弱性スキャンおよびインベントリ収集サービスを担当するチームを率いています。AWS 入社前は、金融業界とセキュリティ業界で 20 年間にわたり、研究と製品開発の両分野で上級職を務めました。 Anthony Verleysen Anthony は Amazon Inspector 担当の Senior Technical Product Management です。Amazon Inspector の前は、AWS Systems Manager の Product Manager として Node Management 機能を担当していました。仕事以外では、テニスとサッカーに熱心に取り組んでいます。 本ブログは Security Solutions Architect の 中島 章博 が翻訳しました。























