Incident response
How TDACorp Identity detects and handles security incidents today, which alerts exist, what runbooks an operator follows, what emails a person receives, and where the process is still pending an owner decision. It describes what is built and says plainly what is not.
On this page
Draft
Status
Last reviewed 5 October 2026. TDACorp Identity is pre-production. This page summarises the operator runbooks and the monitoring code in the repository. It is not a service commitment, and it does not claim a security operations team, an on-call rota or a response time.
Overview
| Question | Answer |
|---|---|
| Who responds? | The platform's operator. The repository describes a solo-operator setup and includes a recovery procedure for it |
| Is there a 24x7 rota? | No. None is described in the repository |
| Is there a public status page? | None exists in the repository |
| Are there defined severity levels? | No formal levels are documented. Runbooks classify by incident type and by which regulatory clock applies |
| What happens first? | The operator starts a timestamped incident log at the moment of awareness, then follows the matching runbook |
| Where do reports come from? | The operations monitor, the platform's own detection (for example token reuse), a person's report, or an outside researcher through vulnerability disclosure |
Detection and monitoring
Operations monitor
An operations monitor worker is enabled in the production configuration. It runs once per daily scheduled pass. Three of the alert kinds below (worker_tick_failed, client_registered, email_budget_exhausted) are raised by the worker tick, the client registration handler and the email drain, not by the monitor pass. Each check re-reads a recent window and raises an alert by email to a configured operator mailbox. It reads state only and writes no audit rows.
| Alert kind | Condition | Notes |
|---|---|---|
worker_stale | A recorded background worker has not run within its allowed quiet time | Production sets a floor of 26 hours so the normal daily gap does not page |
worker_tick_failed | A worker pass failed | Raised from the per-tick failure hook |
backup_stale | The backup heartbeat is older than the configured age, or the last backup reported failure | Configured at 36 hours in the production target configuration; the configuration the live deployment runs sets it to 0, which turns this check off. The heartbeat is written only by the operator backup command, not by the nightly GitHub Actions backup workflow. That workflow and a monthly restore-drill workflow are written but have not yet run, and the operator timer is not installed |
breakglass_activated | A break-glass activation was recorded | See break-glass access |
elevation_activated | An elevated-access grant was activated | |
webhook_auto_paused | A webhook was paused after repeated failures | |
deliveries_dead_lettered | Deliveries gave up after their attempts | By channel |
auth_anomaly | One network address failed sign-in against at least 10 distinct accounts, or failed at least 40 times, within a one-hour window | Rows with no recorded address are not counted. At most 10 addresses are listed per alert. The monitor runs once a day, so this looks only at the hour before each run |
client_registered | A client was registered | |
email_budget_exhausted | The email send budget was reached | |
privileged_action | A privileged audit event kind from a configured list was recorded in the look-back window | One digest per kind with a count, not one alert per event. Setting changes are always included. Webhook and audit kinds are never alerted. A platform access row counts only when it names an actor. The look-back is about two passes, so a daily pass reports events from about the last two days |
| Alert behaviour | Detail |
|---|---|
| Delivery | Email through the same provider as other platform email |
| De-duplication | One alert per kind and key per cooldown, 1 hour by default. The cooldown is in memory, so a restart can repeat one alert |
| Content | A short subject, a summary and counts or names. Never a principal's email, token, URL or error body |
| Failure | A failed alert never throws and never turns an incident into a second failure |
| Counter | Every check also counts an ops_alerts_total metric and logs, whether or not a recipient is set |
Dead-man's switch and external signals
| Signal | State |
|---|---|
| Healthcheck ping after each successful monitor pass | Built. Active only if the owner sets a healthcheck URL. The service is chosen by the owner and is not configured in the repository |
| External pinger for sub-daily scheduling | Optional. Not configured in the repository |
| Uptime alerting | The deployment plan says uptime alerting is the owner's healthcheck service |
| Vercel runtime logs | One JSON line per request. The hosting plan retains runtime logs for 1 hour |
| Telemetry export | Optional OTLP shipping of metrics, traces and the request log. No collector is configured in the deployment documents |
Detection built into the protocol
| Detection | What the platform does automatically |
|---|---|
| Refresh-token reuse | A retired refresh token presented again after the 10-second retry window revokes the whole token family and its session and bumps the account's access version. The account owner is also emailed once the reuse cleanup has run |
| Passkey signature counter regression | The sign-in is refused and the event is recorded |
| Sign-in abuse | Rate limits on sign-in and recovery attempts |
| Audit chain tamper | The hash chain makes tampering detectable on verification. A scheduled anchoring job and an offline anchor verification command (idctl anchor verify) are built. The scheduled job is off by default and is not enabled in the production configuration |
Service objectives that bear on incidents
Service level objectives and alert thresholds are defined in the repository as the intent for measurement. They are not an uptime guarantee, and most are not measurable yet.
| Objective or alert | Target defined | Measurable today |
|---|---|---|
| Authorization decision latency | p99 at most 15 ms | Yes, metric wired |
| Token endpoint latency | p99 at most 100 ms | No |
| Hosted sign-in availability | 99.9% | No |
| Authorization API availability | 99.95% | No |
| Invalidation propagation | p99 at most 2 seconds | No |
| Audit anchor age | At most 26 hours | No |
| Alert: high-risk indeterminate decision | Any occurrence | Yes |
| Alert: refresh-token reuse | Any occurrence | No |
The window is 30 days rolling. Seven metrics are wired in the catalog, four of them the authorization metrics that feed an objective, and thirteen are declared but not emitted. No exporter is bound unless the optional collector is configured. The terms of service state that no uptime guarantee or service level agreement exists.
Runbooks
Seven operator runbooks and one notification file exist. Each step is tagged BUILT or NOT BUILT, so that an operator is not told to run a command that does not exist.
| Runbook | When it applies | Containment summary | Notable gaps stated in the runbook |
|---|---|---|---|
| Signing key compromise | A signature that cannot be accounted for, a leaked key reference, or an unexpected key id in use | Take the key out of service, retire key rows and bump every application permission version with an emergency rotation command, revoke token families, send back-channel logout and an incident signal | No production KMS provider exists. Blanket family revocation and incident fan-out are not built as incident operations |
| Product client key compromise | One product's client assertion key is loose | Revoke the key id, revoke that client's token families, consider suspending the client | Client-wide family revocation is not built as one operation |
| Database read breach | Someone read the database: a dumped backup, an exposed replica, a stolen connection string | Cut access, revoke all sessions and token families, rotate data keys and the key-encryption key, notify | Forcing a password change has no implementation. The runbook marks key and KEK rotation as not built, but idctl deks rotate, rotate-kek and rekey now exist, and rotating a data key rewrites no stored secrets. Cutting access is a provider-console action, not performed by this repository |
| Admin account compromise | A platform or organization administrator is under someone else's control | Suspend the principal, revoke its sessions and token families, revoke the authority it granted, drain the cascade | Revocation is library code with no command. A cascade drain worker exists |
| Audit chain mismatch | A chain verification reported broken, or an anchor does not match | Freeze the platform role, record the failure, stop writing if judged necessary, verify from the last good anchor | Freezing the platform role is not built here. Restoring from point-in-time recovery is not built here |
| Support material exposure | A HAR file, log bundle or screenshot with live credentials reached a support channel | Revoke first and delete second, remove every copy, tell the sender | A screenshot cannot be scrubbed automatically and needs manual review. Binary formats are refused by the scrub command |
| Solo-operator break-glass recovery | The only platform admin is locked out, or a second admin identity or an offline key backup is needed | An offline recovery command opens a one-hour console-only session. Then create a second admin identity and keep an offline key backup | The runbook marks key rotation and re-wrapping as not built, but idctl deks now provides them. If the key is exposed, the database read breach runbook applies |
| Notification clocks and template | Any incident above, to decide who to tell and by when | A template that records awareness, classification, nature, timing, extent, consequences, mitigation and contact | See breach notification below |
Each runbook has the same shape: what you are seeing, whether it is really this incident (with look-alikes ruled out), containment, diagnosis, recovery, and what to capture. The system the runbooks act on is described honestly: for example, idctl has fifteen commands, there is no production KMS provider, and a database session obtained through the provider console behaves differently as owner and as superuser.
Tooling that supports response
| Tool | State |
|---|---|
idctl scrub | Built. Scrubs a text support bundle of tokens, credentials, private keys, password hashes and email addresses, or rejects an unscrubbed one. Refuses binary formats |
idctl keys rotate --emergency | Built. Requires an explicit key provider |
idctl breakglass | Built. Dual-control challenge and activation, and a solo-recovery command |
idctl anchor | Built. Signs and publishes the current audit chain head, and anchor verify checks published anchors against a key set obtained out of band |
idctl backup | Built. A dump with heartbeat, and a restore drill into a throwaway database. The timer is not installed |
idctl principal and idctl sweep | Built. Operator erasure of a deactivated principal and sweeps |
| Audit read and verify | A library reader and verifier exist, and idctl anchor verify checks published anchors. Commands named audit verify and drill restore do not exist (idctl backup drill is the restore drill) |
| Blanket session and token revocation | Per-principal library functions exist. Database-wide revocation is SQL |
Break-glass access
| Item | Detail |
|---|---|
| Purpose | Let a platform operator act when ordinary admin access is unavailable, with evidence kept |
| Dual-control path | A challenge and activation procedure needs two admin identities. It produces a console-only break-glass session |
| Solo path | When there is a single effective platform admin, an offline command opens a one-hour console-only session. It refuses if a second effective admin exists |
| What it requires | Database credentials for the platform role and the key-encryption key in the operator's shell, from an offline backup. The command verifies every key opens under that key before writing anything |
| What it records | Platform-access and break-glass audit rows, and a mirrored audit trail |
| Alerting | An activation raises a breakglass_activated alert to the operator mailbox |
| Database access | A human gets a database session only through the provider console, as a documented, audited break-glass procedure. The three application roles cannot be logged into |
| What it cannot do | Even a superuser cannot update or delete audit events, because event-table guards apply to the owner and superusers |
| Support impersonation | An administrator can open a time-boxed impersonation of an account. The target is emailed at the start and when the window closes |
What people are emailed
These emails are built and sent by the platform. Security notices are sent from a separate security sender address where one is configured.
| Trigger | |
|---|---|
| New sign-in to your account | A sign-in from a browser the account has not seen. Contains the label, country, address and method. Links let the person secure the account |
| Your account was secured | The person acted on a new-sign-in alert |
| Security alert | A password set, change or removal; passkey added, renamed or removed; authenticator enabled or disabled; recovery codes regenerated; personal access token created; device trust changed; export requested; deactivation requested |
| Your primary email was changed | The primary address changed. The old address receives a 24-hour link that reverts the change and signs everything out |
| Verify your email address | Email verification |
| Your TDACorp Identity sign-in link | A magic-link sign-in |
| Confirm your email address, You already have an account | Registration |
| Recover your TDACorp Identity account | Recovery link |
| Account recovery started: cancel it if this was not you | A recovery began. Sent so a real owner can cancel |
| Your account recovery is on hold | A recovery is in its 24-hour hold |
| A recovery code was not accepted | A recovery code failed |
| Admin action notices | An administrator reset a sign-in method, signed out a session, suspended or deactivated the account, removed an app connection, or an elevated access request involving the account was approved or activated |
| Sessions ended | A session, all sessions, or a device's sessions were ended from the account page |
| A support engineer is accessing your account | Start of an impersonation. A closing notice follows when the window closes |
| Refresh-token reuse | A reused refresh token ended every session and API token on the account. Sent once the reuse cleanup has run |
| Blocked risky sign-in, failed-attempt burst | A sign-in was blocked as risky, or many failed sign-in attempts hit the account. Each is rate limited per account |
| A role was granted to you, a role was removed from you | A role binding in an organization changed |
| You have been invited | An invitation to an organization |
| Security alert, data export ready | An export finished |
There is no permanent lockout after repeated failed sign-in attempts.
Breach notification
| Item | Current state |
|---|---|
| Internal clock | The notification file records two clocks that start at awareness, not at containment: 6 hours for reporting to CERT-In for listed incident classes, and 72 hours for a detailed report to the Data Protection Board. Intimation of affected people is to happen without delay |
| Which incidents cross which clock | The file gives defaults by runbook as judgement, to be overridden by a lawyer. For example, a database read breach crosses both clocks. A signing key compromise crosses the CERT-In clock and the Board clock if tokens minted under it reached personal data |
| What a Board report carries | Nature, timing, extent, consequences, mitigation and a contact |
| Affected people | Notification is a manual mail merge from a database query. There is no export of every affected principal, and there is no per-incident fan-out to every affected person. Shared Signals session-revoked and account-disabled events are delivered to configured receivers by a worker, which on the current hosting plan can run up to about a day late |
| 24x7 contact | The specification calls for a recorded 24x7 point of contact. The template leaves it as a field and no named contact is published here |
| Notice to a product or customer | No contractual period is defined |
| Public commitment | This page makes no breach-notification commitment beyond what applicable law requires |
| Regime applicability | The runbooks treat the CERT-In Directions as in force and the Data Protection rules as phased in. The platform does not decide applicability during an incident: the instruction is to write the report |
Note
A formal breach-notification process, including who is the contact for each clock and any contractual notice to products, is pending decision by the owner. Pending legal review by the owner.
Communication during an incident
| Channel | State |
|---|---|
| Status page | None exists |
| Email to affected people | Manual, per above |
| Email to the operator | Operational alerts, per the monitor above |
| Public statement | Not defined. Researchers are told by email on the schedule of the disclosure policy |
| Sharing evidence | Anything attached to a report passes through the scrub command first. Times are written in IST with the UTC offset, because the clocks require synchronised time |
After an incident
Every runbook ends with a capture section, and the notification template has an Open field for what is still unknown at the time of the report.
| Captured | Why |
|---|---|
| First possible exposure, last known good, detection and containment times | Establish the timeline |
| The audit sequence range covering the incident, with the verification result | The audit log is the evidence |
| Which containment steps could and could not be performed because the tooling does not exist | The runbooks call this the honest half of the report |
| The notification file, filled forward | Meets the clocks |
| The argument for what to build next | For example, a cloud KMS provider, a blanket revocation path and back-channel fan-out |
A formal post-incident review meeting, a published post-mortem and a review deadline are not defined in the repository.
Contacts
| Purpose | Contact |
|---|---|
| Security reports and questions about an incident | security@tdacorp.in |
| If you think someone else is in your account | Review and revoke every session, check your activity and change your credentials in your account, then email the address above |
| Machine-readable contact | /.well-known/security.txt |
No response time is promised. TDACorp runs no round-the-clock rotation.
What this page does not claim
| Not claimed | Detail |
|---|---|
| A security operations centre or 24x7 coverage | Not described anywhere in the repository |
| A response or resolution time | None |
| Severity levels or an incident classification standard | None formally documented |
| A status page | None exists |
| A tested incident drill | No incident drill is recorded. The first restore drill has not yet been run |
| A completed breach-notification process | Pending decision by the owner. Notification of people is manual |
| That every runbook step is automated | Several steps are marked NOT BUILT and name the manual alternative |
| A production key-management provider | Pre-production key management is software, which the database read breach runbook treats as a larger blast radius |
| Measurable service objectives | Only some metrics are emitted, and no exporter is bound unless the optional collector is configured |
| That the monitor is a complete detector | It runs once per daily pass and covers the listed conditions only |
| Certification or external audit | None. No SOC 2 report, no ISO 27001 certificate and no third-party penetration test report |