eva.integration.ingest.v1 4 métodos

Ingest de hallazgos y aspectos

PAT con alcance ingest. El JWT de sesión responde 401. Ingest no crea proyectos ni escenarios: si la identidad no existe, 404. Para alta use Asegurar proyecto. No se envían evidencias binarias.

Identidad del destino

El cliente no envía UUIDs de proyecto. Identifica con:

source_system es obligatorio. origin persistido = integration.

El usuario dueño del PAT debe poder crear hallazgos en ese proyecto. Identidad o permiso incorrectos → 404 (no se distingue existencia).

Por qué siempre draft

Siempre draft El hallazgo queda en draft con origin=integration. Ingest no cambia el flujo de revisión en EVA ni crea un job de retest. execution_scope es opcional (cloud | on_prem | manual); si se omite, queda vacío.

Los cuatro métodos

MétodoRutaBodyÉxito
JSON hallazgoPOST /api/v1/integrations/ingest/vulnerabilitiesJSON201
CSV hallazgosPOST /api/v1/integrations/ingest/vulnerabilities.csvmultipart/form-data campo file200 parcial
JSON aspectoPOST /api/v1/integrations/ingest/positive-aspectsJSON201
CSV aspectosPOST /api/v1/integrations/ingest/positive-aspects.csvmultipart/form-data campo file200

CSV: máximo 200 filas / 1 MB. Respuesta { created, failed, items[] } incluso si todas las filas fallan. No hay 202.

Scope ingest. CSV: campo multipart file. Fixtures en examples/.

Campos

Comunes: project_name, project_code, scenario_key, source_system, assets.

Hallazgos además: title (obligatorio, máx. 200), severity, cvss_score, cvss_vector, cwes, cves, mitre_tactic_ids, mitre_technique_id, tool_id, execution_scope (opcional: cloud | on_prem | manual). Si envía executor, se ignora.

Aspectos además: title, description.

assets en JSON es una unión: string o {value, asset_type?, label?, meta?}. Se pueden mezclar. Máx. 100. value máx. 1000, label máx. 300. Tipo inválido → 422. Si asset_type se omite, EVA infiere (IPv4/IPv6 → ip; http(s) en escenario API → endpoint, si no url; si no es URL/IP, el mapa del escenario; fallback host). Un tipo explícito siempre gana. Match-or-create por proyecto + escenario + tipo + valor; si ya existe no se pisan label/meta. El GET extract ya lee asset_type / value / label.

CSV: assets separados por ;. Columnas opcionales asset_types y asset_labels (listas ; paralelas). Distinta cardinalidad → 422 en esa fila. Celda de tipo vacía = inferir. meta no va en CSV.

MITRE = tácticas + una técnica.

1. JSON hallazgo

curl -sS -X POST \
  -H "Authorization: Bearer ${EVA_INTEGRATION_TOKEN}" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d @examples/ingest_vulnerability.example.json \
  "${EVA_API_BASE_URL}/api/v1/integrations/ingest/vulnerabilities"

Fixture: ingest_vulnerability.example.json

{
  "project_name": "Banco XYZ 2026",
  "project_code": "BNK-1",
  "scenario_key": "WEB",
  "source_system": "acme-scanner",
  "title": "Reflected XSS in login",
  "severity": "high",
  "cvss_score": 7.5,
  "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
  "cwes": ["CWE-79"],
  "cves": [],
  "mitre_tactic_ids": ["TA0001"],
  "mitre_technique_id": "T1190",
  "tool_id": "nuclei",
  "assets": [
    "https://app.example.com/login",
    {
      "value": "https://apisux.example.com/v1/users",
      "asset_type": "endpoint",
      "label": "Users API",
      "meta": { "method": "GET", "env": "qa" }
    }
  ],
  "executor": "optional-ignored"
}

Esperado: HTTP 201, "contract": "eva.integration.ingest.v1", "origin": "integration", "status": "draft".

2. CSV hallazgos

curl -sS -X POST \
  -H "Authorization: Bearer ${EVA_INTEGRATION_TOKEN}" \
  -H "Accept: application/json" \
  -F "file=@examples/ingest_vulnerability.example.csv" \
  "${EVA_API_BASE_URL}/api/v1/integrations/ingest/vulnerabilities.csv"

Fixture: ingest_vulnerability.example.csv

project_name,project_code,scenario_key,source_system,title,severity,cvss_score,cvss_vector,cwes,cves,mitre_tactic_ids,mitre_technique_id,tool_id,assets,asset_types,asset_labels,executor
Banco XYZ 2026,BNK-1,WEB,acme-scanner,Reflected XSS in login,high,7.5,CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N,CWE-79,,TA0001,T1190,nuclei,https://app.example.com/login,,,
Banco XYZ 2026,BNK-1,API,acme-scanner,Broken object level authorization,high,8.1,CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N,CWE-639,,TA0001,T1190,nuclei,https://api.example.com/v1/users,endpoint,Users API,

Esperado: HTTP 200 con created / failed / items[] por fila.

3. JSON aspecto positivo

curl -sS -X POST \
  -H "Authorization: Bearer ${EVA_INTEGRATION_TOKEN}" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d @examples/ingest_positive_aspect.example.json \
  "${EVA_API_BASE_URL}/api/v1/integrations/ingest/positive-aspects"

Fixture: ingest_positive_aspect.example.json

{
  "project_name": "Banco XYZ 2026",
  "project_code": "BNK-1",
  "scenario_key": "WEB",
  "source_system": "acme-scanner",
  "title": "TLS 1.2 enforced on public login",
  "description": "The public login endpoint rejects TLS 1.0 and 1.1.",
  "assets": [
    "https://app.example.com/login",
    {
      "value": "https://apisux.example.com/v1/users",
      "asset_type": "endpoint",
      "label": "Users API",
      "meta": { "method": "GET", "env": "qa" }
    }
  ]
}

Esperado: HTTP 201, mismo contrato e origin=integration.

4. CSV aspectos positivos

curl -sS -X POST \
  -H "Authorization: Bearer ${EVA_INTEGRATION_TOKEN}" \
  -H "Accept: application/json" \
  -F "file=@examples/ingest_positive_aspect.example.csv" \
  "${EVA_API_BASE_URL}/api/v1/integrations/ingest/positive-aspects.csv"

Fixture: ingest_positive_aspect.example.csv

project_name,project_code,scenario_key,source_system,title,description,assets,asset_types,asset_labels
Banco XYZ 2026,BNK-1,WEB,acme-scanner,TLS 1.2 enforced on public login,The public login endpoint rejects TLS 1.0 and 1.1.,https://app.example.com/login,,

Comprobaciones

CasoEsperado
JWT de sesión en Bearer401 TOKEN_TYPE_NOT_ALLOWED
PAT solo-extract403 INTEGRATION_SCOPE_DENIED
IP fuera de allowed_cidrs403 INTEGRATION_IP_DENIED
Más de 60 POST/min del mismo PAT429 INTEGRATION_RATE_LIMITED + Retry-After
CSV de 200 filasCuenta como 1 POST en el cupo
scenario_key ambiguo409 SCENARIO_AMBIGUOUS
Proyecto/permiso incorrecto404
Body inválido / asset_type desconocido / CSV con distinta aridad assets vs asset_types422

El cupo de 60 peticiones/minuto es por PAT y superficie ingest (independiente de extract). Un CSV cuenta como 1 POST. Retest no lo comparte. Rangos de IP en notación CIDR (10.0.0.0/8), no con guion.