Amazon Web Services ブログ

Spec it safe:セキュリティ要件で仕様をハードニングし、シフトレフトを実現する

本記事は 2026 年 10 月 30 日に Kiro ブログで公開された「Spec it safe: shift left and harden your specs with security requirements」を翻訳したものです。

SDD の広がりと、その死角

仕様駆動開発 (SDD) は、大規模なチームのソフトウェア開発のやり方を変えつつあります。プロンプトだけに頼るのではなく仕様を書き、それに沿って AI コーディングエージェントに設計、タスク、実装を進めさせます。本記事ではこの流れを Specify → Plan → Execute と略して書きます。ただし、ほとんどの 仕様に書かれているのは、ソフトウェアが何をすべきか、つまりユーザーが目にして実際に使う機能です。誤用されたときにどう耐えるかは書かれていません。機能要件は、もともと動かせば正しさが分かります。ログインが壊れていれば、誰かが気づきます。セキュリティ要件はそうでないことが多く、インジェクションや認可の欠陥が開いたままでも、アプリはすべての機能チェックに通ることがあります。失敗するケースを誰も試さないからです。そのため、仕様にセキュリティの制約が欠けていると、エージェントにはセキュリティを目標に作り込み、繰り返し改善していく対象がありません。エージェントはフレームワークの既定の動作に頼ります。それで足りることもあれば穴が残ることもあり、仕様にはどちらなのかを問う記述がありません。

このギャップを埋める方法は、より良いモデルを足すことでも、最後にセキュリティレビューを付け足すことでもありません。作業を前倒しして、自分で完全にコントロールできる層、つまり仕様に移すことです。仕様にセキュリティ上の義務を書けば、コーディングエージェントには作り込み、繰り返し改善していく具体的な目標ができます。コードができあがってからレビュアーが見つけるのを待つ必要はありません。これは、設計が固まってからセキュリティを後付けする考え方から、最初からセキュリティを前提に設計する考え方への転換です。たいていはアーキテクチャにも良い効果があります。セキュリティ要件を早い段階で書いておくと、よりすっきりした設計につながることがあります。後から改修するのではなく要件を前提に組み立てるので、要件を満たすのが簡単になるからです。仕様の品質は、コードが 1 行も無いうちから作り込めます。仕様のハードニング (specification hardening) は、そのために設計しました。

仕様のハードニングのご紹介

仕様をハードニングする Kiro Power を紹介します。SDD のワークフローにステップを 1 つ追加するプラグインです。機能要件を書き終えてから計画を始めるまでの間に、EARS 形式の要件を読み、その書き方から想定される脆弱性の種類を指摘して、追跡とテストができるセキュリティ上の義務を提案します。提案は、CWE の脆弱性タイプを再利用できるセキュリティ上の義務に対応付けた、厳選したライブラリから作られます。そのため、仕様のハードニングの提案はモデルの推測ではなく、既知の脆弱性に基づいています。提案が的外れにならないのは、要件そのものから導くからです。仕様のハードニングが義務を提案するのは、ユーザー入力、認証、データの扱いなど、最初の書き方がその義務で対処する脆弱性を示しているときだけです。そのため、アプリケーションが実際に行うことに合った制約だけが提示されます。セキュリティは後のコードレビューではなく仕様そのものに入ります。それでも、後段にセキュリティレビューのステップを入れることを強くお勧めします。

「Kiro Spec-Driven Development」という題のフロー図。左から右へ矢印でつながった 5 つの箱が並ぶ。Specify (EARS 要件)、harden the spec、Plan (設計 + タスク)、Execute (コード生成)、Implementation candidate (適合性テストとセキュリティテストが必要) の順。「harden the spec」の箱の下には、コードができる前にここでセキュリティが入るという注記がある。
図 1: spec のハードニングは、Kiro の Specify → Plan → Execute の流れに、コードを書く前の早い段階の管理ポイントとして、ハードニングのステップを 1 つ挿入します。

仕様のハードニングは上流の管理ポイントであり、実行時のセキュリティ境界ではありません。よくある脆弱性タイプが抜け落ちる可能性を減らし、セキュリティの意図を明示的で、追跡でき、テストできる形にします。生成されたコードを検証したり、脆弱性が無いことを証明したりはしません。SAST、DAST、依存関係のスキャン、人によるレビューの代わりにもなりません。

仕様のハードニングが対象とするもの:要件として表現でき、かつ EARS の書き方から検出できる脆弱性タイプです。インジェクション、認証の欠如、弱い認証情報の保存、エンコードされていない出力、そしてこれらに類する抜け漏れが当てはまります。

仕様のハードニングが対象としないもの:データフローやコードレベルの解析が必要な脆弱性、字句上の手がかりが無い認可の抜け、実行環境、インフラストラクチャ、依存関係に関するものすべてです。これらには引き続き、人のレビュアーと後段のツールが必要です。

仕様ハードニングエンジンの中核:CWE から仕様への対応付けライブラリ

すべての脆弱性を仕様で直せるわけではありません。多くはアーキテクチャに起因するか、コードでしか見えません。しかし、要件として表現できる CWE クラスを選んで、再利用できるセキュリティ上の義務に対応付けることはできます。この義務を正しく実装して検証すれば、それぞれの脆弱性のよくある形を緩和できます。仕様のハードニングは、この対応付けを決定的に動く TypeScript のライブラリと CLI として提供しているので、どの制約も再現できます。

CWE 脆弱性 トリガー条件 必要な EARS 制約
CWE-89 SQL インジェクション 要件が外部入力を使ってデータベースを照会する すべてのクエリをパラメータ化クエリ / プリペアドステートメントだけで実行し、文字列の連結は決して使わない。
CWE-862 認可の欠如 プリンシパルが所有するリソースを返す、または変更する 要求元のプリンシパルがリソースを所有しているか、明示的な許可を持っていることを確認する。
CWE-306 認証の欠如 エンドポイントやリソースを公開する 処理の前に、有効な認証済みセッションまたは署名付きトークンを必須にする。
CWE-79 クロスサイトスクリプティング ユーザーが入力したコンテンツをブラウザで表示する すべての出力をコンテキストに応じてエンコードし、多層防御として Content Security Policy ヘッダーを追加する。
CWE-522 不十分な認証情報の保護 パスワードを保存または検証する 強力な適応型関数でパスワードをハッシュ化する (平文や元に戻せる形式は決して使わない)。
CWE-434 無制限のファイルアップロード ファイルのアップロードを受け付ける マジックバイトを MIME の許可リストと照合し、サイズに上限を設け、Web ルートの外に保存する。
CWE-639 IDOR / BOLA 呼び出し元が指定した識別子でオブジェクトを取得する 返す前に、オブジェクトを所有するテナントが認証済みの呼び出し元と一致することを確認する。

ライブラリには、8 つのセキュリティ領域にわたる 60 の CWE の対応付けが含まれています (インジェクション、認証、認可、データの露出、暗号、入力の検証、セキュリティの設定ミス、ビジネスロジック)。脆弱性は MITRE の Common Weakness Enumeration (CWE) から取っています。どれを選ぶかと検出パターンは、主に OWASP Top 10:2021 と Amazon Q Developer Detector Library (旧 CodeGuru) をもとにしています。各対応付けには、OWASP のカテゴリーと、人が割り当てた深刻度が付いています。表の制約は代表的な例を言い換えたもので、どのアーキテクチャにもそのまま使える完全な対策ではありません。

エンジンの仕組み

仕様のハードニングは、要件ファイルごとに 3 段階のパイプラインを実行します。エンジンの動作は決定的です。ツールのバージョン、対応付け、オプション、ハードニング前の入力が同じなら、同じ仕様には常に同じハードニングが施されます。その後に続くエージェントの意味的なレビュー (図 2) は決定的ではありません。

1. トリガーの検出

エンジンは、2 種類の字句上のシグナルを使って、各要件を 60 の CWE の対応付けと照合します。部分文字列のキーワード (「database」「upload」「session」) と、正規表現のパターンです。ヒットを組み合わせて上限のあるスコアにし、スコアがしきい値 (既定は 0.3) を超えた CWE に印を付けます。照合は意味ではなく字句に基づくので、書き方に明示されていない脆弱性は見逃すことがあります。

2. カバレッジの確認

エンジンは、印を付けた CWE ごとに、必要な基本要素がすでに仕様に書かれているかを確認します。たとえば、パラメータ化クエリの条項、所有者の確認、出力のエンコードです。提案が作られるのは、まだ対応されていない抜けだけです。確認はファイル全体を対象に字句で行うので、ある要件に書かれた対策によって、別の要件への提案が出なくなることがあります。

3. 制約の提案

エンジンは処理を止めるのではなく、足りない制約を EARS の項目として 1 つずつ挿入します。プレースホルダーは、可能なら元の要件の言葉で埋め、できないときは一般的な言い方 (「a user」「the resource」) にします。挿入した各行には提案 ID と [SECURITY: …] タグが付くので、作成者は変更を差分としてレビューできます。複数の対策を重ねる対応付けもあります。CWE-89 では、パラメータ化クエリの条項と入力の検証の条項の両方が追加されます。プログラムから使う API は、各制約の CWE、優先度、根拠をまとめた構造化レポートを返します。CLI は制約をファイルに直接書き込み、このレポートは保存しません。

仕様のハードニングが読むのは仕様だけで、コードは読みません。要件の文面が示す、CWE に由来する制約を提案し、作成者がそれを実際に動いているシステムと照らし合わせて確かめます。こうした不変条件を明示しておくと、守り続けることもテストすることも容易になります。

2 段の帯で構成されたワークフロー図。上の紫の帯は「Specification Hardening Engine · Kiro Power that runs over requirements.md」で、左から右へ次の箱が並ぶ。Requirements (EARS 形式の requirements.md。点線の「review marker」の箱に、再実行では新しい要件と変更された要件だけをハードニングすると書かれている)、Detect CWE triggers (キーワードと正規表現のパターンを 61 の CWE の対応付けと照合し、スコアが 0.3 以上ならその CWE が当てはまる)、Close coverage gaps (各 CWE の制約を spec 全体と照合し、足りないものを要件の言葉で EARS として書く)、Hardened spec (ファイルをその場で編集し、[SECURITY:] の項目を入れ子にして Security Baseline も加える)。Detect CWE triggers から下のオレンジの帯「Agent semantic review」へ「weak signal」の矢印が下りている。この帯には、字句の照合では所有者やテナントの確認の欠如が見えないという注記がある。帯の中は Review candidates (CWE-639/862 など、オブジェクト単位の認可に関する CWE でスコアが 0.05〜0.3 の弱いヒット)、Boundary sweep (プリンシパルが所有するすべてのリソースで、呼び出し元からリソースへのアクセスが制限されているか)、Add constraint (確認できた抜けを、「active care relationship」のようなその分野の言葉で [SECURITY:] の EARS 項目にする) と進み、上の Hardened spec に戻る。上端を横切る点線のループには「Spec changed → re-run on the same file (only new/changed requirements hardened)」と書かれている。
図 2: エンドツーエンドのワークフロー。エンジン (紫) は各要件を 60 の CWE の対応付けと照合し、足りない制約を EARS の [SECURITY:] 項目として書き込みます。エージェントの意味的なレビュー (オレンジ) は、認可に関する弱いヒットを確認し、確認できた抜けごとにその分野に合った制約を追加します。仕様が変わったら、同じファイルでエンジンを再実行すると、新しい要件と変更された要件だけがハードニングされます。どの要件をレビュー済みかは、レビューマーカーでエンジンに伝わります (点線のループ)。

具体例:患者記録の検索

これがなぜ重要かを理解するいちばん簡単な方法は、同じ機能を 2 回リリースしてみることです。1 回目は機能だけを書いた仕様から、2 回目は仕様ハードニングエンジンでハードニングした仕様からです。あるデジタルヘルスのスタートアップが、医師が患者名で診療記録を検索できる患者ポータルを作っています。プロダクトマネージャーは、多くの人が書くであろう要件を書きます。

要件:最低限の機能だけで、セキュリティには触れていない (EARS、イベント駆動)

WHEN a physician searches for clinical notes by patient name,
THE SYSTEM SHALL return all matching clinical notes from the records database.

一見すると完全に見えますが、最低限の機能しか書かれていません。どう一致させるか、ページング、結果が空のとき、どのフィールドを返してよいか、誰が実行できるかについては何も書かれていません。コーディングエージェントは、次のような Express.js のエンドポイントを生成するかもしれません (説明用の疑似コード)。

明示的なセキュリティの制約が無い場合 (説明用の疑似コード)

// Agent-generated code from the security-silent spec
app.get('/clinical-notes/search', async (req, res) => {
  const { patientName } = req.query;
  const notes = await db.query(
    `SELECT * FROM clinical_notes WHERE patient_name LIKE '%${patientName}%'`
  );
  res.json(notes);
});

ここには 2 つの脆弱性が埋め込まれています。SQL 文に文字列を埋め込んでいる箇所は、教科書どおりの CWE-89 (SQL インジェクション) です。%' OR '1'='1' -- のような入力を与えると、常に真になる条件が追加されて残りがコメントアウトされ、すべての行が返されます (正確な構文はデータベースによって異なります)。そして、このエンドポイントには認証の確認がまったくありません。これは CWE-306 (Missing Authentication for Critical Function、重要な機能に対する認証の欠如) です。3 つ目のより根の深い問題、つまり認証済みの臨床医がすべての患者の記録を見てよいのかという問題は、後で取り上げます。抜け落ちは要件の段階で起きており、実装も既定では防御的になっていませんでした。

では、同じ要件を仕様ハードニングエンジンにかけてみます。字句ベースの検出器は 2 つの脆弱性タイプ (CWE-89 SQL インジェクションと CWE-306 認証の欠如) に印を付け、枝番付きの EARS 制約を 7 つ要件に書き戻します。下に示すとおり、それぞれに追跡できる提案 ID と [SECURITY: …] タグが付きます。3 つ以上の要件で提案された条項は、共通の ## Security Baseline (Cross-Cutting) セクションに 1 回だけ移されます。これはヒューリスティックなので、差分で確かめてください。

レビュー後に requirements.md に書き戻された、仕様のハードニングの出力。エンジンは 2 つの CWE にわたって 7 つの義務を追加します。CWE-89 (パラメータ化クエリ) の 1.1〜1.2、CWE-89 (入力の検証) の 1.3〜1.4、CWE-306 (認証) の 1.5〜1.7 です。続いてレビューの工程 (Power では Kiro エージェントが、CLI では自分で行います) で、1.8 (データの最小化、CWE-200) と 1.9 (診療関係に基づく認可、CWE-862) を追加します。ここでは代表的な条項だけを示し、残りは省略しています。プログラムから使う API は、エンジンが追加した各制約の CWE、優先度、根拠をまとめた構造化レポートも返します。CLI はこのレポートを保存しません。

1. WHEN a physician searches for clinical notes by patient name, THE SYSTEM SHALL return all matching clinical notes from the records database.
1.1. SEC-REQUIREMENTS-REQ-1-EC-89-1-1 [SECURITY: parameterized-query] THE SYSTEM SHALL execute all database queries using parameterized statements or prepared statements only.
1.5. SEC-REQUIREMENTS-REQ-1-EC-306-1 [SECURITY: authentication-required] IF a request that the specification does not explicitly allow without authentication arrives without a valid authenticated session or signed token, THEN THE SYSTEM SHALL reject the request.
… engine clauses 1.2–1.4 and 1.6–1.7 omitted for brevity.
1.8. SEC-REQUIREMENTS-REQ-1-SEM-200-1 [SECURITY: data-minimization] THE SYSTEM SHALL return only the patient name, note date and summary fields of each matching clinical note.
1.9. SEC-REQUIREMENTS-REQ-1-SEM-862-1 [SECURITY: ownership-verification] THE SYSTEM SHALL return clinical notes only for patients with whom the requesting physician has an active care relationship.

ID は次のように読みます。SEC-REQUIREMENTS-REQ-1-EC-89-1-1 は左から順に、SEC (セキュリティの制約)、REQUIREMENTS-REQ-1 (requirements.md の 1 つ目の要件。「Requirement N」という見出しのある仕様では、Requirement 1 の基準 1 を表す REQ-1.1 になります)、EC-89-1 (CWE-89 の対応付けにある 1 つ目の EARS 制約テンプレート) を表します。末尾の 1 は条項の番号で、1 つのテンプレートが複数の EARS の行に展開されるときだけ付きます。条項が 1 つだけの EC-306-1 に末尾の番号が無いのはこのためです。レビューで追加した条項では、EC の代わりに SEM を使います (例:SEM-862-1)。

開発者はハードニングされた仕様の差分をレビューし、条項ごとに判断します。エンジンの条項はインジェクションと認証をカバーしていますが、返すフィールドを絞るべきだと示す言葉は要件に無かったので、レビューで 1.8 を追加します (--semantic-all を付けて実行すると、CWE-200 も候補として表示されます)。仕様に問題が無ければ、エージェントはそこからコードを生成し、推測ではなく明示された義務に沿って作ります。次の実装候補は、フェイルクローズの 401 でエンドポイントを保護し (1.5〜1.7)、検索語を検証し (1.3)、明示的な LIKE のエスケープを付けてパラメータとしてバインドし (1.1〜1.2)、1.8 が挙げたフィールドだけを返します。

適合性を確かめるための下書き:パラメータ化クエリ (1.1〜1.2)、入力の検証 (1.3)、認証 (1.5〜1.7)、データの最小化 (1.8) の条項を満たしていますが、まだ実際の患者データに使えるほど安全ではありません。1.9 をまだ守っていないからです。

// Implementation candidate for the security clauses above. authenticate, req.user,
// and the schema are application-specific placeholders; still NOT object-level authorized (1.9).
app.get('/clinical-notes/search', authenticate, async (req, res) => {
  // 1.5–1.7: fail closed if the request is not authenticated; return no data
  if (!req.user) return res.status(401).end();

  const raw = String(req.query.patientName ?? '').trim();
  // 1.3: reject blank or too-short terms so an empty search cannot match every row
  if (raw.length < 2) return res.status(400).end();

  // 1.1–1.2: parameterized query; escape LIKE metacharacters incl. the
  // escape char itself, with an explicit ESCAPE clause
  const term = `%${raw.replace(/[\\%_]/g, c => '\\' + c)}%`;
  // 1.8: return only the fields the requirement names (data-minimization)
  const notes = await db.query(
    `SELECT patient_name, note_date, summary
       FROM clinical_notes
      WHERE patient_name ILIKE $1 ESCAPE '\\'
      ORDER BY note_date DESC`, [term]);
  res.status(200).json(notes.rows);
});

同じくらい重要なのは、ツールが印を付けたのに書き込まなかったものです。認証済みの医師なら誰でも、どの患者の記録でも読めてしまいます。診療関係の確認が無いからです。これは認可の確認の欠如 (CWE-862) ですが、スコアがしきい値を下回っています。ツールはこれを書き込む代わりに、レビュアーが確かめるべき意味上の候補として示します。Kiro Power ではエージェントが、CLI では自分がレビュアーです。確認された抜けは、その分野に合わせた条項 1.9「要求している医師と有効な診療関係にある患者の診療記録だけを返す」になります。するとクエリは、認可の範囲に絞られたものになります。

認可の範囲に絞ったクエリ:パラメータ化された認証済みの読み取りに、レビューで追加した診療関係の条件 (1.9、CWE-862) を加えています。必要な ILIKE の動作をする PostgreSQL のロケールと照合順序を前提にしています。スキーマとポリシーは説明用です。

// Authorization-scoped query (1.9): parameterized, authenticated read plus the review-added CWE-862
// care-relationship constraint. Schema and policy are illustrative. EXISTS enforces
// an ACTIVE relationship without duplicating notes when several rows match.
const notes = await db.query(
  `SELECT cn.patient_name, cn.note_date, cn.summary
     FROM clinical_notes cn
    WHERE cn.patient_name ILIKE $2 ESCAPE '\\'
      AND EXISTS (
        SELECT 1 FROM care_relationships cr
         WHERE cr.patient_id = cn.patient_id
           AND cr.physician_id = $1
           AND cr.status = 'active')
    ORDER BY cn.note_date DESC`,
  [req.user.physicianId, term]);

この例では、決定的なハードニングによって、2 つの CWE にわたる 7 つの義務が明らかになりました。データの最小化のルールと、その分野に固有の認可のルールは、やはりレビューから生まれています。

仕様のハードニングはセキュリティの判断を補うものであり、置き換えるものではありません。

実行方法

仕様のハードニングは Kiro Power としてパッケージ化されており、GitHub の sample-kiro-spec-security-hardening リポジトリで入手できます。実行方法は 2 つあります。

Kiro Power から実行する。機能要件が固まったら、Kiro に「harden my specs」と頼みます。harden-spec スキルは次のことを行います。

  • 同梱のエンジンを見つけ、初回の利用時にビルドします。
  • .kiro/specs/*/requirements.md に対してエンジンを実行します。
  • キーワードの照合では見えない認可の確認の欠如について、意味的なレビューを行います。
  • 追加した制約と、注意が必要な警告をまとめて報告します。

CLI から実行する。cwe-to-spec-mapping-library/ で npm install を 1 回実行してビルドします。その後、コンパイル済みの CLI に spec ファイルか specs ディレクトリを絶対パスで指定します。

node dist/cli/harden-requirements.js /abs/path/to/.kiro/specs/ --semantic-candidates

--semantic-candidates フラグを付けると、認可に関する弱い一致をレビュー用に表示しますが、ファイルには書き込みません。

どちらの方法でも、requirements.md をその場で編集します。そのため、ハードニングをバージョン管理とコードレビューの流れに組み込めます。エンジンは行を追加するだけで、要件を変更、分割、並べ替えすることはありません。厳密な EARS になっていない要件は、書き換えずに報告します。git の差分は必ずレビューしてください。再実行では差分だけを処理します。隠れたレビューマーカーにハードニング済みの要件が記録されているので、仕様のハードニングは新しい要件と変更された要件だけをハードニングし、レビュアーが削除した制約を再び提案することはありません。今ある仕様にそのまま実行してください。ハードニング前のコピーを取っておく必要はありません。

Kiro IDE の Power の詳細ページ。Rajdeep Mukherjee による「specification-hardening-engine」で、Try power、Uninstall、Check for updates のボタンがある。security、spec-driven development、EARS、CWE、OWASP、requirements hardening、shift left、Kiro power のタグと、コードを書く前にセキュリティの基本要素で Kiro の spec をハードニングするという説明、GitHub Repository のボタンが続く。Skills の一覧には harden-spec と security-primitives-reference の 2 つが、それぞれ 1 行の説明付きで並んでいる。
図 3: Kiro IDE にインストールした仕様のハードニングの Power。
Kiro IDE のチャット。ユーザーが「harden the specs」と入力し、Kiro が spec security hardening の Power を使うので最初にそれを有効にすると返している。展開された Kiro Powers のツールの結果には、harden-spec スキルの frontmatter (name は harden-spec、license は MIT-0、author は Rajdeep Mukherjee、version は 1.0.0) が表示され、その下に「Harden a Spec with Security Primitives」という見出しと、同梱の Specification Hardening Engine を EARS 形式の要件に対して実行し、足りないセキュリティの制約を見つけるという説明が続いている。
図 4: Kiro IDE で仕様のハードニングの Power を呼び出す。
Kiro の Requirements Analysis パネル。イベント駆動の要件「WHEN the Chat_Server kicks a Member, THE Chat_Server SHALL create a System_Message that names the kicked user, the Moderator and the reason when the Moderator gave one」を引用し、View Requirement のリンクがある。続いて Requirement 16.5 について、理由が示されなかったときにメッセージから理由のテキストを省く必要があるかを尋ねる質問と、2 つの回答の選択肢 (1 つ目の「must omit reason content… (equality semantics)」が選ばれている)、Submit と Cancel のボタンがある。
図 5: ハードニングした要件に対して Requirements Analysis を実行する。

仕様のハードニングの使いどころ:グリーンフィールド、ブラウンフィールド、モダナイゼーション

仕様のハードニングはコードではなく仕様に対して働くので、同じハードニングのステップを 3 種類の作業に組み込めます。違うのは、要件がどこから来るかと、仕様のハードニングをいつ実行するかだけです。

グリーンフィールド (新規プロジェクト)。ハードニングは、コードを書く前の仕様のワークフローの 1 ステップとして実行します。EARS の要件を受け取った仕様のハードニングが、足りない [SECURITY: …] の制約を追加し、エージェントはハードニングされた仕様に沿って実装します。セキュリティの意図は、後から付け足されるのではなく、最初のコミットから仕様に入っています。

ブラウンフィールド (既存のコード)。要件を追加または変更したら、既存の仕様に対して仕様のハードニングを再実行します。新しい要件と変更された要件だけがハードニングされ、レビュー済みの要件はそのまま残ります。追加された SEC- の制約は、機能の作業と並んでレビューできる差分として現れるので、変更は修正した部分だけに収まります。仕様のハードニングが読むのはコードではなく仕様なので、提案された義務が既存のシステムの実際の動作と合っているかを 1 つずつ確かめてください。食い違っていれば、コードの抜けか、合わせて調整すべき制約が見つかったことになります。

仕様駆動のモダナイゼーション (レガシーの書き直し)。まずレガシーシステムから要件を取り出して EARS の仕様にします。たとえば AWS Transform を使います。続いて、再実装の前に、取り出した要件を仕様のハードニングでまとめてハードニングします。モダナイズしたシステムは、元の抜けを引き継がず、明示的にハードニングされた仕様に沿って作り直されます。

3 つの列が横に並び、それぞれが縦に 3 ステップの流れになっていて、真ん中には同じオレンジのハードニングのステップがある。Greenfield (新規プロジェクト)「Harden every new spec, before code」は Write EARS requirements、hardens the spec (pre-code)、Agent builds to the hardened spec。Brownfield (既存のコード)「Harden the spec guarding each change」は Add / edit a requirement、hardens the changed spec、Implement · review SEC- diff。Modernization (レガシーから spec 駆動へ)「Batch-harden recovered specs」は Reverse-engineer specs from legacy code、hardens the recovered specs、Re-implement to the hardened spec。
図 6: 3 種類のプロジェクトで仕様のハードニングを呼び出す場所。ハードニングのステップ (オレンジ) はどれも同じで、違うのは読み込む仕様の出どころだけです。

おわりに

仕様のハードニングは、比較的新しい分野への最初の一歩です。仕様そのものを安全にし、それを使ってコーディングエージェントを設計段階から安全なコードへ導く分野です。仕様は人もエージェントも最初に扱う成果物なので、ここから始めるのが適切だと私たちは考えています。ただし、仕様のハードニングは研究段階のプロトタイプであり、1 つの仮説です。このブログでは、仕様のレベルで対策を入れることの意義を論じました。

また、仕様のハードニングはエージェントから安全なコードを得る唯一の方法ではなく、ほかの方法と組み合わせたときに最も効果を発揮します。脅威モデリングは、キーワードの照合では見逃しうる設計レベルのリスクを捉えます。Kiro の steering ファイルのように、エージェントのコンテキストに入れたセキュアコーディングのガイダンスは、書かれる時点でコードを方向づけます。安全な既定値を持つフレームワークは、ミスを種類ごと取り除きます。後段では、SAST、DAST、依存関係のスキャン、AI を活用したコードのセキュリティスキャン (AWS Continuum が提供するものなど)、セキュリティテスト、人によるレビューが、作られたものを引き続き確認します。仕様のハードニングは、意図が書き留められる時点にもう 1 つの層を加えます。

ハードニングされた仕様はリポジトリに残るので、タスクを実行した後も、それぞれの義務の理由を監査できます。チームで SDD を導入しているなら、完了の定義 (definition of done) に仕様のセキュリティレビューを加えてください。エージェントは最初からセキュリティを組み込めますが、それには、セキュリティが何を意味するのかを私たちが伝える必要があります。

翻訳者について