Deploy ggshield at scale with a service account token
Use this page to deploy Developer Endpoint Protection on managed endpoints: machine scanning, honeytoken protection, AI Hooks, and an inventory of the AI agents and MCP servers in use. The same pattern works with Jamf Pro, Iru (formerly Kandji), Intune, Workspace ONE, Mosyle, Addigy, Canonical Landscape, or configuration management tools such as Ansible, Puppet, Chef, and Salt.
We recommend storing no GitGuardian token on the endpoint. Inject an admin
service account token at runtime, only for the jobs your MDM runs: install
ggshield, install the machine scanning plugin, plant the honeytoken, and running the scans.
AI and git hooks are the exception. They fire in the developer's own session, not in your MDM job, so that machine needs a developer login. Log the developer in and the AI inventory is attributed to them too.
Deployment model
Deploy each capability as its own MDM job so you can audit, repair, or roll it back independently. The capabilities are optional and independent of each other, so you can scan a machine without planting a honeytoken, or install AI Hooks without scanning. Yet, we strongly recommend enabling all of them.
| Job | What it does | GitGuardian token | Good MDM fit |
|---|---|---|---|
Install or update ggshield | Deploy the CLI binary | Not needed | package deployment or remediation script |
Install or update machine_scan | Add the Machine Scan plugin for the scan user | Admin SAT, injected at runtime | audit/remediation script |
| Machine setup | Run ggshield machine setup to configure AI Hooks, git hooks, and the honeytoken | Admin SAT, injected at runtime | scheduled script, daily |
| Developer login | ggshield auth logout --no-revoke, then ggshield auth login as the logged-in user | Developer personal token | remediation script, once per machine |
| Inventory scan | Run ggshield machine report | Admin SAT, injected at runtime, or the developer token | scheduled script, once per day |
| AI and MCP inventory | Run ggshield ai discover | Developer token | scheduled script |
In MDM terms, an audit script checks whether a machine is compliant, and a remediation script installs or repairs what is missing.
Do not persist the admin SAT with ggshield auth login --method token. That
overwrites the developer's login, so every later ggshield command on that
machine reports as the shared account, and an admin token sits on disk.
Step 1 - Create the admin token
Create one dedicated Service Account token for the MDM jobs. Add only the scopes those jobs need:
| Scope | Why it is needed |
|---|---|
scan | Install the machine_scan plugin by name, which downloads it from the GitGuardian catalog |
endpoints:send | Upload scan results, when the MDM job runs ggshield machine report itself |
honeytokens:write | Plant honeytokens on the machine. Only needed for honeytoken protection |
honeytokens:check | Recognize your own honeytokens during a scan and avoid reporting it as a regular secret. Only needed for machine scans |
Store the token in your MDM secret store or secret manager and inject it at runtime.
Step 2 - Install and manage ggshield
Use the deployment method your fleet already trusts:
- Jamf Pro / Iru (formerly Kandji) / Mosyle / Addigy: deploy the signed macOS package or run an install remediation script.
- Intune: deploy the Windows installer or run the PowerShell installer. For macOS/Linux, use shell scripts or remediations.
- Workspace ONE / RMM tools: use a managed package or script assignment.
- Landscape / Ansible / Puppet / Chef / Salt: install the
.deb/.rpmor run the official installer, then enforce the version.
For script-based macOS/Linux deployments, install only the binary first, and set
GGSHIELD_BIN_DIR to a directory already on everyone's PATH:
curl -sSfL https://raw.githubusercontent.com/GitGuardian/ggshield/main/scripts/install/install.sh | \
GGSHIELD_BIN_DIR=/usr/local/bin bash -s -- --install-only
Audit with ggshield --version, which must report 1.53.0 or later for the
commands on this page.
For large fleets, prefer an internal package cache or MDM-hosted package so every machine does not download from the internet at the same time.
The examples below reuse this loggedInUser lookup. On Linux, or on a server with
a dedicated scan user, use that user instead.
loggedInUser=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ && !/loginwindow/ {print $3}')
Step 3 - Install Machine Scan for the scan user
Machine scan plugin must be installed and enabled before running
ggshield machine report. Install it for the user that will run the scan. Run the
job as root instead and the plugin is installed machine-wide so every user can
load it, which is the better fit when several people share a host.
If your MDM can stage a signed or internal plugin package, install that package and the job needs no token:
sudo -i -u "$loggedInUser" -- ggshield plugin install /path/to/machine-scan-plugin.whl
If you install by name, ggshield plugin install machine_scan contacts the
GitGuardian plugin catalog. Pipe the admin SAT into GITGUARDIAN_API_KEY through
stdin so it stays out of command lines and shell history. Disable shell tracing
(set -x) in scripts that handle it.
The token still lives in the process environment, where ps -E on macOS and
/proc/<pid>/environ as root on Linux can read it. Configure your MDM to redact
secret environment variables and captured stdin from its own logs.
# Your MDM injects the admin SAT as GGSHIELD_SAT.
printf '%s\n' "$GGSHIELD_SAT" | sudo -i -u "$loggedInUser" -- /bin/zsh -c \
'IFS= read -r GITGUARDIAN_API_KEY && export GITGUARDIAN_API_KEY && exec ggshield plugin install machine_scan < /dev/null'
sudo -i -u "$loggedInUser" -- ggshield plugin list
For Linux, use /bin/bash and your target user.
auth login and later ggshield commands default to US SaaS. For EU or
self-hosted instances, pass GITGUARDIAN_INSTANCE into the inner shell. The
instance URL is not a secret:
export GITGUARDIAN_INSTANCE=https://dashboard.eu1.gitguardian.com # or your self-hosted instance URL
printf '%s\n' "$GGSHIELD_SAT" | sudo -i -u "$loggedInUser" -- \
env GITGUARDIAN_INSTANCE="$GITGUARDIAN_INSTANCE" /bin/zsh -c \
'IFS= read -r GITGUARDIAN_API_KEY && export GITGUARDIAN_API_KEY && exec ggshield plugin install machine_scan < /dev/null'
Use that same env GITGUARDIAN_INSTANCE=... prefix on every later admin job.
Audit success when ggshield plugin list shows machine_scan for the scan user.
Step 4 - Set up hooks and honeytokens
ggshield machine setup configures the 3 endpoint protections in one
idempotent run. Drop any of them with --no-ai-hooks, --no-git-hooks, or
--no-honeytokens. Run it as the logged-in user, and inject the admin SAT the
same way as in Step 3:
printf '%s\n' "$GGSHIELD_SAT" | sudo -i -u "$loggedInUser" -- /bin/zsh -c \
'IFS= read -r GITGUARDIAN_API_KEY && export GITGUARDIAN_API_KEY && exec ggshield machine setup < /dev/null'
Schedule this job daily. It adds missing protections and leaves ones already in place:
- Writes hooks for AI agents that are not yet protected, and git hooks when they are not yet enforced.
- Reconciles honeytoken protection against GitGuardian on every run: it rewrites a
deleted honeytoken and removes one you revoked in the dashboard. That is why
planting stays an MDM job with
honeytokens:write.
To pick specific assistants, use --agent. To skip some, use --exclude-agent.
Both accept claude-code, codex, copilot, cursor, vibe, and vscode.
Honeytoken
Planting writes a decoy AWS profile in ~/.aws. The process user decides whose
home that is:
- As the logged-in user: that account only, which is enough on a one-person laptop.
- As root: every user home it finds (
/Users,/home, and similar), so every account on a shared host gets a decoy.
On a shared host, run the honeytoken part as root and inject the SAT through stdin:
printf '%s\n' "$GGSHIELD_SAT" | sudo /bin/sh -c \
'IFS= read -r GITGUARDIAN_API_KEY && export GITGUARDIAN_API_KEY && exec ggshield machine setup --no-ai-hooks --no-git-hooks < /dev/null'
Preview targets without writing anything: ggshield honeytoken plant --list-targets.
See Protect endpoints with Honeytokens.
AI Hooks
Hooks are written in the current user's AI agent config (~/.claude,
~/.cursor, and the other supported tools).
machine setup configures every AI agent it detects for that account.
The hook later runs in the developer's session. It reads that user's credential store, which is why Step 5 logs them in as themselves. If they are not logged in, the hook fails open: it does not block, and the tool shows a warning that the content was not scanned.
On a shared host, keep AI Hooks in the logged-in user job and skip them in the
root job (--no-ai-hooks). See Prevent leaks with AI Hooks.
Git hooks
Installs global pre-commit and pre-push hooks. Scope follows the process
user:
- Root, or
--system: git's systemcore.hooksPath, covering every user on the machine. - Not root, no
--system: git's globalcore.hooksPathfor the current user.
A core.hooksPath that points somewhere else (Husky, lefthook) takes precedence,
and the ggshield hook never fires there. A global install is the exception: when
a global core.hooksPath already exists, ggshield writes its hook inside that
directory instead of being shadowed by it.
ggshield machine doctor reports this per repository, and it resolves the
effective path from the current directory. Run your MDM audit script inside a
repository, otherwise the check means nothing.
On a shared host, install system hooks as root and skip git hooks in the
user job, so you do not set a user-global core.hooksPath that shadows
system for that account. System git hooks do not need a token:
sudo ggshield machine setup --system --no-ai-hooks --no-honeytokens
Step 5 - Log the developer in
After the admin jobs, log the developer in so AI Hooks, git hooks, and the AI
inventory run under their membership. ggshield auth login requests scan,
honeytokens:check, endpoints:send, and ai-discover:send automatically.
auth login keeps an existing token if one is already stored, so log out first.
Use --no-revoke so logout only clears the stored token.
sudo -i -u "$loggedInUser" -- ggshield auth logout --no-revoke
sudo -i -u "$loggedInUser" -- ggshield auth login
The login command opens a browser in the developer's session and provisions a
personal token. For EU or self-hosted instances, pass --instance on both
commands:
sudo -i -u "$loggedInUser" -- ggshield auth logout --no-revoke --instance https://dashboard.eu1.gitguardian.com
sudo -i -u "$loggedInUser" -- ggshield auth login --instance https://dashboard.eu1.gitguardian.com
US SaaS needs no --instance flag.
Step 6 - Run the daily inventory scan
Schedule one MDM job per day that runs ggshield machine report. It scans and
sends in one step, see Commands.
Inject the admin SAT for this job, the same way as in Step 3. The scan does not depend on the developer login, which is simpler across a fleet:
printf '%s\n' "$GGSHIELD_SAT" | sudo -i -u "$loggedInUser" -- /bin/zsh -c \
'IFS= read -r GITGUARDIAN_API_KEY && export GITGUARDIAN_API_KEY && exec ggshield machine report < /dev/null'
This job needs endpoints:send only. Detection runs locally, and the plugin
checks that scope before it uploads. scan is for downloading the plugin from
the catalog, not for scanning the machine.
If you want each scan attributed to the developer instead, drop the injection and run it as the logged-in user after Step 5, which uses their token:
sudo -i -u "$loggedInUser" -- ggshield machine report
For EU or self-hosted instances, pass the instance through env, the same way as
in Step 3. Login does not
store it for later commands.
If your MDM cannot run scheduled scripts reliably, you can use launchd or a
systemd timer as a fallback.
Step 7 - Collect the AI and MCP inventory
ggshield ai discover parses the configuration files of supported AI assistants
and reports which agents and MCP servers are present on the machine. This is its
own job, separate from the machine scan, so schedule it separately. Run it as the
logged-in user after Step 5, so it uses their
token and the inventory is attributed to them. AI tooling changes less often than
scan results, so a daily or weekly pass is enough.
sudo -i -u "$loggedInUser" -- ggshield ai discover
For EU or self-hosted instances, pass the instance through env, the same way as
in Step 3.
For what the inventory collects and how security teams use it, see AI agents and MCP inventory.
Step 8 - Roll out and verify
Start with 10 to 20 monitored machines, then expand in waves. On a sample
machine, run the readiness check as the logged-in user, from inside a git
repository so the git hooks check resolves the effective core.hooksPath:
sudo -i -u "$loggedInUser" -- /bin/zsh -c 'cd /path/to/a/repo && exec ggshield machine doctor'
machine doctor is read-only and exits non-zero when any check fails, so you can
use it directly as an MDM audit script and gate the next wave on it. Every failed
check prints its own fix. After Step 5, it should
report that the developer token reaches GitGuardian with the scopes the
protections need.
Then trigger the MDM inventory job once. Go to Endpoint protection → Endpoints
in GitGuardian and confirm the machine appears with a recent scan, and reports as
Protected if you deployed honeytoken protection. See
Monitor coverage.
To roll back, disable the scheduled jobs first, especially machine setup. Then
clear what is already on disk. ggshield auth logout as the logged-in user
removes their personal token. Revoke the admin SAT in your workspace to cover
the whole fleet at once.
For the pilot checklist, the wave sizes, and what to monitor at each gate, see Validate and roll out.