Abuse analytics helps security, trust, and product teams understand automated misuse, account attacks, resource exhaustion, policy enforcement, and customer friction. It must preserve the difference between a signal, a decision, and an eventual outcome.
GraphJSON does not enforce rate limits, block traffic, challenge identities, or run a risk engine. Those decisions belong in the request path and their authoritative services. Send bounded decision facts to GraphJSON for trend, quality, and operational analysis.
This recipe complements Security audit event analytics, which covers evidence for privileged actions.
Separate observation, decision, and outcome#
request or behavior observed
→ signals calculated
→ policy or model evaluated
→ allow, challenge, limit, or block decision
→ customer and abuse outcome observed later
Do not label a blocked request as confirmed abuse. The decision may be wrong, and true abuse may pass undetected.
Model rate-limit decisions#
{
"event": "rate_limit_evaluated",
"decision_id": "rl_01J...",
"request_id": "req_91",
"actor_type": "api_key",
"actor_id": "key_hash_42",
"account_id": "acct_7",
"resource": "event_ingestion",
"operation": "write",
"limit_key": "workspace_ingest_rps",
"policy_version": 6,
"window": "1s",
"limit": 40,
"observed": 47,
"decision": "deny",
"reason": "rate_exceeded",
"environment": "production"
}
Hash or tokenize sensitive actor identifiers upstream with a keyed method. Never send API keys, session tokens, authorization headers, full IP addresses, or request bodies.
Use controlled resource, operation, decision, and reason values so
queries remain stable.
Model authentication challenges#
{
"event": "authentication_risk_decision",
"decision_id": "auth_81",
"actor_id": "usr_hash_7",
"account_id": "acct_7",
"action": "sign_in",
"decision": "challenge",
"challenge_type": "step_up_mfa",
"signal_categories": ["new_device", "velocity"],
"risk_band": "high",
"ruleset_version": 12,
"model_version": "risk_2026_07_1"
}
Prefer categories and bands over raw fingerprints, IPs, user agents, or model feature vectors. Keep sensitive signals in the risk service with appropriate access and retention.
Preserve rule and model versions#
Without a version, a sudden block-rate increase is ambiguous. It may reflect:
- more abuse
- a policy deployment
- a threshold change
- a telemetry bug
- traffic mix
- an outage in a dependency
Send the exact evaluated ruleset or model version and emit deployment events. Do not apply today’s rule labels retroactively to old decisions unless the analysis is explicitly a replay.
Query decision rates with compatible denominators#
SELECT
toStartOfHour(toDateTime(timestamp)) AS hour,
JSONExtractString(json, 'resource') AS resource,
count() AS evaluations,
countIf(JSONExtractString(json, 'decision') = 'deny') AS denied,
denied / nullIf(evaluations, 0) AS deny_rate
FROM risk_events
WHERE
JSONExtractString(json, 'event') = 'rate_limit_evaluated'
AND JSONExtractString(json, 'environment') = 'production'
GROUP BY hour, resource
ORDER BY hour, resource
Decide whether one retry is another evaluation or part of the same logical request. Show unique actors and accounts beside request counts; one abusive actor can dominate volume.
Track challenge completion#
A challenge creates friction even when it protects the account:
{
"event": "authentication_challenge_completed",
"decision_id": "auth_81",
"challenge_type": "step_up_mfa",
"outcome": "passed",
"duration_ms": 18400,
"attempts": 1
}
Measure:
- challenge rate among eligible attempts
- pass, fail, abandon, and error rates
- time to complete
- later account-compromise outcomes
- support contacts after challenge
Do not interpret abandonment as malicious. Legitimate users can be unable to complete a challenge.
Create delayed outcome labels#
Risk quality often becomes known later:
- account owner confirms activity
- fraud or abuse investigation closes
- chargeback occurs
- appeal succeeds
- security incident is confirmed
- rate-limit exception is approved
{
"event": "risk_decision_reviewed",
"decision_id": "auth_81",
"review_outcome": "legitimate_activity",
"review_source": "customer_appeal",
"review_policy_version": 3,
"days_to_label": 2
}
Keep investigation notes and evidence outside GraphJSON. Send only controlled outcome categories.
Labels are incomplete and selected: reviewed decisions are not a random sample of all decisions. A low observed false-positive rate may simply mean few users appealed.
Evaluate false positives and false negatives#
When labels are mature:
observed false-positive rate
= decisions that blocked or challenged legitimate activity
/ labeled blocked or challenged decisions
Always show:
- labeled decision count
- total decision count
- label coverage
- maturity window
- review source
- policy and model version
False negatives are harder because allowed abuse is often never discovered. Use confirmed incidents and retrospective investigations, but do not claim a complete detection rate without complete labels.
Protect privacy and access#
Risk data can reveal security controls and user behavior. Minimize:
- raw network identifiers
- device fingerprints
- endpoint paths containing IDs
- rule internals that enable evasion
- investigation notes
- customer content
- secrets and tokens
Use keyed pseudonyms when correlation is required, rotate under a documented policy, and keep the key outside GraphJSON. Restrict detailed security dashboards and avoid public links.
GraphJSON currently uses one workspace-wide API key rather than fine-grained resource credentials. Put sensitive query access behind an authorized server and return bounded, role-appropriate results.
Monitor enforcement health#
Build an operating view with:
- evaluations, decisions, and unique actors
- deny, challenge, and throttle rates
- decision rates by resource and ruleset version
- challenge pass, fail, abandon, and error outcomes
- top controlled reason categories
- confirmed outcome labels and label coverage
- customer-friction guardrails such as support contacts
- pipeline lag and missing version fields
Alert in the enforcement system for immediate protection. GraphJSON’s supported alerts are useful for compatible aggregate monitoring, but are not a substitute for an in-path circuit breaker or pager with acknowledgment and escalation.
Validate policy changes#
Before rollout:
- replay the proposed policy on a representative governed dataset
- compare decisions by actor, resource, and customer segment
- review added and removed blocks
- estimate legitimate-user friction
- deploy gradually with a rollback owner
- monitor data and enforcement health independently
Do not send shadow decisions into the same metric as enforced decisions.
Include mode: "shadow" or use a separate event.
Launch checklist#
- Enforcement remains outside GraphJSON.
- Signals, decisions, challenges, and later outcomes are separate facts.
- Every decision has a stable ID and policy or model version.
- Raw secrets, IP addresses, fingerprints, and request bodies are excluded.
- Request, actor, account, and resource denominators are visible.
- Challenge friction is measured beside protection outcomes.
- False-positive analysis shows label coverage and maturity.
- Shadow and enforced decisions cannot be mixed.
- Sensitive reports are served through authorized bounded endpoints.
- Policy changes have replay, gradual rollout, rollback, and monitoring.