Connect a Git repository
The GitHub App or the GitLab OAuth application, what happens on every push and pull request, and the .ctadel.yaml file.
Ctadel analyses your repositories on every push and every pull request: Terraform, Kubernetes manifests, Dockerfiles, secrets in source files and vulnerable dependencies. The result comes back into the pull request as a comment and a status check, and into the application.
GitHub or GitLab
| GitHub | GitLab | |
|---|---|---|
| Authorization | A GitHub App installed on your organization. The installation token is minted on demand and never stored. | An OAuth application. Ctadel keeps the token of the user who authorized it and refreshes it automatically. |
| Repositories covered | The ones you select at install time. | The projects of the instance that carry the webhook and the secret. |
| Webhooks | Delivered automatically by Ctadel. | Created by you in each project, with the secret shown once. |
| Comment in the pull request | IaC, secrets, dependencies. | IaC only, in the merge request. |
| Status check | ctadel/iac, ctadel/secrets, ctadel/sca. | One status for IaC. |
| Tagging pull request and folder written back to the repository | Yes. | No. |
| Self-managed instance | No, github.com only. | Yes, when the OAuth application was created on your instance. |
Install the GitHub App
- Open "Settings", tab "Integrations", sub-tab "Git (IaC)".
- Click "Install Ctadel on GitHub". Your instance sends you to GitHub; the link is valid for ten minutes.
- On GitHub, pick the organization and the repositories to cover, all of them or a selection.
- GitHub brings you back to your instance. The message "GitHub App installed" confirms the integration: "Webhooks are delivered automatically. The first scan triggers on the next push or PR."
Ctadel stores only the installation id. Whenever it needs one, Ctadel obtains a short-lived token from GitHub, never stored.
The App asks for contents and pull_requests write access, needed to open the tagging pull request described below and to write a folder into the repository. When an older installation lacks them, a banner "Re-authorize Ctadel on …" appears with the button "Review permissions on GitHub". Scans go on without those permissions; only the opening of pull requests waits.
The App also reads the self-hosted runners, with two read-only permissions: "Self-hosted runners" on the organization and "Administration" on the repositories. The graph then shows the runners your workflows can run on, with their labels, operating system and state, the jobs each one serves, and the machine it runs on when that machine is in one of your cloud accounts. Without these permissions, nothing else changes.
With the read-only "Organization administration" permission, the App also lists the other GitHub Apps installed on your organization: each one appears in the graph as a service account held by the organization, with its installation number, the account that publishes the app and the permissions it was granted. Without it, nothing else changes.
Ctadel verifies the signature of every GitHub delivery before handling it. A delivery your instance cannot take at that moment is kept and retried for 48 hours.
Connect GitLab
- In the same sub-tab, click "Connect Ctadel to GitLab". GitLab asks you to authorize the application with the
apiandread_repositoryscopes. - Back on your instance, the banner "GitLab connected — finish the setup" shows a webhook secret. It is shown only once.
- In each GitLab project to analyse, create a webhook to
https://<INSTANCE>/webhook/git(your Ctadel instance), paste that secret into the webhook's secret token field, and tick the push and merge request events. - Edit the integration to give it a "Label".
The OAuth token is the token of the user who authorized: scans reach the projects with that user's permissions. It is kept in your instance's database and refreshed automatically before each scan that needs it. When that user is a Maintainer of the project, the graph also shows the runners registered on the project and on its groups.
What happens on every push and pull request
Two switches per integration, on by default: "Scan on Push" and "Scan on Pull Request". On GitHub, Ctadel receives the push and pull_request events (opened, new commit, reopened). On GitLab, the push and merge request events (open, reopen, update).
For each event, Ctadel clones the commit alone, without history, runs the three families on that copy, then deletes it.
| Family | What is read |
|---|---|
| Infrastructure as Code | .tf and .tf.json files, with the variables of the .tfvars files; .yaml and .yml files recognised as Kubernetes manifests; Dockerfile and Dockerfile.<suffix> files. Files over 10 MB are skipped. |
| Secrets | Every text file under 1 MB, excluding .git, node_modules, vendor, dist, build, .next and .terraform. |
| Dependencies (SCA) | package-lock.json, yarn.lock, go.sum (with the replace and exclude directives of go.mod), requirements.txt, Pipfile.lock, Cargo.lock, Gemfile.lock, composer.lock. The packages are then matched against the NVD. |
Each scan has its own page in the application, linked from the pull request comment. The findings join the IaC, Secrets and Vulnerabilities pages.
A pull request closed without being merged will never deploy: the IaC findings it introduced are resolved at once.
The comment and the status check
On GitHub, each family posts a comment in the pull request and a status on the commit. The comment is updated on each new commit rather than duplicated. The IaC comment lists at most ten findings, from the most to the least severe, with a link to the full report. The ctadel/sca status stays "pending" while the packages are matched against the NVD, then takes its final state.
The integration's "Per-scanner policy" decides, family by family, what the status is worth:
| Family | Enabled | "Block PR on failure" | "Block if findings ≥" |
|---|---|---|---|
| Infrastructure as Code | Yes | Yes | HIGH |
| Secrets | Yes | Yes | ANY |
| Dependencies (SCA) | Yes | No | CRITICAL |
With blocking on, the status fails as soon as one finding reaches the threshold. With blocking off, the status always succeeds and the integration shows "(info)" next to the family. In both cases, GitHub only blocks the merge when your branch protection rule requires the matching status context.
On GitLab, the IaC scan posts a note in the merge request, updated on each commit, and a status on the commit. The secrets and dependencies families post nothing to GitLab; their findings are in the application.
The .ctadel.yaml file
A .ctadel.yaml file at the root of the repository carries two things: the folder the repository is filed under in the application, and ignore rules.
version: 1
folder: "Production/Europe/France"
ignores:
- rule: IAC-AWS-S3-001
paths:
- "**/sandbox/*.tf"
resources:
- aws_s3_bucket.logs
reason: "Internal log bucket, public access required by the CDN"
expires: "2027-01-31"rule: an exact rule key, or a prefix followed by*. Required.paths: path patterns. A pattern without/or**only applies at the root of the repository.resources: resource addresses (type.namein Terraform,Kind/namespace/namein Kubernetes).reason: required, at least ten characters.expires: optional. Past that date the entry is dropped and the finding comes back.
A finding covered by a rule is kept with the "Ignored" status, not deleted: it stays visible in the application with the reason. A rule only covers the IaC and secrets families. An invalid file does not stop the scan: the error is reported in the scan report and no rule applies.
The folder field files the repository in the application's "Folders" tree; missing folders are created. The other way round, moving a GitHub repository into a folder from the application opens a pull request that writes the folder: line of the file. The file remains the reference: the folder is confirmed at the next scan, after the merge.
The tagging pull request
After the GitHub install, Ctadel opens in every reachable repository that contains Terraform a pull request on a ctadel/iac-tagging-<date> branch, titled "Ctadel: enable code-to-cloud tagging". It adds to each resource a ctadel:id tag carrying an opaque identifier (tags in general, labels on Google Cloud); no path or name from your code is written into the cloud. Once the pull request is merged, the next inventory links each cloud resource to the file that declares it.
The rules of that pull request:
- a single open pull request per repository at a time;
- closed without merge, it is only proposed again after 30 days, and once; reopened, it is tracked again;
- a repository without Terraform is skipped, then looked at again on the first push to its default branch that adds some;
- after several consecutive opening failures, Ctadel stops trying on that repository;
- commits are signed
ctadel-bot, andctadel/iac-tagging-branches are never scanned.
After the merge, a second pull request may follow to cover the resources added since.
Limits
- GitHub and GitLab only. Bitbucket, Azure DevOps, Gitea or a bare Git server are not supported.
- GitHub Enterprise Server is not supported: the App talks to github.com.
- On GitLab, only the IaC family posts a note and a status. The tagging pull request and the write-back of the folder into
.ctadel.yamlexist on GitHub only. - On GitLab, the merge request update event also covers a title change, which costs one scan.
- Helm templates and Kustomize overlays are not valid YAML and are not analysed; they are counted as unread files in the report, with the reason.
- Dependencies: Maven, Gradle, pnpm, Poetry, PDM, NuGet, Conda, Helm, Nix, Bun, SwiftPM, CocoaPods, Conan, vcpkg, Hex and Pub are not read. When such a manifest is present without a supported one, the
ctadel/scastatus is in error and says so, rather than in success. - Detected secrets are marked "UNKNOWN" in the comment until their validity has been tested.
- For a GitHub repository, the dependencies status is only posted in pull requests.
- Scans are triggered by webhooks only: a repository with no push and no pull request is not scanned again.