How VSCode Secretly Handles Git Authentication (And I Reverse Engineered It)
Reason:
Until now after setting up my git repo using git init, i would immediately open it in VS Code as a project and would run the git commands through the terminal in VS Code.
So, in the previous post, [link]
I noticed that setting up a private git repo on both termux and my laptop required SSH, and it required me to generate a public key and add that key to github. However, I had never needed to go through such a process while linking a local folder to Git.
That made me wonder whether this was an issue with my git repo being private.
So, I started looking into it.
After exploring, I found out that, wheter private or public, to link a git repo to the local folder, you need either an SSH key or a PAT token to authenticate your laptop.
PAT tokens are high security modern version of passwords. They are used when you want to link repo using http Git stopped allowing real passwords to be entered for command line actions in 2021. You must generate these in Github settings and use them when the terminal asks for password.
SSH keys are a pair of assymetric cryptographic keys that act as a lock and key.
private keys are stored in the computer.
public keys are is given to Github
this allows your computer and Github to make a handshake to prove that they match.
I first checked my local folder that was linked to git to check which method I had used to authenticate.
anu@laptop:~/anu/programming/devops-journey$ git remote -v
origin https://github.com/moonlitpath1/devops-journey.git (fetch)
origin https://github.com/moonlitpath1/devops-journey.git (push)
Thus, I had used HTTPS method to link my folders. And this indicates that there should be a PAT token stored somewhere within my system.. so after browsing a bit how I could find these keys, these are my results
anu@laptop:~/anu/programming/devops-journey$ git config --system --get credential.helper
anu@laptop:~/anu/programming/devops-journey$ git config --show-origin --get-all credential.helper
As we can clearly see, there was no output displayed. This means that no PATs stored in the system. Git has no crediential helper configured at any level of the system.
I looked into it some more, and found out this:
| Operation | Public repo | Private repo |
|---|---|---|
| clone | ❌ no auth | ✅ auth required |
| pull | ❌ no auth | ✅ auth required |
| push | ✅ auth required | ✅ auth required |
I went to my local repo and tested out those commands.
As seen below, git push did not work since there was no authentication. This proved that, there wasn't a special credential manager configured in my system that managed the PAT tokens.
If there had been one, then git pull should have worked automatically. But it didn't.
anu@laptop:~/anu/programming/devops-journey$ git clone https://github.com/moonlitpath1/devops-journey.git
Cloning into 'devops-journey'...
remote: Enumerating objects: 33, done.
remote: Counting objects: 100% (33/33), done.
remote: Compressing objects: 100% (27/27), done.
remote: Total 33 (delta 4), reused 23 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (33/33), 14.41 KiB | 7.20 MiB/s, done.
Resolving deltas: 100% (4/4), done.
anu@laptop:~/anu/programming/devops-journey$ git pull
Already up to date.
anu@laptop:~/anu/programming/devops-journey$ git push
Username for 'https://github.com': moonlitpath1
Password for 'https://moonlitpath1@github.com':
remote: Invalid username or token. Password authentication is not supported for Git operations.
fatal: Authentication failed for 'https://github.com/moonlitpath1/devops-journey.git/'
anu@laptop:~/anu/programming/devops-journey$
I further verified this by going into seahorse, which is a keyring manager that manages encryption keys and passwords.
There was no token stored for a seperately for 'github'.
However, I noticed a token stored by VS Code.
This changed the direction of my thinking and made me realize that until now, all my git commands had been entered via the terminal within VS Code. And I had logged into git within VS Code for my college projects a while ago.
So, VS Code must be the one doing something behind the scenes. Perhaps it is managing the PAT token all by itself.
So, I tested inside VS Code and found that indeed, git push works for my local folder that's synced to github.
Note: this is within VS Code's terminal
anu@laptop:~/anu/programming/devops-journey$ git push
Everything up-to-date
so, I decided to diagnose how git push was working, and get a complete trace of what was happening bts when i run this command
GIT_TRACE=1 GIT_CURL_VERBOSE=1 git push
Breakdown of command:
| Variable | What you see |
|---|---|
GIT_TRACE=1 |
Shows alias expansion, built-in commands being launched, and how Git is handing off tasks to sub-processes. |
GIT_CURL_VERBOSE=1 |
Shows the "handshake" between my computer and the server (GitHub/GitLab). It reveals HTTP headers, cookies, and SSL certificate details. |
git push |
The command to upload my local commits to the remote repository. |
And this was the output!
anu@laptop:~/anu/programming/devops-journey$ GIT_TRACE=1 GIT_CURL_VERBOSE=1 git push
13:34:21.545827 git.c:463 trace: built-in: git push
13:34:21.546245 run-command.c:659 trace: run_command: GIT_DIR=.git git remote-https origin https://github.com/moonlitpath1/devops-journey.git
13:34:21.548297 git.c:749 trace: exec: git-remote-https origin https://github.com/moonlitpath1/devops-journey.git
13:34:21.548364 run-command.c:659 trace: run_command: git-remote-https origin https://github.com/moonlitpath1/devops-journey.git
13:34:21.556691 http.c:845 Info: Couldn't find host github.com in the .netrc file; using defaults
13:34:21.587934 http.c:845 Info: Host github.com:443 was resolved.
13:34:21.587968 http.c:845 Info: IPv6: (none)
13:34:21.587975 http.c:845 Info: IPv4: 20.207.73.82
13:34:21.588016 http.c:845 Info: Trying 20.207.73.82:443...
13:34:21.593585 http.c:845 Info: Connected to github.com (20.207.73.82) port 443
13:34:21.652665 http.c:845 Info: found 438 certificates in /etc/ssl/certs
13:34:21.652738 http.c:845 Info: GnuTLS ciphers: NORMAL:-ARCFOUR-128:-CTYPE-ALL:+CTYPE-X509:-VERS-SSL3.0
13:34:21.652759 http.c:845 Info: ALPN: curl offers h2,http/1.1
13:34:21.674729 http.c:845 Info: SSL connection using TLS1.3 / ECDHE_RSA_AES_128_GCM_SHA256
13:34:21.679020 http.c:845 Info: server certificate verification OK
13:34:21.679042 http.c:845 Info: server certificate status verification SKIPPED
13:34:21.679188 http.c:845 Info: common name: github.com (matched)
13:34:21.679199 http.c:845 Info: server certificate expiration date OK
13:34:21.679211 http.c:845 Info: server certificate activation date OK
13:34:21.679227 http.c:845 Info: certificate public key: EC/ECDSA
13:34:21.679236 http.c:845 Info: certificate version: #3
13:34:21.679268 http.c:845 Info: subject: CN=github.com
13:34:21.679283 http.c:845 Info: start date: Tue, 06 Jan 2026 00:00:00 GMT
13:34:21.679293 http.c:845 Info: expire date: Sun, 05 Apr 2026 23:59:59 GMT
13:34:21.679324 http.c:845 Info: issuer: C=GB,O=Sectigo Limited,CN=Sectigo Public Server Authentication CA DV E36
13:34:21.679348 http.c:845 Info: ALPN: server accepted h2
13:34:21.679488 http.c:845 Info: using HTTP/2
13:34:21.679550 http.c:845 Info: [HTTP/2] [1] OPENED stream for https://github.com/moonlitpath1/devops-journey.git/info/refs?service=git-receive-pack
13:34:21.679560 http.c:845 Info: [HTTP/2] [1] [:method: GET]
13:34:21.679568 http.c:845 Info: [HTTP/2] [1] [:scheme: https]
13:34:21.679577 http.c:845 Info: [HTTP/2] [1] [:authority: github.com]
13:34:21.679585 http.c:845 Info: [HTTP/2] [1] [:path: /moonlitpath1/devops-journey.git/info/refs?service=git-receive-pack]
13:34:21.679593 http.c:845 Info: [HTTP/2] [1] [user-agent: git/2.43.0]
13:34:21.679600 http.c:845 Info: [HTTP/2] [1] [accept: */*]
13:34:21.679610 http.c:845 Info: [HTTP/2] [1] [accept-encoding: deflate, gzip, br, zstd]
13:34:21.679617 http.c:845 Info: [HTTP/2] [1] [accept-language: en-US, *;q=0.9]
13:34:21.679624 http.c:845 Info: [HTTP/2] [1] [pragma: no-cache]
13:34:21.679669 http.c:792 => Send header, 0000000230 bytes (0x000000e6)
13:34:21.679680 http.c:804 => Send header: GET /moonlitpath1/devops-journey.git/info/refs?service=git-receive-pack HTTP/2
13:34:21.679687 http.c:804 => Send header: Host: github.com
13:34:21.679691 http.c:804 => Send header: User-Agent: git/2.43.0
13:34:21.679698 http.c:804 => Send header: Accept: */*
13:34:21.679704 http.c:804 => Send header: Accept-Encoding: deflate, gzip, br, zstd
13:34:21.679711 http.c:804 => Send header: Accept-Language: en-US, *;q=0.9
13:34:21.679715 http.c:804 => Send header: Pragma: no-cache
13:34:21.679721 http.c:804 => Send header:
13:34:22.013432 http.c:792 <= Recv header, 0000000013 bytes (0x0000000d)
13:34:22.013459 http.c:804 <= Recv header: HTTP/2 401
13:34:22.013482 http.c:804 <= Recv header: server: GitHub-Babel/3.0
13:34:22.013530 http.c:804 <= Recv header: www-authenticate: Basic realm="GitHub"
13:34:22.013562 http.c:804 <= Recv header: date: Fri, 30 Jan 2026 08:04:21 GMT
13:34:22.013620 http.c:804 <= Recv header: x-github-request-id: [REDACTED]
13:34:22.013638 http.c:804 <= Recv header:
13:34:22.013660 http.c:845 Info: Connection #0 to host github.com left intact
💡💡This is the part where I found out what was happening
13:34:22.013713 run-command.c:659 trace: run_command: [<redacted_path>/askpass.sh] 'Username for '\''https://github.com'\'': '
13:34:22.166637 run-command.c:659 trace: run_command: [<redacted_path>/askpass.sh] 'Password for '\''https://012345678@github.com'\'': '
13:34:22.301742 http.c:845 Info: Found bundle for host: [ADDRESS] [can multiplex]
13:34:22.301774 http.c:845 Info: Re-using existing connection with host github.com
13:34:22.301787 http.c:845 Info: Server auth using Basic with user '[ID]'
13:34:22.301838 http.c:845 Info: [HTTP/2] [3] OPENED stream for https://github.com/moonlitpath1/devops-journey.git/info/refs?service=git-receive-pack
13:34:22.301878 http.c:845 Info: [HTTP/2] [3] [authorization: Basic <redacted>]
13:34:22.301981 http.c:792 => Send header, 0000000321 bytes (0x00000141)
13:34:22.301995 http.c:804 => Send header: GET /moonlitpath1/devops-journey.git/info/refs?service=git-receive-pack HTTP/2
13:34:22.301999 http.c:804 => Send header: Host: github.com
13:34:22.302003 http.c:804 => Send header: Authorization: Basic <redacted>
13:34:22.302029 http.c:804 => Send header:
13:34:22.634639 http.c:792 <= Recv header, 0000000013 bytes (0x0000000d)
13:34:22.634666 http.c:804 <= Recv header: HTTP/2 200
13:34:22.634690 http.c:804 <= Recv header: server: GitHub-Babel/3.0
13:34:22.634709 http.c:804 <= Recv header: content-type: application/x-git-receive-pack-advertisement
13:34:22.634870 http.c:804 <= Recv header: date: Fri, 30 Jan 2026 08:04:22 GMT
13:34:22.634934 http.c:804 <= Recv header: x-github-request-id: [REDACTED]
13:34:22.634959 http.c:804 <= Recv header:
13:34:22.634997 http.c:845 Info: Connection #0 to host github.com left intact
Everything up-to-date
let's observe this snippet:
13:34:22.013713 run-command.c:659 trace: run_command:
📌[<redacted_path>/askpass.sh]
💡'Username for '\''https://github.com'\'': '
13:34:22.166637 run-command.c:659 trace: run_command:
📌[<redacted_path>/askpass.sh]
💡'Password for '\''https://012345678@github.com'\'': '
Here, Git invokes the askpass.sh script within VS Code to get credentials (username and password).
When I first signed into VS Code, it had performed an OAuth handshake with Github and stored the token in the seahorse keyring.
So, when git asked for VS Code for the credentials, VS Code executed the askpass script and fetched the credentials.
This is what was within the askpass.sh script
anu@laptop:~$ cat <redacted_path>/askpass.sh
#!/bin/sh
VSCODE_GIT_ASKPASS_PIPE=`mktemp`
ELECTRON_RUN_AS_NODE="1" VSCODE_GIT_ASKPASS_PIPE="$VSCODE_GIT_ASKPASS_PIPE" VSCODE_GIT_ASKPASS_TYPE="https" "$VSCODE_GIT_ASKPASS_NODE" "$VSCODE_GIT_ASKPASS_MAIN" $VSCODE_GIT_ASKPASS_EXTRA_ARGS $*
cat $VSCODE_GIT_ASKPASS_PIPE
rm $VSCODE_GIT_ASKPASS_PIPE
And this is the answer! This script is how VS Code automatically links git repos to the local folder.
Explaination of code:
mktemp is a command used to create temporary files. so a temp file is created whose path is stored in VSCODE_GIT_ASKPASS_PIPE (a variable)
VS Code then invokes Electron (the browser that it's build upon) and tells it to act as a quiet Node.js tool.
Note
I will look into the askpass_main.js file next to understand how VS Code fetches the stored OAuth token from the keyring
Comments
Post a Comment