ChatGPT や Claude で、誰でも素早くアプリが作れる時代になりました。でも「動く」ことと「安全に公開できる」ことは別です。AIは「動く」を優先するので、セキュリティは明示的に頼まないと抜けがちで、しかも作った本人はその抜けに気づきにくい。
私はクラウドソーシングで「AIで作ったアプリの公開前セキュリティ点検」を承っています。本記事は、その納品レポートの見本を、架空アプリを点検する形で作った「制作記」です。
⚠️ この記事のスタンス
- 題材は実在しない架空アプリです。鍵はダミーです。
サンプル一式(点検対象・改善版・レポート)の置き場所:
https://github.com/sleepycat12341013/security-audit-sample
題材の作り方(透明性のために明記します)
この見本の題材は、デモ用に「AIに、セキュリティをあえて頼まず、よくある作り方のまま=脆弱性を残す形で書かせた」架空のLINE Botです(Node.js・Express・MySQL)。
特殊な細工はしていません。AIに「ポイント機能を作って」とだけ頼むと出てきがちな、ありがちな穴をそのまま残しています。つまりこの Before は「AIに任せた直後のコード」に近いものです。
補足:私自身、下調べや題材生成にAIを使います。AIで作ること自体は否定していません。言いたいのは「AIが作る → 人が点検する」という最後の一手が要る、ということです。
点検の前に:3つのものさし
コードを読む前に、いつもこの3つを持って臨みます。
- 「ある」と「効いている」は違う … 対策が「書いてある」ことと「実際に効いている」ことは別。
- 信頼境界 … どこから来たものを疑うか。ユーザー側の値は全部疑い、最終判断はサーバーでする。
- リスクの置き場所 … 全部完璧にするより、被害の大きい順(個人情報・金銭)から「消す・移す・受容する」を決める。
どんな目線で見るか(6領域)
各領域、Before(AIが書いたままの状態) と、そこで確認する観点を示します。
1. 認証・署名(そのリクエスト、本当に正規の相手から?)
app.post('/webhook', (req, res) => {
const events = req.body.events;
events.forEach((event) => { if (event.type === 'message') handleMessage(event); });
res.sendStatus(200);
});
確認する観点: 届いたリクエストが本当にLINEから来たものか、署名を検証して、失敗時に止めているか。ここが信頼境界の最前線。
🤖 AIあるある: 「まず動くWebhook」を出すので、署名検証は頼まないと付きません。
2. 秘密情報の置き場所(鍵はコードに残っていないか)
const CHANNEL_ACCESS_TOKEN = 'ダミー'; // コードに直書き
const db = mysql.createConnection({ user: 'root', password: 'root', /* ... */ });
確認する観点: 鍵・トークン・DB認証がコード・ログ・Git履歴に露出していないか。「消したつもりが履歴に残る」罠も。
🤖 AIあるある: 説明を簡単にするため鍵を直書きで出しがち。
3. 認可・なりすまし(他人のデータが見えないか=IDOR)
app.get('/api/points', (req, res) => {
const userId = req.query.userId; // クライアントが自由に指定できる値
db.query(`SELECT * FROM users WHERE line_user_id = '${userId}'`, ...);
});
確認する観点: ログインできること(認証)と、持ち主であること(認可)は別。「誰のデータを返すか」をクライアントの値で決めていないか。
🤖 AIあるある: 「動く」だけなら通るので、頼まないと認可(持ち主チェック)が入りません。
4. 入力の検証(サーバー側で最終判断)
db.query(`SELECT points FROM users WHERE line_user_id = '${userId}'`, ...);
確認する観点: ユーザー由来の値をSQLに文字列連結していないかを、1箇所も漏らさず横断して見る。
🤖 AIあるある: クイックな例では文字列連結のまま出てくることがあります。
5. レート制限・濫用耐性
この題材は、実はここはできていました。点検は穴探しではなく、効いている所は効いていると判定するのも仕事です。
app.use(rateLimit({ windowMs: 60 * 1000, max: 60 })); // 1分60回まで
確認する観点: 連打で外部APIコストやDBを食い潰せる入口がないか。全体に上限があり、一次的な歯止めが効いています。
6. LIFF / API
確認する観点: クライアントが送る lineUserId を検証せず信用していないか/レスポンスに画面に出す以上のデータ(パスワードハッシュ等)が混ざっていないか/エラーで内部情報を返していないか。
🤖 AIあるある: クライアントの値をそのまま信用したり、SELECT * で全部返すコードを出しがちです。
なぜ「自分」や「AIだけ」では確認しきれないのか
ここが一番伝えたい所です。
- AIに「これ安全?」と聞いても、AIは自分が書いたコードの穴を見落とします。しかも、もっともらしく「大丈夫です」と間違った答えを返すことがあります。
- 一番こわいのは「書いてあるけど効いていない」状態です。たとえば署名検証が書いてあっても、検証の仕方が違えば素通りします。これは「ある/ない」では分からず、効いているかを確かめないと見抜けません。
- チェックリストを全部✅にしても、その✅が本当に効いているかは別問題。だからこそ、第三者の人の目が要ります。
AIが作る時代だからこそ、最後に「効いているか」を人が確かめる工程が抜けがちなのです。
納品レポートの形(点検でお渡しするもの)
- 領域ごとに ✅効いている/⚠️要改善 を、該当箇所(ファイル・行)と理由つきで明記
- 深刻度(最高/高/中)と、直す優先順位(被害の大きい順)
- 要改善の箇所は、直し方(改善版コードの方向性)までお渡しします=「指摘」を「次の一手」に
- 必ず免責を添えます(ご依頼日時点・指定範囲の確認であり、すべての脆弱性の不存在を保証するものではありません)
公開前のセルフチェック
公開前に、ご自身で確かめてみてください。1つでも「自信がない」があれば、そこが穴かもしれません。
- □ 署名検証は、書いてあるだけでなく失敗時に本当に止まっていますか?
- □ 他人のIDを指定して他人のデータが見える経路は、1つも無いと言い切れますか?
- □ 鍵は、ログやGit履歴のどこにも残っていませんか?
- □ ユーザー入力をSQLに連結している箇所は、本当に0ですか?
- □ 「AIが安全と言った」を、誰か(人)が確かめましたか?
まとめ:AIが作る時代の「最後の一手」
AIは「動く」は作れても、「安全」は頼まないと作りません。本人もAIも、その抜けに気づきにくい。
だから「動くからOK」で公開せず、信頼境界とリスクの置き場所で「効いているか」を確かめる──AIが作る → 人が点検する。これがAI時代の最後の一手だと思っています。
公開前点検をクラウドソーシングで承っています。
- ご依頼(ココナラ):https://coconala.com/services/4280247
- サンプル一式(GitHub):https://github.com/sleepycat12341013/security-audit-sample
脆弱性の評価・判定は、すべて筆者本人が行っています。
(題材の生成や下調べにAIを補助的に利用していますが、最終判断は筆者が行っています。)