Skip to content
Documentation

Modules

Secrets

Where Ctadel looks for exposed credentials (buckets, disks, functions, repositories, images), how it masks them, how it checks whether they are still valid, and the Secrets page.

The secrets module looks for credentials (API keys, tokens, passwords, private keys) in what your cloud accounts and repositories hold, stores them masked, then asks the vendor whether some of them are still valid. The "Secrets" page presents the result.

Where Ctadel looks

The same rule catalogue is applied to every source; each rule applies to some or all of them.

SourceWhat is readWhat is skipped
Object buckets (S3, Scaleway, OUTSCALE, OVHcloud, Azure Blob, Google Cloud Storage)The first 10 MB of each text object; PDFs, archives (zip, tar, gz, tgz) and office documents (xlsx, xlsm, docx, pptx, odt, ods) are downloaded in full up to 50 MB and their text extracted, including the files inside an archiveLegacy binary doc and xls, an archive cut at 50 MB, an object in an unreadable format: counted as "not scannable" without making the result incomplete
Disks (a volume copy mounted on a scan machine)The whole file system, files up to 256 MB; binary files are read by extracting their printable stringsSystem directories (proc, sys, dev, boot, var/log, the container engine's data, libraries under usr/lib, Windows WinSxS, Installer and others), third-party dependencies at any depth (node_modules, site-packages, vendor, .terraform), home directory caches
Serverless functions and containers (AWS, Azure, Google Cloud, Scaleway, OVHcloud)The deployed code (including dist/ and build/, which is the code that runs), files up to 10 MB and 10,000 files per function; environment variables, read as NAME=value and as the bare value; the layers of a container's imagenode_modules, vendor, .git, compiled files, media, dependency lock files (package-lock.json, go.sum, yarn.lock)
Git repositories (on every push and pull request)Every text file of the cloned repository, line by line, files up to 1 MB.git, node_modules, vendor, dist, build, .next, .terraform, images, fonts, archives and .lock files
Container images (Kubernetes clusters and functions deployed from an image)Secrets reported by Trivy's built-in scanner in the image layers; each known Trivy type is mapped to the matching Ctadel rule, the others are filed under a single rule "Secret in a container image layer" that keeps the Trivy identifier

Rule families

Each rule is a regular expression taken from the vendor's documentation or from the Gitleaks default ruleset, with a severity and a confidence level. The families:

FamilyExamples
Cloud keysAWS, Google Cloud, Azure (storage keys, SAS, Service Bus, DevOps), Alibaba, DigitalOcean, Heroku
Git platforms and registriesGitHub, GitLab, Bitbucket, Atlassian, npm, PyPI, NuGet, RubyGems, Docker
PaymentsStripe, Square, PayPal Braintree, Plaid, GoCardless
CommunicationSlack, Discord, Twilio, Telegram, SendGrid, Mailgun, Mailchimp, Zoom
AI and MLOpenAI, Anthropic, OpenRouter, Hugging Face, Cohere, Replicate, Pinecone, Groq
CryptographyPEM private keys, PKCS, SSH keys, JWT, Age, Fernet, ASP.NET validationKey, GPP cpassword
DevOps and observabilityCircleCI, Buildkite, Datadog, New Relic, Grafana, Sentry, HCP Terraform and Vault, Pulumi, JFrog, Doppler, Okta
DatabasesJDBC and ODBC strings, Redis URIs, Snowflake, Databricks, PlanetScale, Supabase, generic passwords
Collaboration SaaSSalesforce, HubSpot, Zendesk, Notion, Trello, Figma, Jira, Airtable
Storage and CDNDropbox, Cloudflare, Fastly, Shopify, Algolia, Mapbox, Tailscale, 1Password

Three shared filters remove the obvious false positives:

  • Example values published by vendors (AKIAIOSFODNN7EXAMPLE, the Azurite emulator key, any AWS key ending in EXAMPLE) are never reported.
  • When a vendor rule and a generic rule (environment file, quoted value, credential in a URL) find the same secret, only the vendor rule is kept.
  • In serverless code, a medium-confidence rule reports nothing when the value has low entropy or contains test, example, mock, dummy, fake, sample, placeholder, changeme and a few other markers.

For the other scanners and the whole catalogue, see the scanners section of the site.

Masking

A secret's value is never stored in clear. Each finding carries:

  • a masked snippet: at most the first four and last four characters, and only when the value is at least 16 characters long (20 for Git repositories); below that, everything is replaced by stars;
  • a SHA-256 hash of the value, the same whichever source it was found in, used to deduplicate across sources and to attach the verification verdict;
  • the file path, the hosting resource, and for a repository the line number, branch and commit.

The clear value is used only for the validity check, inside your instance, where it travels encrypted (AES-256-GCM). It is never stored and the graph never receives it.

Validity check

For rules that declare a verification endpoint at the vendor, Ctadel makes an authenticated GET request to that endpoint, with the authentication scheme the vendor expects.

VerdictWhen
ACTIVE ("Valid" in the list, "Active" in the panel)Response 200; or 403, read as "key accepted but not allowed on this endpoint"; for Slack, ok: true; for Cloudflare, a token with status active
INACTIVE ("Invalid", "Inactive")Response 401; for Slack, invalid_auth, token_revoked, token_expired; for Cloudflare, any other status; for Datadog, a 403
UNKNOWN ("Unknown")Response 429 or unexpected, network error, unknown scheme, or no check possible

An unreachable vendor is retried a few times over several minutes; after the last attempt the finding is published as UNKNOWN. The verdict is written on the finding and in the graph.

Verification is on by default and is switched off in "Settings", "General" tab, "Secret Validation" toggle ("Validate detected secrets against external services").

Risk score

Each finding gets a score from 0 to 100 where validity outweighs severity: a base of 80 for CRITICAL, 60 for HIGH, 40 for MEDIUM, 20 for LOW, multiplied by 1.0 when the secret is ACTIVE, 0.75 when UNKNOWN, 0.15 when INACTIVE; plus 10 when the hosting resource is publicly exposed, plus 10 when the secret has been visible for more than 90 days. A revoked key therefore stays low-risk whatever its severity. The detail panel shows the breakdown.

The Secrets page

The "Secrets" page ("Secrets" entry of the menu) lists the findings with a "Search secrets by name, path, resource..." field, an "Export CSV" button and grouping by "Severity", "Secret type" or "Resource".

ColumnContent
"Risk"The 0 to 100 score
"Severity"The rule's severity
"Type"The rule's family (cloud, SaaS, database and so on)
"Name"The rule's name
"Location"The file path, or ENV:NAME for an environment variable
"Resource"The bucket, volume, function, image or repository (git://organisation/repository)
"Snippet"The masked value
"Validity""Valid", "Invalid" or "Unknown"

Filters are "Severity", "Validity", "Secret Type" and "Resource" (the 30 most frequent values).

A click opens the detail panel with three tabs: "Overview" (rule, affected resource, "Evidence" with the snippet and path, validity, score), "Rotation" ("Rotation procedure" step by step, "Verify if still active" with the curl command of the vendor endpoint when one exists, and "Git history scrub", which recalls that removing the secret in a later commit does not invalidate it) and "History".

The Secrets page with the list of findings, their score, their validity and the filters.
Figure 1. Figure 1. The "Secrets" page on the demo instance. Demo data.

Secrets in pull requests

On a GitHub repository, each pull request gets a comment listing the repository's secrets (rule, severity, file, line) and a ctadel/secrets commit status that your branch protection can require. The status fails according to the threshold set on the repository in "Settings" ("Block PR on failure", "Block if findings ≥" for the "Secrets" family). An ignores: entry in the repository's .ctadel.yaml excludes an accepted false positive (a fake key in a test fixture): it is still stored, marked ignored, and no longer counts for the status. See Infrastructure as code.

Limits

  • Validity checks only cover the rules that declare a vendor endpoint, a part of the catalogue; the others stay "Unknown". Private keys, database passwords and generic tokens are never verified.
  • Secrets found in a Git repository are not sent for verification: the value is not kept when a repository is scanned. Their validity stays "Unknown", and the pull request comment shows it that way.
  • A secret found in an image layer by Trivy is reported with Trivy's type; it is not verified.
  • Buckets: only the first 10 MB of a text object are read; a secret placed beyond is not seen. Legacy doc and xls formats are not read. An object unreadable because of network or permissions makes the scan incomplete, and the bucket's earlier findings are not closed.
  • Disks: a file over 256 MB is counted as skipped. The system and dependency directories listed above are not read. Serverless code skips a file over 10 MB and stops at 10,000 files; the result is then marked incomplete and closes no earlier finding.
  • In a repository, a file over 1 MB is not read, and a line over 1 MB stops the reading of its file.
  • The entropy and test-token filters only apply to serverless code, not to buckets, disks or repositories.
  • The pull request comment and status exist on GitHub only; on GitLab, the findings are stored but the merge request gets neither comment nor status.
  • A 403 from the vendor is read as "valid" except for Datadog; for a vendor that answers 403 to an invalid key, the verdict is wrong.