Repository navigation
Security
hub2lab implements several security measures to protect your repositories and prevent unexpected behavior.
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 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.
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:
- hub2lab inspects all commit messages in the payload
- If any commit message contains
[hub2lab], the entire webhook is silently dropped - 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).
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.
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
-
Never commit
.envto version control (.gitignoreexcludes 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 —
apiandwrite_repositoryare the minimum required
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
# ...Always run hub2lab behind a reverse proxy (nginx, Caddy, Traefik) with HTTPS. Webhook secrets are transmitted in headers; HTTPS encrypts them in transit.
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
- Run hub2lab as a non-root user (the Docker image uses the default
nodeuser) - Use
--security-opt=no-new-privileges:true - Mount volumes read-only where possible
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.