Scaffold for an Azure DevOps extension that lets you share a single ggshield API key across every pipeline in an organization — without editing each YAML or routing through variable groups / Key Vault. It ships:
- A custom task (
ggshield@0) that reads credentials from a typed Generic service connection (connectedService:Generic) — the only ADO context where endpoint fields actually resolve in YAML pipelines. - A pipeline decorator that auto-injects that task right after the implicit
checkoutstep of every agent job.
- Node.js 18+ and npm
tfx-cli—npm install -g tfx-cli- A publisher account on the Visual Studio Marketplace
In vss-extension.json:
- Replace
REPLACE-WITH-YOUR-PUBLISHER-IDwith your Marketplace publisher ID. Required —tfxwill refuse to package otherwise, and the.vsixfilename encodes this value. - Bump the top-level
versionfor each release. The build script intentionally does not auto-bump.
ADO tracks two versions for this kind of extension: vss-extension.json → version (drives Marketplace upgrade detection) and ggshield-scan-task/task.json → version: { Major, Minor, Patch } (drives whether agents pull new task bits). They must agree, or you get a stale task on agents or a rejected upload.
Edit only vss-extension.json's version. build.sh parses it and rewrites the Major/Minor/Patch block in task.json before packaging — commit both files together.
./build.shOutput: a .vsix in the project root, named <publisher>.ggshield-ado-private-extension-<version>.vsix.
-
Go to https://marketplace.visualstudio.com/manage and upload the
.vsix. The extension shows up under your publisher with Availability: Private (shared with…): -
Click
...→ Share/Unshare and add your ADO organization (e.g.https://dev.azure.com/myorg). -
In your ADO org: Organization Settings → Extensions → Shared → click the extension → Install:
Private extensions are not reviewed by Microsoft and become available to the target org immediately after sharing.
In the target ADO project: Project Settings → Service connections → New service connection → Generic, then:
- Server URL: your GitGuardian dashboard URL —
https://dashboard.gitguardian.com(SaaS US),https://dashboard.eu1.gitguardian.com(SaaS EU), or your self-hosted dashboard URL. ggshield derives the API URL from this; passing the API URL directly will fail to authenticate. Leave blank for the SaaS US default. - Username: leave blank.
- Password/Token Key: your ggshield API key.
- Service connection name:
gitguardian-api(must match exactly — the decorator YAML references this name). - Tick Grant access permission to all pipelines.
-
In a throwaway repo, create a minimal
azure-pipelines.yml:trigger: [main] pool: vmImage: ubuntu-latest steps: - script: echo "my real build steps go here"
-
Run the pipeline — a
ggshield - secret scanstep should appear right afterCheckout. -
Add a hardcoded test secret to verify the env-var plumbing; the scan should fail the build:
variables:
skipGGShield: trueUseful for the pipeline that builds this extension itself (otherwise you'll get infinite recursion of self-scans).
The decorator fires on every agent job in every pipeline, so a broad rollout meaningfully increases ggshield API traffic. Those calls are subject to API rate limits shared across your workspace — review your quotas and headroom first: usage, quotas, and rate limiting.
Built-in safety net. The task's scanTimeoutSeconds input (default 80) terminates ggshield and completes the step as SucceededWithIssues when the scan runs long. This contains rate-limit incidents: pygitguardian retries 429s indefinitely, so without the cap a transient event would turn into an org-wide pipeline outage. Tune it down (e.g. 30) on fast pipelines, or up for large monorepos.
- ggshield is auto-installed on demand if missing. The task tries
pipx, thenpython3 -m pip,python -m pip,pip3,pip, in that order. Bake ggshield into self-hosted agent images to remove ~5s of cold-start overhead per job. - Only
secret scan ci/path/dockermodes are exposed. - Windows-hosted agents need Python 3.8+ on
PATHfor the auto-install fallback. - The decorator fires on every agent job; gate by branch or path with
${{ if ... }}indecorator/ggshield-decorator.ymlif needed.
- Publish scan results as an artifact / custom tab in the run summary.
- JSON → SARIF conversion for the ADO security UI.



