QualityPilot

Platform

Security for developers

The marketing security page covers compliance and policy. This page covers the things you'll actually paste into code — HMAC verification, secret rotation, and what we do (and don't do) with your repo source.

Looking for compliance, encryption-at-rest, or incident response? That's on the /security page. This page is for developers wiring up integrations.

Verifying webhook signatures

Every outbound webhook carries an X-QLens-Signature header of the form sha256=<hex>. The signature is HMAC-SHA-256 of the raw request body using your shared secret as the key.

Node.js (Express)

import crypto from "node:crypto";
import express from "express";

const app = express();

// IMPORTANT: keep the body raw — do not parse before verifying.
app.post(
  "/qlens",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const sig = req.header("X-QLens-Signature") ?? "";
    const expected =
      "sha256=" +
      crypto
        .createHmac("sha256", process.env.QLENS_WEBHOOK_SECRET!)
        .update(req.body) // Buffer, not parsed JSON
        .digest("hex");

    const ok =
      sig.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));

    if (!ok) return res.status(401).send("bad signature");

    const event = JSON.parse(req.body.toString("utf8"));
    // ...handle event...
    res.status(204).end();
  },
);

Python (Flask)

import hmac, hashlib, os
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["QLENS_WEBHOOK_SECRET"].encode()

@app.post("/qlens")
def qlens_hook():
    raw = request.get_data()  # raw bytes
    sig = request.headers.get("X-QLens-Signature", "")
    expected = "sha256=" + hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(sig, expected):
        abort(401)
    event = request.get_json()
    # ...handle event...
    return "", 204

Always use a constant-time compare (timingSafeEqual/compare_digest). A naive == leaks via timing channels.

Rotating secrets

Secrets you control:

  • API keys (qlens_…) — rotate at /dashboard/keys. Old keys stop working immediately on revoke.
  • Webhook signing secret — rotate at /dashboard/settings. Both old and new secrets are accepted for a 24-hour grace window to give your receiver time to roll.
  • Slack webhook URL — managed in Slack, pasted into our settings. No grace window: if you change it, paste the new URL immediately.

What we read from your repo

For auto-fix proposals, the GitHub App requests these read scopes only on opted-in repos:

  • Contents — to read failing test files and their direct imports.
  • Pull requests — to open the fix PR and update its body.
  • Checks — to attach the auto-fix status check.

We don't mirror your code. Source files are read transiently per proposal, passed to the LLM with a short retention window, and dropped after the patch is generated. The diff we keep is the patch itself, scoped to the lines you'd see in the PR.

Reporting a vulnerability

Please email security@iklab.dev rather than opening a public issue. We respond within one business day, and we'll confirm a fix timeline within five.