0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

サイバー攻撃に悪用されたAI「ARTEX」を調べ、Python独学半年の私が自作Flaskアプリの安全性を検証してみた

0
Posted at

AI脅威とBakery Hubの安全性検証.png

はじめに

最近、企業へのサイバー攻撃や情報漏洩のニュースを目にする機会が増えました。

システムの停止、情報漏洩、復旧までにかかる時間。

ニュースを見るたびに、いろいろな疑問が浮かびます。

  • そもそも、情報漏洩ってどこからが情報漏洩なのだろう?
  • 脆弱性って、具体的に何のこと?
  • 長年運用されているシステムでも、なぜ問題が起こるのだろう?
  • 最近はAIまでサイバー攻撃に使われるのか?

そんなとき、ARTEX というAIを利用したツールの存在を知りました。

セキュリティを調べるためにも使える技術が、実際のサイバー攻撃に悪用されていたというニュースです。

私は普段、物流ドライバーとして働いています。

2026年5月12日からPythonを中心にプログラミングを独学し、AIと対話しながらWebアプリを開発してきました。

その一つが、パン屋さんの店舗運営を支援する Bakery Hub です。

最初は売上を記録するような小さなアプリでした。

それから商品管理、売上分析、仕込み管理、レジ会計など、少しずつ機能を増やしてきました。

テストも書き、問題が見つかれば修正しながら開発しています。

でも、ARTEXについて調べているうちに、ふと思いました。

AIの力を借りてここまで作ってきたけれど、自分のWebアプリの安全性は、どこまで確認できているのだろう?

これまでテストはたくさん書いてきました。

ただ、それは自分が考えた条件でのテストです。

そもそも、自分が思いついていない問題が残っているかもしれません。

そこで、新しい機能の開発をいったん止め、AIと一緒にBakery Hubの安全性を調べてみることにしました。

すると、実際に見直すべき問題が見つかりました。

今回は、発見した問題だけでなく、そのとき何を考え、なぜその方法を選び、どこまで確かめたのかを記録してみようと思います。

📌 この記事を3行でまとめると

  • AI「ARTEX」がサイバー攻撃に悪用されたニュースをきっかけに、自作Flaskアプリの安全性を調べてみた。
  • 管理者のログアウトやオフライン会計の保存について、実際に問題を発見し、改善を進めた。
  • テストが成功しても、確認できていないことが残っていれば、本番公開を急がないという判断をした。

この記事は、2026年10月30日までの開発・検証記録をもとにしています。

Bakery HubがARTEXによる攻撃を受けたわけではありません。また、ARTEXそのものを使って検証したわけでもありません。


1. サイバー攻撃に悪用されたAI「ARTEX」を知った

今回のきっかけになったニュースから紹介します。

2026年10月21日、Microsoftは、AIを利用した侵入テスト用フレームワーク「ARTEX」がサイバー攻撃に悪用されていたことを報告しました。

Microsoftが「Storm-0940」と呼ぶ攻撃者が、ARTEXを使って世界中の数百の組織を標的とした攻撃を自動化していたそうです。

報告によると、一部の攻撃では、標的を狙い始めてから3時間以内にクラウド環境の高い権限を取得したケースもあったとのこと。

Microsoft Security Blog:AI-driven, large-scale nation-state campaign using ARTEX enables supply chain compromise

ARTEXって、そもそも何?

調べてみると、ARTEXはオープンソースの侵入テスト用フレームワークでした。

ここで、また知らない言葉が出てきました。

ペネトレーションテスト。

どうやら、許可されたシステムに対して、実際に侵入できてしまうような弱点がないか確かめるセキュリティ検証のことだそうです。

ARTEXは、複数のAIエージェントを使って、さまざまな作業を自動化できる仕組みです。

AIが状況を判断しながら次の作業を進められる。

便利な技術だと思います。

でも、その便利さを攻撃者も利用していた。

私が特に驚いたのは、AIによって攻撃の試行を大量かつ短時間に進められることでした。

人間が一つずつ操作していたことを、自動的に進められる。

守る側にとっても、考えさせられる話です。

国内でも注意喚起が出ていた

JPCERT/CCも、2026年10月23日に国内組織への不正アクセスに関する注意喚起を更新しています。

その中には、AIを用いたペネトレーションテスト基盤が悪用された攻撃試行についての記載がありました。

また、Webアプリケーションの既知の脆弱性を探したり、設定ファイルやバックアップファイルを狙ったりする手口も紹介されています。

JPCERT/CC:直近で相次いでいる国内組織における不正アクセスに関する注意喚起

ただし、すべての事件でARTEXが使われたというわけではありません。

ニュースを読んでいても、確認された事実と、まだ分かっていないことは区別しないといけないんですね。

AIを使って開発している自分だからこそ

私は普段からAIに助けてもらいながら開発しています。

設計を相談し、コードを書き、テストを作り、問題があれば修正する。

AIがなければ、今のようなペースで開発することは難しかったと思います。

だからこそ、今回のニュースには考えさせられるものがありました。

同じ技術でも、守るために使うこともできれば、攻撃に悪用されることもある。

ならば、自分もAIの力を借りて、作ったアプリの安全性を確かめてみよう。

そんな考えから、今回の検証を始めました。


2. トラックの運転にも、プログラムにも死角がある

少し、プログラミングから離れます。

私はトラックドライバーとして8年間働いてきました。

長く運転していて思うのは、経験を積んだからといって、必ず安全になるわけではないということです。

例えば、

  • ミラーを確認したつもり
  • 一時停止したつもり
  • 十分に減速したつもり
  • いつも通る道だから大丈夫だろう

こうした思い込みは、誰にでも起こり得ます。

もちろん、私自身にも見落としはあります。

だからこそ、普段から防衛運転や安全確認を意識しています。

プログラミングを学ぶようになってから、この考え方は開発にも通じるのではないかと感じました。

トラックの運転 Webアプリ開発
ミラーから見えない死角 まだ検証していない処理
確認したつもり 対策したつもり
いつもの道だから安心 今まで動いていたから安心
危険を予測する防衛運転 異常な動作も想定する設計
日常点検 テスト・監査・運用確認

もちろん、交通事故とサイバー攻撃は同じものではありません。

でも、

自分にも見えていない問題があるかもしれない。

という考え方は、共通するように思います。

今回、ニュースを調べる中で「脆弱性」という言葉も改めて学びました。

私なりには、セキュリティ上の弱点によって、本来許されない操作などができてしまう問題だと理解しています。

ただし、脆弱性が見つかったからといって、実際に攻撃されたとは限りません。

この違いも、今回きちんと意識したいと思いました。

守るのは個人情報だけではない

最初、私はセキュリティといえば情報漏洩を防ぐことだと思っていました。

でも、調べてみると、それだけではありませんでした。

考え方 意味 Bakery Hubで考えたこと
機密性 許可された人だけが情報を見られる 他店舗の売上を勝手に閲覧できない
完全性 データが不正に変更されない 会計金額や売上を勝手に変更できない
可用性 必要なときに利用できる 通信障害に備えて会計記録を保持する

情報セキュリティの基本にある「CIAの3要素」という考え方です。

英語の頭文字だそうですが、私はこの表の方が分かりやすかったです(笑)。

Bakery Hubで守りたいのは、個人情報だけではありません。

店主さんが毎日記録する商品や売上も、大切なデータです。

このことが、後のオフラインレジの検証につながりました。


3. まず、自分のアプリで何を確かめるか

Bakery Hubは、PythonとFlaskを中心に開発している店舗運営支援Webアプリです。

主な技術構成は次のとおりです。

分類 使用技術
バックエンド Python / Flask
データベース PostgreSQL / SQLite
DB操作 SQLAlchemy / Alembic
フロントエンド HTML / CSS / JavaScript
端末内のデータ保存 IndexedDB
テスト pytest / Node.js / Playwright
バージョン管理 Git / GitHub
公開環境 Render

最初は売上を記録する機能から始めました。

そこに商品管理や仕込み管理を追加し、現在はレジ会計も扱うようになっています。

機能が増えるのは楽しいです。

ただ、増えた分だけ、確認することも多くなりました。

今回、AIと相談しながら優先して調べたのは、次のような部分です。

  • 管理者のログインとログアウト
  • 認証の有効期限
  • 店舗ごとのデータの分離
  • オフライン中の会計保存
  • 通信復旧後の同期
  • エラーが起きたときのデータ保護

なかでも気になったのが、誰が操作しているのかを正しく確認できることと、売上の記録を失わないことでした。

管理者以外が勝手に操作できてしまうのは困ります。

一方で、認証を厳しくしたことで会計が消えてしまっても困ります。

この2つを意識しながら調べることにしました。

検証環境は本番と分ける

検証には、架空の商品や会計データを使いました。

実際の店舗データを使わず、隔離した環境で試します。

本番データを守るために検証しているのに、その検証で売上を壊してしまっては意味がありません。

この判断は、技術的な知識というより、普段の仕事で大切にしている安全確認に近い感覚だったかもしれません。

⚠️ 今回の検証について

この記事は、自分が管理するBakery Hubを隔離環境で検証した記録です。

ARTEXを使って攻撃を再現したわけではありません。

また、Bakery Hubが実際にサイバー攻撃を受けたという記録でもありません。

実際の認証情報や秘密値、悪用につながる具体的な再現手順は掲載していません。


4. ログアウトしたのに、以前の認証が使える問題

今回、最初に大きな発見があったのが、管理者のセッション管理でした。

私はログアウトを、かなり単純に考えていました。

ログアウトボタンを押す。

ログイン画面に戻る。

それで以前のログイン情報は使えなくなる。

でも、実際に調べてみると、そうとは限りませんでした。

ログアウトした「つもり」になっていた

Webアプリでは、ログインした利用者を識別するために、セッションという仕組みが使われます。

Flaskの標準セッションでは、署名付きCookieが利用されます。

署名によって、Cookieの内容が勝手に書き換えられていないかを確認できます。

ただ、それだけでは、そのログインを今も許可してよいかまでは分かりません。

ブラウザでログアウトしても、以前の認証情報が別の場所に残っている可能性があります。

サーバー側でその情報を失効させる仕組みがなければ、古い認証を受け付けてしまう場合があるわけです。

今回のBakery Hubでも、管理者セッションの失効管理に問題が見つかりました。

画面ではログアウトできているように見えても、それだけでは不十分だったのです。

サーバー側でも失効を確認する方法を採用した

AIとコードを調べながら、ログイン状態をサーバー側でも管理する方法を確認しました。

これなら、以前のログイン情報が残っていたとしても、サーバー側で失効済みと判断できます。

今回の修正では、管理者がログインした記録をDBに保持し、認証時にその状態を確認する方法を採用しました。

実際の修正コードから、一部を紹介します。

def active_admin_session(token):
    digest = token_hash(token)

    if digest is None:
        return False

    now = utc_now()

    return db.session.execute(
        select(AdminAuthSession.token_hash).where(
            AdminAuthSession.token_hash == digest,
            AdminAuthSession.revoked_at.is_(None),
            AdminAuthSession.issued_at <= now,
            AdminAuthSession.expires_at > now,
        )
    ).first() is not None

※読みやすいように改行を調整した、実際のコードの抜粋です。

最初に見たときは、すぐに理解できたわけではありません。

でも、特に重要な部分は少しずつ分かってきました。

まず、

digest = token_hash(token)

ここでは、認証に使う値をハッシュ化して、照合に使う値を作っています。

認証用の値そのものをDBへ保存するのではなく、ハッシュ化した値を保存する仕組みです。

そして、今回特に重要だったのが、この条件です。

AdminAuthSession.revoked_at.is_(None)

revoked_at は、セッションが失効した日時を記録する項目です。

None は、ここでは失効日時が記録されていない状態です。

つまり、失効済みのセッションを認めないための条件になっています。

さらに、

AdminAuthSession.expires_at > now

この条件で、有効期限が過ぎていないことも確認しています。

こうして、画面上のログイン状態だけではなく、サーバー側の記録も確認するようにしました。

DBで確認できない場合はどうするか

ここで、もう一つ考えることがありました。

サーバー側で認証を管理するということは、DBで問題が起きた場合の動作も考えないといけません。

例えば、認証を確認しようとしたときに、DBへ接続できなかった場合です。

そのとき、確認できないからという理由で操作を許可してしまうと、失効済みの認証まで受け付ける可能性があります。

だから今回は、認証状態を確認できない場合は、管理者操作を許可しない方針にしました。

ただ、これは利用者にとって不便になる可能性もあります。

DBに問題が起きれば、正しくログインしていた人も一時的に操作できなくなるかもしれません。

それでも、管理者として操作してよいか確認できない状態で処理を進めるより、安全だと判断しました。

この判断が正しいかどうかは、サービスの用途によっても変わるのだと思います。

少なくとも、今回のBakery Hubではそう考えました。

テストで確認したこと

修正後は、正常にログインできるかだけでなく、次のような条件も確かめました。

確認したこと 期待する動作
正常なログイン 管理者として操作できる
ログアウト後の古い認証 拒否される
有効期限切れ 拒否される
別の独立したログイン 不必要に巻き込まれない
DB処理に失敗した場合 不完全な変更を残さない

修正内容とテストは、GitHubのPR #150に記録しています。

PR #150:Adminセッションの失効管理とPostgreSQL回帰テスト

このPRはmainへマージ済みです。

今回ここで学んだのは、

ログアウトボタンが正常に動くことと、古い認証情報が確実に拒否されることは、別の確認だということ。

トラックの運転でいえば、ミラーを見たことと、安全を確認できたことが必ずしも同じではないのに似ている気がしました。


5. 認証を厳しくしたら、今度は会計データが心配になった

ここから、さらに考えることが増えました。

Bakery Hubには、オフラインレジの仕組みがあります。

通信できないときでも、事前に準備した条件の範囲で会計を端末内に記録し、通信復旧後にサーバーと同期する仕組みです。

まだ制約のある機能ですが、レジの開発では大切にしたい部分です。

なぜなら、インターネットに接続できないときでも、お客様との会計が発生する可能性があるからです。

ログイン期限が切れたら、会計はどうなるのか

最初は、ログイン期限が切れたら、もう一度ログインしてもらえばよいと思っていました。

でも、オフラインレジのことを考えると、それだけでは解決しません。

端末には、まだサーバーへ送っていない会計が残っているかもしれません。

その状態でログイン期限が切れた場合です。

セキュリティを考えれば、期限切れの認証でサーバーへ送信することは許可したくありません。

でも、だからといって、端末に残っている会計記録まで消してしまったら困ります。

認証を守るために、店主さんの売上を失ってしまっては意味がない。

ここで、認証とデータ保存を分けて考える必要があると気づきました。

私が分けて考えた2つのこと

① サーバーへの送信を許可してよいか。

② 端末に残っている会計記録をどう守るか。

この2つは、似ているようで別の問題でした。

認証が切れているから、サーバーへの送信は拒否する。

でも、元の会計記録は保持する。

再ログインや必要な確認を経て、安全に同期できる状態になったら送信する。

今回の設計では、その考え方を大切にしました。

会計を記録
    |
    v
端末内に保存
    |
    v
通信できる?
    |
    +-- NO --> 記録を保持
    |
    +-- YES
           |
           v
      認証・端末許可を確認
           |
           +-- 無効 --> 記録を保持
           |
           +-- 有効
                  |
                  v
             サーバーへ送信
                  |
                  v
               保存結果を確認

※実際の処理を理解しやすく簡略化した図です。

こうして整理してみると、単にエラー処理を増やすだけの話ではありませんでした。

何を許可して、何を残すのか。

そこを決める必要があったのです。

ブラウザ側だけで止めても十分ではない

例えば、JavaScriptで次のような判断をするとします。

// 考え方を示すための簡略例

if (!authReady) {
    keepLocalReceipt();
    return;
}

trySync();

authReady が false なら、同期を進めず、端末の会計を保持するという考え方です。

ただし、これだけでは十分ではありません。

ブラウザ側の処理は、サーバーに送られるリクエストの安全性まで保証してくれるわけではないからです。

サーバー側でも、認証や端末許可を確認する必要があります。

ここは今回、特に理解が深まった部分でした。

端末に残せば、絶対に安全というわけでもない

もう一つ、気づいたことがあります。

端末内の記録を削除しない設計にしても、それだけで会計が絶対に失われないとは言い切れません。

端末そのものが故障するかもしれません。

ブラウザの保存データが削除されることも考えられます。

つまり、勝手に削除しない仕組みを作ることと、どんな状況でも失わないことは別なんですね。

今回はまず、認証エラーなどを理由に元の会計を削除しないことを優先しました。

それでも、データ保護について考えるべきことは残っています。

📌 今回の設計判断

  • 認証が無効なら、サーバーへの送信を許可しない。
  • 送信できなくても、端末内の元の会計を勝手に削除しない。
  • 再ログイン後も、必要な確認を経てから同期する。
  • 端末内保存だけで、あらゆるデータ消失を防げるとは考えない。

「送信を許可しない」と「会計を消す」は、別々に判断する。

今回、私が特に大切にした考え方です。


6. 同じ会計を2回送って、売上が2倍になったら困る

次に考えたのは、通信エラーが起きたときの会計です。

例えば、300円の商品を販売したとします。

その会計をサーバーへ送信しました。

ところが、途中で通信が切れてしまいました。

アプリには成功の返事が届きません。

ここで私は、通信エラーにもいろいろな状況があることを知りました。

例えば、

状況 サーバー側で起きている可能性
送信前に通信が切れた 会計が届いていない
サーバーの処理中に失敗した 保存できていない可能性がある
保存後に返事だけ届かなかった 会計は保存されている可能性がある

画面では同じようなエラーに見えても、実際の保存状態は違うかもしれません。

この違いを考えず、新しい会計として送信し直したらどうなるでしょう。

300円の売上が600円になってしまう可能性があります。

元の会計を識別できるようにする

Bakery Hubでは、会計ごとに識別用のキーを持たせています。

考え方を簡単なPythonの辞書で表すと、次のようになります。

# 説明用の簡略例

receipt = {
    "submission_key": "元の会計を識別するキー",
    "total_yen": 300,
    "status": "pending",
}

submission_key は、その会計を識別するための値です。

通信エラーになっても、元のキーを勝手に変更しない。

同じ会計であれば、同じ識別情報を使う。

そして、サーバー側でも重複を防ぎ、元の会計と内容が一致しているか確認する必要があります。

ここで出てきたのが、**冪等性(べきとうせい)**という言葉でした。

最初は読み方から分かりませんでした(笑)。

私なりには、同じ処理を繰り返しても、意図しない追加の結果を生まないようにする考え方だと理解しています。

今回のレジでいえば、同じ会計を再送しただけで、売上が二重に登録されないようにすることです。

判断できないものを、勝手に成功扱いしない

ただ、キーが同じなら何でも受け付けてよいわけではありません。

同じキーなのに、金額や商品明細が違っていたら困ります。

その場合は、無理に成功扱いするのではなく、元の記録を保持して確認が必要な状態にする。

今回の設計では、そうした考え方を取り入れています。

ここでも、トラックの運転で大切にしていることと似た感覚がありました。

状況が分からないまま進めるより、確認する。

処理を止めることが、データを守ることにつながる場合もある。

レジを開発する中で、そんなことを考えるようになりました。


7. Lenovo G580で見つけた、予想外の表示

ここからは、実際に操作して検証した話です。

手元のLenovo G580に隔離した環境を用意し、架空の商品を使って会計を作りました。

商品 数量 金額
検証クロワッサン 1個 120円
検証食パン 1個 300円
合計 2個 420円

この2件をオフライン状態で端末に記録しました。

その後、管理者のログイン期限が切れた状態にして、動作を確認します。

調べたかったのは、認証が切れた場合でも、元の会計記録が失われないかということでした。

再ログイン後、画面には次のように表示されました。

項目 表示された件数
サーバー保存済み 2件
未同期 0件

2件とも同期済みと表示されました。

ひとまず、期待した方向に動いているように見えました。

ところが、「保存状況を確認」というボタンを押したときです。

次の案内が表示されました。

サーバーへの反映を確認できませんでした。端末の記録を保持しています。

あれ?

さっきは保存済みが2件だったのに。

保存できているのか、それともできていないのか。

この表示には違和感がありました。

原因を調べてみた

AIと一緒にコードを調べていくと、保存確認の対象を選ぶ処理に問題がありました。

以前のコードでは、次の条件が使われていました。

record.status !== "synced"

!== は、JavaScriptで「等しくない」という意味です。

つまり、ここでは "synced" ではない会計だけを確認対象にしています。

すでに同期済みの2件は、新たな確認対象から外れるわけです。

ところが、その結果として確認対象が0件になった場合にも、確認できなかったという案内が表示されていました。

端末に会計が2件ある
        |
        v
どちらも同期済み
        |
        v
今回の確認対象は0件
        |
        v
「確認できませんでした」
        |
        v
表示が実際の処理と合っていない

「今回新しく確認する会計がない」ことを、「確認に失敗した」かのように表示していたのです。

サーバーのデータが消えたと確認されたわけではありません。

でも、利用者にとっては分かりにくい表示です。

私が気になったのは、店主さんがどう受け取るか

例えば、一日の営業が終わり、店主さんが売上を確認するとします。

そこで突然、

「サーバーへの反映を確認できませんでした」

と表示されたら、不安になるかもしれません。

保存できていないと思って、同じ会計を登録し直してしまう可能性もあります。

そう考えると、これは単なる文章の問題ではないと思いました。

画面の案内によって、利用者が間違った判断をするかもしれない。

そこまで考える必要があると感じました。

過去の結果と、今回の結果を分ける

今回の修正では、保存状態の案内を分けることにしました。

状態 利用者に伝えたいこと
会計記録がない まだ会計記録がない
以前の同期結果で保存済み 過去に保存済みと判定されている
未同期の会計がある まだ送信待ちの会計がある
認証が無効 ログイン状態の確認が必要
通信に失敗した 今回の確認はできなかった
内容が一致しない 元の記録を保持して確認が必要

特に大切にしたのは、以前の確認結果と、今回実際に確認した結果を混同しないことです。

以前は保存済みと判定されていても、今回新しい照合をしていないのであれば、そのことを伝える。

逆に、確認対象がないだけなのに、失敗したかのように表示しない。

また、長い会計キーなどは通常の画面から外し、必要な場合だけ詳細で確認できるようにしました。

店主さんにとって大切なのは、長い英数字ではありません。

今、売上がどういう状態なのか。次に何をすればよいのか。

そこが分かる画面にしたかったのです。

📌 今回の表示改善で考えたこと

  • 保存済みという表示だけで、今この瞬間にDBを照合したとは限らない。
  • 新しい確認をしていないことと、確認に失敗したことは別。
  • 利用者が誤解しないように、状態の違いを伝える。
  • 実際の保存結果を確認できない場合、勝手に成功扱いしない。

処理の正しさだけでなく、その状態を正しく伝えることも大切だと感じました。


8. テストは成功した。でも、確認はまだ残っていた

今回の保存状況UIの修正では、まず問題を再現するテストを追加しました。

修正前には失敗するテストを用意し、その後でコードを直します。

以前から学んでいる、テストを使った開発の考え方です。

テストで何を確かめたいのか

説明用に簡略化すると、こんなJavaScriptのテストになります。

// 説明用の簡略例
// 実際のBakery Hubのテストコードではありません

const assert = require("node:assert/strict");
const test = require("node:test");

function savedMessage(records) {
    if (records.length === 0) {
        return "会計記録がありません";
    }

    if (records.every(r => r.status === "synced")) {
        return "以前の同期結果では保存済みです";
    }

    return "未確認の会計があります";
}

test("すべて同期済みなら失敗扱いしない", () => {
    const records = [
        { status: "synced" },
        { status: "synced" },
    ];

    assert.equal(
        savedMessage(records),
        "以前の同期結果では保存済みです"
    );
});

every() は、配列のすべての要素が条件を満たすか確認するものです。

今回なら、2件とも同期済みの状態かを確認しています。

ただし、このテストで確かめているのは表示の判断です。

サーバーのDBに2件保存されているかを調べるテストではありません。

この違いを意識するようになったことも、今回の学びでした。

実際のテスト結果

今回の保存状況UI修正では、次の結果が得られました。

項目 結果
新しく追加したテスト 10件
対象JavaScriptテスト 36 passed
failed 0
skipped 0

対象のJavaScriptテストは、36件すべて成功しました。

これは嬉しい結果です。

ただ、そこで検証がすべて終わったわけではありません。

Chromiumを使ったブラウザE2Eテストは、検証環境でブラウザの導入に失敗したため、実行できませんでした。

また、G580で確認した会計2件についても、サーバーDBに元の識別キーで2件保存されているか、独立して照合する作業が残っています。

画面には同期済みと表示されていました。

でも、画面上の表示と、DBの中身を独立して確認した結果は別です。

今回は、まだ確認できていないことを、そのまま未確認として残しました。

テストの件数が増えることは嬉しい。でも……

Bakery Hubでは、これまでに多くのpytestを追加してきました。

今回の管理者セッションの検証でも、PostgreSQLを使ったテストを実施しています。

関連するDraft PR #152では、PostgreSQL 16系・18系を使用した検証が記録されています。

また、別のコミットを対象としたCIでは、Pythonテスト1,493件の成功も確認されています。

PR #152:Adminセッションの運用検証(Draft)

ただし、これらは今回の保存状況UI修正より前のコミットに対する結果です。

そのため、最新のUI修正まで検証できたという意味にはなりません。

最初のころは、成功したテストの件数が増えることがとても嬉しかったです。

もちろん、今でも嬉しいです。

ただ、最近は件数を見るだけでなく、

「そのテストで、何を確認できたのか」

を考えるようになりました。

今回の36件も、今回対象にしたJavaScriptの処理について確認できたという意味です。

実機やDBまで含めて、すべての検証が終わったという意味ではありません。

こうした区別を、これからも大切にしたいと思います。


9. なぜ、テストが成功しても本番公開を止めたのか

今回、もう一つ大きな判断がありました。

安全確認が終わるまで、修正版の本番公開を急がないことです。

Bakery HubはRenderで公開しているWebアプリです。

開発したものがブラウザから使えるのは、やはり嬉しいです。

新しい修正ができたら、早く使える状態にしたい気持ちもあります。

でも、今回の検証では、認証や会計データの保護に関する確認がまだ残っていました。

修正できた部分はあります。

関連テストも成功しています。

それでも、まだ確かめられていないことがある。

そこで、公開を急ぐのではなく、いったん止めて確認することにしました。

コードを直すことと、本番へ反映することは違った

例えば、管理者セッションの管理方法を変更すると、新しいDBテーブルが必要になります。

開発環境では正しく動いていても、本番環境ではどうなるのでしょうか。

既存のデータはそのまま残るのか。

古い処理が動いている間に、新しい処理を入れても問題ないのか。

失敗した場合に、元の状態へ戻せるのか。

会計データを失わずに復旧できるのか。

こうしたことを考えると、コードを修正する作業とは別の難しさがあると分かりました。

ここは、私にとってまだまだ勉強が必要なところです。

今回、公開判断を分けて考えた

状態 今回の判断
修正コードが完成した まだ公開を決めない
関連テストが成功した 次の検証へ進む
実機での確認が残っている 未確認として記録する
DBの状態を十分確認できていない 本番DBの変更を急がない
バックアップや復元の確認が必要 手順を確かめてから進める

今回の検証では、一時保守版への切り替えを承認し、安全確認を優先する方針を取りました。

保守版はメンテナンス表示を行い、HTTP 503を返す設計です。

503は、サーバーが一時的にリクエストを処理できないことを表すHTTPステータスコードです。

ただし、この検証記録だけでは、実際の稼働コミットやHTTP応答、旧処理の終了まで最終確認できたとは言えません。

そのため、保守版の切り替えや本番復旧が完了したという断定は避けています。

なぜ、NO-GOと判断したのか

今回の判断は、修正したコードがすべて間違っているからではありません。

修正できた部分もあります。

テストで確認できた部分もあります。

それでも、本番へ反映してよいと判断するための確認が、まだ足りていなかったのです。

今回は、正式公開を見送ることにしました。

⚠️ Phase 5検証時点の公開判断:NO-GO

対象 検証記録上の状態
管理者セッションの修正 PR #150でmainへマージ済み
PostgreSQL運用検証 PR #152で関連テスト成功・Draft継続
保存状況UIの修正 ローカル修正済み
関連JavaScriptテスト 36件成功
修正版のブラウザE2E 未完了
G580の会計DB独立照合 未完了
この段階での正式本番反映 NO-GO

これは今回の検証段階での判断です。記事公開時点の本番稼働状況そのものを示すものではありません。

サービスの規模や利用者、運用体制によって、公開の判断基準は変わると思います。

ただ、今回のBakery Hubでは、早く公開することよりも、会計データを守ることを優先しました。


10. AIと開発しているからこそ、自分の判断を持ちたい

今回のセキュリティ検証でも、AIには本当に助けられました。

コードを調べ、問題を整理し、修正案を作り、テストを追加する。

独学で開発している私にとって、とてもありがたい存在です。

でも、AIが作ったコードだから安全というわけではありません。

人間が書いたコードでも、AIが書いたコードでも、確認は必要です。

それに、コードが正しく動いても、それが利用者にとって良い動作とは限りません。

今回、特にそう感じたのが、認証と会計データの保護でした。

認証期限が切れたら、サーバーへの送信は拒否したい。

でも、端末に残っている会計記録は守りたい。

この2つを考えるために、何を許可して、何を保持するかを分けました。

保存状況の表示でも同じです。

エラーを表示しないようにするだけなら、もっと単純な修正も考えられます。

でも、本当に大切なのは、店主さんが正しい状況を理解できることでした。

だから、過去に確認したことと、今回確認したことを区別するようにしました。

そして、本番公開についても、テストが成功したという理由だけでは進めませんでした。

何のために作っているのか

AIは、考えを形にするのが得意です。

でも、何を作るのか。

誰が使うのか。

どんなときに困るのか。

どんなメリットがあるのか。

そこは、開発する側が考え続ける必要があると思います。

Bakery Hubの場合、私が守りたいのは店主さんの仕事です。

パンを売る。

会計を記録する。

一日の売上を確かめる。

その当たり前の仕事を、安心して続けてもらいたい。

だから、アプリの内部が複雑になっても、利用者にはできるだけ分かりやすい画面を提供したいと考えています。

今回の検証では、セキュリティと使いやすさは別々ではないのだと感じました。

安全に処理することも、正しい状態を分かりやすく伝えることも、利用者を守るために必要な設計です。

AIに助けてもらいながら開発しているからこそ、こうした目的や判断の理由を、自分でも理解していたいと思います。


11. 今回、自分なりに考えて選んだこと

今回の検証を振り返ると、いくつかの大切な判断がありました。

問題 今回選んだこと その理由
ログアウト後の古い認証 サーバー側で失効状態を管理する 画面のログアウトだけに頼らないため
DBで認証を確認できない 管理者操作を許可しない 有効か分からない認証を通さないため
認証期限切れの未同期会計 送信を拒否しても記録は保持する 認証を守るために売上を失わないため
通信エラー後の会計再送 元の識別キーを維持する 二重登録を防ぐため
保存状況の誤表示 過去の確認と今回の確認を分ける 利用者の誤解を防ぐため
本番公開 最終確認が残るため見送る 未確認の状態でデータに影響を与えないため

最初は、セキュリティという大きな言葉に、少し難しさを感じていました。

でも、実際に調べてみると、一つひとつの判断には理由がありました。

なぜ、そこで拒否するのか。

なぜ、そのデータを残すのか。

なぜ、確認できないものを成功扱いしないのか。

なぜ、テストが通っても公開を止めるのか。

こうしたことを考えるうちに、セキュリティが少しずつ身近になってきた気がします。

今回選んだ方法が、あらゆる場面で唯一の正解だとは思っていません。

もっと良い方法もあるかもしれません。

でも、少なくとも今回の判断には、自分なりの理由があります。

その理由を考えながら開発できたことは、以前の自分から少し成長できた部分だと思っています。


おわりに

今回の取り組みは、サイバー攻撃のニュースを見て生まれた疑問から始まりました。

情報漏洩とは何か。

脆弱性とは何か。

AIがサイバー攻撃に悪用される時代に、自分のWebアプリをどう守るのか。

最初は、そんな疑問でした。

そこから自分のアプリを調べ、問題を発見し、修正しました。

テストで確認できたこともあれば、まだ確認が残っていることもあります。

それでも、今回自分のコードを疑ってみたことには、大きな意味があったと思います。

トラックの運転にも、プログラムにも、死角があります。

慣れているから大丈夫。

昨日まで問題がなかったから大丈夫。

テストが通ったから大丈夫。

そう思いたくなることもあります。

でも、

「大丈夫なはず」と「実際に確認できた」は違う。

今回、一番学んだことはそこかもしれません。

独学を始めて半年。

ここまでBakery Hubを開発してきましたが、プログラミングも設計も、まだまだ学ぶことがあります。

それでも、自分が作ったアプリだからこそ、分からないままにせず、一つずつ確かめていきたい。

これからもAIを活用しながら、作りたいものを形にし、理解を深めていきたいと思っています。

自分の成果には自信を持つ。

でも、知らないことを知ろうとする姿勢は忘れない。

そして、今回の検証を通して、自分なりにたどり着いた考えがあります。

セキュリティとは、攻撃を防ぐためだけのものではない。

利用者が安心して、いつもの仕事を続けられるようにするための設計でもある。

これからも、そのことを大切にして開発を続けていきます。

最後までお読みいただき、ありがとうございました。


参考資料・開発記録

サイバー攻撃・セキュリティ関連

Bakery Hubの開発記録


📌 記事についての補足

本記事は、2026年10月30日までの開発・検証記録に基づいています。

  • Bakery Hubがサイバー攻撃を受けたという記録ではありません。
  • ARTEXを使用して侵入テストを行ったわけではありません。
  • 記事中には、理解しやすくするための簡略化したコード例を含みます。
  • 確認できた内容と未確認の内容を区別しています。
  • 検証段階の公開判断と、記事公開時点の本番稼働状況は別のものです。
  • 今回の対策によって、将来にわたる安全性が保証されるわけではありません。

今後の検証状況によって、記事の内容を更新する場合があります。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?