Skip to content

gitleaks: no detection rules for ServiceTitan or Zoho credentials; table-cell values pass the scan #32

Description

@CameronBrooks11

Gap

Gitleaks' default rules have no detector for ServiceTitan or Zoho
credentials, and the org config adds none. Committed values are caught only
when the surrounding text happens to match generic-api-key, e.g.
client_secret=<value> or ST_APP_KEY = "<value>". The same values in a
Markdown table cell, or assigned to a variable without a key-like name, pass
the scan.

A full-history scan with the org config reported a repo clean while it still
held a ServiceTitan client secret, a Zoho OAuth refresh token and a Zoho Cliq
webhook key. All three were in Markdown tables in spec documents. (Remediation
is tracked privately; see eng-governance#88 for the coverage-gap record this
belongs to.)

Credential shapes

These prefixes are observed on issued credentials, not taken from vendor format
documentation:

Credential Shape
ServiceTitan OAuth client secret cs<digit>. + long lowercase alphanumeric body (cs1. and cs3. seen)
ServiceTitan App Key (ST-App-Key header) ak<digit>. + alphanumeric body (ak1. seen)
Zoho OAuth access/refresh token 1000.<32 hex>.<32 hex>
Zoho Cliq webhook token (zapikey) 1001.<32 hex>.<32 hex>

Not covered by anything below: the Zoho OAuth client secret is bare hex with
no prefix, so no narrow rule can target it. Zoho client IDs (1000. + 30
uppercase alphanumerics) are identifiers, not secrets, so the Zoho rule doesn't
match them.

Proposed rules

[[rules]]
id = "servicetitan-client-secret"
description = "ServiceTitan OAuth client secret"
regex = '''\bcs[0-9]\.[A-Za-z0-9]{30,}'''
keywords = ["cs0.", "cs1.", "cs2.", "cs3.", "cs4.", "cs5.", "cs6.", "cs7.", "cs8.", "cs9."]
entropy = 0

[[rules]]
id = "servicetitan-app-key"
description = "ServiceTitan App Key"
regex = '''\bak[0-9]\.[A-Za-z0-9]{16,}'''
keywords = ["ak0.", "ak1.", "ak2.", "ak3.", "ak4.", "ak5.", "ak6.", "ak7.", "ak8.", "ak9."]
entropy = 0

# Zoho OAuth tokens (1000.) and Cliq zapikeys (1001.) share this shape.
[[rules]]
id = "zoho-token"
description = "Zoho OAuth token or Cliq webhook token"
regex = '''\b100[0-9]\.[0-9a-f]{32}\.[0-9a-f]{32}\b'''
keywords = ["1000.", "1001.", "1002.", "1003.", "1004.", "1005.", "1006.", "1007.", "1008.", "1009."]
entropy = 0

Evidence

Detection: a fixture of random tokens in each shape, in two contexts (table
cells, and code/header assignments), was scanned with gitleaks 8.16.0.

Config Findings
Org .gitleaks.toml only 4 of 9 (only lines where generic-api-key matched): all four table cells and a plain refresh = "…" assignment missed
Org config + rules above 9 of 9

Runs on the pinned binary: the same rules, in a repo-local config that
inherits this one through [extend] path, parse and fire under the reusable
workflow's gitleaks 8.21.2 (#30).

False positives: none found.

  • Working trees of 67 local org clones (excluding node_modules, .venv,
    build output) with only these rules: 0 findings. A positive-control run of
    the same config on the fixture found all 9.
  • Full reachable git history of the same 67 clones: the rules fired only on
    the one known commit, and nowhere else.

The fixed prefixes (cs<digit>., ak<digit>., 100<digit>. followed by the
exact hex structure) are what keep these narrow, so they need no entropy
threshold or path scoping.

Note for consumers

Repos that inherit this file via [extend] path = ".gitleaks.org.toml" pick the
rules up automatically. Repos with a forked local config don't (eng-governance#94).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions