[{"data":1,"prerenderedAt":2347},["ShallowReactive",2],{"\u002Fchecklist\u002Fflask-security-checklist":3},{"id":4,"title":5,"body":6,"description":2337,"extension":2338,"meta":2339,"navigation":80,"path":2343,"seo":2344,"stem":2345,"__hash__":2346},"content\u002Fchecklist\u002Fflask-security-checklist.md","Flask Security Checklist",{"type":7,"value":8,"toc":2318},"minimark",[9,13,17,22,25,290,293,297,300,307,311,1579,1583,1649,1653,1656,1661,1705,1708,1723,1727,1771,1773,1797,1801,1843,1845,1859,1863,1884,1886,1916,1920,1940,1942,1953,1957,2024,2026,2037,2041,2078,2080,2091,2095,2217,2221,2237,2241,2252,2263,2271,2296,2304,2308,2311,2314],[10,11,5],"h1",{"id":12},"flask-security-checklist",[14,15,16],"p",{},"If you're preparing a Flask app for production and need to reduce security risk, this guide gives you a practical hardening checklist. Use it to lock down configuration, transport security, process permissions, and common deployment weak points before launch or during an audit.",[18,19,21],"h2",{"id":20},"quick-fix-quick-setup","Quick Fix \u002F Quick Setup",[14,23,24],{},"Apply the highest-impact controls first:",[26,27,32],"pre",{"className":28,"code":29,"language":30,"meta":31,"style":31},"language-bash shiki shiki-themes github-light github-dark","# 1) Ensure Flask is not running in debug mode\nexport FLASK_ENV=production\nexport DEBUG=0\n\n# 2) Generate a strong secret key\npython - \u003C\u003C'PY'\nimport secrets\nprint(secrets.token_hex(32))\nPY\n\n# 3) Restrict Gunicorn\u002Fsystemd process user\nsudo systemctl edit gunicorn\n# Add or verify:\n# [Service]\n# User=www-data\n# Group=www-data\n# NoNewPrivileges=true\n# PrivateTmp=true\n\n# 4) Enable firewall for web traffic only\nsudo ufw allow OpenSSH\nsudo ufw allow 'Nginx Full'\nsudo ufw enable\n\n# 5) Check HTTPS and security headers\ncurl -I https:\u002F\u002Fyour-domain.com\n\n# 6) Validate Nginx config before reload\nsudo nginx -t && sudo systemctl reload nginx\n","bash","",[33,34,35,44,61,75,82,88,105,111,117,123,128,134,149,155,161,167,173,179,185,190,196,210,222,232,237,243,255,260,266],"code",{"__ignoreMap":31},[36,37,40],"span",{"class":38,"line":39},"line",1,[36,41,43],{"class":42},"sJ8bj","# 1) Ensure Flask is not running in debug mode\n",[36,45,47,51,55,58],{"class":38,"line":46},2,[36,48,50],{"class":49},"szBVR","export",[36,52,54],{"class":53},"sVt8B"," FLASK_ENV",[36,56,57],{"class":49},"=",[36,59,60],{"class":53},"production\n",[36,62,64,66,69,71],{"class":38,"line":63},3,[36,65,50],{"class":49},[36,67,68],{"class":53}," DEBUG",[36,70,57],{"class":49},[36,72,74],{"class":73},"sj4cs","0\n",[36,76,78],{"class":38,"line":77},4,[36,79,81],{"emptyLinePlaceholder":80},true,"\n",[36,83,85],{"class":38,"line":84},5,[36,86,87],{"class":42},"# 2) Generate a strong secret key\n",[36,89,91,95,99,102],{"class":38,"line":90},6,[36,92,94],{"class":93},"sScJk","python",[36,96,98],{"class":97},"sZZnC"," -",[36,100,101],{"class":49}," \u003C\u003C",[36,103,104],{"class":97},"'PY'\n",[36,106,108],{"class":38,"line":107},7,[36,109,110],{"class":97},"import secrets\n",[36,112,114],{"class":38,"line":113},8,[36,115,116],{"class":97},"print(secrets.token_hex(32))\n",[36,118,120],{"class":38,"line":119},9,[36,121,122],{"class":97},"PY\n",[36,124,126],{"class":38,"line":125},10,[36,127,81],{"emptyLinePlaceholder":80},[36,129,131],{"class":38,"line":130},11,[36,132,133],{"class":42},"# 3) Restrict Gunicorn\u002Fsystemd process user\n",[36,135,137,140,143,146],{"class":38,"line":136},12,[36,138,139],{"class":93},"sudo",[36,141,142],{"class":97}," systemctl",[36,144,145],{"class":97}," edit",[36,147,148],{"class":97}," gunicorn\n",[36,150,152],{"class":38,"line":151},13,[36,153,154],{"class":42},"# Add or verify:\n",[36,156,158],{"class":38,"line":157},14,[36,159,160],{"class":42},"# [Service]\n",[36,162,164],{"class":38,"line":163},15,[36,165,166],{"class":42},"# User=www-data\n",[36,168,170],{"class":38,"line":169},16,[36,171,172],{"class":42},"# Group=www-data\n",[36,174,176],{"class":38,"line":175},17,[36,177,178],{"class":42},"# NoNewPrivileges=true\n",[36,180,182],{"class":38,"line":181},18,[36,183,184],{"class":42},"# PrivateTmp=true\n",[36,186,188],{"class":38,"line":187},19,[36,189,81],{"emptyLinePlaceholder":80},[36,191,193],{"class":38,"line":192},20,[36,194,195],{"class":42},"# 4) Enable firewall for web traffic only\n",[36,197,199,201,204,207],{"class":38,"line":198},21,[36,200,139],{"class":93},[36,202,203],{"class":97}," ufw",[36,205,206],{"class":97}," allow",[36,208,209],{"class":97}," OpenSSH\n",[36,211,213,215,217,219],{"class":38,"line":212},22,[36,214,139],{"class":93},[36,216,203],{"class":97},[36,218,206],{"class":97},[36,220,221],{"class":97}," 'Nginx Full'\n",[36,223,225,227,229],{"class":38,"line":224},23,[36,226,139],{"class":93},[36,228,203],{"class":97},[36,230,231],{"class":97}," enable\n",[36,233,235],{"class":38,"line":234},24,[36,236,81],{"emptyLinePlaceholder":80},[36,238,240],{"class":38,"line":239},25,[36,241,242],{"class":42},"# 5) Check HTTPS and security headers\n",[36,244,246,249,252],{"class":38,"line":245},26,[36,247,248],{"class":93},"curl",[36,250,251],{"class":73}," -I",[36,253,254],{"class":97}," https:\u002F\u002Fyour-domain.com\n",[36,256,258],{"class":38,"line":257},27,[36,259,81],{"emptyLinePlaceholder":80},[36,261,263],{"class":38,"line":262},28,[36,264,265],{"class":42},"# 6) Validate Nginx config before reload\n",[36,267,269,271,274,277,280,282,284,287],{"class":38,"line":268},29,[36,270,139],{"class":93},[36,272,273],{"class":97}," nginx",[36,275,276],{"class":73}," -t",[36,278,279],{"class":53}," && ",[36,281,139],{"class":93},[36,283,142],{"class":97},[36,285,286],{"class":97}," reload",[36,288,289],{"class":97}," nginx\n",[14,291,292],{},"This covers the most common production gaps first: debug mode, weak secrets, over-privileged processes, unnecessary open ports, and missing HTTPS validation.",[18,294,296],{"id":295},"whats-happening","What’s Happening",[14,298,299],{},"Flask production security depends on multiple layers: app configuration, Gunicorn\u002Fsystemd isolation, Nginx edge controls, firewall rules, secrets handling, and restricted database access.",[14,301,302,303,306],{},"Most production security failures come from deployment mistakes, not Flask itself: debug mode left on, exposed ",[33,304,305],{},".env"," files, public Gunicorn binds, missing HTTPS, weak upload handling, or excessive service permissions.",[18,308,310],{"id":309},"step-by-step-guide","Step-by-Step Guide",[312,313,314,379,458,524,630,687,839,908,978,1023,1056,1132,1214,1261,1321,1402,1472,1480,1505,1551],"ol",{},[315,316,317,321,324,325,350,352,353],"li",{},[318,319,320],"strong",{},"Disable development settings",[322,323],"br",{},"Confirm Flask debug mode is off and do not use the Werkzeug development server in production.",[26,326,328],{"className":28,"code":327,"language":30,"meta":31,"style":31},"export FLASK_ENV=production\nexport DEBUG=0\n",[33,329,330,340],{"__ignoreMap":31},[36,331,332,334,336,338],{"class":38,"line":39},[36,333,50],{"class":49},[36,335,54],{"class":53},[36,337,57],{"class":49},[36,339,60],{"class":53},[36,341,342,344,346,348],{"class":38,"line":46},[36,343,50],{"class":49},[36,345,68],{"class":53},[36,347,57],{"class":49},[36,349,74],{"class":73},[322,351],{},"In your Flask config:",[26,354,357],{"className":355,"code":356,"language":94,"meta":31,"style":31},"language-python shiki shiki-themes github-light github-dark","DEBUG = False\nTESTING = False\n",[33,358,359,370],{"__ignoreMap":31},[36,360,361,364,367],{"class":38,"line":39},[36,362,363],{"class":73},"DEBUG",[36,365,366],{"class":49}," =",[36,368,369],{"class":73}," False\n",[36,371,372,375,377],{"class":38,"line":46},[36,373,374],{"class":73},"TESTING",[36,376,366],{"class":49},[36,378,369],{"class":73},[315,380,381,388,390,391,418,420,421,449,451,452,457],{},[318,382,383,384,387],{},"Set a strong ",[33,385,386],{},"SECRET_KEY"," outside source control",[322,389],{},"Generate a key:",[26,392,394],{"className":28,"code":393,"language":30,"meta":31,"style":31},"python - \u003C\u003C'PY'\nimport secrets\nprint(secrets.token_hex(32))\nPY\n",[33,395,396,406,410,414],{"__ignoreMap":31},[36,397,398,400,402,404],{"class":38,"line":39},[36,399,94],{"class":93},[36,401,98],{"class":97},[36,403,101],{"class":49},[36,405,104],{"class":97},[36,407,408],{"class":38,"line":46},[36,409,110],{"class":97},[36,411,412],{"class":38,"line":63},[36,413,116],{"class":97},[36,415,416],{"class":38,"line":77},[36,417,122],{"class":97},[322,419],{},"Load it from the environment:",[26,422,424],{"className":355,"code":423,"language":94,"meta":31,"style":31},"import os\nSECRET_KEY = os.environ[\"SECRET_KEY\"]\n",[33,425,426,434],{"__ignoreMap":31},[36,427,428,431],{"class":38,"line":39},[36,429,430],{"class":49},"import",[36,432,433],{"class":53}," os\n",[36,435,436,438,440,443,446],{"class":38,"line":46},[36,437,386],{"class":73},[36,439,366],{"class":49},[36,441,442],{"class":53}," os.environ[",[36,444,445],{"class":97},"\"SECRET_KEY\"",[36,447,448],{"class":53},"]\n",[322,450],{},"If you need a full setup pattern, use ",[453,454,456],"a",{"href":455},"\u002Fdeploy\u002Fflask-environment-variables-and-secrets-setup","Flask Environment Variables and Secrets Setup",".",[315,459,460,463,465,466,468,469,491,493,494],{},[318,461,462],{},"Move credentials out of the repository",[322,464],{},"Do not hardcode database URLs, API keys, SMTP passwords, or token secrets.",[322,467],{},"Example systemd environment file:",[26,470,474],{"className":471,"code":472,"language":473,"meta":31,"style":31},"language-ini shiki shiki-themes github-light github-dark","# \u002Fetc\u002Fmyapp\u002Fmyapp.env\nSECRET_KEY=replace_me\nDATABASE_URL=postgresql:\u002F\u002Fappuser:strongpassword@127.0.0.1\u002Fmydb\n","ini",[33,475,476,481,486],{"__ignoreMap":31},[36,477,478],{"class":38,"line":39},[36,479,480],{},"# \u002Fetc\u002Fmyapp\u002Fmyapp.env\n",[36,482,483],{"class":38,"line":46},[36,484,485],{},"SECRET_KEY=replace_me\n",[36,487,488],{"class":38,"line":63},[36,489,490],{},"DATABASE_URL=postgresql:\u002F\u002Fappuser:strongpassword@127.0.0.1\u002Fmydb\n",[322,492],{},"Restrict permissions:",[26,495,497],{"className":28,"code":496,"language":30,"meta":31,"style":31},"sudo chown root:www-data \u002Fetc\u002Fmyapp\u002Fmyapp.env\nsudo chmod 640 \u002Fetc\u002Fmyapp\u002Fmyapp.env\n",[33,498,499,512],{"__ignoreMap":31},[36,500,501,503,506,509],{"class":38,"line":39},[36,502,139],{"class":93},[36,504,505],{"class":97}," chown",[36,507,508],{"class":97}," root:www-data",[36,510,511],{"class":97}," \u002Fetc\u002Fmyapp\u002Fmyapp.env\n",[36,513,514,516,519,522],{"class":38,"line":46},[36,515,139],{"class":93},[36,517,518],{"class":97}," chmod",[36,520,521],{"class":73}," 640",[36,523,511],{"class":97},[315,525,526,529,531,532,587,589,590],{},[318,527,528],{},"Run Gunicorn as a dedicated non-root user",[322,530],{},"In your systemd service:",[26,533,535],{"className":471,"code":534,"language":473,"meta":31,"style":31},"[Service]\nUser=www-data\nGroup=www-data\nWorkingDirectory=\u002Fsrv\u002Fmyapp\nEnvironmentFile=\u002Fetc\u002Fmyapp\u002Fmyapp.env\nExecStart=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fgunicorn --workers 3 --bind unix:\u002Frun\u002Fgunicorn.sock wsgi:app\nNoNewPrivileges=true\nPrivateTmp=true\nProtectSystem=full\nProtectHome=true\n",[33,536,537,542,547,552,557,562,567,572,577,582],{"__ignoreMap":31},[36,538,539],{"class":38,"line":39},[36,540,541],{},"[Service]\n",[36,543,544],{"class":38,"line":46},[36,545,546],{},"User=www-data\n",[36,548,549],{"class":38,"line":63},[36,550,551],{},"Group=www-data\n",[36,553,554],{"class":38,"line":77},[36,555,556],{},"WorkingDirectory=\u002Fsrv\u002Fmyapp\n",[36,558,559],{"class":38,"line":84},[36,560,561],{},"EnvironmentFile=\u002Fetc\u002Fmyapp\u002Fmyapp.env\n",[36,563,564],{"class":38,"line":90},[36,565,566],{},"ExecStart=\u002Fsrv\u002Fmyapp\u002Fvenv\u002Fbin\u002Fgunicorn --workers 3 --bind unix:\u002Frun\u002Fgunicorn.sock wsgi:app\n",[36,568,569],{"class":38,"line":107},[36,570,571],{},"NoNewPrivileges=true\n",[36,573,574],{"class":38,"line":113},[36,575,576],{},"PrivateTmp=true\n",[36,578,579],{"class":38,"line":119},[36,580,581],{},"ProtectSystem=full\n",[36,583,584],{"class":38,"line":125},[36,585,586],{},"ProtectHome=true\n",[322,588],{},"Reload and restart:",[26,591,593],{"className":28,"code":592,"language":30,"meta":31,"style":31},"sudo systemctl daemon-reload\nsudo systemctl restart gunicorn\nsudo systemctl status gunicorn --no-pager\n",[33,594,595,604,615],{"__ignoreMap":31},[36,596,597,599,601],{"class":38,"line":39},[36,598,139],{"class":93},[36,600,142],{"class":97},[36,602,603],{"class":97}," daemon-reload\n",[36,605,606,608,610,613],{"class":38,"line":46},[36,607,139],{"class":93},[36,609,142],{"class":97},[36,611,612],{"class":97}," restart",[36,614,148],{"class":97},[36,616,617,619,621,624,627],{"class":38,"line":63},[36,618,139],{"class":93},[36,620,142],{"class":97},[36,622,623],{"class":97}," status",[36,625,626],{"class":97}," gunicorn",[36,628,629],{"class":73}," --no-pager\n",[315,631,632,635,637,638],{},[318,633,634],{},"Restrict file permissions",[322,636],{},"Application code should not be world-writable. Secrets files should be readable only by root or the service group.",[26,639,641],{"className":28,"code":640,"language":30,"meta":31,"style":31},"sudo find \u002Fsrv\u002Fmyapp -type f -perm \u002Fo+w\nsudo chmod 640 \u002Fetc\u002Fmyapp\u002Fmyapp.env\nsudo chmod 755 \u002Fsrv\u002Fmyapp\n",[33,642,643,665,675],{"__ignoreMap":31},[36,644,645,647,650,653,656,659,662],{"class":38,"line":39},[36,646,139],{"class":93},[36,648,649],{"class":97}," find",[36,651,652],{"class":97}," \u002Fsrv\u002Fmyapp",[36,654,655],{"class":73}," -type",[36,657,658],{"class":97}," f",[36,660,661],{"class":73}," -perm",[36,663,664],{"class":97}," \u002Fo+w\n",[36,666,667,669,671,673],{"class":38,"line":46},[36,668,139],{"class":93},[36,670,518],{"class":97},[36,672,521],{"class":73},[36,674,511],{"class":97},[36,676,677,679,681,684],{"class":38,"line":63},[36,678,139],{"class":93},[36,680,518],{"class":97},[36,682,683],{"class":73}," 755",[36,685,686],{"class":97}," \u002Fsrv\u002Fmyapp\n",[315,688,689,692,694,695,697,698,789,791,792,832,834,835,457],{},[318,690,691],{},"Terminate TLS at Nginx",[322,693],{},"Enforce HTTPS and use a valid certificate.",[322,696],{},"Example server blocks:",[26,699,703],{"className":700,"code":701,"language":702,"meta":31,"style":31},"language-nginx shiki shiki-themes github-light github-dark","server {\n    listen 80;\n    server_name your-domain.com www.your-domain.com;\n    return 301 https:\u002F\u002F$host$request_uri;\n}\n\nserver {\n    listen 443 ssl http2;\n    server_name your-domain.com www.your-domain.com;\n\n    ssl_certificate \u002Fetc\u002Fletsencrypt\u002Flive\u002Fyour-domain.com\u002Ffullchain.pem;\n    ssl_certificate_key \u002Fetc\u002Fletsencrypt\u002Flive\u002Fyour-domain.com\u002Fprivkey.pem;\n\n    location \u002F {\n        include proxy_params;\n        proxy_pass http:\u002F\u002Funix:\u002Frun\u002Fgunicorn.sock;\n    }\n}\n","nginx",[33,704,705,710,715,720,725,730,734,738,743,747,751,756,761,765,770,775,780,785],{"__ignoreMap":31},[36,706,707],{"class":38,"line":39},[36,708,709],{},"server {\n",[36,711,712],{"class":38,"line":46},[36,713,714],{},"    listen 80;\n",[36,716,717],{"class":38,"line":63},[36,718,719],{},"    server_name your-domain.com www.your-domain.com;\n",[36,721,722],{"class":38,"line":77},[36,723,724],{},"    return 301 https:\u002F\u002F$host$request_uri;\n",[36,726,727],{"class":38,"line":84},[36,728,729],{},"}\n",[36,731,732],{"class":38,"line":90},[36,733,81],{"emptyLinePlaceholder":80},[36,735,736],{"class":38,"line":107},[36,737,709],{},[36,739,740],{"class":38,"line":113},[36,741,742],{},"    listen 443 ssl http2;\n",[36,744,745],{"class":38,"line":119},[36,746,719],{},[36,748,749],{"class":38,"line":125},[36,750,81],{"emptyLinePlaceholder":80},[36,752,753],{"class":38,"line":130},[36,754,755],{},"    ssl_certificate \u002Fetc\u002Fletsencrypt\u002Flive\u002Fyour-domain.com\u002Ffullchain.pem;\n",[36,757,758],{"class":38,"line":136},[36,759,760],{},"    ssl_certificate_key \u002Fetc\u002Fletsencrypt\u002Flive\u002Fyour-domain.com\u002Fprivkey.pem;\n",[36,762,763],{"class":38,"line":151},[36,764,81],{"emptyLinePlaceholder":80},[36,766,767],{"class":38,"line":157},[36,768,769],{},"    location \u002F {\n",[36,771,772],{"class":38,"line":163},[36,773,774],{},"        include proxy_params;\n",[36,776,777],{"class":38,"line":169},[36,778,779],{},"        proxy_pass http:\u002F\u002Funix:\u002Frun\u002Fgunicorn.sock;\n",[36,781,782],{"class":38,"line":175},[36,783,784],{},"    }\n",[36,786,787],{"class":38,"line":181},[36,788,729],{},[322,790],{},"Validate:",[26,793,795],{"className":28,"code":794,"language":30,"meta":31,"style":31},"sudo nginx -t && sudo systemctl reload nginx\ncurl -I http:\u002F\u002Fyour-domain.com\ncurl -I https:\u002F\u002Fyour-domain.com\n",[33,796,797,815,824],{"__ignoreMap":31},[36,798,799,801,803,805,807,809,811,813],{"class":38,"line":39},[36,800,139],{"class":93},[36,802,273],{"class":97},[36,804,276],{"class":73},[36,806,279],{"class":53},[36,808,139],{"class":93},[36,810,142],{"class":97},[36,812,286],{"class":97},[36,814,289],{"class":97},[36,816,817,819,821],{"class":38,"line":46},[36,818,248],{"class":93},[36,820,251],{"class":73},[36,822,823],{"class":97}," http:\u002F\u002Fyour-domain.com\n",[36,825,826,828,830],{"class":38,"line":63},[36,827,248],{"class":93},[36,829,251],{"class":73},[36,831,254],{"class":97},[322,833],{},"Also review ",[453,836,838],{"href":837},"\u002Fchecklist\u002Fflask-https-and-domain-checklist","Flask HTTPS and Domain Checklist",[315,840,841,844,846,847],{},[318,842,843],{},"Enable secure cookie settings",[322,845],{},"In Flask config:",[26,848,850],{"className":355,"code":849,"language":94,"meta":31,"style":31},"SESSION_COOKIE_SECURE = True\nSESSION_COOKIE_HTTPONLY = True\nSESSION_COOKIE_SAMESITE = \"Lax\"\nREMEMBER_COOKIE_SECURE = True\nREMEMBER_COOKIE_HTTPONLY = True\nREMEMBER_COOKIE_SAMESITE = \"Lax\"\n",[33,851,852,862,871,881,890,899],{"__ignoreMap":31},[36,853,854,857,859],{"class":38,"line":39},[36,855,856],{"class":73},"SESSION_COOKIE_SECURE",[36,858,366],{"class":49},[36,860,861],{"class":73}," True\n",[36,863,864,867,869],{"class":38,"line":46},[36,865,866],{"class":73},"SESSION_COOKIE_HTTPONLY",[36,868,366],{"class":49},[36,870,861],{"class":73},[36,872,873,876,878],{"class":38,"line":63},[36,874,875],{"class":73},"SESSION_COOKIE_SAMESITE",[36,877,366],{"class":49},[36,879,880],{"class":97}," \"Lax\"\n",[36,882,883,886,888],{"class":38,"line":77},[36,884,885],{"class":73},"REMEMBER_COOKIE_SECURE",[36,887,366],{"class":49},[36,889,861],{"class":73},[36,891,892,895,897],{"class":38,"line":84},[36,893,894],{"class":73},"REMEMBER_COOKIE_HTTPONLY",[36,896,366],{"class":49},[36,898,861],{"class":73},[36,900,901,904,906],{"class":38,"line":90},[36,902,903],{"class":73},"REMEMBER_COOKIE_SAMESITE",[36,905,366],{"class":49},[36,907,880],{"class":97},[315,909,910,913,915,916,975,977],{},[318,911,912],{},"Configure proxy awareness correctly",[322,914],{},"If Nginx terminates TLS, Flask must trust only the expected proxy headers.",[26,917,919],{"className":355,"code":918,"language":94,"meta":31,"style":31},"from werkzeug.middleware.proxy_fix import ProxyFix\napp.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)\n",[33,920,921,934],{"__ignoreMap":31},[36,922,923,926,929,931],{"class":38,"line":39},[36,924,925],{"class":49},"from",[36,927,928],{"class":53}," werkzeug.middleware.proxy_fix ",[36,930,430],{"class":49},[36,932,933],{"class":53}," ProxyFix\n",[36,935,936,939,941,944,948,950,953,956,959,961,963,965,968,970,972],{"class":38,"line":46},[36,937,938],{"class":53},"app.wsgi_app ",[36,940,57],{"class":49},[36,942,943],{"class":53}," ProxyFix(app.wsgi_app, ",[36,945,947],{"class":946},"s4XuR","x_for",[36,949,57],{"class":49},[36,951,952],{"class":73},"1",[36,954,955],{"class":53},", ",[36,957,958],{"class":946},"x_proto",[36,960,57],{"class":49},[36,962,952],{"class":73},[36,964,955],{"class":53},[36,966,967],{"class":946},"x_host",[36,969,57],{"class":49},[36,971,952],{"class":73},[36,973,974],{"class":53},")\n",[322,976],{},"Do this only when Flask is behind your trusted reverse proxy and Gunicorn is not publicly exposed.",[315,979,980,983,985,986,1011,1013,1014],{},[318,981,982],{},"Add core Nginx security headers",[322,984],{},"Add headers in the TLS server block:",[26,987,989],{"className":700,"code":988,"language":702,"meta":31,"style":31},"add_header X-Content-Type-Options \"nosniff\" always;\nadd_header X-Frame-Options \"SAMEORIGIN\" always;\nadd_header Referrer-Policy \"strict-origin-when-cross-origin\" always;\nadd_header Strict-Transport-Security \"max-age=31536000; includeSubDomains\" always;\n",[33,990,991,996,1001,1006],{"__ignoreMap":31},[36,992,993],{"class":38,"line":39},[36,994,995],{},"add_header X-Content-Type-Options \"nosniff\" always;\n",[36,997,998],{"class":38,"line":46},[36,999,1000],{},"add_header X-Frame-Options \"SAMEORIGIN\" always;\n",[36,1002,1003],{"class":38,"line":63},[36,1004,1005],{},"add_header Referrer-Policy \"strict-origin-when-cross-origin\" always;\n",[36,1007,1008],{"class":38,"line":77},[36,1009,1010],{},"add_header Strict-Transport-Security \"max-age=31536000; includeSubDomains\" always;\n",[322,1012],{},"If compatible with your frontend, add a Content Security Policy:",[26,1015,1017],{"className":700,"code":1016,"language":702,"meta":31,"style":31},"add_header Content-Security-Policy \"default-src 'self';\" always;\n",[33,1018,1019],{"__ignoreMap":31},[36,1020,1021],{"class":38,"line":39},[36,1022,1016],{},[315,1024,1025,1028,1030,1031,1034,1035],{},[318,1026,1027],{},"Lock down allowed hosts and domain routing",[322,1029],{},"Use only expected ",[33,1032,1033],{},"server_name"," values and remove default server blocks that can expose unintended apps.",[26,1036,1038],{"className":28,"code":1037,"language":30,"meta":31,"style":31},"sudo grep -R \"server_name\" \u002Fetc\u002Fnginx\u002Fsites-enabled\n",[33,1039,1040],{"__ignoreMap":31},[36,1041,1042,1044,1047,1050,1053],{"class":38,"line":39},[36,1043,139],{"class":93},[36,1045,1046],{"class":97}," grep",[36,1048,1049],{"class":73}," -R",[36,1051,1052],{"class":97}," \"server_name\"",[36,1054,1055],{"class":97}," \u002Fetc\u002Fnginx\u002Fsites-enabled\n",[315,1057,1058,1061,1063,1064,457,1067,1091,1093,1094,1113,1115,1116],{},[318,1059,1060],{},"Restrict network exposure",[322,1062],{},"Expose only 80 and 443 publicly. Bind Gunicorn to a Unix socket or ",[33,1065,1066],{},"127.0.0.1",[26,1068,1070],{"className":28,"code":1069,"language":30,"meta":31,"style":31},"ss -tulpn\nsudo ufw status verbose\n",[33,1071,1072,1080],{"__ignoreMap":31},[36,1073,1074,1077],{"class":38,"line":39},[36,1075,1076],{"class":93},"ss",[36,1078,1079],{"class":73}," -tulpn\n",[36,1081,1082,1084,1086,1088],{"class":38,"line":46},[36,1083,139],{"class":93},[36,1085,203],{"class":97},[36,1087,623],{"class":97},[36,1089,1090],{"class":97}," verbose\n",[322,1092],{},"Gunicorn example:",[26,1095,1097],{"className":28,"code":1096,"language":30,"meta":31,"style":31},"gunicorn --bind 127.0.0.1:8000 wsgi:app\n",[33,1098,1099],{"__ignoreMap":31},[36,1100,1101,1104,1107,1110],{"class":38,"line":39},[36,1102,1103],{"class":93},"gunicorn",[36,1105,1106],{"class":73}," --bind",[36,1108,1109],{"class":97}," 127.0.0.1:8000",[36,1111,1112],{"class":97}," wsgi:app\n",[322,1114],{},"Preferred:",[26,1117,1119],{"className":28,"code":1118,"language":30,"meta":31,"style":31},"gunicorn --bind unix:\u002Frun\u002Fgunicorn.sock wsgi:app\n",[33,1120,1121],{"__ignoreMap":31},[36,1122,1123,1125,1127,1130],{"class":38,"line":39},[36,1124,1103],{"class":93},[36,1126,1106],{"class":73},[36,1128,1129],{"class":97}," unix:\u002Frun\u002Fgunicorn.sock",[36,1131,1112],{"class":97},[315,1133,1134,1137,1139,1140,1142,1143,1169,1171,1172,1199,1201,1202,1211,1213],{},[318,1135,1136],{},"Harden file uploads",[322,1138],{},"Enforce size limits and sanitize names.",[322,1141],{},"In Flask:",[26,1144,1146],{"className":355,"code":1145,"language":94,"meta":31,"style":31},"MAX_CONTENT_LENGTH = 16 * 1024 * 1024\n",[33,1147,1148],{"__ignoreMap":31},[36,1149,1150,1153,1155,1158,1161,1164,1166],{"class":38,"line":39},[36,1151,1152],{"class":73},"MAX_CONTENT_LENGTH",[36,1154,366],{"class":49},[36,1156,1157],{"class":73}," 16",[36,1159,1160],{"class":49}," *",[36,1162,1163],{"class":73}," 1024",[36,1165,1160],{"class":49},[36,1167,1168],{"class":73}," 1024\n",[322,1170],{},"Use safe filenames:",[26,1173,1175],{"className":355,"code":1174,"language":94,"meta":31,"style":31},"from werkzeug.utils import secure_filename\nfilename = secure_filename(upload.filename)\n",[33,1176,1177,1189],{"__ignoreMap":31},[36,1178,1179,1181,1184,1186],{"class":38,"line":39},[36,1180,925],{"class":49},[36,1182,1183],{"class":53}," werkzeug.utils ",[36,1185,430],{"class":49},[36,1187,1188],{"class":53}," secure_filename\n",[36,1190,1191,1194,1196],{"class":38,"line":46},[36,1192,1193],{"class":53},"filename ",[36,1195,57],{"class":49},[36,1197,1198],{"class":53}," secure_filename(upload.filename)\n",[322,1200],{},"In Nginx:",[26,1203,1205],{"className":700,"code":1204,"language":702,"meta":31,"style":31},"client_max_body_size 16M;\n",[33,1206,1207],{"__ignoreMap":31},[36,1208,1209],{"class":38,"line":39},[36,1210,1204],{},[322,1212],{},"Store uploads outside executable paths and avoid serving raw user uploads directly unless required.",[315,1215,1216,1219,1221,1222,1224,1225,1258,1260],{},[318,1217,1218],{},"Secure the database connection",[322,1220],{},"Use least-privilege database users and do not expose the database publicly unless necessary.",[322,1223],{},"Check listening addresses:",[26,1226,1228],{"className":28,"code":1227,"language":30,"meta":31,"style":31},"ss -tulpn | grep 5432\nss -tulpn | grep 3306\n",[33,1229,1230,1245],{"__ignoreMap":31},[36,1231,1232,1234,1237,1240,1242],{"class":38,"line":39},[36,1233,1076],{"class":93},[36,1235,1236],{"class":73}," -tulpn",[36,1238,1239],{"class":49}," |",[36,1241,1046],{"class":93},[36,1243,1244],{"class":73}," 5432\n",[36,1246,1247,1249,1251,1253,1255],{"class":38,"line":46},[36,1248,1076],{"class":93},[36,1250,1236],{"class":73},[36,1252,1239],{"class":49},[36,1254,1046],{"class":93},[36,1256,1257],{"class":73}," 3306\n",[322,1259],{},"Restrict access with firewall rules and database bind settings.",[315,1262,1263,1266,1268,1269,1301,1303,1304],{},[318,1264,1265],{},"Install dependency and OS updates",[322,1267],{},"Review outdated Python packages:",[26,1270,1272],{"className":28,"code":1271,"language":30,"meta":31,"style":31},"python -m pip list --outdated\npython -m pip audit\n",[33,1273,1274,1290],{"__ignoreMap":31},[36,1275,1276,1278,1281,1284,1287],{"class":38,"line":39},[36,1277,94],{"class":93},[36,1279,1280],{"class":73}," -m",[36,1282,1283],{"class":97}," pip",[36,1285,1286],{"class":97}," list",[36,1288,1289],{"class":73}," --outdated\n",[36,1291,1292,1294,1296,1298],{"class":38,"line":46},[36,1293,94],{"class":93},[36,1295,1280],{"class":73},[36,1297,1283],{"class":97},[36,1299,1300],{"class":97}," audit\n",[322,1302],{},"Review system packages:",[26,1305,1307],{"className":28,"code":1306,"language":30,"meta":31,"style":31},"sudo apt list --upgradable\n",[33,1308,1309],{"__ignoreMap":31},[36,1310,1311,1313,1316,1318],{"class":38,"line":39},[36,1312,139],{"class":93},[36,1314,1315],{"class":97}," apt",[36,1317,1286],{"class":97},[36,1319,1320],{"class":73}," --upgradable\n",[315,1322,1323,1326,1328,1329,1381,1383,1384],{},[318,1324,1325],{},"Add logging and monitoring",[322,1327],{},"Review logs regularly:",[26,1330,1332],{"className":28,"code":1331,"language":30,"meta":31,"style":31},"sudo journalctl -u gunicorn -n 100 --no-pager\nsudo tail -n 100 \u002Fvar\u002Flog\u002Fnginx\u002Faccess.log\nsudo tail -n 100 \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\n",[33,1333,1334,1354,1368],{"__ignoreMap":31},[36,1335,1336,1338,1341,1344,1346,1349,1352],{"class":38,"line":39},[36,1337,139],{"class":93},[36,1339,1340],{"class":97}," journalctl",[36,1342,1343],{"class":73}," -u",[36,1345,626],{"class":97},[36,1347,1348],{"class":73}," -n",[36,1350,1351],{"class":73}," 100",[36,1353,629],{"class":73},[36,1355,1356,1358,1361,1363,1365],{"class":38,"line":46},[36,1357,139],{"class":93},[36,1359,1360],{"class":97}," tail",[36,1362,1348],{"class":73},[36,1364,1351],{"class":73},[36,1366,1367],{"class":97}," \u002Fvar\u002Flog\u002Fnginx\u002Faccess.log\n",[36,1369,1370,1372,1374,1376,1378],{"class":38,"line":63},[36,1371,139],{"class":93},[36,1373,1360],{"class":97},[36,1375,1348],{"class":73},[36,1377,1351],{"class":73},[36,1379,1380],{"class":97}," \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\n",[322,1382],{},"Track:",[1385,1386,1387,1390,1393,1396,1399],"ul",{},[315,1388,1389],{},"app exceptions",[315,1391,1392],{},"repeated 4xx\u002F5xx spikes",[315,1394,1395],{},"auth failures",[315,1397,1398],{},"abnormal request rates",[315,1400,1401],{},"unexpected upload attempts",[315,1403,1404,1407,1409,1410],{},[318,1405,1406],{},"Set rate limits and request size limits at Nginx",[322,1408],{},"Example:",[26,1411,1413],{"className":700,"code":1412,"language":702,"meta":31,"style":31},"http {\n    limit_req_zone $binary_remote_addr zone=app_limit:10m rate=10r\u002Fs;\n}\n\nserver {\n    client_max_body_size 16M;\n\n    location \u002Flogin {\n        limit_req zone=app_limit burst=20 nodelay;\n        include proxy_params;\n        proxy_pass http:\u002F\u002Funix:\u002Frun\u002Fgunicorn.sock;\n    }\n}\n",[33,1414,1415,1420,1425,1429,1433,1437,1442,1446,1451,1456,1460,1464,1468],{"__ignoreMap":31},[36,1416,1417],{"class":38,"line":39},[36,1418,1419],{},"http {\n",[36,1421,1422],{"class":38,"line":46},[36,1423,1424],{},"    limit_req_zone $binary_remote_addr zone=app_limit:10m rate=10r\u002Fs;\n",[36,1426,1427],{"class":38,"line":63},[36,1428,729],{},[36,1430,1431],{"class":38,"line":77},[36,1432,81],{"emptyLinePlaceholder":80},[36,1434,1435],{"class":38,"line":84},[36,1436,709],{},[36,1438,1439],{"class":38,"line":90},[36,1440,1441],{},"    client_max_body_size 16M;\n",[36,1443,1444],{"class":38,"line":107},[36,1445,81],{"emptyLinePlaceholder":80},[36,1447,1448],{"class":38,"line":113},[36,1449,1450],{},"    location \u002Flogin {\n",[36,1452,1453],{"class":38,"line":119},[36,1454,1455],{},"        limit_req zone=app_limit burst=20 nodelay;\n",[36,1457,1458],{"class":38,"line":125},[36,1459,774],{},[36,1461,1462],{"class":38,"line":130},[36,1463,779],{},[36,1465,1466],{"class":38,"line":136},[36,1467,784],{},[36,1469,1470],{"class":38,"line":151},[36,1471,729],{},[315,1473,1474,1477,1479],{},[318,1475,1476],{},"Review admin interfaces and background tools",[322,1478],{},"Protect dashboards, queue monitors, internal APIs, and task runners with authentication and network restrictions. Do not leave admin paths publicly accessible by default.",[315,1481,1482,1485,1487,1488,1490,1491],{},[318,1483,1484],{},"Back up critical data securely",[322,1486],{},"Ensure backups exist, are access-controlled, and can be restored.",[322,1489],{},"Validation should include:",[1385,1492,1493,1496,1499,1502],{},[315,1494,1495],{},"backup schedule exists",[315,1497,1498],{},"restore test completed",[315,1500,1501],{},"backup storage permissions restricted",[315,1503,1504],{},"encryption used where required",[315,1506,1507,1510,1512,1513],{},[318,1508,1509],{},"Scan your deployment",[322,1511],{},"Validate runtime configuration and exposure.",[26,1514,1516],{"className":28,"code":1515,"language":30,"meta":31,"style":31},"curl -I https:\u002F\u002Fyour-domain.com\nss -tulpn\nsudo nginx -t\npython -m pip audit\n",[33,1517,1518,1526,1532,1541],{"__ignoreMap":31},[36,1519,1520,1522,1524],{"class":38,"line":39},[36,1521,248],{"class":93},[36,1523,251],{"class":73},[36,1525,254],{"class":97},[36,1527,1528,1530],{"class":38,"line":46},[36,1529,1076],{"class":93},[36,1531,1079],{"class":73},[36,1533,1534,1536,1538],{"class":38,"line":63},[36,1535,139],{"class":93},[36,1537,273],{"class":97},[36,1539,1540],{"class":73}," -t\n",[36,1542,1543,1545,1547,1549],{"class":38,"line":77},[36,1544,94],{"class":93},[36,1546,1280],{"class":73},[36,1548,1283],{"class":97},[36,1550,1300],{"class":97},[315,1552,1553,1556,1558,1559],{},[318,1554,1555],{},"Re-run the checklist after every infrastructure or app change",[322,1557],{},"Re-check security after:",[1385,1560,1561,1564,1567,1570,1573,1576],{},[315,1562,1563],{},"Nginx changes",[315,1565,1566],{},"DNS updates",[315,1568,1569],{},"new upload features",[315,1571,1572],{},"CI\u002FCD changes",[315,1574,1575],{},"service user changes",[315,1577,1578],{},"added background workers",[18,1580,1582],{"id":1581},"common-causes","Common Causes",[1385,1584,1585,1591,1599,1605,1613,1619,1625,1631,1637,1643],{},[315,1586,1587,1590],{},[318,1588,1589],{},"Debug mode left enabled"," → exposes stack traces and unsafe debugger behavior → set production config explicitly and verify runtime values.",[315,1592,1593,1598],{},[318,1594,1595,1597],{},[33,1596,386],{}," committed to source control or reused across environments"," → weakens session and token integrity → rotate the key and load it from environment variables.",[315,1600,1601,1604],{},[318,1602,1603],{},"Gunicorn running as root"," → increases blast radius if compromised → run under a dedicated service account.",[315,1606,1607,1610,1611,457],{},[318,1608,1609],{},"Gunicorn bound to a public interface"," → bypasses Nginx protections → bind to a Unix socket or ",[33,1612,1066],{},[315,1614,1615,1618],{},[318,1616,1617],{},"Missing HTTPS redirect or invalid certificate"," → breaks transport security and browser trust → fix Nginx TLS config and renewal.",[315,1620,1621,1624],{},[318,1622,1623],{},"Environment files readable by all users"," → secrets can leak locally → restrict ownership and permissions.",[315,1626,1627,1630],{},[318,1628,1629],{},"Database port exposed publicly"," → increases attack surface → restrict inbound access and use least-privilege credentials.",[315,1632,1633,1636],{},[318,1634,1635],{},"File uploads stored unsafely"," → allows malicious file handling or overwrite issues → validate, sanitize, and isolate uploads.",[315,1638,1639,1642],{},[318,1640,1641],{},"Missing security headers"," → reduces browser-side protections → configure headers in Nginx or Flask.",[315,1644,1645,1648],{},[318,1646,1647],{},"Outdated dependencies or OS packages"," → known vulnerabilities remain exploitable → patch regularly and audit installed packages.",[18,1650,1652],{"id":1651},"debugging-section","Debugging Section",[14,1654,1655],{},"Check actual runtime state, not only static config files.",[1657,1658,1660],"h3",{"id":1659},"validate-flask-security-settings","Validate Flask security settings",[26,1662,1664],{"className":28,"code":1663,"language":30,"meta":31,"style":31},"python - \u003C\u003C'PY'\nfrom app import app\nprint('DEBUG=', app.config.get('DEBUG'))\nprint('SESSION_COOKIE_SECURE=', app.config.get('SESSION_COOKIE_SECURE'))\nprint('SESSION_COOKIE_HTTPONLY=', app.config.get('SESSION_COOKIE_HTTPONLY'))\nprint('SECRET_KEY_SET=', bool(app.config.get('SECRET_KEY')))\nPY\n",[33,1665,1666,1676,1681,1686,1691,1696,1701],{"__ignoreMap":31},[36,1667,1668,1670,1672,1674],{"class":38,"line":39},[36,1669,94],{"class":93},[36,1671,98],{"class":97},[36,1673,101],{"class":49},[36,1675,104],{"class":97},[36,1677,1678],{"class":38,"line":46},[36,1679,1680],{"class":97},"from app import app\n",[36,1682,1683],{"class":38,"line":63},[36,1684,1685],{"class":97},"print('DEBUG=', app.config.get('DEBUG'))\n",[36,1687,1688],{"class":38,"line":77},[36,1689,1690],{"class":97},"print('SESSION_COOKIE_SECURE=', app.config.get('SESSION_COOKIE_SECURE'))\n",[36,1692,1693],{"class":38,"line":84},[36,1694,1695],{"class":97},"print('SESSION_COOKIE_HTTPONLY=', app.config.get('SESSION_COOKIE_HTTPONLY'))\n",[36,1697,1698],{"class":38,"line":90},[36,1699,1700],{"class":97},"print('SECRET_KEY_SET=', bool(app.config.get('SECRET_KEY')))\n",[36,1702,1703],{"class":38,"line":107},[36,1704,122],{"class":97},[14,1706,1707],{},"What to look for:",[1385,1709,1710,1715,1718],{},[315,1711,1712],{},[33,1713,1714],{},"DEBUG=False",[315,1716,1717],{},"secure cookie flags enabled",[315,1719,1720],{},[33,1721,1722],{},"SECRET_KEY_SET=True",[1657,1724,1726],{"id":1725},"inspect-gunicorn-systemd-configuration","Inspect Gunicorn systemd configuration",[26,1728,1730],{"className":28,"code":1729,"language":30,"meta":31,"style":31},"sudo systemctl cat gunicorn\nsudo systemctl status gunicorn --no-pager\nsudo journalctl -u gunicorn -n 100 --no-pager\n",[33,1731,1732,1743,1755],{"__ignoreMap":31},[36,1733,1734,1736,1738,1741],{"class":38,"line":39},[36,1735,139],{"class":93},[36,1737,142],{"class":97},[36,1739,1740],{"class":97}," cat",[36,1742,148],{"class":97},[36,1744,1745,1747,1749,1751,1753],{"class":38,"line":46},[36,1746,139],{"class":93},[36,1748,142],{"class":97},[36,1750,623],{"class":97},[36,1752,626],{"class":97},[36,1754,629],{"class":73},[36,1756,1757,1759,1761,1763,1765,1767,1769],{"class":38,"line":63},[36,1758,139],{"class":93},[36,1760,1340],{"class":97},[36,1762,1343],{"class":73},[36,1764,626],{"class":97},[36,1766,1348],{"class":73},[36,1768,1351],{"class":73},[36,1770,629],{"class":73},[14,1772,1707],{},[1385,1774,1775,1785,1791,1794],{},[315,1776,1777,1780,1781,1784],{},[33,1778,1779],{},"User="," and ",[33,1782,1783],{},"Group="," set to a non-root account",[315,1786,1787,1790],{},[33,1788,1789],{},"EnvironmentFile="," points to the correct path",[315,1792,1793],{},"hardening directives are applied",[315,1795,1796],{},"no startup failures from permissions or missing environment variables",[1657,1798,1800],{"id":1799},"validate-nginx-security-configuration","Validate Nginx security configuration",[26,1802,1804],{"className":28,"code":1803,"language":30,"meta":31,"style":31},"sudo nginx -t\nsudo systemctl status nginx --no-pager\nsudo grep -R \"server_name\\|ssl_certificate\\|add_header\\|client_max_body_size\" \u002Fetc\u002Fnginx\u002Fsites-enabled \u002Fetc\u002Fnginx\u002Fnginx.conf\n",[33,1805,1806,1814,1826],{"__ignoreMap":31},[36,1807,1808,1810,1812],{"class":38,"line":39},[36,1809,139],{"class":93},[36,1811,273],{"class":97},[36,1813,1540],{"class":73},[36,1815,1816,1818,1820,1822,1824],{"class":38,"line":46},[36,1817,139],{"class":93},[36,1819,142],{"class":97},[36,1821,623],{"class":97},[36,1823,273],{"class":97},[36,1825,629],{"class":73},[36,1827,1828,1830,1832,1834,1837,1840],{"class":38,"line":63},[36,1829,139],{"class":93},[36,1831,1046],{"class":97},[36,1833,1049],{"class":73},[36,1835,1836],{"class":97}," \"server_name\\|ssl_certificate\\|add_header\\|client_max_body_size\"",[36,1838,1839],{"class":97}," \u002Fetc\u002Fnginx\u002Fsites-enabled",[36,1841,1842],{"class":97}," \u002Fetc\u002Fnginx\u002Fnginx.conf\n",[14,1844,1707],{},[1385,1846,1847,1850,1853,1856],{},[315,1848,1849],{},"HTTP to HTTPS redirect exists",[315,1851,1852],{},"valid certificate paths",[315,1854,1855],{},"headers are present in the TLS block",[315,1857,1858],{},"upload\u002Fbody limits are defined where needed",[1657,1860,1862],{"id":1861},"test-headers-and-redirects","Test headers and redirects",[26,1864,1866],{"className":28,"code":1865,"language":30,"meta":31,"style":31},"curl -I http:\u002F\u002Fyour-domain.com\ncurl -I https:\u002F\u002Fyour-domain.com\n",[33,1867,1868,1876],{"__ignoreMap":31},[36,1869,1870,1872,1874],{"class":38,"line":39},[36,1871,248],{"class":93},[36,1873,251],{"class":73},[36,1875,823],{"class":97},[36,1877,1878,1880,1882],{"class":38,"line":46},[36,1879,248],{"class":93},[36,1881,251],{"class":73},[36,1883,254],{"class":97},[14,1885,1707],{},[1385,1887,1888,1898,1904,1909],{},[315,1889,1890,1893,1894,1897],{},[33,1891,1892],{},"301"," or ",[33,1895,1896],{},"308"," redirect from HTTP to HTTPS",[315,1899,1900,1903],{},[33,1901,1902],{},"Strict-Transport-Security"," after HTTPS is stable",[315,1905,1906],{},[33,1907,1908],{},"X-Content-Type-Options: nosniff",[315,1910,1911,1912,1915],{},"correct ",[33,1913,1914],{},"Set-Cookie"," attributes",[1657,1917,1919],{"id":1918},"verify-listening-ports-and-firewall-state","Verify listening ports and firewall state",[26,1921,1922],{"className":28,"code":1069,"language":30,"meta":31,"style":31},[33,1923,1924,1930],{"__ignoreMap":31},[36,1925,1926,1928],{"class":38,"line":39},[36,1927,1076],{"class":93},[36,1929,1079],{"class":73},[36,1931,1932,1934,1936,1938],{"class":38,"line":46},[36,1933,139],{"class":93},[36,1935,203],{"class":97},[36,1937,623],{"class":97},[36,1939,1090],{"class":97},[14,1941,1707],{},[1385,1943,1944,1947,1950],{},[315,1945,1946],{},"only expected public ports exposed",[315,1948,1949],{},"Gunicorn not listening on a public interface",[315,1951,1952],{},"database ports not publicly reachable unless explicitly required",[1657,1954,1956],{"id":1955},"check-file-and-secret-permissions","Check file and secret permissions",[26,1958,1960],{"className":28,"code":1959,"language":30,"meta":31,"style":31},"ls -lah \u002Fpath\u002Fto\u002Fapp\nls -lah \u002Fpath\u002Fto\u002Fapp\u002F.env\nstat \u002Fpath\u002Fto\u002Fapp\u002F.env\nsudo -u www-data test -r \u002Fpath\u002Fto\u002Fapp\u002F.env && echo readable || echo not-readable\n",[33,1961,1962,1973,1982,1989],{"__ignoreMap":31},[36,1963,1964,1967,1970],{"class":38,"line":39},[36,1965,1966],{"class":93},"ls",[36,1968,1969],{"class":73}," -lah",[36,1971,1972],{"class":97}," \u002Fpath\u002Fto\u002Fapp\n",[36,1974,1975,1977,1979],{"class":38,"line":46},[36,1976,1966],{"class":93},[36,1978,1969],{"class":73},[36,1980,1981],{"class":97}," \u002Fpath\u002Fto\u002Fapp\u002F.env\n",[36,1983,1984,1987],{"class":38,"line":63},[36,1985,1986],{"class":73},"stat",[36,1988,1981],{"class":97},[36,1990,1991,1993,1995,1998,2001,2004,2007,2009,2012,2015,2018,2021],{"class":38,"line":77},[36,1992,139],{"class":93},[36,1994,1343],{"class":73},[36,1996,1997],{"class":97}," www-data",[36,1999,2000],{"class":97}," test",[36,2002,2003],{"class":73}," -r",[36,2005,2006],{"class":97}," \u002Fpath\u002Fto\u002Fapp\u002F.env",[36,2008,279],{"class":53},[36,2010,2011],{"class":73},"echo",[36,2013,2014],{"class":97}," readable",[36,2016,2017],{"class":49}," ||",[36,2019,2020],{"class":73}," echo",[36,2022,2023],{"class":97}," not-readable\n",[14,2025,1707],{},[1385,2027,2028,2031,2034],{},[315,2029,2030],{},"secrets not world-readable",[315,2032,2033],{},"app user can read only what it needs",[315,2035,2036],{},"upload directories are writable only where required",[1657,2038,2040],{"id":2039},"audit-package-state","Audit package state",[26,2042,2044],{"className":28,"code":2043,"language":30,"meta":31,"style":31},"python -m pip list --outdated\npython -m pip audit\nsudo apt list --upgradable\n",[33,2045,2046,2058,2068],{"__ignoreMap":31},[36,2047,2048,2050,2052,2054,2056],{"class":38,"line":39},[36,2049,94],{"class":93},[36,2051,1280],{"class":73},[36,2053,1283],{"class":97},[36,2055,1286],{"class":97},[36,2057,1289],{"class":73},[36,2059,2060,2062,2064,2066],{"class":38,"line":46},[36,2061,94],{"class":93},[36,2063,1280],{"class":73},[36,2065,1283],{"class":97},[36,2067,1300],{"class":97},[36,2069,2070,2072,2074,2076],{"class":38,"line":63},[36,2071,139],{"class":93},[36,2073,1315],{"class":97},[36,2075,1286],{"class":97},[36,2077,1320],{"class":73},[14,2079,1707],{},[1385,2081,2082,2085,2088],{},[315,2083,2084],{},"vulnerable Python packages",[315,2086,2087],{},"pending security updates",[315,2089,2090],{},"outdated web stack components",[18,2092,2094],{"id":2093},"checklist","Checklist",[1385,2096,2099,2108,2117,2123,2129,2135,2141,2147,2153,2159,2169,2175,2181,2187,2193,2199,2205,2211],{"className":2097},[2098],"contains-task-list",[315,2100,2103,2107],{"className":2101},[2102],"task-list-item",[2104,2105],"input",{"disabled":80,"type":2106},"checkbox"," Flask debug mode is disabled in production.",[315,2109,2111,2113,2114,2116],{"className":2110},[2102],[2104,2112],{"disabled":80,"type":2106}," ",[33,2115,386],{}," is strong and not committed to version control.",[315,2118,2120,2122],{"className":2119},[2102],[2104,2121],{"disabled":80,"type":2106}," Database credentials and API secrets are loaded from environment variables or a secure secret source.",[315,2124,2126,2128],{"className":2125},[2102],[2104,2127],{"disabled":80,"type":2106}," Gunicorn runs as a dedicated non-root user.",[315,2130,2132,2134],{"className":2131},[2102],[2104,2133],{"disabled":80,"type":2106}," systemd service files and environment files have restricted permissions.",[315,2136,2138,2140],{"className":2137},[2102],[2104,2139],{"disabled":80,"type":2106}," Gunicorn is bound to a Unix socket or localhost, not a public interface unless required.",[315,2142,2144,2146],{"className":2143},[2102],[2104,2145],{"disabled":80,"type":2106}," UFW or cloud firewall exposes only required ports.",[315,2148,2150,2152],{"className":2149},[2102],[2104,2151],{"disabled":80,"type":2106}," Nginx is serving a valid TLS certificate.",[315,2154,2156,2158],{"className":2155},[2102],[2104,2157],{"disabled":80,"type":2106}," HTTP requests redirect to HTTPS.",[315,2160,2162,2113,2164,1780,2166,2168],{"className":2161},[2102],[2104,2163],{"disabled":80,"type":2106},[33,2165,856],{},[33,2167,866],{}," are enabled.",[315,2170,2172,2174],{"className":2171},[2102],[2104,2173],{"disabled":80,"type":2106}," Security headers are returned by Nginx or the app.",[315,2176,2178,2180],{"className":2177},[2102],[2104,2179],{"disabled":80,"type":2106}," File upload limits and validation are configured.",[315,2182,2184,2186],{"className":2183},[2102],[2104,2185],{"disabled":80,"type":2106}," Uploaded files are stored with safe permissions and non-executable paths.",[315,2188,2190,2192],{"className":2189},[2102],[2104,2191],{"disabled":80,"type":2106}," Database user permissions are limited to application needs.",[315,2194,2196,2198],{"className":2195},[2102],[2104,2197],{"disabled":80,"type":2106}," Server packages and Python dependencies are updated.",[315,2200,2202,2204],{"className":2201},[2102],[2104,2203],{"disabled":80,"type":2106}," Access logs and error logs are enabled and reviewed.",[315,2206,2208,2210],{"className":2207},[2102],[2104,2209],{"disabled":80,"type":2106}," Backups exist and restore testing has been performed.",[315,2212,2214,2216],{"className":2213},[2102],[2104,2215],{"disabled":80,"type":2106}," No default credentials, sample configs, or unused services remain on the server.",[18,2218,2220],{"id":2219},"related-guides","Related Guides",[1385,2222,2223,2227,2233],{},[315,2224,2225],{},[453,2226,456],{"href":455},[315,2228,2229],{},[453,2230,2232],{"href":2231},"\u002Fchecklist\u002Fflask-production-checklist-everything-you-must-do","Flask Production Checklist (Everything You Must Do)",[315,2234,2235],{},[453,2236,838],{"href":837},[18,2238,2240],{"id":2239},"faq","FAQ",[14,2242,2243,2246,2248,2249,2251],{},[318,2244,2245],{},"Q: What are the minimum security settings for a Flask production deploy?",[322,2247],{},"\nA: Disable debug, use a strong ",[33,2250,386],{},", run behind Nginx with HTTPS, load secrets from environment variables, run Gunicorn as a non-root user, and restrict public ports.",[14,2253,2254,2260,2262],{},[318,2255,2256,2257,2259],{},"Q: Should I store ",[33,2258,305],{}," on the server?",[322,2261],{},"\nA: Only if necessary, with strict permissions and limited ownership. A secrets manager or protected systemd environment file is safer.",[14,2264,2265,2268,2270],{},[318,2266,2267],{},"Q: Do I need both firewall rules and Nginx restrictions?",[322,2269],{},"\nA: Yes. Nginx protects at the web layer; firewall rules reduce exposed network surface.",[14,2272,2273,2276,2278,2279,2281,2282,2284,2285,955,2288,2291,2292,2295],{},[318,2274,2275],{},"Q: How do I verify secure cookies are working?",[322,2277],{},"\nA: Inspect ",[33,2280,1914],{}," headers in browser developer tools or with ",[33,2283,248],{}," and confirm ",[33,2286,2287],{},"Secure",[33,2289,2290],{},"HttpOnly",", and ",[33,2293,2294],{},"SameSite"," attributes are present.",[14,2297,2298,2301,2303],{},[318,2299,2300],{},"Q: Is UFW enough to secure database access?",[322,2302],{},"\nA: It helps, but also restrict access in cloud security groups, database bind settings, and database user privileges.",[18,2305,2307],{"id":2306},"final-takeaway","Final Takeaway",[14,2309,2310],{},"Flask security in production is mostly a deployment discipline problem: disable debug, protect secrets, enforce HTTPS, isolate services, restrict network exposure, and validate the result continuously.",[14,2312,2313],{},"Use this checklist as a release gate and re-run it after every infrastructure or application change.",[2315,2316,2317],"style",{},"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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":31,"searchDepth":46,"depth":46,"links":2319},[2320,2321,2322,2323,2324,2333,2334,2335,2336],{"id":20,"depth":46,"text":21},{"id":295,"depth":46,"text":296},{"id":309,"depth":46,"text":310},{"id":1581,"depth":46,"text":1582},{"id":1651,"depth":46,"text":1652,"children":2325},[2326,2327,2328,2329,2330,2331,2332],{"id":1659,"depth":63,"text":1660},{"id":1725,"depth":63,"text":1726},{"id":1799,"depth":63,"text":1800},{"id":1861,"depth":63,"text":1862},{"id":1918,"depth":63,"text":1919},{"id":1955,"depth":63,"text":1956},{"id":2039,"depth":63,"text":2040},{"id":2093,"depth":46,"text":2094},{"id":2219,"depth":46,"text":2220},{"id":2239,"depth":46,"text":2240},{"id":2306,"depth":46,"text":2307},"Complete guide on flask security checklist for Flask production environments.","md",{"ogTitle":5,"ogDescription":2337,"twitterCard":2340,"robots":2341,"canonical":2342},"summary_large_image","index, follow","https:\u002F\u002Fflask-deployment.com\u002Fchecklist\u002Fflask-security-checklist","\u002Fchecklist\u002Fflask-security-checklist",{"title":5,"description":2337},"checklist\u002Fflask-security-checklist","FewcH9cj22_o0hqtpmW0V1Wm7BC0cnzGi1bkx7ygHRk",1776805765071]