DEV Community

moonlitpath
moonlitpath

Posted on • Originally published at moonlittech.blogspot.com

How VSCode Secretly Handles Git Authentication (And I Reverse Engineered It)

Written: February 02, 2026

Reason to search:
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 about setting up git to sync folders, 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, whether 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 asymmetric 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)

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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$ 

Enter fullscreen mode Exit fullscreen mode

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 separately 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
Enter fullscreen mode Exit fullscreen mode

so, I decided to diagnose how git push was working, and get a complete trace of what was happening behind the scene when i run this command

GIT_TRACE=1 GIT_CURL_VERBOSE=1 git push
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

💡💡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'\'': '
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

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'\'': '
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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, and update this post soon.

Top comments (0)