Supprimer les fichiers sensibles et leurs validations de l'historique Git


354

Je voudrais mettre un projet Git sur GitHub mais il contient certains fichiers avec des données sensibles (noms d'utilisateur et mots de passe, comme /config/deploy.rb pour capistrano).

Je sais que je peux ajouter ces noms de fichiers à .gitignore , mais cela ne supprimera pas leur historique dans Git.

Je ne veux pas non plus recommencer en supprimant le répertoire /.git.

Existe-t-il un moyen de supprimer toutes les traces d'un fichier particulier dans votre historique Git?



Réponses:


449

À toutes fins pratiques, la première chose qui devrait vous inquiéter est de CHANGER VOS MOTS DE PASSE! Il ne ressort pas clairement de votre question si votre référentiel git est entièrement local ou si vous avez un référentiel distant ailleurs encore; s'il est éloigné et n'est pas protégé des autres, vous avez un problème. Si quelqu'un a cloné ce référentiel avant de résoudre ce problème, il aura une copie de vos mots de passe sur sa machine locale, et il n'y a aucun moyen de les forcer à mettre à jour vers votre version "fixe" avec elle est passée de l'histoire. La seule chose sûre que vous pouvez faire est de changer votre mot de passe en quelque chose d'autre partout où vous l'avez utilisé.


Avec cela à l'écart, voici comment y remédier. GitHub a répondu exactement à cette question en tant que FAQ :

Remarque pour les utilisateurs de Windows : utilisez des guillemets doubles (") au lieu de simples dans cette commande

git filter-branch --index-filter \
'git update-index --remove PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA' <introduction-revision-sha1>..HEAD
git push --force --verbose --dry-run
git push --force

Mise à jour 2019:

Voici le code actuel de la FAQ:

  git filter-branch --force --index-filter \
  "git rm --cached --ignore-unmatch PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA" \
  --prune-empty --tag-name-filter cat -- --all
  git push --force --verbose --dry-run
  git push --force

Gardez à l'esprit qu'une fois que vous avez poussé ce code vers un référentiel distant comme GitHub et que d'autres ont cloné ce référentiel distant, vous êtes maintenant dans une situation où vous réécrivez l'historique. Lorsque d'autres essaient de dérouler vos dernières modifications après cela, ils recevront un message indiquant que les modifications ne peuvent pas être appliquées car ce n'est pas une avance rapide.

Pour résoudre ce problème, ils devront soit supprimer leur référentiel existant et le recloner, soit suivre les instructions sous "RECOVERING FROM UPSTREAM REBASE" dans la page de manuel git-rebase .

Astuce : exécutergit rebase --interactive


À l'avenir, si vous effectuez accidentellement des modifications avec des informations sensibles mais que vous le remarquez avant de passer à un référentiel distant, il existe des correctifs plus faciles. Si votre dernier commit est celui pour ajouter les informations sensibles, vous pouvez simplement supprimer les informations sensibles, puis exécuter:

git commit -a --amend

Cela modifiera la validation précédente avec toutes les nouvelles modifications que vous avez apportées, y compris les suppressions de fichiers entières effectuées avec a git rm. Si les modifications remontent plus loin dans l'historique mais ne sont toujours pas transmises à un référentiel distant, vous pouvez effectuer un rebasage interactif:

git rebase -i origin/master

Cela ouvre un éditeur avec les validations que vous avez faites depuis votre dernier ancêtre commun avec le référentiel distant. Remplacez «choisir» par «modifier» sur toutes les lignes représentant un commit contenant des informations sensibles, puis enregistrez et quittez. Git vous guidera à travers les changements et vous laissera à un endroit où vous pourrez:

$EDITOR file-to-fix
git commit -a --amend
git rebase --continue

Pour chaque changement avec des informations sensibles. Finalement, vous vous retrouverez dans votre branche et vous pourrez pousser les nouvelles modifications en toute sécurité.


5
Mec parfait, c'est une excellente réponse. Vous sauvez ma journée.
zzeroo

18
Juste pour ajouter un bit - sous Windows, vous devez utiliser des guillemets doubles (") au lieu de simples.
ripper234

4
Je l'ai fait fonctionner. J'étais perdu dans les traductions. J'ai utilisé le lien au lieu de la commande ici. De plus, la commande Windows a fini par exiger des guillemets doubles comme le mentionne ripper234, le chemin complet comme le suggère MigDus, et ne comprenant pas les caractères "\" que le lien a collés en tant que nouveaux indicateurs de retour à la ligne. La commande finale ressemblait à quelque chose comme: git filter-branch --force --index-filter "git rm --cached --ignore-unmatch src [Project] [File]. [Ext]" --prune-empty --tag- chat nom-filtre - --all
Eric Swanson

3
Il semble y avoir des différences importantes entre votre filter-branchcode et celui de la page github à laquelle vous avez lié. Par exemple, leur 3e ligne --prune-empty --tag-name-filter cat -- --all. La solution a-t-elle changé ou manque-t-il quelque chose?
geotheory

2
Cette solution semble assez bonne, mais si j'ai introduit le fichier à supprimer dans la validation initiale <introduction-revision-sha1>..HEADne fonctionne pas. Il supprime uniquement le fichier à partir du deuxième commit. (Comment puis-je inclure la validation initiale dans la plage de validations?) La méthode d'enregistrement est indiquée ici: help.github.com/articles/… git filter-branch --force --index-filter \ 'git rm --cached --ignore-unmatch PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA' \ --prune-empty --tag-name-filter cat -- --all
white_gecko

91

Changer vos mots de passe est une bonne idée, mais pour le processus de suppression des mots de passe de l'historique de votre référentiel, je recommande le BFG Repo-Cleaner , une alternative plus rapide et plus simple à celle git-filter-branchexplicitement conçue pour supprimer les données privées des référentiels Git.

Créez un private.txtfichier répertoriant les mots de passe, etc., que vous souhaitez supprimer (une entrée par ligne), puis exécutez cette commande:

$ java -jar bfg.jar  --replace-text private.txt  my-repo.git

Tous les fichiers sous une taille de seuil (1 Mo par défaut) dans l'historique de votre dépôt seront analysés et toute chaîne correspondante (qui n'est pas dans votre dernier commit) sera remplacée par la chaîne "*** SUPPRIMÉ ***". Vous pouvez ensuite utiliser git gcpour nettoyer les données mortes:

$ git gc --prune=now --aggressive

Le BFG est généralement 10 à 50 fois plus rapide que l'exécution git-filter-branchet les options sont simplifiées et adaptées à ces deux cas d'utilisation courants:

  • Suppression de gros fichiers fous
  • Suppression des mots de passe, des informations d'identification et d'autres données privées

Divulgation complète: je suis l'auteur du BFG Repo-Cleaner.


C'est une option, mais cela pourrait casser votre application lorsque les mots de passe sont utilisés, par exemple pour établir une connexion à la base de données. Je préfère la réponse actuellement acceptée car il est possible de conserver les mots de passe dans votre copie de travail et d'ignorer les fichiers les contenant avec .gitignore.
Henridv

6
C'est une grosse victoire ici. Après quelques essais, j'ai pu utiliser cela pour supprimer très minutieusement les commits contenant des informations sensibles d'un dépôt privé et mettre à jour avec force le dépôt distant avec l'historique révisé. Une note latérale est que vous devez vous assurer que la pointe de votre référentiel (HEAD) est elle-même propre et sans données sensibles car ce commit est considéré comme "protégé" et ne sera pas révisé par cet outil. Si ce n'est pas le cas, nettoyez / remplacez simplement manuellement et git commit. Sinon, +1 pour le nouvel outil dans la boîte à outils du développeur :)
Matt Borja

1
@Henridv Selon mon récent commentaire, cela ne devrait pas casser votre application comme vous pouvez vous y attendre, en supposant que votre application est actuellement située à la pointe ou à la tête de votre branche (c'est-à-dire le dernier commit). Cet outil rendra compte explicitement de votre dernier commit These are your protected commits, and so their contents will NOT be alteredtout en parcourant et en révisant le reste de votre historique de commit. Si vous aviez besoin de revenir en arrière, alors oui, il vous suffirait de faire une recherche ***REMOVED***dans le commit auquel vous venez de revenir.
Matt Borja

1
+1 pour BFG (si Java est installé ou si cela ne vous dérange pas de l'installer). Un hic est que BFG refuse de supprimer un fichier s'il est contenu dans HEAD. Il est donc préférable de faire d'abord un commit où les fichiers souhaités seront supprimés et ensuite d'exécuter BFG. Après cela, vous pouvez annuler ce dernier commit, maintenant cela ne change rien.
Fr0sT

1
Cela devrait en fait être accepté comme la bonne réponse. Fait ce qu'il dit sur la boîte!
gjoris

21

Si vous avez poussé vers GitHub, la poussée forcée ne suffit pas, supprimez le référentiel ou contactez le support

Même si vous forcez à pousser une seconde après, ce n'est pas suffisant comme expliqué ci-dessous.

Les seuls plans d'action valides sont:

  • est ce qui a divulgué une information d'identification modifiable comme un mot de passe?

    • oui: modifiez immédiatement vos mots de passe et envisagez d'utiliser plus de clés OAuth et API!
    • non (photos nues):

      • vous souciez-vous si tous les problèmes du référentiel sont supprimés?

        • non: supprimer le référentiel
        • Oui:

          • Contactez le support
          • si la fuite est très critique pour vous, au point que vous êtes prêt à obtenir un temps d'arrêt du référentiel pour le rendre moins susceptible de fuir, rendez-le privé pendant que vous attendez que le support GitHub vous réponde

Forcer à pousser une seconde plus tard ne suffit pas car:

Si vous supprimez le référentiel au lieu de simplement forcer, cependant, les validations disparaissent immédiatement de l'API et donnent 404, par exemple https://api.github.com/repos/cirosantilli/test-dangling-delete/commits/8c08448b5fbf0f891696819f3b2b2d653f7a3824 Cela fonctionne même si vous recréez un autre référentiel du même nom.

Pour tester cela, j'ai créé un dépôt: https://github.com/cirosantilli/test-dangling et j'ai fait:

git init
git remote add origin git@github.com:cirosantilli/test-dangling.git

touch a
git add .
git commit -m 0
git push

touch b
git add .
git commit -m 1
git push

touch c
git rm b
git add .
git commit --amend --no-edit
git push -f

Voir aussi: Comment supprimer un commit pendant de GitHub?


20

Je recommande ce script de David Underhill, qui a fonctionné comme un charme pour moi.

Il ajoute ces commandes en plus de la branche filtre de natacado pour nettoyer le désordre qu'il laisse derrière lui:

rm -rf .git/refs/original/
git reflog expire --all
git gc --aggressive --prune

Script complet (tout le crédit à David Underhill)

#!/bin/bash
set -o errexit

# Author: David Underhill
# Script to permanently delete files/folders from your git repository.  To use 
# it, cd to your repository's root and then run the script with a list of paths
# you want to delete, e.g., git-delete-history path1 path2

if [ $# -eq 0 ]; then
    exit 0
fi

# make sure we're at the root of git repo
if [ ! -d .git ]; then
    echo "Error: must run this script from the root of a git repository"
    exit 1
fi

# remove all paths passed as arguments from the history of the repo
files=$@
git filter-branch --index-filter \
"git rm -rf --cached --ignore-unmatch $files" HEAD

# remove the temporary history git-filter-branch
# otherwise leaves behind for a long time
rm -rf .git/refs/original/ && \
git reflog expire --all && \
git gc --aggressive --prune

Les deux dernières commandes peuvent mieux fonctionner si elles sont modifiées comme suit:

git reflog expire --expire=now --all && \
git gc --aggressive --prune=now

1
Notez que votre utilisation de expire et prune est incorrecte, si vous ne spécifiez pas la date, par défaut, toutes les validations de plus de 2 semaines sont effectuées pour prune. Ce que vous voulez, c'est tout ce que vous faites:git gc --aggressive --prune=now
Adam Parkin

@Adam Parkin Je vais laisser le code dans la réponse de la même manière car il provient du script sur le site de David Underhill, vous pouvez y commenter et s'il le change je changerais cette réponse car je ne sais vraiment pas git that bien. La commande expire avant le pruneau n'affecte pas cela?
Jason Goemaat

1
@MarkusUnterwaditzer: Celui-ci ne fonctionnera pas pour les commits poussés.
Max Beikirch du

Vous devriez peut-être simplement mettre toutes les commandes dans votre réponse; ce serait beaucoup plus cohérent et ne nécessiterait pas la combinaison mentale de messages séparés :)
Andrew Mao

9

Pour être clair: la réponse acceptée est correcte. Essayez-le d'abord. Cependant, cela peut être inutilement complexe pour certains cas d'utilisation, en particulier si vous rencontrez des erreurs odieuses telles que 'fatal: bad revision --prune-empty', ou si vous ne vous souciez pas vraiment de l'historique de votre dépôt.

Une alternative serait:

  1. cd à la branche de base du projet
  2. Supprimer le code / fichier sensible
  3. rm -rf .git / # Supprimez toutes les informations git de votre code
  4. Accédez à github et supprimez votre référentiel
  5. Suivez ce guide pour pousser votre code vers un nouveau référentiel comme vous le feriez normalement - https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/

Cela supprimera bien sûr toutes les branches d'historique de validation et les problèmes de votre dépôt github et de votre dépôt git local. Si cela est inacceptable, vous devrez utiliser une autre approche.

Appelez cela l'option nucléaire.


9

Vous pouvez utiliser git forget-blob.

L'utilisation est assez simple git forget-blob file-to-forget. Vous pouvez obtenir plus d'informations ici

https://ownyourbits.com/2017/01/18/completely-remove-a-file-from-a-git-repository-with-git-forget-blob/

Il disparaîtra de toutes les validations de votre historique, reflog, tags, etc.

Je rencontre de temps en temps le même problème, et chaque fois que je dois revenir à ce poste et à d'autres, c'est pourquoi j'ai automatisé le processus.

Crédits aux contributeurs de Stack Overflow qui m'ont permis de mettre cela ensemble


8

Voici ma solution sous windows

git filter-branch --tree-filter "rm -f 'depositedir / filename'" HEAD

git push --force

assurez-vous que le chemin est correct sinon il ne fonctionnera pas

J'espère que ça aide


8

Utiliser une branche de filtre :

git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch *file_path_relative_to_git_repo*' --prune-empty --tag-name-filter cat -- --all

git push origin *branch_name* -f

3

J'ai dû le faire plusieurs fois à ce jour. Notez que cela ne fonctionne que sur 1 fichier à la fois.

  1. Obtenez une liste de toutes les validations qui ont modifié un fichier. Celui en bas sera le premier commit:

    git log --pretty=oneline --branches -- pathToFile

  2. Pour supprimer le fichier de l'historique, utilisez le premier commit sha1 et le chemin d'accès au fichier de la commande précédente, et remplissez-les dans cette commande:

    git filter-branch --index-filter 'git rm --cached --ignore-unmatch <path-to-file>' -- <sha1-where-the-file-was-first-added>..


3

Donc, cela ressemble à ceci:

git rm --cached /config/deploy.rb
echo /config/deploy.rb >> .gitignore

Supprimer le cache du fichier suivi de git et ajouter ce fichier à la .gitignoreliste


2

Dans mon projet Android, j'avais admob_keys.xml en tant que fichier xml séparé dans le dossier app / src / main / res / values ​​/ . Pour supprimer ce fichier sensible, j'ai utilisé le script ci-dessous et j'ai parfaitement fonctionné.

git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch  app/src/main/res/values/admob_keys.xml' \
--prune-empty --tag-name-filter cat -- --all
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.