{"record":{"id":"d0b9d31bf0b5388e","repo":"Zie619/n8n-workflows","slug":"str-e-d0b9d3","errorCode":null,"errorMessage":"str(e)","messagePattern":"str\\(e\\)","errorType":"http","errorClass":"HTTPException","httpStatus":500,"severity":"error","filePath":"src/integration_hub.py","lineNumber":257,"sourceCode":"        else:\n            return {\"status\": \"error\", \"message\": \"Webhook endpoint not found\"}\n\n\n# Initialize integration hub\nintegration_hub = IntegrationHub()\n\n# FastAPI app for Integration Hub\nintegration_app = FastAPI(title=\"N8N Integration Hub\", version=\"1.0.0\")\n\n\n@integration_app.post(\"/integrations/github/sync\")\nasync def sync_github(repo: str, token: str):\n    \"\"\"Sync workflows with GitHub repository.\"\"\"\n    try:\n        result = await integration_hub.sync_with_github(repo, token)\n        return result\n    except Exception as e:\n        raise HTTPException(status_code=500, detail=str(e))\n\n\n@integration_app.post(\"/integrations/slack/notify\")\nasync def notify_slack(webhook_url: str, message: str):\n    \"\"\"Send notification to Slack.\"\"\"\n    try:\n        result = await integration_hub.sync_with_slack(webhook_url, message)\n        return result\n    except Exception as e:\n        raise HTTPException(status_code=500, detail=str(e))\n\n\n@integration_app.post(\"/integrations/discord/notify\")\nasync def notify_discord(webhook_url: str, message: str):\n    \"\"\"Send notification to Discord.\"\"\"\n    try:\n        result = await integration_hub.sync_with_discord(webhook_url, message)\n        return result","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/Zie619/n8n-workflows/blob/94007c1445d9258a7da116646b79473e7c7c3282/src/integration_hub.py#L239-L275","documentation":"A generic 500 from POST /integrations/github/sync. The handler calls integration_hub.sync_with_github(repo, token) and wraps any failure as detail=str(e). Note a design flaw: both repo and token are plain query parameters, so the GitHub token lands in access logs and browser history. Failures usually come from GitHub: bad credentials, nonexistent repo, rate limits, or network egress blocked.","triggerScenarios":"POST /integrations/github/sync?repo=owner/name&token=ghp_xxx with an expired/revoked PAT, insufficient scope (no repo contents read), a repo the token cannot access, or when the server has no outbound network access to api.github.com.","commonSituations":"Fine-grained PAT missing the 'Contents: read' permission; org SAML enforcement requiring authorization; token pasted with whitespace; self-hosted server behind a firewall blocking GitHub; rate-limited unauthenticated fallback.","solutions":["Test the credential directly: curl -H 'Authorization: Bearer <token>' https://api.github.com/repos/owner/name — a 401/403 there reproduces the server error.","Use a PAT with at least repo/contents-read scope and confirm it is not expired or SAML-blocked.","Verify the repo slug format is owner/name and the token's owner can see it.","Refactor the endpoint to take the token in a header/body instead of a query string, and check server egress to api.github.com."],"exampleFix":"# before\n@integration_app.post(\"/integrations/github/sync\")\nasync def sync_github(repo: str, token: str):  # token leaks into access logs\n    ...\n\n# after\nfrom fastapi import Header\n\n@integration_app.post(\"/integrations/github/sync\")\nasync def sync_github(repo: str, x_github_token: str = Header(...)):\n    try:\n        result = await integration_hub.sync_with_github(repo, x_github_token)\n        return result\n    except Exception as e:\n        raise HTTPException(status_code=500, detail=str(e))","handlingStrategy":"validation","validationCode":"import re\n\ndef github_inputs_ok(repo: str, token: str) -> bool:\n    return bool(re.fullmatch(r\"[\\w.-]+/[\\w.-]+\", repo)) and bool(token) and not token.isspace()","typeGuard":null,"tryCatchPattern":"try:\n    result = client.post(\"/integrations/github/sync\", json={\"repo\": repo, \"token\": token}).json()\nexcept HTTPError as e:\n    detail = e.response.text\n    if \"Bad credentials\" in detail or \"401\" in detail:\n        raise PermissionError(\"GitHub token rejected — refresh the PAT\") from e\n    if \"rate limit\" in detail.lower():\n        # bounded single retry after the reset window, never a tight loop\n        time.sleep(60)\n        result = client.post(\"/integrations/github/sync\", json={\"repo\": repo, \"token\": token}).json()\n    else:\n        raise","preventionTips":["Verify the PAT with a direct GitHub API call before wiring it into the sync endpoint.","Never send credentials as query parameters — move the token to a header or request body (server change).","Give fine-grained PATs exactly the needed scope (Contents: read) and rotate them on a schedule.","Confirm server egress to api.github.com is open before enabling sync jobs."],"tags":["http-500","github","oauth-token","sync","security","fastapi"],"backgroundTag":null,"analyzedSha":"94007c1445d9258a7da116646b79473e7c7c3282","analyzedAt":"2026-08-15T04:10:37.591Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}