Skip to content

Security

wildfirebill edited this page Jul 20, 2026 · 1 revision

Security

hub2lab implements several security measures to protect your repositories and prevent unexpected behavior.

Webhook Verification

GitHub: HMAC-SHA256

Every incoming GitHub webhook request is verified using HMAC-SHA256 against the GITHUB_WEBHOOK_SECRET. The verification uses Node.js crypto.timingSafeEqual() to prevent timing attacks.

// src/github/webhook.ts
const hmac = crypto
  .createHmac('sha256', config.github.webhookSecret)
  .update(rawBody, 'utf-8')
  .digest('hex');
const expected = `sha256=${hmac}`;
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));

Requests with invalid signatures receive a 401 response and are logged.

GitLab: Shared Secret Token

GitLab webhooks are verified via the X-GitLab-Token header. The token is compared against GITLAB_WEBHOOK_SECRET:

// src/gitlab/webhook.ts
const token = req.headers['x-gitlab-token'] as string;
return token === config.gitlab.webhookSecret;

Requests with mismatched tokens receive a 401 response and are logged.

Loop Prevention

The most critical safety feature: hub2lab never re-syncs its own commits.

Every commit hub2lab creates (via API pushes or deletion markers) includes the SYNC_MARKER string (default: [hub2lab]). When a webhook arrives:

  1. hub2lab inspects all commit messages in the payload
  2. If any commit message contains [hub2lab], the entire webhook is silently dropped
  3. This prevents the infinite loop: GitHub push → hub2lab → GitLab push → hub2lab → GitHub push → ...
// src/github/webhook.ts
export function isGitHubSyncEvent(payload: any, marker: string): boolean {
  if (payload.commits) {
    return payload.commits.some((c: any) =>
      (c.message || '').includes(marker)
    );
  }
  ...
}

You can customize the marker via the SYNC_MARKER environment variable if needed (e.g., for teams running multiple hub2lab instances).

Deletion Safety

hub2lab never automatically deletes branches, tags, or repositories on the target side.

When a delete event is received, hub2lab writes a deletion marker file to the target repository instead of performing the deletion:

deletion-branch-feature-x.md
deletion-tag-v1.0.md
deletion-repository.md

Each marker file contains:

  • What was deleted (branch, tag, or repository)
  • Source platform and org/group
  • Timestamp of the deletion
  • A warning that the target was NOT deleted

This prevents accidental data loss from:

  • Mistaken deletions on one side
  • Malicious actor compromising one platform
  • Webhook replay attacks

To complete a deletion, a human must manually remove the ref on the target side.

Token Security

Environment Variables

Credentials are loaded from .env file at startup. The following are sensitive:

  • GITHUB_TOKEN — Full repository access
  • GITHUB_WEBHOOK_SECRET — Webhook signing key
  • GITLAB_TOKEN — Full API access
  • GITLAB_WEBHOOK_SECRET — Webhook authentication

Best Practices

  • Never commit .env to version control (.gitignore excludes it)
  • Use secrets management in production (Docker secrets, HashiCorp Vault, etc.)
  • Rotate tokens periodically
  • Use fine-grained GitHub tokens scoped to specific repositories when possible
  • Use minimal GitLab scopes — api and write_repository are the minimum required

Docker Secret Example

version: '3.8'
services:
  hub2lab:
    image: ghcr.io/wildfirebill-docker/hub2lab:latest
    secrets:
      - github_token
      - github_webhook_secret
      - gitlab_token
      - gitlab_webhook_secret
    environment:
      GITHUB_TOKEN_FILE: /run/secrets/github_token
      # ...

Network Security

HTTPS

Always run hub2lab behind a reverse proxy (nginx, Caddy, Traefik) with HTTPS. Webhook secrets are transmitted in headers; HTTPS encrypts them in transit.

Firewall

Restrict inbound access to port 3000 (or your PORT) to only necessary sources. If possible:

  • Use a reverse proxy that terminates TLS
  • Enable fail2ban or similar rate-limiting
  • Consider running behind Cloudflare or similar WAF

Docker

  • Run hub2lab as a non-root user (the Docker image uses the default node user)
  • Use --security-opt=no-new-privileges:true
  • Mount volumes read-only where possible

Logging Security

Logs may contain repo names and event metadata but do not contain tokens or secrets. Log levels:

  • info: Normal operation (events processed, pushes completed)
  • warn: Suspicious activity (invalid signatures, missing tokens, unmapped repos)
  • error: Operational failures (API errors, missing config)

Log files rotate at 10 MB with 5 retained copies.

Clone this wiki locally