{"record":{"id":"1626f07ae76cf534","repo":"can1357/oh-my-pi","slug":"invalid-signature","errorCode":null,"errorMessage":"invalid signature","messagePattern":"invalid signature","errorType":"http","errorClass":"HTTPException","httpStatus":401,"severity":"warning","filePath":"python/robomp/src/server.py","lineNumber":342,"sourceCode":"            raise HTTPException(503, \"not initialized\")\n        return {\"status\": \"ready\"}\n\n    @app.post(\"/webhook/github\")\n    async def webhook(\n        request: Request,\n        x_github_event: str = Header(..., alias=\"X-GitHub-Event\"),\n        x_github_delivery: str = Header(..., alias=\"X-GitHub-Delivery\"),\n        x_hub_signature_256: str | None = Header(None, alias=\"X-Hub-Signature-256\"),\n    ) -> JSONResponse:\n        bag = request.app.state.bag\n        cfg: Settings = bag[\"settings\"]\n        body = await request.body()\n        if not github_events.verify_signature(\n            cfg.github_webhook_secret.get_secret_value(),\n            body,\n            x_hub_signature_256,\n        ):\n            raise HTTPException(status.HTTP_401_UNAUTHORIZED, \"invalid signature\")\n        try:\n            payload = await request.json()\n        except Exception as exc:\n            raise HTTPException(status.HTTP_400_BAD_REQUEST, f\"invalid json: {exc}\") from exc\n\n        db: Database = bag[\"db\"]\n        issue_cache: _IssueBrowseCache = bag[\"issue_browse_cache\"]\n        await issue_cache.apply_webhook(\n            event_type=x_github_event,\n            payload=payload,\n            allowlist=cfg.repo_allowlist,\n        )\n        # Keep the local search index fresh from every delivery that carries an\n        # issue/PR object — including ones the router will skip.\n        if x_github_event in (\"issues\", \"issue_comment\") or x_github_event.startswith(\"pull_request\"):\n            repo_full = str((payload.get(\"repository\") or {}).get(\"full_name\") or \"\")\n            if repo_full and repo_full in cfg.repo_allowlist:\n                try:","sourceCodeStart":324,"sourceCodeEnd":360,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/robomp/src/server.py#L324-L360","documentation":"POST /webhook/github verifies the X-Hub-Signature-256 HMAC-SHA256 header against GITHUB_WEBHOOK_SECRET using github_events.verify_signature; a mismatch raises HTTPException 401 'invalid signature' so forged or misconfigured deliveries never enter the queue.","triggerScenarios":"Webhook delivery whose HMAC does not match the configured secret — wrong/rotated GITHUB_WEBHOOK_SECRET on the robomp side, secret not updated in the GitHub repo/app webhook settings, a proxy re-signing or mangling the raw body, or replaying a captured request.","commonSituations":"Secret updated in GitHub but not in robomp's .env (or vice versa); trailing whitespace/quotes in the secret value; an intermediary (nginx, ngrok, tunnel) altering the payload before hashing; pointing two environments at one webhook.","solutions":["Confirm GITHUB_WEBHOOK_SECRET in robomp exactly matches the webhook secret configured in GitHub settings (no quotes/whitespace).","After changing the secret, restart the orchestrator so Settings reload.","Ensure any reverse proxy forwards the raw body unmodified and passes through X-Hub-Signature-256.","Verify with a redelivery from the GitHub webhook 'Recent Deliveries' pane after fixing."],"exampleFix":"// before (.env)\nGITHUB_WEBHOOK_SECRET=\"my secret \"  # stray quotes/trailing space\n// after (.env)\nGITHUB_WEBHOOK_SECRET=my-secret","handlingStrategy":"validation","validationCode":"import hmac, hashlib\nsig = 'sha256=' + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()\nif not hmac.compare_digest(sig, headers['X-Hub-Signature-256']):\n    raise ValueError('local signature check failed — secret or body mismatch')","typeGuard":null,"tryCatchPattern":"try:\n    resp = httpx.post(url, content=body, headers=headers)\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code == 401:\n        ...  # re-check GITHUB_WEBHOOK_SECRET on both sides before retrying","preventionTips":["Keep GITHUB_WEBHOOK_SECRET byte-identical in GitHub webhook settings and robomp .env (no quotes/whitespace)","Rotate the secret in both places simultaneously and restart","Ensure proxies forward the raw body and signature headers untouched"],"tags":["webhook","hmac","security","http"],"backgroundTag":"webhook-signature-verification-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}