DEV Community

Jordan Huang
Jordan Huang

Posted on

FAQ: The Free Agent Host Cannot Hold CI_JOB_TOKEN

Can a free agent host replace your GitLab runner?
I refuse that swap, even after a green compile.
The missing piece is always CI_JOB_TOKEN.

Where would that token live after you delete the runner?
If you cannot answer, you are mixing machines.
This FAQ exists to unmix them before you paste.

The three boxes people smash together

People describe one environment during standup, every week.
They actually mean three machines with separate trust.

My laptop authors the change I can defend.
The scratch host iterates on synthetic fixtures only.
GitLab's runner remains the only merge receipt.

Those boxes do not share variables or disks.
They do not share identity or backup policy.
Why do we keep pretending they are equivalent?

Disclosure: This article was prepared as part of MonkeyCode's product outreach.
I treated MonkeyCode's free model access as a scratch brain.
I treated the free server option as a throwaway compiler.

That is the whole product claim I will make.
I will not name models, quotas, or hardware here.
I will not invent uptime or duration numbers either.

The useful part survives without any product name.
Keep GitLab as the gate for every merge.
Use the extra box only for disposable drafts.

Myth: a shell with git is already a runner

Is every prompt secretly a pipeline job?
A runner has tags, images, and protected variables.
A runner also has rules: plus artifact expiry.

A free host has a prompt and a working tree.
That is not the same contract. Do you see it?
Would you tag that host in .gitlab-ci.yml tonight?

Run this inside a real GitLab job first.

env | awk -F= '/^(CI_|GITLAB_)/ {print $1}' | sort
Enter fullscreen mode Exit fullscreen mode

Now run the same command on the scratch host.
You want an empty list, not a familiar dump.
Anything else means you leaked CI identity across boxes.

I keep this reminder in CONTRIBUTING.md for reviewers.

Scratch host: no CI_* variables, no glpat tokens.
GitLab runner: the only process that may see them.
Enter fullscreen mode Exit fullscreen mode

If a guide says "just export the token," close it.
A copied token is not a runner. It is an incident.

Myth: free means the paste buffer is private

Would you paste glpat- into a public pastebin?
Then why paste it into a free remote prompt?
Does "free" suddenly mint a vault you can audit?

Free model access does not encrypt your variables.
A free server option does not sign an NDA.
Corrected picture: the host is a stranger's laptop.

Allow public fixtures. Allow obviously fake users.
Allow logs you would already show on a slide.
Deny deploy tokens, customer dumps, and SSH keys.

Here is a proposed pre-send scan, not a shipped test.

# proposal: run locally before you paste a prompt
FORBIDDEN = (
    "CI_JOB_TOKEN",
    "GITLAB_TOKEN",
    "glpat-",
    "BEGIN OPENSSH PRIVATE KEY",
    "DATABASE_URL",
)

def scan(text: str) -> list[str]:
    return [n for n in FORBIDDEN if n in text]

hits = scan(open("/tmp/prompt.txt", encoding="utf-8").read())
if hits:
    raise SystemExit(f"refusing prompt, found {hits}")
Enter fullscreen mode Exit fullscreen mode

If hits is not empty, rewrite the prompt immediately.
Use a fixture. Do not tell yourself "just this once."
Would production still accept that excuse after a leak?

Myth: green on the host is a merge signal

Did that compile actually read .gitlab-ci.yml?
Did it pull the same image digest you pinned?
Did it run on the same CPU architecture as GitLab?

A green scratch build is a hint, nothing more.
It is not evidence. Who signed that green pixel?
I still want the contract job on GitLab itself.

# .gitlab-ci.yml
verify:
  stage: test
  image: python:3.12-slim
  script:
    - python -m pytest -q
    - python scripts/assert_no_ci_leak.py
  rules:
    - if: $CI_COMMIT_BRANCH
Enter fullscreen mode Exit fullscreen mode

The agent may draft the patch on the scratch host.
The runner still executes the yaml a human reviewed.
Ask the myth in review before you click merge.

Which job produced this green, chat or verify?
If the answer is the chat, the answer is no.
Hints do not protect main. Pipelines do that.

Myth: the free disk will still be there tomorrow

Who filed a backup ticket for that scratch box?
I have no durability claim I can honestly repeat.
"Free server option" means a box you can try.

It does not mean snapshots, retention, or restore drills.
Corrected picture: anything precious leaves as git.
If the host vanishes at noon, what still exists?

git status --short
git diff origin/main > /tmp/scratch.diff
git format-patch origin/main --stdout > /tmp/scratch.patch
Enter fullscreen mode Exit fullscreen mode

Copy those files back onto your laptop clone.
Apply them where you can sign the commit yourself.

git apply /tmp/scratch.diff
git log -1 --format='%an %ae'
Enter fullscreen mode Exit fullscreen mode

If you only kept a chat transcript, you have folklore.
Folklore does not revert. A patch file does revert.
Why gamble the only copy on a courtesy disk?

Myth: host commits share your GitLab identity

Does user.email match the CI bot you expect?
Does the SSH key match a project deploy key?
Usually neither condition is true on a scratch host.

The host often commits as a nickname, or as nobody.
I will not merge that identity onto protected main.
Would you accept an unsigned stranger on the branch?

git log -1 --show-signature
git log -1 --format='%an <%ae> %G?'
Enter fullscreen mode Exit fullscreen mode

Unsigned scratch identity is a review flag only.
It is not a protected-branch flag. Rewrite it locally.

git commit --amend --reset-author --no-edit
Enter fullscreen mode Exit fullscreen mode

Then push the branch and wait for GitLab's job.
Identity belongs to the merge receipt, not the draft.
Who actually authored the bytes you are shipping?

Artifact: allow and deny table

Print this table and keep it beside the keyboard.
Ask the question out loud before every remote paste.
If an action is not in the allow column, stop.

Action Scratch host plus a free model GitLab runner
Draft a refactor Allow Not required
Install random packages Allow, throwaway Pin the image
Read CI_JOB_TOKEN Deny Allow, job-scoped
Publish a package Deny Allow on protected
Call production APIs Deny Allow only if protected
Keep the only copy of a patch Deny Not applicable; use git
Approve the merge Deny Human plus pipeline

The table is the workflow, not a slogan.
Everything below is just commentary around that grid.
Which cell were you about to violate, honestly?

A proposed debugging loop

This loop is a proposal, not a measured benchmark.
I am not claiming production timings or pass rates.
Use it as a review script, then adapt the yaml.

  1. Clone locally with secrets kept out of the tree.
  2. Open the scratch host with synthetic fixtures only.
  3. Ask for a patch, and never ask for a merge.
  4. Export git diff or git format-patch immediately.
  5. Rebase on the laptop against origin/main yourself.
  6. Push a branch and wait for the verify job.
  7. Merge only when GitLab reports that job green.

The leak contract script can look like this.

#!/usr/bin/env python3
"""Fail CI if GitLab secrets landed in tracked files."""
from pathlib import Path
import sys

NEEDLES = (
    "CI_JOB_TOKEN=",
    "-----BEGIN OPENSSH PRIVATE KEY-----",
    "glpat-",
)

bad = []
for path in Path(".").rglob("*"):
    if not path.is_file() or ".git" in path.parts:
        continue
    try:
        data = path.read_text(encoding="utf-8", errors="ignore")
    except OSError:
        continue
    for needle in NEEDLES:
        if needle in data:
            bad.append(f"{path}: {needle}")

if bad:
    print("leak contract failed")
    print("\n".join(bad))
    sys.exit(1)

print("leak contract passed")
Enter fullscreen mode Exit fullscreen mode

Drop it in scripts/assert_no_ci_leak.py and commit it.
Call it from the verify job shown above, always.
Now a green pipeline means a specific local contract.

It does not mean the chat sounded sure of itself.
Confidence is not CI_JOB_TOKEN. Never confuse them.
What else are you treating as a receipt today?

What this FAQ does not claim

This is not a penetration test of any host.
It does not rank models or compile speeds.
It does not promise the scratch host is isolated.

I have no quota numbers, so I will not invent them.
I have no hardware list, so I will not invent one.
Need those facts? Wait for a primary vendor document.

Skip this loop when the repo holds regulated data.
Skip it when you cannot name one synthetic fixture.
Skip it when you need to page the scratch host.

Skip it when merge gates live only inside chat logs.
Those teams should stay on private runners, full stop.
They should keep agents entirely off the secret path.

The mental model I want in review

Three boxes. Three jobs. No leftover poetry.
Laptop: write the intent you can later defend.
Scratch host: cheap iteration against fake data only.

GitLab runner: the only receipt allowed to merge.
If a sentence mixes those boxes, call it a myth.
Which box holds the token you almost pasted?

Which box can vanish at noon without a ticket?
Which box is allowed to merge into main?
Answer those three questions. Then paste, or do not.

If you spin up a scratch host, keep GitLab as the only merge receipt.

Top comments (0)