Skip to content

Generic (HTTP)

The enhance capability of a provider: generic Integration turns any external HTTP process into a context enhancer: patchy POSTs each opened finding's issue view to your endpoint, and the response body is the enrichment — exactly what an in-tree pkg/enhance plugin would contribute. It is how a real CMDB integrates without a rebuild, and it runs for findings from every source, not just your own.

One provider, three capabilities

The enhancer is one capability of the same provider: generic Integration as the inbound source and verdict resolver — configuration, identity rules and the HMAC authentication scheme live on that page. The integrations overview maps every provider's roles.

Configuration

The enhance block on the Integration (full configuration):

spec:
  provider: generic
  secretRef:
    name: patchy-warehouse # key webhookSecret: the shared HMAC secret
  generic:
    enhance:
      enabled: true # synchronous enrichment call per opened finding
      url: https://warehouse.internal/patchy/enhance
      timeout: 60s

A pure enhancer is a valid Integration — leave source off entirely. N generic Integrations may all enable enhance: the context-controller calls each one per finding, in name order, after the cloud lookups, each request signed with that Integration's own secret and each enrichment attributed to that Integration's name.

Every call is signed with the same HMAC scheme as every other generic exchange:

X-Patchy-Signature-256: sha256=<hex of HMAC-SHA256(secret, body)>

The signature is computed over the raw request body, keyed with the shared secret under the credential Secret's webhookSecret key; verify with a constant-time comparison over the exact bytes received. Signing in Go:

mac := hmac.New(sha256.New, secret)
mac.Write(body)
sig := "sha256=" + hex.EncodeToString(mac.Sum(nil))

The enhancer call

For each freshly opened finding patchy POSTs to spec.generic.enhance.url, bounded by timeout (default 60s):

{
  "version": "v1",
  "integration": "warehouse",
  "issue": {
    "repo": { "owner": "acme", "name": "orders" },
    "number": 17,
    "title": "SQL injection in the nightly export",
    "body": "…finding description markdown…",
    "labels": ["security-finding: opened"],
    "cloudResource": null
  }
}

integration names the Integration the call is for, so one process can serve several from a single endpoint. Respond 204 (or 200 with an empty body) when you have nothing to contribute, else 200 with:

{
  "owners": ["alice", "bob"],
  "commentMarkdown": "Owned by team-warehouse; runbook: https://…",
  "attributes": { "system": "warehouse", "tier": "1" },
  "repository": { "provider": "github", "url": "https://github.com/acme/orders" }
}
  • owners drive tracking-issue assignment, in preference order.
  • commentMarkdown becomes one sticky comment per integration on the tracking issue, edited in place on change.
  • attributes project as security-context: k=v tracking labels; on key collisions the first enhancer in the chain wins, and generic integrations run in name order after the cloud enhancers.
  • repository resolves a cloud finding that arrived repo-less. It is honoured only when the finding has no repository yet, only once, and only for provider: "github".

Semantics to build against: the call is synchronous and per-finding; an error or timeout is logged and skipped — the finding advances without your enrichment — except a repo-less cloud finding, which holds and retries for as long as its accumulation window allows, because "could not find out" must not be confused with "no repository exists". The finding stores at most 8 enrichments (excess drop in chain order) and truncates each comment at 16384 characters.

Testing

patchy dev enhance fires a single signed enhancer exchange at your endpoint from a findings payload file — no cluster, no server to host. The local-testing walkthrough covers it alongside the full patchy dev generic harness.

Network posture

The context-controller dials your enhancer endpoint from inside the cluster; the default NetworkPolicy allows egress on TCP 443 only, and an endpoint on another port needs the contextController.networkPolicy.extraEgress chart value. As with every generic endpoint, treat it as part of your security boundary and keep it off the public internet where you can.