DEV Community

João André Gomes Marques
João André Gomes Marques

Posted on • Originally published at asqav.hashnode.dev

Every rejection leaves a trail, persistent log of rejected attempts

A rejection is data. Until last week we were throwing it away.

If an attacker submitted a forged signature against the public verify endpoint, we returned 404 signature_not_found and that was the entire footprint. Same for a cross-org access attempt on the replay endpoint, an agent that got suspended mid-run trying to sign once more, or a probe walking through random sig_* ids.

An older trust-data-infrastructure project we read during a cold audit is explicit about this: log every invocation including the invalid ones, "para auditoria futura". It is the right call. We borrowed it.

What landed

One new table, rejected_attempts. Indexed on (organization_id, created_at) and (agent_id, created_at) for fast org-scoped time-window scans.

One helper, log_rejected_attempt(db, request, endpoint, failure_reason, ...). Captures IP, User-Agent, X-Request-ID. Truncates user-supplied bytes. Commits its own row. Swallows persistence failures so logging cannot become a 5xx.

Wired today at four sites: GET /verify/{record_id} (signature and approval not-found branches), GET /signatures/{id} and POST /signatures/{id}/replay (not-found and cross-org branches). Sign and countersign sites in routes/agents.py come next.

How operators query it

GET /api/v1/observability/rejected-attempts, Pro+ tier, scoped to your organization. Filters: failure_reason, agent_id, time window, pagination.

What to do with the data

  1. Probe alerting. N rejections from one IP in M minutes is a probe.
  2. Suspended-agent retries. A revoked or decommissioned agent that keeps trying is misconfigured or compromised.
  3. Cross-org access attempts. A bearer token from one org hitting a signature_id from another org is the kind of thing you find out about months later. Now you find out the same hour.

PR with the diff, the migration, and tests: github.com/jagmarques/asqav#140

Top comments (0)