Contents
API docs

Alerts

The events the monitor records, what their timestamps mean, and how to poll for them.

View as Markdown

An alert is an event Feint's monitor recorded about one of your canaries. The one that matters most is the first use of a decoy; the other three say the decoy itself has changed underneath you.

Kinds#

kind Meaning
canary.used The token was used at least once since Feint last looked. This is the event the product exists to produce.
canary.revoked_externally The credential was revoked or disabled at the provider by something other than Feint: an operator cleaning up, or someone burning the credential.
canary.missing The provider has no record of the credential at all.
canary.expired The canary passed its expires_at. The credential no longer works and the decoy no longer detects anything. Raised once, within minutes of the expiry.

Your team is emailed for all four, and each is POSTed to any webhook endpoints you register.

Fields#

Field Meaning
id The event's id. De-duplicate on it.
canary_token_id The canary it concerns.
kind One of the kinds above.
occurred_at For canary.used, the provider's own timestamp: GitLab's throttled last-used time, or the first CloudTrail record of the window for an AWS decoy. For canary.expired, the canary's expires_at. For the other two kinds, when Feint's monitor noticed the change.
detected_at When Feint's monitor saw it.
source_ips Addresses the provider reported for the use. May be empty for GitLab at first; complete for an AWS decoy.
detail Context for the notification: for canary.used, the placement, the provider and the credential's id at the provider (external_id: the access key ID for an AWS decoy). For an AWS decoy also CloudTrail's summary of the window: count (exact), calls (one entry per call, in order, up to the first 50), first_seen_at, last_seen_at, records_truncated and, when AWS found the key in public and quarantined it, aws_quarantined_at.
created_at When the event was written.
records_url Where to fetch the provider's records behind the event: for an AWS decoy, every CloudTrail record of the window, via GET /v1/alerts/{id}/records with alerts:read. Empty for GitLab, which keeps none.

What a canary.used event means#

A GitLab token. GitLab records a token's last use at most once every ten minutes or so, and Feint polls every few minutes. So:

  • One event means the canary was used at least once in a window of roughly ten minutes ending at occurred_at. Several uses inside one window look like one. Do not present the number of events as the number of uses.
  • Expect detected_at to trail the use by up to a quarter of an hour.
  • source_ips is what GitLab attributes to the token, on its own schedule. A use may show an empty list at first; the address then appears on the canary's last_used_ips.

An AWS access key. Every call made with the key is a CloudTrail record, and Feint reads the trail as it is delivered. So:

  • detail.count is exact, and detail.calls lists what was tried, one entry per call in order: occurred_at, action as service:Operation (sts:GetCallerIdentity succeeds, everything else is denied), error with AWS's error code or null, source_ip and user_agent, together, so an address reads against the call it made. The first 50 calls are listed; count says how many there were, and every one is at records_url.
  • detail.aws_quarantined_at is set when AWS's own scanning found the key in public and attached its quarantine policy to the user. That attach is AWS's call, not the actor's: it is not among calls or in count, though its record is at records_url.
  • Calls within about fifteen minutes of the first are gathered into one event; a later call opens a new one.
  • source_ips and the addresses and user agents in detail.calls come from the records themselves, so they are complete from the first delivery.
  • CloudTrail delivers in a few minutes, so detected_at trails the use by five to fifteen minutes.
  • The records themselves are at records_url, in CloudTrail's own field names and reduced to what describes the request and the answer: time, service and action, Region, source address, user agent, error code and message, request parameters, response elements, resources, TLS details, and of the identity only the access key ID. Feint's account, AWS request ids and CloudTrail's bookkeeping are left out.

Listing#

GET /v1/alerts returns events for every canary in the organization, newest detected_at first. limit defaults to 100 and is capped at 500; values outside 1–500 are clamped rather than refused.

curl -s "https://api.feintsecurity.com/v1/alerts?limit=200" \
  -H "Authorization: Bearer $FEINT_API_KEY"
r = requests.get(f"{API}/alerts", headers=HEADERS, params={"limit": 200})
r.raise_for_status()
events = r.json()

Polling#

To be told rather than to ask, register a webhook endpoint. To poll instead: there is no cursor yet. The list is newest-first, so a poller walks it and skips what it has already handled:

import time

seen: set[str] = set()

while True:
    r = requests.get(f"{API}/alerts", headers=HEADERS, params={"limit": 200})
    r.raise_for_status()
    for event in reversed(r.json()):  # oldest first, so handling is in order
        if event["id"] in seen:
            continue
        seen.add(event["id"])
        handle(event)  # yours: page someone, open a ticket, forward to the SIEM
    time.sleep(300)

Poll no more often than every few minutes. The source data cannot change faster than the provider reports it, and a tighter loop learns nothing sooner.

Acknowledging#

An acknowledgement is recorded on the canary, not the event: needs_attention on the token means used, and nobody has acknowledged since. It is done in the console today; the API reflects it in acknowledged_at and acknowledged_by.