[{"data":1,"prerenderedAt":2440},["ShallowReactive",2],{"content-/security-for-indie-dev":3,"all-pages-for-dir":2418,"related-/security-for-indie-dev":2419,"og-image-/security-for-indie-dev":2439},{"id":4,"title":5,"body":6,"category":2404,"concepts":2404,"description":2405,"extension":2406,"meta":2407,"navigation":236,"ogImage":2404,"path":2408,"project_name":2404,"published":2409,"publishedAt":2410,"seo":2411,"source":2404,"stem":2412,"tags":2413,"todo":2404,"unpublished":2409,"updatedAt":2410,"__hash__":2417},"pages/2025-12/2025-12-19/security-for-indie-dev.md","個人開発で意識したセキュリティ対策",{"type":7,"value":8,"toc":2366},"minimark",[9,12,16,21,24,157,161,168,173,293,296,300,547,554,557,633,637,640,755,758,781,945,949,966,970,973,984,992,996,999,1045,1048,1081,1112,1116,1148,1154,1158,1161,1225,1228,1254,1257,1267,1271,1274,1503,1507,1510,1722,1726,1862,1866,1869,1910,1913,1936,1940,2088,2092,2095,2109,2112,2123,2228,2231,2234,2301,2304,2309,2324,2329,2343,2348,2362],[10,11,5],"h1",{"id":5},[13,14,15],"p",{},"個人開発だからといってセキュリティを疎かにすると、ユーザーに迷惑をかけるだけでなく自分の信用も失う。最低限押さえるべきポイントをまとめる。",[17,18,20],"h2",{"id":19},"owasp-top-10-を意識する","OWASP Top 10 を意識する",[13,22,23],{},"Webアプリケーションにおけるセキュリティリスクのトップ10である。これを知っているだけで大半の脆弱性は防げる。",[25,26,27,43],"table",{},[28,29,30],"thead",{},[31,32,33,37,40],"tr",{},[34,35,36],"th",{},"順位",[34,38,39],{},"リスク",[34,41,42],{},"概要",[44,45,46,58,69,80,91,102,113,124,135,146],"tbody",{},[31,47,48,52,55],{},[49,50,51],"td",{},"1",[49,53,54],{},"Broken Access Control",[49,56,57],{},"認可の不備",[31,59,60,63,66],{},[49,61,62],{},"2",[49,64,65],{},"Cryptographic Failures",[49,67,68],{},"暗号化の失敗",[31,70,71,74,77],{},[49,72,73],{},"3",[49,75,76],{},"Injection",[49,78,79],{},"SQLi, XSS, コマンドインジェクション",[31,81,82,85,88],{},[49,83,84],{},"4",[49,86,87],{},"Insecure Design",[49,89,90],{},"設計段階の問題",[31,92,93,96,99],{},[49,94,95],{},"5",[49,97,98],{},"Security Misconfiguration",[49,100,101],{},"設定ミス",[31,103,104,107,110],{},[49,105,106],{},"6",[49,108,109],{},"Vulnerable Components",[49,111,112],{},"脆弱な依存関係",[31,114,115,118,121],{},[49,116,117],{},"7",[49,119,120],{},"Authentication Failures",[49,122,123],{},"認証の不備",[31,125,126,129,132],{},[49,127,128],{},"8",[49,130,131],{},"Software Integrity Failures",[49,133,134],{},"整合性の問題",[31,136,137,140,143],{},[49,138,139],{},"9",[49,141,142],{},"Logging Failures",[49,144,145],{},"ログ・監視の不備",[31,147,148,151,154],{},[49,149,150],{},"10",[49,152,153],{},"SSRF",[49,155,156],{},"サーバーサイドリクエストフォージェリ",[17,158,160],{"id":159},"_1-入力値は全て信用しない","1. 入力値は全て信用しない",[13,162,163,167],{},[164,165,166],"strong",{},"大原則",": ユーザーからの入力は全て悪意があると仮定する。",[169,170,172],"h3",{"id":171},"sqlインジェクション対策","SQLインジェクション対策",[174,175,180],"pre",{"className":176,"code":177,"language":178,"meta":179,"style":179},"language-javascript shiki shiki-themes vitesse-light vitesse-light","// NG: 文字列結合\nconst query = `SELECT * FROM users WHERE id = ${userId}`;\n\n// OK: プリペアドステートメント\nconst query = 'SELECT * FROM users WHERE id = ?';\ndb.query(query, [userId]);\n","javascript","",[181,182,183,192,231,238,244,264],"code",{"__ignoreMap":179},[184,185,188],"span",{"class":186,"line":187},"line",1,[184,189,191],{"class":190},"sxvE3","// NG: 文字列結合\n",[184,193,195,199,203,207,211,215,219,222,225,228],{"class":186,"line":194},2,[184,196,198],{"class":197},"stQ0i","const",[184,200,202],{"class":201},"s4oTP"," query",[184,204,206],{"class":205},"shFtX"," =",[184,208,210],{"class":209},"sMJiu"," `",[184,212,214],{"class":213},"sdGka","SELECT * FROM users WHERE id = ",[184,216,218],{"class":217},"sHkkW","${",[184,220,221],{"class":213},"userId",[184,223,224],{"class":217},"}",[184,226,227],{"class":209},"`",[184,229,230],{"class":205},";\n",[184,232,234],{"class":186,"line":233},3,[184,235,237],{"emptyLinePlaceholder":236},true,"\n",[184,239,241],{"class":186,"line":240},4,[184,242,243],{"class":190},"// OK: プリペアドステートメント\n",[184,245,247,249,251,253,256,259,262],{"class":186,"line":246},5,[184,248,198],{"class":197},[184,250,202],{"class":201},[184,252,206],{"class":205},[184,254,255],{"class":209}," '",[184,257,258],{"class":213},"SELECT * FROM users WHERE id = ?",[184,260,261],{"class":209},"'",[184,263,230],{"class":205},[184,265,267,270,273,277,280,282,285,288,290],{"class":186,"line":266},6,[184,268,269],{"class":201},"db",[184,271,272],{"class":205},".",[184,274,276],{"class":275},"senZ8","query",[184,278,279],{"class":205},"(",[184,281,276],{"class":201},[184,283,284],{"class":205},",",[184,286,287],{"class":205}," [",[184,289,221],{"class":201},[184,291,292],{"class":205},"]);\n",[13,294,295],{},"ORMを使っていても、生SQLを書く箇所は要注意。",[169,297,299],{"id":298},"xss対策","XSS対策",[174,301,303],{"className":176,"code":302,"language":178,"meta":179,"style":179},"// NG: innerHTML に直接代入\nelement.innerHTML = userInput;\n\n// OK: textContent を使う\nelement.textContent = userInput;\n\n// または適切にエスケープ\nfunction escapeHtml(str) {\n  return str\n    .replace(/&/g, '&amp;')\n    .replace(/\u003C/g, '&lt;')\n    .replace(/>/g, '&gt;')\n    .replace(/\"/g, '&quot;')\n    .replace(/'/g, '&#039;');\n}\n",[181,304,305,310,327,331,336,351,355,361,381,390,425,454,483,512,541],{"__ignoreMap":179},[184,306,307],{"class":186,"line":187},[184,308,309],{"class":190},"// NG: innerHTML に直接代入\n",[184,311,312,315,317,320,322,325],{"class":186,"line":194},[184,313,314],{"class":201},"element",[184,316,272],{"class":205},[184,318,319],{"class":201},"innerHTML",[184,321,206],{"class":205},[184,323,324],{"class":201}," userInput",[184,326,230],{"class":205},[184,328,329],{"class":186,"line":233},[184,330,237],{"emptyLinePlaceholder":236},[184,332,333],{"class":186,"line":240},[184,334,335],{"class":190},"// OK: textContent を使う\n",[184,337,338,340,342,345,347,349],{"class":186,"line":246},[184,339,314],{"class":201},[184,341,272],{"class":205},[184,343,344],{"class":201},"textContent",[184,346,206],{"class":205},[184,348,324],{"class":201},[184,350,230],{"class":205},[184,352,353],{"class":186,"line":266},[184,354,237],{"emptyLinePlaceholder":236},[184,356,358],{"class":186,"line":357},7,[184,359,360],{"class":190},"// または適切にエスケープ\n",[184,362,364,367,370,372,375,378],{"class":186,"line":363},8,[184,365,366],{"class":197},"function",[184,368,369],{"class":275}," escapeHtml",[184,371,279],{"class":205},[184,373,374],{"class":201},"str",[184,376,377],{"class":205},")",[184,379,380],{"class":205}," {\n",[184,382,384,387],{"class":186,"line":383},9,[184,385,386],{"class":217},"  return",[184,388,389],{"class":201}," str\n",[184,391,393,396,399,401,404,408,410,413,415,417,420,422],{"class":186,"line":392},10,[184,394,395],{"class":205},"    .",[184,397,398],{"class":275},"replace",[184,400,279],{"class":205},[184,402,403],{"class":209},"/",[184,405,407],{"class":406},"sLkVR","&",[184,409,403],{"class":209},[184,411,412],{"class":217},"g",[184,414,284],{"class":205},[184,416,255],{"class":209},[184,418,419],{"class":213},"&amp;",[184,421,261],{"class":209},[184,423,424],{"class":205},")\n",[184,426,428,430,432,434,436,439,441,443,445,447,450,452],{"class":186,"line":427},11,[184,429,395],{"class":205},[184,431,398],{"class":275},[184,433,279],{"class":205},[184,435,403],{"class":209},[184,437,438],{"class":406},"\u003C",[184,440,403],{"class":209},[184,442,412],{"class":217},[184,444,284],{"class":205},[184,446,255],{"class":209},[184,448,449],{"class":213},"&lt;",[184,451,261],{"class":209},[184,453,424],{"class":205},[184,455,457,459,461,463,465,468,470,472,474,476,479,481],{"class":186,"line":456},12,[184,458,395],{"class":205},[184,460,398],{"class":275},[184,462,279],{"class":205},[184,464,403],{"class":209},[184,466,467],{"class":406},">",[184,469,403],{"class":209},[184,471,412],{"class":217},[184,473,284],{"class":205},[184,475,255],{"class":209},[184,477,478],{"class":213},"&gt;",[184,480,261],{"class":209},[184,482,424],{"class":205},[184,484,486,488,490,492,494,497,499,501,503,505,508,510],{"class":186,"line":485},13,[184,487,395],{"class":205},[184,489,398],{"class":275},[184,491,279],{"class":205},[184,493,403],{"class":209},[184,495,496],{"class":406},"\"",[184,498,403],{"class":209},[184,500,412],{"class":217},[184,502,284],{"class":205},[184,504,255],{"class":209},[184,506,507],{"class":213},"&quot;",[184,509,261],{"class":209},[184,511,424],{"class":205},[184,513,515,517,519,521,523,525,527,529,531,533,536,538],{"class":186,"line":514},14,[184,516,395],{"class":205},[184,518,398],{"class":275},[184,520,279],{"class":205},[184,522,403],{"class":209},[184,524,261],{"class":406},[184,526,403],{"class":209},[184,528,412],{"class":217},[184,530,284],{"class":205},[184,532,255],{"class":209},[184,534,535],{"class":213},"&#039;",[184,537,261],{"class":209},[184,539,540],{"class":205},");\n",[184,542,544],{"class":186,"line":543},15,[184,545,546],{"class":205},"}\n",[13,548,549,550,553],{},"Reactなどのフレームワークはデフォルトでエスケープするが、",[181,551,552],{},"dangerouslySetInnerHTML","は要注意である。",[169,555,556],{"id":556},"コマンドインジェクション対策",[174,558,560],{"className":176,"code":559,"language":178,"meta":179,"style":179},"// NG: ユーザー入力をそのままコマンドに\nexec(`convert ${filename} output.png`);\n\n// OK: 引数として渡す\nexecFile('convert', [filename, 'output.png']);\n",[181,561,562,567,593,597,602],{"__ignoreMap":179},[184,563,564],{"class":186,"line":187},[184,565,566],{"class":190},"// NG: ユーザー入力をそのままコマンドに\n",[184,568,569,572,574,576,579,581,584,586,589,591],{"class":186,"line":194},[184,570,571],{"class":275},"exec",[184,573,279],{"class":205},[184,575,227],{"class":209},[184,577,578],{"class":213},"convert ",[184,580,218],{"class":217},[184,582,583],{"class":213},"filename",[184,585,224],{"class":217},[184,587,588],{"class":213}," output.png",[184,590,227],{"class":209},[184,592,540],{"class":205},[184,594,595],{"class":186,"line":233},[184,596,237],{"emptyLinePlaceholder":236},[184,598,599],{"class":186,"line":240},[184,600,601],{"class":190},"// OK: 引数として渡す\n",[184,603,604,607,609,611,614,616,618,620,622,624,626,629,631],{"class":186,"line":246},[184,605,606],{"class":275},"execFile",[184,608,279],{"class":205},[184,610,261],{"class":209},[184,612,613],{"class":213},"convert",[184,615,261],{"class":209},[184,617,284],{"class":205},[184,619,287],{"class":205},[184,621,583],{"class":201},[184,623,284],{"class":205},[184,625,255],{"class":209},[184,627,628],{"class":213},"output.png",[184,630,261],{"class":209},[184,632,292],{"class":205},[17,634,636],{"id":635},"_2-認証認可","2. 認証・認可",[169,638,639],{"id":639},"パスワードの保存",[174,641,643],{"className":176,"code":642,"language":178,"meta":179,"style":179},"// NG: 平文やMD5\nconst password = userInput; // 平文\nconst hash = md5(password); // 弱いハッシュ\n\n// OK: bcrypt や Argon2\nconst bcrypt = require('bcrypt');\nconst hash = await bcrypt.hash(password, 12); // コスト12以上\n",[181,644,645,650,667,690,694,699,722],{"__ignoreMap":179},[184,646,647],{"class":186,"line":187},[184,648,649],{"class":190},"// NG: 平文やMD5\n",[184,651,652,654,657,659,661,664],{"class":186,"line":194},[184,653,198],{"class":197},[184,655,656],{"class":201}," password",[184,658,206],{"class":205},[184,660,324],{"class":201},[184,662,663],{"class":205},";",[184,665,666],{"class":190}," // 平文\n",[184,668,669,671,674,676,679,681,684,687],{"class":186,"line":233},[184,670,198],{"class":197},[184,672,673],{"class":201}," hash",[184,675,206],{"class":205},[184,677,678],{"class":275}," md5",[184,680,279],{"class":205},[184,682,683],{"class":201},"password",[184,685,686],{"class":205},");",[184,688,689],{"class":190}," // 弱いハッシュ\n",[184,691,692],{"class":186,"line":240},[184,693,237],{"emptyLinePlaceholder":236},[184,695,696],{"class":186,"line":246},[184,697,698],{"class":190},"// OK: bcrypt や Argon2\n",[184,700,701,703,706,708,711,713,715,718,720],{"class":186,"line":266},[184,702,198],{"class":197},[184,704,705],{"class":201}," bcrypt",[184,707,206],{"class":205},[184,709,710],{"class":275}," require",[184,712,279],{"class":205},[184,714,261],{"class":209},[184,716,717],{"class":213},"bcrypt",[184,719,261],{"class":209},[184,721,540],{"class":205},[184,723,724,726,728,730,733,735,737,740,742,744,746,750,752],{"class":186,"line":357},[184,725,198],{"class":197},[184,727,673],{"class":201},[184,729,206],{"class":205},[184,731,732],{"class":217}," await",[184,734,705],{"class":201},[184,736,272],{"class":205},[184,738,739],{"class":275},"hash",[184,741,279],{"class":205},[184,743,683],{"class":201},[184,745,284],{"class":205},[184,747,749],{"class":748},"sM54T"," 12",[184,751,686],{"class":205},[184,753,754],{"class":190}," // コスト12以上\n",[169,756,757],{"id":757},"セッション管理",[759,760,761,765,768],"ul",{},[762,763,764],"li",{},"セッションIDは十分にランダムに（暗号学的乱数生成器を使う）",[762,766,767],{},"ログイン後はセッションIDを再生成（セッション固定攻撃対策）",[762,769,770,773,774,773,777,780],{},[181,771,772],{},"HttpOnly",", ",[181,775,776],{},"Secure",[181,778,779],{},"SameSite"," 属性を設定",[174,782,784],{"className":176,"code":783,"language":178,"meta":179,"style":179},"// Express の例\napp.use(session({\n  secret: process.env.SESSION_SECRET,\n  cookie: {\n    httpOnly: true,\n    secure: true, // HTTPS必須\n    sameSite: 'strict',\n    maxAge: 24 * 60 * 60 * 1000 // 1日\n  },\n  resave: false,\n  saveUninitialized: false\n}));\n",[181,785,786,791,809,834,843,855,869,885,913,918,930,940],{"__ignoreMap":179},[184,787,788],{"class":186,"line":187},[184,789,790],{"class":190},"// Express の例\n",[184,792,793,796,798,801,803,806],{"class":186,"line":194},[184,794,795],{"class":201},"app",[184,797,272],{"class":205},[184,799,800],{"class":275},"use",[184,802,279],{"class":205},[184,804,805],{"class":275},"session",[184,807,808],{"class":205},"({\n",[184,810,811,815,818,821,823,826,828,831],{"class":186,"line":233},[184,812,814],{"class":813},"sz8Xr","  secret",[184,816,817],{"class":205},":",[184,819,820],{"class":201}," process",[184,822,272],{"class":205},[184,824,825],{"class":201},"env",[184,827,272],{"class":205},[184,829,830],{"class":201},"SESSION_SECRET",[184,832,833],{"class":205},",\n",[184,835,836,839,841],{"class":186,"line":240},[184,837,838],{"class":813},"  cookie",[184,840,817],{"class":205},[184,842,380],{"class":205},[184,844,845,848,850,853],{"class":186,"line":246},[184,846,847],{"class":813},"    httpOnly",[184,849,817],{"class":205},[184,851,852],{"class":217}," true",[184,854,833],{"class":205},[184,856,857,860,862,864,866],{"class":186,"line":266},[184,858,859],{"class":813},"    secure",[184,861,817],{"class":205},[184,863,852],{"class":217},[184,865,284],{"class":205},[184,867,868],{"class":190}," // HTTPS必須\n",[184,870,871,874,876,878,881,883],{"class":186,"line":357},[184,872,873],{"class":813},"    sameSite",[184,875,817],{"class":205},[184,877,255],{"class":209},[184,879,880],{"class":213},"strict",[184,882,261],{"class":209},[184,884,833],{"class":205},[184,886,887,890,892,895,898,901,903,905,907,910],{"class":186,"line":363},[184,888,889],{"class":813},"    maxAge",[184,891,817],{"class":205},[184,893,894],{"class":748}," 24",[184,896,897],{"class":197}," *",[184,899,900],{"class":748}," 60",[184,902,897],{"class":197},[184,904,900],{"class":748},[184,906,897],{"class":197},[184,908,909],{"class":748}," 1000",[184,911,912],{"class":190}," // 1日\n",[184,914,915],{"class":186,"line":383},[184,916,917],{"class":205},"  },\n",[184,919,920,923,925,928],{"class":186,"line":392},[184,921,922],{"class":813},"  resave",[184,924,817],{"class":205},[184,926,927],{"class":217}," false",[184,929,833],{"class":205},[184,931,932,935,937],{"class":186,"line":427},[184,933,934],{"class":813},"  saveUninitialized",[184,936,817],{"class":205},[184,938,939],{"class":217}," false\n",[184,941,942],{"class":186,"line":456},[184,943,944],{"class":205},"}));\n",[169,946,948],{"id":947},"jwt-を使う場合","JWT を使う場合",[759,950,951,954,960,963],{},[762,952,953],{},"秘密鍵は十分に長く（256bit以上）",[762,955,956,959],{},[181,957,958],{},"alg: none"," 攻撃に注意（ライブラリで明示的にアルゴリズムを指定）",[762,961,962],{},"有効期限を短めに設定",[762,964,965],{},"リフレッシュトークンとアクセストークンを分離",[17,967,969],{"id":968},"_3-https必須","3. HTTPS必須",[13,971,972],{},"2024年以降、HTTPは論外。",[759,974,975,978,981],{},[762,976,977],{},"Let's Encrypt で無料でSSL証明書取得可能",[762,979,980],{},"Cloudflare や Vercel を使えば自動で設定される",[762,982,983],{},"HSTS ヘッダーを設定",[174,985,990],{"className":986,"code":988,"language":989},[987],"language-text","Strict-Transport-Security: max-age=31536000; includeSubDomains\n","text",[181,991,988],{"__ignoreMap":179},[17,993,995],{"id":994},"_4-環境変数と秘密情報の管理","4. 環境変数と秘密情報の管理",[169,997,998],{"id":998},"絶対にやってはいけないこと",[174,1000,1002],{"className":176,"code":1001,"language":178,"meta":179,"style":179},"// NG: コードにハードコード\nconst API_KEY = 'sk-1234567890abcdef';\nconst DB_PASSWORD = 'password123';\n",[181,1003,1004,1009,1027],{"__ignoreMap":179},[184,1005,1006],{"class":186,"line":187},[184,1007,1008],{"class":190},"// NG: コードにハードコード\n",[184,1010,1011,1013,1016,1018,1020,1023,1025],{"class":186,"line":194},[184,1012,198],{"class":197},[184,1014,1015],{"class":201}," API_KEY",[184,1017,206],{"class":205},[184,1019,255],{"class":209},[184,1021,1022],{"class":213},"sk-1234567890abcdef",[184,1024,261],{"class":209},[184,1026,230],{"class":205},[184,1028,1029,1031,1034,1036,1038,1041,1043],{"class":186,"line":233},[184,1030,198],{"class":197},[184,1032,1033],{"class":201}," DB_PASSWORD",[184,1035,206],{"class":205},[184,1037,255],{"class":209},[184,1039,1040],{"class":213},"password123",[184,1042,261],{"class":209},[184,1044,230],{"class":205},[169,1046,1047],{"id":1047},"正しい管理方法",[174,1049,1053],{"className":1050,"code":1051,"language":1052,"meta":179,"style":179},"language-bash shiki shiki-themes vitesse-light vitesse-light","# .env ファイル（.gitignore に追加必須）\nAPI_KEY=sk-1234567890abcdef\nDB_PASSWORD=supersecretpassword\n","bash",[181,1054,1055,1060,1071],{"__ignoreMap":179},[184,1056,1057],{"class":186,"line":187},[184,1058,1059],{"class":190},"# .env ファイル（.gitignore に追加必須）\n",[184,1061,1062,1065,1068],{"class":186,"line":194},[184,1063,1064],{"class":201},"API_KEY",[184,1066,1067],{"class":205},"=",[184,1069,1070],{"class":213},"sk-1234567890abcdef\n",[184,1072,1073,1076,1078],{"class":186,"line":233},[184,1074,1075],{"class":201},"DB_PASSWORD",[184,1077,1067],{"class":205},[184,1079,1080],{"class":213},"supersecretpassword\n",[174,1082,1084],{"className":176,"code":1083,"language":178,"meta":179,"style":179},"// 環境変数から読み込む\nconst apiKey = process.env.API_KEY;\n",[181,1085,1086,1091],{"__ignoreMap":179},[184,1087,1088],{"class":186,"line":187},[184,1089,1090],{"class":190},"// 環境変数から読み込む\n",[184,1092,1093,1095,1098,1100,1102,1104,1106,1108,1110],{"class":186,"line":194},[184,1094,198],{"class":197},[184,1096,1097],{"class":201}," apiKey",[184,1099,206],{"class":205},[184,1101,820],{"class":201},[184,1103,272],{"class":205},[184,1105,825],{"class":201},[184,1107,272],{"class":205},[184,1109,1064],{"class":201},[184,1111,230],{"class":205},[169,1113,1115],{"id":1114},"gitignore-の設定",".gitignore の設定",[174,1117,1121],{"className":1118,"code":1119,"language":1120,"meta":179,"style":179},"language-gitignore shiki shiki-themes vitesse-light vitesse-light",".env\n.env.local\n.env.*.local\n*.pem\n*.key\n","gitignore",[181,1122,1123,1128,1133,1138,1143],{"__ignoreMap":179},[184,1124,1125],{"class":186,"line":187},[184,1126,1127],{},".env\n",[184,1129,1130],{"class":186,"line":194},[184,1131,1132],{},".env.local\n",[184,1134,1135],{"class":186,"line":233},[184,1136,1137],{},".env.*.local\n",[184,1139,1140],{"class":186,"line":240},[184,1141,1142],{},"*.pem\n",[184,1144,1145],{"class":186,"line":246},[184,1146,1147],{},"*.key\n",[13,1149,1150,1153],{},[164,1151,1152],{},"git履歴に入ってしまった場合",": 秘密鍵は即座にローテーション。履歴から消すのは困難なので、漏れた前提で対応。",[17,1155,1157],{"id":1156},"_5-依存関係の管理","5. 依存関係の管理",[169,1159,1160],{"id":1160},"脆弱性スキャン",[174,1162,1164],{"className":1050,"code":1163,"language":1052,"meta":179,"style":179},"# npm\nnpm audit\nnpm audit fix\n\n# yarn\nyarn audit\n\n# Snyk（より詳細）\nnpx snyk test\n",[181,1165,1166,1171,1179,1189,1193,1198,1205,1209,1214],{"__ignoreMap":179},[184,1167,1168],{"class":186,"line":187},[184,1169,1170],{"class":190},"# npm\n",[184,1172,1173,1176],{"class":186,"line":194},[184,1174,1175],{"class":275},"npm",[184,1177,1178],{"class":213}," audit\n",[184,1180,1181,1183,1186],{"class":186,"line":233},[184,1182,1175],{"class":275},[184,1184,1185],{"class":213}," audit",[184,1187,1188],{"class":213}," fix\n",[184,1190,1191],{"class":186,"line":240},[184,1192,237],{"emptyLinePlaceholder":236},[184,1194,1195],{"class":186,"line":246},[184,1196,1197],{"class":190},"# yarn\n",[184,1199,1200,1203],{"class":186,"line":266},[184,1201,1202],{"class":275},"yarn",[184,1204,1178],{"class":213},[184,1206,1207],{"class":186,"line":357},[184,1208,237],{"emptyLinePlaceholder":236},[184,1210,1211],{"class":186,"line":363},[184,1212,1213],{"class":190},"# Snyk（より詳細）\n",[184,1215,1216,1219,1222],{"class":186,"line":383},[184,1217,1218],{"class":275},"npx",[184,1220,1221],{"class":213}," snyk",[184,1223,1224],{"class":213}," test\n",[169,1226,1227],{"id":1227},"定期的なアップデート",[174,1229,1231],{"className":1050,"code":1230,"language":1052,"meta":179,"style":179},"# 更新可能なパッケージを確認\nnpm outdated\n\n# Dependabot や Renovate を設定して自動PR\n",[181,1232,1233,1238,1245,1249],{"__ignoreMap":179},[184,1234,1235],{"class":186,"line":187},[184,1236,1237],{"class":190},"# 更新可能なパッケージを確認\n",[184,1239,1240,1242],{"class":186,"line":194},[184,1241,1175],{"class":275},[184,1243,1244],{"class":213}," outdated\n",[184,1246,1247],{"class":186,"line":233},[184,1248,237],{"emptyLinePlaceholder":236},[184,1250,1251],{"class":186,"line":240},[184,1252,1253],{"class":190},"# Dependabot や Renovate を設定して自動PR\n",[169,1255,1256],{"id":1256},"lockfileをコミット",[13,1258,1259,1262,1263,1266],{},[181,1260,1261],{},"package-lock.json"," や ",[181,1264,1265],{},"yarn.lock"," は必ずコミット。依存関係のバージョンを固定することで、サプライチェーン攻撃のリスクを軽減。",[17,1268,1270],{"id":1269},"_6-エラーハンドリング","6. エラーハンドリング",[169,1272,1273],{"id":1273},"本番環境でスタックトレースを見せない",[174,1275,1277],{"className":176,"code":1276,"language":178,"meta":179,"style":179},"// NG: エラー詳細をそのまま返す\napp.use((err, req, res, next) => {\n  res.status(500).json({ error: err.stack });\n});\n\n// OK: 本番では汎用メッセージ\napp.use((err, req, res, next) => {\n  console.error(err); // ログには記録\n  res.status(500).json({\n    error: process.env.NODE_ENV === 'production'\n      ? 'Internal Server Error'\n      : err.message\n  });\n});\n",[181,1278,1279,1284,1320,1360,1365,1369,1374,1404,1423,1441,1470,1482,1494,1499],{"__ignoreMap":179},[184,1280,1281],{"class":186,"line":187},[184,1282,1283],{"class":190},"// NG: エラー詳細をそのまま返す\n",[184,1285,1286,1288,1290,1292,1295,1298,1300,1303,1305,1308,1310,1313,1315,1318],{"class":186,"line":194},[184,1287,795],{"class":201},[184,1289,272],{"class":205},[184,1291,800],{"class":275},[184,1293,1294],{"class":205},"((",[184,1296,1297],{"class":201},"err",[184,1299,284],{"class":205},[184,1301,1302],{"class":201}," req",[184,1304,284],{"class":205},[184,1306,1307],{"class":201}," res",[184,1309,284],{"class":205},[184,1311,1312],{"class":201}," next",[184,1314,377],{"class":205},[184,1316,1317],{"class":205}," =>",[184,1319,380],{"class":205},[184,1321,1322,1325,1327,1330,1332,1335,1338,1341,1344,1347,1349,1352,1354,1357],{"class":186,"line":233},[184,1323,1324],{"class":201},"  res",[184,1326,272],{"class":205},[184,1328,1329],{"class":275},"status",[184,1331,279],{"class":205},[184,1333,1334],{"class":748},"500",[184,1336,1337],{"class":205},").",[184,1339,1340],{"class":275},"json",[184,1342,1343],{"class":205},"({",[184,1345,1346],{"class":813}," error",[184,1348,817],{"class":205},[184,1350,1351],{"class":201}," err",[184,1353,272],{"class":205},[184,1355,1356],{"class":201},"stack",[184,1358,1359],{"class":205}," });\n",[184,1361,1362],{"class":186,"line":240},[184,1363,1364],{"class":205},"});\n",[184,1366,1367],{"class":186,"line":246},[184,1368,237],{"emptyLinePlaceholder":236},[184,1370,1371],{"class":186,"line":266},[184,1372,1373],{"class":190},"// OK: 本番では汎用メッセージ\n",[184,1375,1376,1378,1380,1382,1384,1386,1388,1390,1392,1394,1396,1398,1400,1402],{"class":186,"line":357},[184,1377,795],{"class":201},[184,1379,272],{"class":205},[184,1381,800],{"class":275},[184,1383,1294],{"class":205},[184,1385,1297],{"class":201},[184,1387,284],{"class":205},[184,1389,1302],{"class":201},[184,1391,284],{"class":205},[184,1393,1307],{"class":201},[184,1395,284],{"class":205},[184,1397,1312],{"class":201},[184,1399,377],{"class":205},[184,1401,1317],{"class":205},[184,1403,380],{"class":205},[184,1405,1406,1409,1411,1414,1416,1418,1420],{"class":186,"line":363},[184,1407,1408],{"class":201},"  console",[184,1410,272],{"class":205},[184,1412,1413],{"class":275},"error",[184,1415,279],{"class":205},[184,1417,1297],{"class":201},[184,1419,686],{"class":205},[184,1421,1422],{"class":190}," // ログには記録\n",[184,1424,1425,1427,1429,1431,1433,1435,1437,1439],{"class":186,"line":383},[184,1426,1324],{"class":201},[184,1428,272],{"class":205},[184,1430,1329],{"class":275},[184,1432,279],{"class":205},[184,1434,1334],{"class":748},[184,1436,1337],{"class":205},[184,1438,1340],{"class":275},[184,1440,808],{"class":205},[184,1442,1443,1446,1448,1450,1452,1454,1456,1459,1462,1464,1467],{"class":186,"line":392},[184,1444,1445],{"class":813},"    error",[184,1447,817],{"class":205},[184,1449,820],{"class":201},[184,1451,272],{"class":205},[184,1453,825],{"class":201},[184,1455,272],{"class":205},[184,1457,1458],{"class":201},"NODE_ENV",[184,1460,1461],{"class":197}," ===",[184,1463,255],{"class":209},[184,1465,1466],{"class":213},"production",[184,1468,1469],{"class":209},"'\n",[184,1471,1472,1475,1477,1480],{"class":186,"line":427},[184,1473,1474],{"class":197},"      ?",[184,1476,255],{"class":209},[184,1478,1479],{"class":213},"Internal Server Error",[184,1481,1469],{"class":209},[184,1483,1484,1487,1489,1491],{"class":186,"line":456},[184,1485,1486],{"class":197},"      :",[184,1488,1351],{"class":201},[184,1490,272],{"class":205},[184,1492,1493],{"class":201},"message\n",[184,1495,1496],{"class":186,"line":485},[184,1497,1498],{"class":205},"  });\n",[184,1500,1501],{"class":186,"line":514},[184,1502,1364],{"class":205},[17,1504,1506],{"id":1505},"_7-レート制限","7. レート制限",[13,1508,1509],{},"ブルートフォース攻撃やDoSへの対策である。",[174,1511,1513],{"className":176,"code":1512,"language":178,"meta":179,"style":179},"const rateLimit = require('express-rate-limit');\n\nconst limiter = rateLimit({\n  windowMs: 15 * 60 * 1000, // 15分\n  max: 100, // 100リクエストまで\n  message: 'Too many requests'\n});\n\napp.use('/api/', limiter);\n\n// ログイン試行はより厳しく\nconst loginLimiter = rateLimit({\n  windowMs: 60 * 60 * 1000, // 1時間\n  max: 5, // 5回まで\n});\napp.use('/api/login', loginLimiter);\n",[181,1514,1515,1537,1541,1554,1577,1592,1606,1610,1614,1637,1641,1646,1659,1680,1694,1698],{"__ignoreMap":179},[184,1516,1517,1519,1522,1524,1526,1528,1530,1533,1535],{"class":186,"line":187},[184,1518,198],{"class":197},[184,1520,1521],{"class":201}," rateLimit",[184,1523,206],{"class":205},[184,1525,710],{"class":275},[184,1527,279],{"class":205},[184,1529,261],{"class":209},[184,1531,1532],{"class":213},"express-rate-limit",[184,1534,261],{"class":209},[184,1536,540],{"class":205},[184,1538,1539],{"class":186,"line":194},[184,1540,237],{"emptyLinePlaceholder":236},[184,1542,1543,1545,1548,1550,1552],{"class":186,"line":233},[184,1544,198],{"class":197},[184,1546,1547],{"class":201}," limiter",[184,1549,206],{"class":205},[184,1551,1521],{"class":275},[184,1553,808],{"class":205},[184,1555,1556,1559,1561,1564,1566,1568,1570,1572,1574],{"class":186,"line":240},[184,1557,1558],{"class":813},"  windowMs",[184,1560,817],{"class":205},[184,1562,1563],{"class":748}," 15",[184,1565,897],{"class":197},[184,1567,900],{"class":748},[184,1569,897],{"class":197},[184,1571,909],{"class":748},[184,1573,284],{"class":205},[184,1575,1576],{"class":190}," // 15分\n",[184,1578,1579,1582,1584,1587,1589],{"class":186,"line":246},[184,1580,1581],{"class":813},"  max",[184,1583,817],{"class":205},[184,1585,1586],{"class":748}," 100",[184,1588,284],{"class":205},[184,1590,1591],{"class":190}," // 100リクエストまで\n",[184,1593,1594,1597,1599,1601,1604],{"class":186,"line":266},[184,1595,1596],{"class":813},"  message",[184,1598,817],{"class":205},[184,1600,255],{"class":209},[184,1602,1603],{"class":213},"Too many requests",[184,1605,1469],{"class":209},[184,1607,1608],{"class":186,"line":357},[184,1609,1364],{"class":205},[184,1611,1612],{"class":186,"line":363},[184,1613,237],{"emptyLinePlaceholder":236},[184,1615,1616,1618,1620,1622,1624,1626,1629,1631,1633,1635],{"class":186,"line":383},[184,1617,795],{"class":201},[184,1619,272],{"class":205},[184,1621,800],{"class":275},[184,1623,279],{"class":205},[184,1625,261],{"class":209},[184,1627,1628],{"class":213},"/api/",[184,1630,261],{"class":209},[184,1632,284],{"class":205},[184,1634,1547],{"class":201},[184,1636,540],{"class":205},[184,1638,1639],{"class":186,"line":392},[184,1640,237],{"emptyLinePlaceholder":236},[184,1642,1643],{"class":186,"line":427},[184,1644,1645],{"class":190},"// ログイン試行はより厳しく\n",[184,1647,1648,1650,1653,1655,1657],{"class":186,"line":456},[184,1649,198],{"class":197},[184,1651,1652],{"class":201}," loginLimiter",[184,1654,206],{"class":205},[184,1656,1521],{"class":275},[184,1658,808],{"class":205},[184,1660,1661,1663,1665,1667,1669,1671,1673,1675,1677],{"class":186,"line":485},[184,1662,1558],{"class":813},[184,1664,817],{"class":205},[184,1666,900],{"class":748},[184,1668,897],{"class":197},[184,1670,900],{"class":748},[184,1672,897],{"class":197},[184,1674,909],{"class":748},[184,1676,284],{"class":205},[184,1678,1679],{"class":190}," // 1時間\n",[184,1681,1682,1684,1686,1689,1691],{"class":186,"line":514},[184,1683,1581],{"class":813},[184,1685,817],{"class":205},[184,1687,1688],{"class":748}," 5",[184,1690,284],{"class":205},[184,1692,1693],{"class":190}," // 5回まで\n",[184,1695,1696],{"class":186,"line":543},[184,1697,1364],{"class":205},[184,1699,1701,1703,1705,1707,1709,1711,1714,1716,1718,1720],{"class":186,"line":1700},16,[184,1702,795],{"class":201},[184,1704,272],{"class":205},[184,1706,800],{"class":275},[184,1708,279],{"class":205},[184,1710,261],{"class":209},[184,1712,1713],{"class":213},"/api/login",[184,1715,261],{"class":209},[184,1717,284],{"class":205},[184,1719,1652],{"class":201},[184,1721,540],{"class":205},[17,1723,1725],{"id":1724},"_8-cors設定","8. CORS設定",[174,1727,1729],{"className":176,"code":1728,"language":178,"meta":179,"style":179},"// NG: 全許可\napp.use(cors());\n\n// OK: 必要なオリジンのみ\napp.use(cors({\n  origin: ['https://myapp.com', 'https://www.myapp.com'],\n  methods: ['GET', 'POST', 'PUT', 'DELETE'],\n  credentials: true\n}));\n",[181,1730,1731,1736,1752,1756,1761,1775,1803,1848,1858],{"__ignoreMap":179},[184,1732,1733],{"class":186,"line":187},[184,1734,1735],{"class":190},"// NG: 全許可\n",[184,1737,1738,1740,1742,1744,1746,1749],{"class":186,"line":194},[184,1739,795],{"class":201},[184,1741,272],{"class":205},[184,1743,800],{"class":275},[184,1745,279],{"class":205},[184,1747,1748],{"class":275},"cors",[184,1750,1751],{"class":205},"());\n",[184,1753,1754],{"class":186,"line":233},[184,1755,237],{"emptyLinePlaceholder":236},[184,1757,1758],{"class":186,"line":240},[184,1759,1760],{"class":190},"// OK: 必要なオリジンのみ\n",[184,1762,1763,1765,1767,1769,1771,1773],{"class":186,"line":246},[184,1764,795],{"class":201},[184,1766,272],{"class":205},[184,1768,800],{"class":275},[184,1770,279],{"class":205},[184,1772,1748],{"class":275},[184,1774,808],{"class":205},[184,1776,1777,1780,1782,1784,1786,1789,1791,1793,1795,1798,1800],{"class":186,"line":266},[184,1778,1779],{"class":813},"  origin",[184,1781,817],{"class":205},[184,1783,287],{"class":205},[184,1785,261],{"class":209},[184,1787,1788],{"class":213},"https://myapp.com",[184,1790,261],{"class":209},[184,1792,284],{"class":205},[184,1794,255],{"class":209},[184,1796,1797],{"class":213},"https://www.myapp.com",[184,1799,261],{"class":209},[184,1801,1802],{"class":205},"],\n",[184,1804,1805,1808,1810,1812,1814,1817,1819,1821,1823,1826,1828,1830,1832,1835,1837,1839,1841,1844,1846],{"class":186,"line":357},[184,1806,1807],{"class":813},"  methods",[184,1809,817],{"class":205},[184,1811,287],{"class":205},[184,1813,261],{"class":209},[184,1815,1816],{"class":213},"GET",[184,1818,261],{"class":209},[184,1820,284],{"class":205},[184,1822,255],{"class":209},[184,1824,1825],{"class":213},"POST",[184,1827,261],{"class":209},[184,1829,284],{"class":205},[184,1831,255],{"class":209},[184,1833,1834],{"class":213},"PUT",[184,1836,261],{"class":209},[184,1838,284],{"class":205},[184,1840,255],{"class":209},[184,1842,1843],{"class":213},"DELETE",[184,1845,261],{"class":209},[184,1847,1802],{"class":205},[184,1849,1850,1853,1855],{"class":186,"line":363},[184,1851,1852],{"class":813},"  credentials",[184,1854,817],{"class":205},[184,1856,1857],{"class":217}," true\n",[184,1859,1860],{"class":186,"line":383},[184,1861,944],{"class":205},[17,1863,1865],{"id":1864},"_9-セキュリティヘッダー","9. セキュリティヘッダー",[13,1867,1868],{},"helmetミドルウェアで一括設定する。",[174,1870,1872],{"className":176,"code":1871,"language":178,"meta":179,"style":179},"const helmet = require('helmet');\napp.use(helmet());\n",[181,1873,1874,1896],{"__ignoreMap":179},[184,1875,1876,1878,1881,1883,1885,1887,1889,1892,1894],{"class":186,"line":187},[184,1877,198],{"class":197},[184,1879,1880],{"class":201}," helmet",[184,1882,206],{"class":205},[184,1884,710],{"class":275},[184,1886,279],{"class":205},[184,1888,261],{"class":209},[184,1890,1891],{"class":213},"helmet",[184,1893,261],{"class":209},[184,1895,540],{"class":205},[184,1897,1898,1900,1902,1904,1906,1908],{"class":186,"line":194},[184,1899,795],{"class":201},[184,1901,272],{"class":205},[184,1903,800],{"class":275},[184,1905,279],{"class":205},[184,1907,1891],{"class":275},[184,1909,1751],{"class":205},[13,1911,1912],{},"設定されるヘッダー:",[759,1914,1915,1920,1925,1930],{},[762,1916,1917],{},[181,1918,1919],{},"X-Content-Type-Options: nosniff",[762,1921,1922],{},[181,1923,1924],{},"X-Frame-Options: DENY",[762,1926,1927],{},[181,1928,1929],{},"Content-Security-Policy",[762,1931,1932,1935],{},[181,1933,1934],{},"X-XSS-Protection","（レガシーブラウザ向け）",[169,1937,1939],{"id":1938},"cspcontent-security-policyの例","CSP（Content Security Policy）の例",[174,1941,1943],{"className":176,"code":1942,"language":178,"meta":179,"style":179},"app.use(helmet.contentSecurityPolicy({\n  directives: {\n    defaultSrc: [\"'self'\"],\n    scriptSrc: [\"'self'\", \"https://cdn.example.com\"],\n    styleSrc: [\"'self'\", \"'unsafe-inline'\"],\n    imgSrc: [\"'self'\", \"data:\", \"https:\"],\n  }\n}));\n",[181,1944,1945,1964,1973,1991,2018,2044,2079,2084],{"__ignoreMap":179},[184,1946,1947,1949,1951,1953,1955,1957,1959,1962],{"class":186,"line":187},[184,1948,795],{"class":201},[184,1950,272],{"class":205},[184,1952,800],{"class":275},[184,1954,279],{"class":205},[184,1956,1891],{"class":201},[184,1958,272],{"class":205},[184,1960,1961],{"class":275},"contentSecurityPolicy",[184,1963,808],{"class":205},[184,1965,1966,1969,1971],{"class":186,"line":194},[184,1967,1968],{"class":813},"  directives",[184,1970,817],{"class":205},[184,1972,380],{"class":205},[184,1974,1975,1978,1980,1982,1984,1987,1989],{"class":186,"line":233},[184,1976,1977],{"class":813},"    defaultSrc",[184,1979,817],{"class":205},[184,1981,287],{"class":205},[184,1983,496],{"class":209},[184,1985,1986],{"class":213},"'self'",[184,1988,496],{"class":209},[184,1990,1802],{"class":205},[184,1992,1993,1996,1998,2000,2002,2004,2006,2008,2011,2014,2016],{"class":186,"line":240},[184,1994,1995],{"class":813},"    scriptSrc",[184,1997,817],{"class":205},[184,1999,287],{"class":205},[184,2001,496],{"class":209},[184,2003,1986],{"class":213},[184,2005,496],{"class":209},[184,2007,284],{"class":205},[184,2009,2010],{"class":209}," \"",[184,2012,2013],{"class":213},"https://cdn.example.com",[184,2015,496],{"class":209},[184,2017,1802],{"class":205},[184,2019,2020,2023,2025,2027,2029,2031,2033,2035,2037,2040,2042],{"class":186,"line":246},[184,2021,2022],{"class":813},"    styleSrc",[184,2024,817],{"class":205},[184,2026,287],{"class":205},[184,2028,496],{"class":209},[184,2030,1986],{"class":213},[184,2032,496],{"class":209},[184,2034,284],{"class":205},[184,2036,2010],{"class":209},[184,2038,2039],{"class":213},"'unsafe-inline'",[184,2041,496],{"class":209},[184,2043,1802],{"class":205},[184,2045,2046,2049,2051,2053,2055,2057,2059,2061,2063,2066,2068,2070,2072,2075,2077],{"class":186,"line":266},[184,2047,2048],{"class":813},"    imgSrc",[184,2050,817],{"class":205},[184,2052,287],{"class":205},[184,2054,496],{"class":209},[184,2056,1986],{"class":213},[184,2058,496],{"class":209},[184,2060,284],{"class":205},[184,2062,2010],{"class":209},[184,2064,2065],{"class":213},"data:",[184,2067,496],{"class":209},[184,2069,284],{"class":205},[184,2071,2010],{"class":209},[184,2073,2074],{"class":213},"https:",[184,2076,496],{"class":209},[184,2078,1802],{"class":205},[184,2080,2081],{"class":186,"line":357},[184,2082,2083],{"class":205},"  }\n",[184,2085,2086],{"class":186,"line":363},[184,2087,944],{"class":205},[17,2089,2091],{"id":2090},"_10-ログと監視","10. ログと監視",[169,2093,2094],{"id":2094},"記録すべき情報",[759,2096,2097,2100,2103,2106],{},[762,2098,2099],{},"認証の成功/失敗",[762,2101,2102],{},"認可エラー",[762,2104,2105],{},"入力バリデーションエラー",[762,2107,2108],{},"例外・エラー",[169,2110,2111],{"id":2111},"記録してはいけない情報",[759,2113,2114,2117,2120],{},[762,2115,2116],{},"パスワード",[762,2118,2119],{},"クレジットカード番号",[762,2121,2122],{},"個人情報（必要最小限に）",[174,2124,2126],{"className":176,"code":2125,"language":178,"meta":179,"style":179},"// NG\nconsole.log(`Login attempt: user=${email}, password=${password}`);\n\n// OK\nconsole.log(`Login attempt: user=${email}, success=${success}, ip=${req.ip}`);\n",[181,2127,2128,2133,2170,2174,2179],{"__ignoreMap":179},[184,2129,2130],{"class":186,"line":187},[184,2131,2132],{"class":190},"// NG\n",[184,2134,2135,2138,2140,2143,2145,2147,2150,2152,2155,2157,2160,2162,2164,2166,2168],{"class":186,"line":194},[184,2136,2137],{"class":201},"console",[184,2139,272],{"class":205},[184,2141,2142],{"class":275},"log",[184,2144,279],{"class":205},[184,2146,227],{"class":209},[184,2148,2149],{"class":213},"Login attempt: user=",[184,2151,218],{"class":217},[184,2153,2154],{"class":213},"email",[184,2156,224],{"class":217},[184,2158,2159],{"class":213},", password=",[184,2161,218],{"class":217},[184,2163,683],{"class":213},[184,2165,224],{"class":217},[184,2167,227],{"class":209},[184,2169,540],{"class":205},[184,2171,2172],{"class":186,"line":233},[184,2173,237],{"emptyLinePlaceholder":236},[184,2175,2176],{"class":186,"line":240},[184,2177,2178],{"class":190},"// OK\n",[184,2180,2181,2183,2185,2187,2189,2191,2193,2195,2197,2199,2202,2204,2207,2209,2212,2214,2217,2219,2222,2224,2226],{"class":186,"line":246},[184,2182,2137],{"class":201},[184,2184,272],{"class":205},[184,2186,2142],{"class":275},[184,2188,279],{"class":205},[184,2190,227],{"class":209},[184,2192,2149],{"class":213},[184,2194,218],{"class":217},[184,2196,2154],{"class":213},[184,2198,224],{"class":217},[184,2200,2201],{"class":213},", success=",[184,2203,218],{"class":217},[184,2205,2206],{"class":213},"success",[184,2208,224],{"class":217},[184,2210,2211],{"class":213},", ip=",[184,2213,218],{"class":217},[184,2215,2216],{"class":213},"req",[184,2218,272],{"class":205},[184,2220,2221],{"class":213},"ip",[184,2223,224],{"class":217},[184,2225,227],{"class":209},[184,2227,540],{"class":205},[17,2229,2230],{"id":2230},"チェックリスト",[13,2232,2233],{},"個人開発でも最低限これだけは確認：",[759,2235,2238,2247,2253,2259,2265,2271,2277,2283,2289,2295],{"className":2236},[2237],"contains-task-list",[762,2239,2242,2246],{"className":2240},[2241],"task-list-item",[2243,2244],"input",{"disabled":236,"type":2245},"checkbox"," HTTPS を使っている",[762,2248,2250,2252],{"className":2249},[2241],[2243,2251],{"disabled":236,"type":2245}," パスワードは bcrypt/Argon2 でハッシュ化",[762,2254,2256,2258],{"className":2255},[2241],[2243,2257],{"disabled":236,"type":2245}," SQLはプリペアドステートメント",[762,2260,2262,2264],{"className":2261},[2241],[2243,2263],{"disabled":236,"type":2245}," ユーザー入力はエスケープ/サニタイズ",[762,2266,2268,2270],{"className":2267},[2241],[2243,2269],{"disabled":236,"type":2245}," .env は .gitignore に入っている",[762,2272,2274,2276],{"className":2273},[2241],[2243,2275],{"disabled":236,"type":2245}," 本番でエラー詳細を露出していない",[762,2278,2280,2282],{"className":2279},[2241],[2243,2281],{"disabled":236,"type":2245}," npm audit でクリティカルな脆弱性がない",[762,2284,2286,2288],{"className":2285},[2241],[2243,2287],{"disabled":236,"type":2245}," Cookie に HttpOnly, Secure, SameSite 設定",[762,2290,2292,2294],{"className":2291},[2241],[2243,2293],{"disabled":236,"type":2245}," CORS は必要なオリジンのみ許可",[762,2296,2298,2300],{"className":2297},[2241],[2243,2299],{"disabled":236,"type":2245}," レート制限を設定している",[17,2302,2303],{"id":2303},"面接での回答ポイント",[13,2305,2306],{},[164,2307,2308],{},"「セキュリティで意識していることは？」",[2310,2311,2312,2315,2318,2321],"ol",{},[762,2313,2314],{},"OWASP Top 10 を把握し、特にインジェクション系を意識",[762,2316,2317],{},"入力値は全て信用しない原則",[762,2319,2320],{},"秘密情報は環境変数で管理、コードにハードコードしない",[762,2322,2323],{},"依存関係の脆弱性を定期的にチェック",[13,2325,2326],{},[164,2327,2328],{},"「具体的にどんな対策をした？」",[759,2330,2331,2334,2337,2340],{},[762,2332,2333],{},"bcrypt でパスワードハッシュ化（コスト12）",[762,2335,2336],{},"helmet でセキュリティヘッダー一括設定",[762,2338,2339],{},"express-rate-limit でブルートフォース対策",[762,2341,2342],{},"npm audit を CI に組み込んで自動チェック",[13,2344,2345],{},[164,2346,2347],{},"「万が一漏洩したらどうする？」",[2310,2349,2350,2353,2356,2359],{},[762,2351,2352],{},"影響範囲の特定",[762,2354,2355],{},"漏洩した認証情報のローテーション",[762,2357,2358],{},"ユーザーへの通知（必要に応じて）",[762,2360,2361],{},"原因調査と再発防止策の実施",[2363,2364,2365],"style",{},"html pre.shiki code .sxvE3, html code.shiki .sxvE3{--shiki-default:#A0ADA0;--shiki-dark:#A0ADA0}html pre.shiki code .stQ0i, html code.shiki .stQ0i{--shiki-default:#AB5959;--shiki-dark:#AB5959}html pre.shiki code .s4oTP, html code.shiki .s4oTP{--shiki-default:#B07D48;--shiki-dark:#B07D48}html pre.shiki code .shFtX, html code.shiki .shFtX{--shiki-default:#999999;--shiki-dark:#999999}html pre.shiki code .sMJiu, html code.shiki .sMJiu{--shiki-default:#B5695977;--shiki-dark:#B5695977}html pre.shiki code .sdGka, html code.shiki .sdGka{--shiki-default:#B56959;--shiki-dark:#B56959}html pre.shiki code .sHkkW, html code.shiki .sHkkW{--shiki-default:#1E754F;--shiki-dark:#1E754F}html pre.shiki code .senZ8, html code.shiki .senZ8{--shiki-default:#59873A;--shiki-dark:#59873A}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sLkVR, html code.shiki .sLkVR{--shiki-default:#AB5E3F;--shiki-dark:#AB5E3F}html pre.shiki code .sM54T, html code.shiki .sM54T{--shiki-default:#2F798A;--shiki-dark:#2F798A}html pre.shiki code .sz8Xr, html code.shiki .sz8Xr{--shiki-default:#998418;--shiki-dark:#998418}",{"title":179,"searchDepth":194,"depth":194,"links":2367},[2368,2369,2374,2379,2380,2385,2390,2393,2394,2395,2398,2402,2403],{"id":19,"depth":194,"text":20},{"id":159,"depth":194,"text":160,"children":2370},[2371,2372,2373],{"id":171,"depth":233,"text":172},{"id":298,"depth":233,"text":299},{"id":556,"depth":233,"text":556},{"id":635,"depth":194,"text":636,"children":2375},[2376,2377,2378],{"id":639,"depth":233,"text":639},{"id":757,"depth":233,"text":757},{"id":947,"depth":233,"text":948},{"id":968,"depth":194,"text":969},{"id":994,"depth":194,"text":995,"children":2381},[2382,2383,2384],{"id":998,"depth":233,"text":998},{"id":1047,"depth":233,"text":1047},{"id":1114,"depth":233,"text":1115},{"id":1156,"depth":194,"text":1157,"children":2386},[2387,2388,2389],{"id":1160,"depth":233,"text":1160},{"id":1227,"depth":233,"text":1227},{"id":1256,"depth":233,"text":1256},{"id":1269,"depth":194,"text":1270,"children":2391},[2392],{"id":1273,"depth":233,"text":1273},{"id":1505,"depth":194,"text":1506},{"id":1724,"depth":194,"text":1725},{"id":1864,"depth":194,"text":1865,"children":2396},[2397],{"id":1938,"depth":233,"text":1939},{"id":2090,"depth":194,"text":2091,"children":2399},[2400,2401],{"id":2094,"depth":233,"text":2094},{"id":2111,"depth":233,"text":2111},{"id":2230,"depth":194,"text":2230},{"id":2303,"depth":194,"text":2303},null,"個人開発者が最低限押さえるべきセキュリティ対策を解説","md",{},"/security-for-indie-dev",false,"2025-12-19T00:00:00.000Z",{"title":5,"description":2405},"2025-12/2025-12-19/security-for-indie-dev",[2414,2415,2416],"security","web","best-practices","DZaO-SIFztdan7soz4LYCssC4spwFSTxlktuQxWtxG0",[],[2420,2424,2428,2431,2435],{"title":2421,"path":2422,"publishedAt":2423},"CT Logとbot対策：AI検索で参照されたいならBot Fight Modeを入れてはいけない","/ct-log-bot-and-ai-search","2026-05-06T00:00:00.000Z",{"title":2425,"path":2426,"publishedAt":2427},"GitHub 158リポを棚卸し、Dependabot・Push Protection・Secret Scanningの現状把握から明日のTODOへ","/github-security-audit-prep","2026-05-02T00:00:00.000Z",{"title":2429,"path":2430,"publishedAt":2427},"マネーフォワードGitHub流出を受けて、ひとり開発の秘密管理を棚卸しした","/moneyforward-leak-lessons",{"title":2432,"path":2433,"publishedAt":2434},"npm axiosサプライチェーン攻撃への対応 - 全プロジェクトスキャンと依存パッケージ棚卸し","/axios-supply-chain-attack-response","2026-03-31T00:00:00.000Z",{"title":2436,"path":2437,"publishedAt":2438},"Electronアプリのセキュリティ分析 ― APIキー窃取の攻撃パターンとChrome拡張の脅威モデル","/electron-app-security-analysis","2026-03-19T00:00:00.000Z","https://log.eurekapu.com/og/blog/security-for-indie-dev.png?v=2025-12-19T00%3A00%3A00.000Z&title=%E5%80%8B%E4%BA%BA%E9%96%8B%E7%99%BA%E3%81%A7%E6%84%8F%E8%AD%98%E3%81%97%E3%81%9F%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3%E5%AF%BE%E7%AD%96&author=Kei%20Komatsu&sig=73a0ff477669e727",1785654977086]