Who among us hasn’t found him- or herself in this very awkward position: committing a config or code file with secrets (such as passwords or API keys) and then semi-panicked googling how to delete it from source control.
Been there and let me tell you the easiest way to delete it: copy all the code on disk, delete the repository completely and then re-create it.
(if this is not an option, well, there’s still a way but with much more work and risk, so do keep that code backup around!)
But you know what’s even better? That’s right, avoid this in the first place! That’s why Git hooks are so useful: they work without you neededing to remember to check your config files every time.
So here’s my solution to this:
- In the repository, go to .git/hooks and rename pre-commit.sample to pre-commit (i.e. remove the extension)
- Open pre-commit with a text editor and replace its contents with the following:
#!/bin/sh
C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -ExecutionPolicy Bypass -Command '.\hooks\pre-commit.ps1'
- Add a new directory on the root of the repository named hooks.
- Inside this, add a text file named pre-commit.ps1 with the following code:
#
# Source: DotJim blog (http://dandraka.com)
# Jim Andrakakis, July 2022
#
Clear-Host
$ErrorActionPreference='Stop'
# ===== Change here =====
$listOfExtensions=@('*.xml','*.config')
$listOfSecretNodes=@('username','password','clientid','secret','connectionstring')
$acceptableString='lalala'
# ===== Change here =====
$codePath = (Get-Item -Path $PSScriptRoot).Parent.Parent.FullName
$errorList=New-Object -TypeName 'System.Collections.ArrayList'
foreach($ext in $listOfExtensions) {
$list = Get-ChildItem -Path $codePath -Recurse -Filter $ext
foreach($file in $list) {
$fileName = $file.FullName
if ($fileName.Contains('\bin\')) {
continue
}
Write-Host "Checking $fileName for secrets"
[xml]$xml=[xml]((Get-Content -Path $fileName).ToLowerInvariant())
foreach($secretName in $listOfSecretNodes) {
$nodes = $xml.SelectNodes("//*[contains(local-name(), '$secretName')]")
foreach($node in $nodes) {
if ($node.InnerText.ToLowerInvariant() -ne $acceptableString) {
$str = "[$fileName] $($node.Name) contains text other than '$acceptableString', please replace this with $acceptableString before commiting."
$errorList.Add($str) | Out-Null
Write-Warning $str
}
}
}
}
}
if ($errorList.Count -gt 0) {
Write-Error 'Commit cancelled, please correct before commiting.'
}
So there you have it. I’m getting automatically stopped every time I tried to commit any .xml or .config file that contains a node with a name that contains username, password, clientid, secret or connectionstring, whenever the value of it is not ‘lalala’.
Obviously the extensions, node names and acceptable string can be changed at the top of the script. You can also change this quite easily to check JSON files as well.
Also note that this works on Windows (because of the Powershell path in the pre-commit hook) but with a minor change in the pre-commit bash script, you should be able to make it work cross-platform with Powershell core. I haven’t tested it but it should be:
#!/usr/bin/env pwsh -File '.\hooks\pre-commit.ps1'
Have fun coding!