New certificates for your domains
Who this is for: security teams who want to know about a new certificate on their domains (or a lookalike using one of their domain names) before an attacker uses it against their customers.
Before you start: the connections crtsh and security-chat, and the company variables company_domains and security_email.
What it does
Section titled “What it does”Every hour, the workflow queries crt.sh (a public certificate transparency log search) for each domain in company_domains. It remembers the highest certificate id it has seen for each domain, so it only reports certificates logged since the last check. When there are new ones, it emails security a CSV and posts a short alert to a chat channel. A new certificate for one of your domains is often routine (a renewal, a new subdomain), but it can also reveal a phishing site or shadow IT using your name before you’d otherwise notice.
What it can access
Section titled “What it can access”crtsh: a connection with no sign-in, basehttps://crt.sh. It only reads crt.sh’s public search results; there is nothing to authenticate.security-chat: a webhook connection to your chat tool (for example a Slack or Microsoft Teams incoming webhook), used only to post the short alert text.- It emails only
security_email, a CSV of the new certificates and nothing else. - State: one small number per domain (the highest certificate id seen), nothing else.
- No secret value is ever in the workflow.
What you need to set up
Section titled “What you need to set up”- Create the
crtshconnection with no sign-in and base addresshttps://crt.sh(see Create a connection). - Create the
security-chatconnection pointing at your chat tool’s incoming webhook. - Add the company variables
company_domains(a list of your domains, for example["bank.example", "partner-portal.example"]) andsecurity_email. - Take it into use from the Liitora library (see Take a library workflow into use), then read the test cases on your copy, run them and approve it.
How to adapt it
Section titled “How to adapt it”- Fewer routine alerts. crt.sh logs every certificate, including automatic renewals. If your certificate authority issues predictably, filter the script’s results by
issuer_nameto alert only on unexpected issuers. - A stricter check for lookalike domains. Add a second, separate schedule that searches for domains that merely contain your brand name (not your own registered domains), to catch phishing domains that aren’t subdomains of yours at all.
- A different chat tool. Change the
security-chatconnection and thenotify_chatstep’surlandjsonto match your tool’s incoming webhook format. - Daily instead of hourly. Certificate transparency logs are not instant; if hourly is more noise than you want, change the
cronto run once or twice a day.
What Liitora shows on the approval page
Section titled “What Liitora shows on the approval page”Every hour, on the hour or when someone starts it by hand: query crt.sh for each domain and find certificates issued since the last check. Depending on that result, build a CSV of the new certificates and post a short alert to the security chat channel, then email security about the new certificates.
Trust details
Section titled “Trust details”| What | Details |
|---|---|
| Publisher | Liitora |
| Version | 1.0.0 |
| Licence | Apache-2.0 |
| Content hash | sha256:f2311d47aaceaa2a333ab35016cd04e980f1e39443808239dfdef23c1d50b7ff |
| Tests | 3 |
| Connections | crtsh, security-chat |
| Company variables | company_domains, security_email |
| Secrets | None |
| Personal data | None. Certificate records name domains and issuers, not people. |
| Saved state | Yes |
| Runs on your own runner | No |
| Actions | platform/csv.build@v1, platform/email.send@v1, platform/webhook.send@v1 |
Liitora signs each version of this workflow. When your company takes it into use, Liitora checks the content hash and the signature again before it copies the workflow into your company. See How the library is kept safe.
The full YAML
Section titled “The full YAML”# A Liitora library workflow for a bank CISO (see the guide, Library > New certificates for# your domains). Connections used: crtsh (no sign-in, base https://crt.sh),# security-chat (a webhook to a chat channel). Company variables# used: company_domains (a list of domain names), security_email.name: New certificates for your domainsdescription: Every hour, query crt.sh for each company domain, remember the highest certificate id seen, and alert security by email and chat about certificates issued since the last check, which can reveal phishing sites or shadow IT.
on: schedule: - cron: "0 * * * *" timezone: UTC manual: true
concurrency: group: certificate-watch cancel_in_progress: false
steps: - id: find_new name: Query crt.sh for each domain and find certificates issued since the last check connections: crtsh: crtsh with: domains: ${{ vars.company_domains }} run: | export default async function run(ctx, input) { const crtsh = ctx.connection("crtsh"); const domains = input.domains || []; const newCerts = []; for (const domain of domains) { const cursorKey = `cert_max_id_${domain.replace(/\./g, "_")}`; const cursor = (await ctx.state.get(cursorKey)) || 0; const res = await crtsh.get("/", { query: { q: `%.${domain}`, output: "json" } }); if (!res.ok) { throw new Error(`crt.sh answered ${res.status} for ${domain}`); } const rows = res.json() || []; let maxId = cursor; for (const row of rows) { const id = Number(row.id); if (id > cursor) { newCerts.push({ domain, id, common_name: row.common_name || row.name_value || "", issuer_name: row.issuer_name || "", entry_timestamp: row.entry_timestamp || "", }); } if (id > maxId) { maxId = id; } } if (maxId > cursor) { await ctx.state.set(cursorKey, maxId); } } newCerts.sort((a, b) => (a.entry_timestamp < b.entry_timestamp ? -1 : 1)); return { newCerts, count: newCerts.length }; }
- id: build_csv name: Build a CSV of the new certificates needs: [find_new] if: ${{ steps.find_new.outputs.newCerts.length > 0 }} uses: platform/csv.build@v1 with: rows: ${{ steps.find_new.outputs.newCerts }} columns: [domain, id, common_name, issuer_name, entry_timestamp] filename: new-certificates.csv
- id: email_security name: Email security about the new certificates needs: [build_csv] if: ${{ steps.find_new.outputs.newCerts.length > 0 }} uses: platform/email.send@v1 with: to: ["${{ vars.security_email }}"] subject: "New certificates for your domains: ${{ steps.find_new.outputs.count }}" text: "crt.sh logged new certificates for your domains since the last check. A new certificate can be routine, or it can be a phishing site or shadow IT using your name. The certificates are attached as a CSV." attachments: ["${{ artifacts.new-certificates.csv }}"]
- id: notify_chat name: Post a short alert to the security chat channel needs: [find_new] if: ${{ steps.find_new.outputs.newCerts.length > 0 }} uses: platform/webhook.send@v1 connections: target: security-chat with: url: /hooks/certs json: text: "${{ steps.find_new.outputs.count }} new certificate(s) found for your domains. Check email for the details."Changelog
Section titled “Changelog”- 1.0.0 (2026-10-04): First version in the library.