◉ANTIGRAVITY LABJP
Articles/Integrations
⬡ Integrations/2026-10-26Intermediate

I Stopped Embedding the Token in the Remote URL. An Old Clone's .git/config Had It Sitting There, Readable by Anyone

How to audit unattended git jobs for tokens embedded in the remote URL and move them to http.extraheader. Full text of three scripts plus the real output from before and after the migration.

Antigravity400Git12credentialsunattended runs8bash5indie developer19

✦ Premium Article

I was re-fetching a repository to hand to an agent when the usual routine stopped. One line was all it printed: detected dubious ownership.

The ownership mismatch itself is easy to deal with by moving to a place you can write, as I described in another article. What I want to write down here is something else I found while narrowing the cause down.

The short version first. If you embed an access token in the remote URL, the value stays in .git/config, a plain-text file. Depending on the umask in effect when the clone was made, other users on the same shared area may be able to read that file. My old clone was in exactly that state.

Below are the migration steps and the output I got when I ran them.

While Looking for the Cause, Something Else Turned Up

The old clone sat in a shared temporary area under a different user's name. This is what ls showed.

$ ls -ld /tmp/repos/antigravitylab.net
drwxr-xr-x 7 nobody nogroup 4096 Oct 26 04:56 /tmp/repos/antigravitylab.net
$ stat -c '%U:%G %a %n' /tmp/repos/antigravitylab.net/.git/config
nobody:nogroup 644 /tmp/repos/antigravitylab.net/.git/config

The owner was nobody, not my uid, so git status was bound to refuse. Here I reached for the usual workaround first.

git config --global --add safe.directory '*'

After that, git status worked and answered "Your branch is up to date." The next write failed, though.

error: could not lock config file .git/config: Permission denied
fatal: could not set 'remote.origin.url' to ...
error: cannot open .git/FETCH_HEAD: Permission denied

safe.directory removes only Git's refusal. It does not change filesystem permissions. Reads pass and writes fail. The display turns green and the first write turns red, which makes it easy to misplace the cause when a job runs unattended.

That continues the earlier article. The rest is the real subject of this one.

To be honest, I came close to thinking "it ran, so it must be fine." A green display brings reassurance ahead of the evidence, and I only noticed that afterwards.

I Counted What Was in .git/config

If I could not rewrite it, I could at least check what it held. Reading is allowed. But I did not want any value in the terminal or in a log. So I wrote an audit script that never reads the value and only counts lines shaped like credentials.

#!/usr/bin/env bash
# audit_remote_creds.sh <parent-dir>...  list clones whose remote URL carries credentials, with the config permissions
# Never prints the credential itself. Exit code: 0=none / 1=found
FOUND=0
for ROOT in "$@"; do
  for CFG in "$ROOT"/*/.git/config; do
    [ -f "$CFG" ] || continue
    # Count only lines shaped like https://<something>@host (get a line count, never the value)
    N=$(grep -cE 'url *= *https://[^/@ ]+@' "$CFG")
    [ "$N" -gt 0 ] || continue
    MODE=$(stat -c '%a' "$CFG"); OWN=$(stat -c '%U' "$CFG")
    # Is the read bit (4) for 'others' set?
    if [ $((8#$MODE & 4)) -ne 0 ]; then WORLD="world-readable"; else WORLD="private"; fi
    echo "CRED $CFG owner=$OWN mode=$MODE $WORLD"
    FOUND=1
  done
done
exit $FOUND

grep -c returns only the number of matching lines, so no value is printed. The lower three bits of the stat mode tell you whether other users can read the file.

This is the result against my clone locations.

$ ./audit_remote_creds.sh /tmp/repos $HOME/repos
CRED /tmp/repos/antigravitylab.net/.git/config owner=nobody mode=644 world-readable
rc=1

One hit. mode=644 means readers other than the owner are allowed. As long as other users exist in that area, the token was readable. I have no sign that anyone read it, and no way to find out. So I decided not to say "it was not read", and to proceed on the assumption that it must be replaced.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦You'll be able to check whether your clones' .git/config files hold credentials, in one command, without printing a single value
✦You'll be able to move existing clones from a URL-embedded token to http.extraheader without breaking them
✦You'll be able to decide what to do when you find an old clone you do not own and cannot rewrite: leave it alone, route around it, and rotate the token
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

◈ Agents & Manager2026-10-25
An Unattended Job Stopped With 11MB Free. du Counted Only 59MB; the Other 9GB Was Somewhere I Couldn't See
Two bash scripts that measure free space on the filesystem you are about to write to and stop an unattended agent run before it starts. Includes why du and df disagreed and how to spot a shortage you cannot fix.
⬡ Integrations2026-05-28
Five Weeks Letting Antigravity's Background Agent Refresh App Store Screenshots Across Four Apps
An operations log from running Antigravity Background Agent for five weeks to keep App Store screenshots for four wallpaper apps in sync across eight locales — including the caption-width validation step that cut manual fixes from 18 to 2.
⬡ Integrations2026-05-23
Apple Search Ads × Antigravity Agent: Autonomous Keyword Bid Tuning Notes from an Indie iOS Studio
How I delegated Apple Search Ads keyword bidding to Antigravity sub-agents for an indie wallpaper app portfolio. JWT auth, ROAS / CPI bid policy, and a Crashlytics-driven safety net.