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).
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>orST_APP_KEY = "<value>". The same values in aMarkdown 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:
cs<digit>.+ long lowercase alphanumeric body (cs1.andcs3.seen)ST-App-Keyheader)ak<digit>.+ alphanumeric body (ak1.seen)1000.<32 hex>.<32 hex>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.+ 30uppercase alphanumerics) are identifiers, not secrets, so the Zoho rule doesn't
match them.
Proposed rules
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.
.gitleaks.tomlonlygeneric-api-keymatched): all four table cells and a plainrefresh = "…"assignment missedRuns on the pinned binary: the same rules, in a repo-local config that
inherits this one through
[extend] path, parse and fire under the reusableworkflow's gitleaks 8.21.2 (#30).
False positives: none found.
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.
the one known commit, and nowhere else.
The fixed prefixes (
cs<digit>.,ak<digit>.,100<digit>.followed by theexact 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 therules up automatically. Repos with a forked local config don't (eng-governance#94).