[{"data":1,"prerenderedAt":662},["ShallowReactive",2],{"page-\u002Fpratiques\u002Fsecurite-de-base":3,"pieces-pratiques":619,"autour-\u002Fpratiques\u002Fsecurite-de-base":644,"prerequis-\u002Fpratiques\u002Fsecurite-de-base":649,"plan-pratiques":651},{"id":4,"title":5,"body":6,"description":586,"extension":587,"ligne":588,"meta":589,"motsCles":590,"navigation":594,"niveau":595,"path":596,"prerequis":597,"ressources":599,"seo":610,"sources":611,"stem":617,"__hash__":618},"docs\u002Fpratiques\u002F05.securite-de-base.md","Sécurité de base",{"type":7,"value":8,"toc":576},"minimark",[9,14,27,31,35,39,47,55,92,95,106,113,119,135,139,142,174,188,191,205,224,228,234,237,399,402,406,409,415,438,448,451,503,509,513,518,524,534,538,572],[10,11,13],"h2",{"id":12},"ce-que-tu-vas-apprendre","Ce que tu vas apprendre",[15,16,17,21,24],"ul",{},[18,19,20],"li",{},"Garder les clés et mots de passe hors de ton dépôt.",[18,22,23],{},"Traiter toute donnée venant d'un utilisateur comme potentiellement dangereuse.",[18,25,26],{},"Surveiller tes dépendances et comprendre pourquoi HTTPS compte.",[10,28,30],{"id":29},"ce-qui-est-en-jeu","Ce qui est en jeu",[32,33,34],"p",{},"Un site en ligne est visible de tous, y compris de personnes malveillantes. Quelques réflexes simples évitent la plupart des accidents. La sécurité ne demande pas d'être expert, mais de ne pas faire d'erreurs évidentes.",[10,36,38],{"id":37},"les-secrets-restent-hors-du-dépôt","Les secrets restent hors du dépôt",[32,40,41,42,46],{},"Un ",[43,44,45],"strong",{},"secret"," est une valeur qui donne un accès : clé d'API, mot de passe de base de données, jeton. Si quelqu'un d'autre la lit, il peut agir à ta place, parfois à tes frais.",[32,48,49,50,54],{},"On range les secrets dans un fichier ",[51,52,53],"code",{},".env"," qui n'est jamais commité :",[56,57,62],"pre",{"className":58,"code":59,"filename":53,"language":60,"meta":61,"style":61},"language-bash shiki shiki-themes github-light-default github-dark-default github-dark-default","DATABASE_URL=postgres:\u002F\u002Futilisateur:motdepasse@serveur\u002Frecettes\nNUXT_API_SECRET=une-cle-longue-et-aleatoire\n","bash","",[51,63,64,81],{"__ignoreMap":61},[65,66,69,73,77],"span",{"class":67,"line":68},"line",1,[65,70,72],{"class":71},"scfrF","DATABASE_URL",[65,74,76],{"class":75},"sOnC1","=",[65,78,80],{"class":79},"shWOn","postgres:\u002F\u002Futilisateur:motdepasse@serveur\u002Frecettes\n",[65,82,84,87,89],{"class":67,"line":83},2,[65,85,86],{"class":71},"NUXT_API_SECRET",[65,88,76],{"class":75},[65,90,91],{"class":79},"une-cle-longue-et-aleatoire\n",[32,93,94],{},"Puis on l'exclut de Git :",[56,96,99],{"className":58,"code":97,"filename":98,"language":60,"meta":61,"style":61},".env\n",".gitignore",[51,100,101],{"__ignoreMap":61},[65,102,103],{"class":67,"line":68},[65,104,97],{"class":105},"sIXEE",[32,107,108,109,112],{},"On commite à la place un fichier ",[51,110,111],{},".env.example"," qui liste les noms sans les vraies valeurs. Un collègue sait ainsi quoi renseigner.",[114,115,116],"warning",{},[32,117,118],{},"Un secret commité reste dans l'historique de Git, même si tu supprimes le fichier ensuite. Si ça arrive, considère la clé comme compromise : révoque-la chez le fournisseur et crée-en une nouvelle.",[32,120,121,122,125,126,130,131,134],{},"GitHub propose l'",[43,123,124],{},"analyse de secrets"," (",[127,128,129],"em",{},"secret scanning","), qui repère les clés connues dans un dépôt. Sa ",[43,132,133],{},"protection contre les push"," peut bloquer un envoi contenant un secret avant qu'il atteigne le dépôt. Ne compte pas dessus comme seule défense.",[10,136,138],{"id":137},"ne-jamais-faire-confiance-à-lutilisateur","Ne jamais faire confiance à l'utilisateur",[32,140,141],{},"Tout ce que l'utilisateur tape dans un formulaire, une adresse ou un champ peut être n'importe quoi, y compris du code. Voici un commentaire sur une recette :",[56,143,147],{"className":144,"code":145,"language":146,"meta":61,"style":61},"language-js shiki shiki-themes github-light-default github-dark-default github-dark-default","const commentaire = formulaire.commentaire\npage.innerHTML = commentaire\n","js",[51,148,149,164],{"__ignoreMap":61},[65,150,151,154,158,161],{"class":67,"line":68},[65,152,153],{"class":75},"const",[65,155,157],{"class":156},"s5iOY"," commentaire",[65,159,160],{"class":75}," =",[65,162,163],{"class":71}," formulaire.commentaire\n",[65,165,166,169,171],{"class":67,"line":83},[65,167,168],{"class":71},"page.innerHTML ",[65,170,76],{"class":75},[65,172,173],{"class":71}," commentaire\n",[32,175,176,177,180,181,125,184,187],{},"Si un visiteur écrit ",[51,178,179],{},"\u003Cimg src=x onerror=\"...\">",", le navigateur exécute ce code dans la page de tous les autres visiteurs. C'est une faille ",[43,182,183],{},"XSS",[127,185,186],{},"Cross-site scripting",", script inter-site), décrite sur MDN.",[32,189,190],{},"La parade principale : afficher du texte comme du texte.",[56,192,194],{"className":144,"code":193,"language":146,"meta":61,"style":61},"page.textContent = commentaire\n",[51,195,196],{"__ignoreMap":61},[65,197,198,201,203],{"class":67,"line":68},[65,199,200],{"class":71},"page.textContent ",[65,202,76],{"class":75},[65,204,173],{"class":71},[32,206,207,208,211,212,215,216,219,220,223],{},"Avec ",[51,209,210],{},"textContent",", le navigateur affiche les caractères tels quels sans les interpréter comme du HTML. Les frameworks comme Vue protègent par défaut quand tu écris ",[51,213,214],{},"{{ commentaire }}",". Le danger apparaît quand tu contournes cette protection, par exemple avec ",[51,217,218],{},"v-html"," ou ",[51,221,222],{},"innerHTML",".",[10,225,227],{"id":226},"valider-côté-serveur","Valider côté serveur",[32,229,41,230,233],{},[43,231,232],{},"formulaire"," peut être vérifié dans le navigateur pour aider l'utilisateur : champ obligatoire, format d'e-mail. Mais n'importe qui peut contourner cette vérification en envoyant la requête directement.",[32,235,236],{},"La règle : la validation côté navigateur sert au confort, la validation côté serveur sert à la sécurité. Vérifie toujours sur le serveur :",[56,238,243],{"className":239,"code":240,"filename":241,"language":242,"meta":61,"style":61},"language-ts shiki shiki-themes github-light-default github-dark-default github-dark-default","export default defineEventHandler(async (event) => {\n  const { titre } = await readBody(event)\n  if (typeof titre !== 'string' || titre.trim().length === 0 || titre.length > 100) {\n    throw createError({ statusCode: 400, statusMessage: 'Titre invalide' })\n  }\n  \u002F\u002F ... enregistrer la recette\n})\n","server\u002Fapi\u002Frecettes.post.ts","ts",[51,244,245,272,297,353,377,383,390],{"__ignoreMap":61},[65,246,247,250,253,257,260,263,266,269],{"class":67,"line":68},[65,248,249],{"class":75},"export",[65,251,252],{"class":75}," default",[65,254,256],{"class":255},"syCW7"," defineEventHandler",[65,258,259],{"class":105},"(",[65,261,262],{"class":75},"async",[65,264,265],{"class":105}," (event) ",[65,267,268],{"class":75},"=>",[65,270,271],{"class":71}," {\n",[65,273,274,277,280,283,286,288,291,294],{"class":67,"line":83},[65,275,276],{"class":75},"  const",[65,278,279],{"class":71}," { ",[65,281,282],{"class":156},"titre",[65,284,285],{"class":71}," } ",[65,287,76],{"class":75},[65,289,290],{"class":75}," await",[65,292,293],{"class":255}," readBody",[65,295,296],{"class":71},"(event)\n",[65,298,300,303,305,308,311,314,317,320,323,326,329,332,335,338,340,342,344,347,350],{"class":67,"line":299},3,[65,301,302],{"class":75},"  if",[65,304,125],{"class":71},[65,306,307],{"class":75},"typeof",[65,309,310],{"class":71}," titre ",[65,312,313],{"class":75},"!==",[65,315,316],{"class":79}," 'string'",[65,318,319],{"class":75}," ||",[65,321,322],{"class":71}," titre.",[65,324,325],{"class":255},"trim",[65,327,328],{"class":71},"().",[65,330,331],{"class":156},"length",[65,333,334],{"class":75}," ===",[65,336,337],{"class":156}," 0",[65,339,319],{"class":75},[65,341,322],{"class":71},[65,343,331],{"class":156},[65,345,346],{"class":75}," >",[65,348,349],{"class":156}," 100",[65,351,352],{"class":71},") {\n",[65,354,356,359,362,365,368,371,374],{"class":67,"line":355},4,[65,357,358],{"class":75},"    throw",[65,360,361],{"class":255}," createError",[65,363,364],{"class":71},"({ statusCode: ",[65,366,367],{"class":156},"400",[65,369,370],{"class":71},", statusMessage: ",[65,372,373],{"class":79},"'Titre invalide'",[65,375,376],{"class":71}," })\n",[65,378,380],{"class":67,"line":379},5,[65,381,382],{"class":71},"  }\n",[65,384,386],{"class":67,"line":385},6,[65,387,389],{"class":388},"sd6ml","  \u002F\u002F ... enregistrer la recette\n",[65,391,393,396],{"class":67,"line":392},7,[65,394,395],{"class":71},"}",[65,397,398],{"class":105},")\n",[32,400,401],{},"Tu contrôles le type, la présence et la longueur avant d'utiliser la donnée. Si tu utilises une base de données, passe par les requêtes paramétrées ou la bibliothèque officielle plutôt que de coller du texte utilisateur dans une requête SQL.",[10,403,405],{"id":404},"surveiller-les-dépendances","Surveiller les dépendances",[32,407,408],{},"Ton projet utilise des dizaines de bibliothèques écrites par d'autres, avec leurs propres dépendances. Une faille découverte dans l'une d'elles devient ta faille.",[32,410,411,414],{},[51,412,413],{},"npm audit"," interroge le registre npm et signale les vulnérabilités connues :",[56,416,418],{"className":58,"code":417,"language":60,"meta":61,"style":61},"npm audit\nnpm audit fix\n",[51,419,420,428],{"__ignoreMap":61},[65,421,422,425],{"class":67,"line":68},[65,423,424],{"class":105},"npm",[65,426,427],{"class":79}," audit\n",[65,429,430,432,435],{"class":67,"line":83},[65,431,424],{"class":105},[65,433,434],{"class":79}," audit",[65,436,437],{"class":79}," fix\n",[32,439,440,443,444,447],{},[51,441,442],{},"npm audit fix"," applique les corrections possibles. L'option ",[51,445,446],{},"--force"," peut installer des versions majeures qui cassent ton code : lis le résultat avant de l'utiliser.",[32,449,450],{},"D'autres habitudes aident :",[452,453,454,467],"table",{},[455,456,457],"thead",{},[458,459,460,464],"tr",{},[461,462,463],"th",{},"Commande",[461,465,466],{},"Rôle",[468,469,470,481,494],"tbody",{},[458,471,472,478],{},[473,474,475],"td",{},[51,476,477],{},"npm outdated",[473,479,480],{},"Liste les paquets qui ont une version plus récente.",[458,482,483,488],{},[473,484,485],{},[51,486,487],{},"npm ci",[473,489,490,491,223],{},"Installe exactement ce que décrit ",[51,492,493],{},"package-lock.json",[458,495,496,500],{},[473,497,498],{},[51,499,413],{},[473,501,502],{},"Signale les vulnérabilités connues.",[32,504,505,506,508],{},"Commite le fichier ",[51,507,493],{}," pour que tout le monde installe les mêmes versions. Installe un paquet seulement si tu en as besoin, et vérifie qu'il est maintenu.",[10,510,512],{"id":511},"https","HTTPS",[32,514,515,517],{},[43,516,512],{}," est la version chiffrée de HTTP. Les échanges entre le navigateur et le serveur ne peuvent pas être lus ni modifiés en chemin, et le visiteur sait qu'il parle bien à ton site.",[32,519,520,521,223],{},"Sans HTTPS, un mot de passe envoyé dans un formulaire peut être lu sur un réseau public. Les hébergeurs modernes fournissent un certificat gratuit et le renouvellement automatique. Vérifie que l'adresse de ton site commence par ",[51,522,523],{},"https:\u002F\u002F",[525,526,527],"note",{},[32,528,529,530,533],{},"Beaucoup de fonctions du navigateur, comme la géolocalisation, ne marchent que sur une page en HTTPS. Le développement local sur ",[51,531,532],{},"localhost"," fait exception.",[10,535,537],{"id":536},"à-retenir","À retenir",[15,539,540,549,552,558,561,569],{},[18,541,542,543,545,546,548],{},"Les secrets vivent dans un ",[51,544,53],{}," ignoré par Git, avec un ",[51,547,111],{}," sans valeurs.",[18,550,551],{},"Un secret commité est compromis : révoque-le, supprimer le fichier ne suffit pas.",[18,553,554,555,557],{},"Toute donnée venue d'un utilisateur est suspecte : affiche-la comme du texte (",[51,556,210],{},").",[18,559,560],{},"La validation côté serveur est obligatoire, celle du navigateur ne sert qu'au confort.",[18,562,563,565,566,568],{},[51,564,413],{}," signale les failles connues dans tes dépendances, ",[51,567,487],{}," installe les versions du lockfile.",[18,570,571],{},"HTTPS chiffre les échanges et doit être actif sur tout site en ligne.",[573,574,575],"style",{},"html pre.shiki code .scfrF, html code.shiki .scfrF{--shiki-light:#1F2328;--shiki-default:#E6EDF3;--shiki-dark:#E6EDF3}html pre.shiki code .sOnC1, html code.shiki .sOnC1{--shiki-light:#CF222E;--shiki-default:#FF7B72;--shiki-dark:#FF7B72}html pre.shiki code .shWOn, html code.shiki .shWOn{--shiki-light:#0A3069;--shiki-default:#A5D6FF;--shiki-dark:#A5D6FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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 .sIXEE, html code.shiki .sIXEE{--shiki-light:#953800;--shiki-default:#FFA657;--shiki-dark:#FFA657}html pre.shiki code .s5iOY, html code.shiki .s5iOY{--shiki-light:#0550AE;--shiki-default:#79C0FF;--shiki-dark:#79C0FF}html pre.shiki code .syCW7, html code.shiki .syCW7{--shiki-light:#8250DF;--shiki-default:#D2A8FF;--shiki-dark:#D2A8FF}html pre.shiki code .sd6ml, html code.shiki .sd6ml{--shiki-light:#6E7781;--shiki-default:#8B949E;--shiki-dark:#8B949E}",{"title":61,"searchDepth":83,"depth":299,"links":577},[578,579,580,581,582,583,584,585],{"id":12,"depth":83,"text":13},{"id":29,"depth":83,"text":30},{"id":37,"depth":83,"text":38},{"id":137,"depth":83,"text":138},{"id":226,"depth":83,"text":227},{"id":404,"depth":83,"text":405},{"id":511,"depth":83,"text":512},{"id":536,"depth":83,"text":537},"Protéger les secrets avec .env, ne pas faire confiance aux entrées des utilisateurs, surveiller les dépendances et utiliser HTTPS.","md","pratiques",{},[45,53,591,183,592,413,593,512],"clé d'API","validation","dépendances",true,"intermediaire","\u002Fpratiques\u002Fsecurite-de-base",[598],"\u002Fpratiques\u002Fgerer-un-projet",[600,604,607],{"nom":601,"url":602,"type":603},"MDN, Sécurité","https:\u002F\u002Fdeveloper.mozilla.org\u002Ffr\u002Fdocs\u002FWeb\u002FSecurity","doc",{"nom":605,"url":606,"type":603},"MDN, Script inter-site (XSS)","https:\u002F\u002Fdeveloper.mozilla.org\u002Ffr\u002Fdocs\u002FWeb\u002FSecurity\u002FAttacks\u002FXSS",{"nom":608,"url":609,"type":603},"GitHub Docs, Analyse de secrets","https:\u002F\u002Fdocs.github.com\u002Ffr\u002Fcode-security\u002Fsecret-scanning\u002Fintroduction\u002Fabout-secret-scanning",{"title":5,"description":586},[612,613,614],{"nom":605,"url":606},{"nom":608,"url":609},{"nom":615,"url":616},"Documentation de npm audit (en anglais)","https:\u002F\u002Fdocs.npmjs.com\u002Fcli\u002Fv11\u002Fcommands\u002Fnpm-audit","pratiques\u002F05.securite-de-base","du-Fyyskfg8eEOiAEe90_6LdqPoTsyi19knZeABDGdY",[620,624,628,632,635,636,640],{"path":621,"title":622,"stem":623},"\u002Fpratiques\u002Fnommer-et-organiser","Nommer et organiser son code","pratiques\u002F01.nommer-et-organiser",{"path":625,"title":626,"stem":627},"\u002Fpratiques\u002Flire-une-erreur","Lire une erreur","pratiques\u002F02.lire-une-erreur",{"path":629,"title":630,"stem":631},"\u002Fpratiques\u002Fdeboguer","Déboguer méthodiquement","pratiques\u002F03.deboguer",{"path":598,"title":633,"stem":634},"Gérer un projet","pratiques\u002F04.gerer-un-projet",{"path":596,"title":5,"stem":617},{"path":637,"title":638,"stem":639},"\u002Fpratiques\u002Fqualite-et-mise-en-ligne","Qualité et mise en ligne","pratiques\u002F06.qualite-et-mise-en-ligne",{"path":641,"title":642,"stem":643},"\u002Fpratiques","Bonnes pratiques","pratiques\u002Findex",[645,647],{"title":633,"path":598,"stem":634,"description":646,"children":-1},"Découper un projet en tâches, suivre l'avancement avec un tableau Kanban et les issues GitHub, et écrire un README utile.",{"title":638,"path":637,"stem":639,"description":648,"children":-1},"Garder un code propre avec ESLint, Prettier et des tests, passer par une revue de code, puis déployer avec les bonnes variables d'environnement.",[650],{"path":598,"title":633,"ligne":588},[652,654,655,656,657,658,660],{"path":621,"title":622,"niveau":653,"stem":623},"debutant",{"path":625,"title":626,"niveau":653,"stem":627},{"path":629,"title":630,"niveau":653,"stem":631},{"path":598,"title":633,"niveau":595,"stem":634},{"path":596,"title":5,"niveau":595,"stem":617},{"path":637,"title":638,"niveau":659,"stem":639},"avance",{"path":641,"title":642,"niveau":661,"stem":643},"memo",1794583452553]