Skip to content

Latest commit

 

History

History
150 lines (119 loc) · 7.47 KB

File metadata and controls

150 lines (119 loc) · 7.47 KB

Job-completion webhook schema (v1.0)

When an async analysis job reaches a terminal state, the server can POST an at-least-once webhook to a configured callback URL so consumers don't have to poll to the end. This is the canonical, versioned envelope every consumer conforms to. The feature is off by default (see Configuration).

  • One shape for all consumers. There is no per-consumer transformation; consumer-specific data rides in the context field (a verbatim echo of what the client supplied at submit).
  • Delivered at terminal. There are no interim submitted/running events — use GET /v1/workflow/async/job/{job_id} for in-progress polling. Delivery is idempotent: a stable event_id per job_id lets a consumer safely de-duplicate a redelivery.
  • Small payloads. Per-stage reports are referenced by report_url, not inlined; pull the full Markdown via GET /reports/....

Envelope

{
  "schema_version": "1.0",
  "event_id": "5f2b0642-…",               // stable UUIDv5 of job_id; idempotency key
  "event_type": "job.completed",          // job.completed | job.failed
  "occurred_at": "2026-07-14T18:03:00Z",  // RFC3339 UTC
  "source": "vuln-analysis",
  "job": {
    "job_id": "f4abf109-…",
    "status": "completed",                // completed | failed (terminal only)
    "image": { "name": "…", "tag": "…", "digest": "sha256:…" },
    "error": null,                        // string on job.failed, else null
    "result": { /* native PipelineResult: outcome, success, elapsed_seconds, results[], … */ },
    "summary": { "vulns": [ /* flattened per-vuln view, see below */ ] }
  },
  "context": { "program_id": 1234, "exception_id": 5678 }  // verbatim echo when a result exists
}

Status → event mapping

JobStore state event_type status job.result job.error context
SUCCESS job.completed completed PipelineResult null present
FAILURE job.failed failed PipelineResult or null message present if a result exists
INTERRUPTED job.failed failed null message absent
  • SUCCESS means the workflow returned a PipelineResult whose outcome is complete or partial. The analysis outcome lives in job.result.outcome and per-result error; partial analysis is still job.completed.
  • FAILURE means no vulnerability produced a usable result or execution raised before producing a result. A structured outcome: failed result, summary, and context are retained when available. If execution raised first, job.result is null and the consumer correlates by job_id (store it from the submit response).
  • INTERRUPTED means the analysis service restarted before the job completed. It follows the same job.failed contract as FAILURE, including correlation by job_id.

job.result.outcome

outcome is the authoritative aggregate analysis state:

value meaning legacy success
complete Every requested vulnerability produced a usable result. true
partial At least one requested vulnerability succeeded and at least one did not. false
failed No requested vulnerability produced a usable result. false

Consumers should branch on outcome; success remains for compatibility with existing clients that only distinguish complete from not-complete.

job.summary.vulns[]

A flattened per-vuln view for upsert. One object per vulnerability:

field notes
vuln_id the submitted identifier (CVE-… or GHSA-…)
cve_id auto-classified from vuln_id
package_name resolved package; null when unresolved
package_version resolved version; null when unresolved
vex_status e.g. not_affected, affected
vex_justification VEX justification, when applicable
success whether analysis for this vuln succeeded
error per-vuln error string, with host paths redacted and length capped

Consumers must tolerate null package_name/package_version. Public error strings and result fields are limited to 2,000 characters and redact absolute host paths. Path-like result fields expose only validated /reports/... routes; unresolved host paths become null. The separate context object remains a verbatim, type-preserving client echo.

context

A verbatim, type-preserving echo of the client's callback_context submitted with the job (POST /v1/workflow/async). Integers stay JSON numbers; the pipeline attaches no meaning to its keys. Present whenever a structured result exists; absent when execution failed or was interrupted before producing one.

Authentication (both optional, independent)

  • Transport bearer. When an outbound OAuth2 identity is configured, requests carry Authorization: Bearer <token> (client-credentials grant). The receiver's gateway validates it. Provider-neutral — no identity provider is named in this project's code or config keys.

  • HMAC signature. When an HMAC secret is configured, each request carries X-Webhook-Timestamp and X-Webhook-Signature: sha256=<hex> (GitHub/Stripe convention). The signature is HMAC-SHA256(secret, "<timestamp>.<canonical_json>") where the canonical JSON is the exact request body:

    json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True)

    A consumer can require either, both, or neither.

Versioning

schema_version allows additive evolution. New fields may be added within 1.x without breaking consumers; breaking changes bump the major version.

Delivery semantics

  • Retries and dead letters. Transport failures, 5xx responses, 408, and 429 are retried with bounded exponential backoff and jitter. Retry-After hints from 429 and 503 responses are honored up to the configured backoff ceiling. When an OAuth token provider is configured, 401 responses are also retried; all other 4xx responses are dead-lettered immediately. A delivery that exhausts the positive VULN_ANALYSIS_WEBHOOK_MAX_ROUNDS limit is also dead-lettered.
  • At-least-once, restart-durable. A SQLite ownership lease prevents concurrent dispatchers from sending the same job at the same time. A process can still stop after the receiver accepts a request but before the acknowledgement is persisted, so consumers must de-duplicate using the stable event_id.

Configuration

All settings are environment variables prefixed VULN_ANALYSIS_WEBHOOK_; the feature is off unless …_ENABLED=true and …_URL is set. See the README section Pushing job completion to a consumer (webhooks) and .env.example.