Skip to content

feat: add GitHub App integration foundation - #13

Merged
mkarson1997 merged 1 commit into
mainfrom
feat/github-app-integration
Sep 4, 2026
Merged

feat: add GitHub App integration foundation#13
mkarson1997 merged 1 commit into
mainfrom
feat/github-app-integration

Conversation

@mkarson1997

@mkarson1997 mkarson1997 commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Adds verified GitHub webhook signatures, a constrained REST client for GitHub App installation tokens, workflow-run and repository access helpers, repository-dispatch support, tests, exports, and least-privilege setup documentation.

This establishes a real GitHub API integration in development and prepares FlowForge for GitHub App registration.

Closes #12

Summary by Sourcery

Establish the foundation for secure GitHub App integration in FlowForge.

New Features:

  • Add verified GitHub webhook signature validation and a GitHub App installation-token REST client for repository access, workflow-run queries, and repository dispatch events.

Enhancements:

  • Enforce secure API access, constrained request paths, token-safe errors, and least-privilege GitHub App usage guidance.

Documentation:

  • Document GitHub App registration, webhook verification, REST API usage, permissions, and deployment-boundary authentication.

Tests:

  • Add coverage for webhook signatures, authenticated API requests, repository dispatches, secure error handling, and HTTPS endpoint enforcement.

Chores:

  • Export the GitHub integration client, types, error, and webhook verifier from the package entry point.

Copilot AI lite review requested due to automatic review settings September 4, 2026 14:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@sourcery-ai

sourcery-ai Bot commented Sep 4, 2026

Copy link
Copy Markdown

Reviewer's Guide

Establishes the GitHub App integration foundation with raw-body webhook signature verification, a security-constrained installation-token REST client for repository, workflow-run, and repository-dispatch operations, package exports, tests, and least-privilege setup documentation.

Sequence diagram for verified GitHub webhook handling

sequenceDiagram
    participant GitHub
    participant WebhookEndpoint
    participant FlowForge

    GitHub->>WebhookEndpoint: POST webhook with raw body and x-hub-signature-256
    WebhookEndpoint->>WebhookEndpoint: verifyGitHubWebhookSignature(secret, rawBody, signatureHeader)
    alt signature valid
        WebhookEndpoint->>FlowForge: Start workflow with parsed event
    else signature invalid
        WebhookEndpoint-->>GitHub: Reject request
    end
Loading

Sequence diagram for GitHub App API operations

sequenceDiagram
    participant FlowForge
    participant GitHubApiClient
    participant GitHubAPI

    FlowForge->>GitHubApiClient: getRepository(owner, repo)
    GitHubApiClient->>GitHubAPI: GET /repos/{owner}/{repo} with Bearer token
    GitHubAPI-->>GitHubApiClient: GitHubRepositorySummary
    GitHubApiClient-->>FlowForge: Repository summary

    FlowForge->>GitHubApiClient: listWorkflowRuns(owner, repo, branch)
    GitHubApiClient->>GitHubAPI: GET /repos/{owner}/{repo}/actions/runs?branch={branch}
    GitHubAPI-->>GitHubApiClient: GitHubWorkflowRunsResponse
    GitHubApiClient-->>FlowForge: Workflow runs

    FlowForge->>GitHubApiClient: createRepositoryDispatch(owner, repo, eventType, clientPayload)
    GitHubApiClient->>GitHubAPI: POST /repos/{owner}/{repo}/dispatches
    GitHubAPI-->>GitHubApiClient: 204 No Content
    GitHubApiClient-->>FlowForge: Dispatch completed
Loading

File-Level Changes

Change Details Files
Add secure GitHub webhook signature verification.
  • Verify raw webhook payloads using HMAC-SHA256 and constant-time comparison.
  • Reject missing, malformed, non-SHA256, and invalid-length signatures.
src/github-app.ts
test/github-app.test.ts
docs/github-app.md
Introduce a constrained GitHub App installation-token REST client.
  • Require a non-empty token and enforce HTTPS, origin, and path-traversal protections for API requests.
  • Set GitHub API version, bearer authentication, user-agent, and JSON headers.
  • Expose repository and workflow-run retrieval helpers with encoded path/query parameters.
  • Expose repository_dispatch creation with validated event types and caller-supplied payloads.
  • Surface status, method, and path through sanitized API errors without response-body leakage.
src/github-app.ts
test/github-app.test.ts
Publish the GitHub integration API and document least-privilege deployment setup.
  • Export the client, error, summary types, and webhook verifier from the package entry point.
  • Document GitHub App registration, secret handling, webhook verification, installation-token boundaries, permissions, and supported API usage.
src/index.ts
docs/github-app.md

Assessment against linked issues

Issue Objective Addressed Explanation
#12 Implement verified GitHub webhook signature handling using the raw request body.
#12 Provide a constrained GitHub REST client authenticated with installation tokens, including repository access, workflow-run helpers, and repository-dispatch support.
#12 Add tests, public exports, and setup documentation for GitHub App integration and least-privilege registration.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sonarqubecloud

sonarqubecloud Bot commented Sep 4, 2026

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/github-app.ts" line_range="49" />
<code_context>
+  rawBody: string | Uint8Array,
+  signatureHeader: string | null | undefined,
+): boolean {
+  if (secret.length === 0 || !signatureHeader?.startsWith("sha256=")) {
+    return false;
+  }
</code_context>
<issue_to_address>
**issue (bug_risk):** Passing a Node.js `IncomingMessage` header directly, as shown in the documentation, supplies `string | string[] | undefined`; when GitHub's signature header is represented as an array, `signatureHeader.startsWith(...)` raises `TypeError` instead of rejecting the request. The documented usage also fails TypeScript checking because the function does not accept `string[]`.

**Triggers:** When the webhook framework exposes duplicate or multi-valued `x-hub-signature-256` headers.

**Suggested fix:** Accept only a string at runtime and return `false` for arrays, or have the integration adapter explicitly reject and normalize multi-valued headers before calling the verifier.

```suggestion
  if (secret.length === 0 || typeof signatureHeader !== "string" || !signatureHeader.startsWith("sha256=")) {
```
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and a defect in webhook signature verification could accept forged GitHub events, while a client or endpoint defect could misuse an installation token or dispatch workflows with repository permissions; those requests and triggered actions cannot be undone by reverting the code. The implementation is isolated foundation code, so the impact depends on how it is wired into production, but incorrect security-boundary behavior would require remediation beyond a simple revert.

Blocking findings: src/github-app.ts:49


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread src/github-app.ts
rawBody: string | Uint8Array,
signatureHeader: string | null | undefined,
): boolean {
if (secret.length === 0 || !signatureHeader?.startsWith("sha256=")) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Passing a Node.js IncomingMessage header directly, as shown in the documentation, supplies string | string[] | undefined; when GitHub's signature header is represented as an array, signatureHeader.startsWith(...) raises TypeError instead of rejecting the request. The documented usage also fails TypeScript checking because the function does not accept string[].

Triggers: When the webhook framework exposes duplicate or multi-valued x-hub-signature-256 headers.

Suggested fix: Accept only a string at runtime and return false for arrays, or have the integration adapter explicitly reject and normalize multi-valued headers before calling the verifier.

Suggested change
if (secret.length === 0 || !signatureHeader?.startsWith("sha256=")) {
if (secret.length === 0 || typeof signatureHeader !== "string" || !signatureHeader.startsWith("sha256=")) {

@mkarson1997
mkarson1997 merged commit dd784c6 into main Sep 4, 2026
6 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9578613a27

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/github-app.ts
}

private buildUrl(path: string): URL {
if (!path.startsWith("/") || path.startsWith("//") || path.includes("..")) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Check traversal by complete path segment

For repositories whose valid GitHub name contains consecutive periods (for example release..archive), encodeURIComponent preserves the periods and this substring check rejects the generated path before any request is sent. Such periods are harmless unless the complete segment is ..; validate path segments rather than searching the entire URL so all three repository helpers remain usable for these repositories.

Useful? React with 👍 / 👎.

Comment thread src/github-app.ts
Comment on lines +116 to +117
if (!/^[A-Za-z0-9._-]{1,100}$/.test(eventType)) {
throw new Error("GitHub repository_dispatch event type is invalid");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow all valid repository-dispatch event types

When a workflow listens for a valid custom event type containing characters outside this private allowlist, such as deploy:completed, the client throws locally even though GitHub's event_type contract only imposes a 100-character maximum. Since the value is JSON-encoded rather than interpolated into a URL, this restriction unnecessarily prevents dispatching events supported by the API.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add GitHub App integration foundation

2 participants