fosrl/pangolin
Identity-aware VPN and tunneled reverse proxy for remote access based on WireGuard®.
Single-maintainer risk — review before adopting
weakest axisnon-standard license (Other)
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit today
- ✓5 active contributors
- ✓Other licensed
- ✓CI configured
- ✓Tests present
- ⚠Single-maintainer risk — top contributor 81% of recent commits
- ⚠Non-standard license (Other) — review terms
What would change the summary?
- →Use as dependency Failing → Mixed if: clarify license terms
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Earn the “Healthy” badge
Current signals for fosrl/pangolin are Mixed. The embed flow is reserved for repos showing Healthy signals — the rest stay informational on this page so we're not putting a public call-out on your README. Address the items in the What would change the summary? dropdown above, then return to grab the embed code.
Common quick wins: green CI on default branch, no Critical CVEs in dependencies, recent commits on the default branch, a permissive license, and a published README.md with a quickstart.
Onboarding doc
Onboarding: fosrl/pangolin
Generated by RepoPilot · 2026-05-06 · Source
Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/fosrl/pangolin shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
Verdict
WAIT — Single-maintainer risk — review before adopting
- Last commit today
- 5 active contributors
- Other licensed
- CI configured
- Tests present
- ⚠ Single-maintainer risk — top contributor 81% of recent commits
- ⚠ Non-standard license (Other) — review terms
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live fosrl/pangolin
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/fosrl/pangolin.
What it runs against: a local clone of fosrl/pangolin — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in fosrl/pangolin | Confirms the artifact applies here, not a fork |
| 2 | License is still Other | Catches relicense before you depend on it |
| 3 | Default branch main exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 30 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of fosrl/pangolin. If you don't
# have one yet, run these first:
#
# git clone https://github.com/fosrl/pangolin.git
# cd pangolin
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of fosrl/pangolin and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "fosrl/pangolin(\\.git)?\\b" \\
&& ok "origin remote is fosrl/pangolin" \\
|| miss "origin remote is not fosrl/pangolin (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(Other)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"Other\"" package.json 2>/dev/null) \\
&& ok "license is Other" \\
|| miss "license drift — was Other at generation time"
# 3. Default branch
git rev-parse --verify main >/dev/null 2>&1 \\
&& ok "default branch main exists" \\
|| miss "default branch main no longer exists"
# 4. Critical files exist
test -f "package.json" \\
&& ok "package.json" \\
|| miss "missing critical file: package.json"
test -f "next.config.ts" \\
&& ok "next.config.ts" \\
|| miss "missing critical file: next.config.ts"
test -f "install/main.go" \\
&& ok "install/main.go" \\
|| miss "missing critical file: install/main.go"
test -f "docker-compose.yml" \\
&& ok "docker-compose.yml" \\
|| miss "missing critical file: docker-compose.yml"
test -f "cli/index.ts" \\
&& ok "cli/index.ts" \\
|| miss "missing critical file: cli/index.ts"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 30 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~0d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/fosrl/pangolin"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
TL;DR
Pangolin is an identity-aware VPN and reverse proxy platform built on WireGuard that enables secure remote access to private resources and web applications with granular access controls. It combines VPN tunneling with browser-based reverse proxy capabilities, supporting NAT traversal and site-based network gateways for accessing resources behind restrictive firewalls without exposing public IPs. Monorepo structure: TypeScript dominates the frontend and CLI tooling (cli/commands/ directory with specific operations like clearExitNodes.ts, deleteClient.ts), while Go powers the backend infrastructure. Configuration lives in config/ (example at config/config.example.yml for Traefik integration), CLI commands in cli/index.ts wrap core functionality, and Docker orchestration spans Dockerfile and Dockerfile.dev for local development.
Who it's for
DevOps engineers and security teams deploying remote access infrastructure, system administrators managing private network access, and organizations needing identity-based conditional access to internal resources. Also targets self-hosters choosing between Community Edition (AGPL-3) and Enterprise Edition (Fossorial Commercial License).
Maturity & risk
Actively maintained and production-ready. The project has a mature CI/CD setup (GitHub Actions workflows for cicd.yml, linting.yml, test.yml, saas.yml), public Docker images on Docker Hub (fosrl/pangolin), and comprehensive documentation at docs.pangolin.net. Docker pull metrics and active Discord/Slack communities indicate production adoption, though the GitHub star count suggests it's still growing in visibility.
Moderate-to-low risk. The TypeScript frontend (7.8M LOC) and Go infrastructure indicate a substantial codebase with clear separation of concerns. Dependency management is visible (dependabot.yml configured), and the project maintains dual-license commercial support. The main risk is vendor lock-in to WireGuard® protocol specifics and the relatively young ecosystem for identity-aware VPN compared to established products; verify security audit status before critical deployments.
Active areas of work
The project maintains active CI/CD pipelines (linting, testing, SaaS deployment workflows visible in .github/workflows/), with dependabot configured for dependency updates. The presence of discussion templates for feature requests and comprehensive CODEOWNERS (.github/CODEOWNERS) suggests organized triage. Recent focus appears to be on Cloud SaaS offering (saas.yml workflow) alongside self-hosted Community and Enterprise editions.
Get running
Clone the repo, install Node (specify via .nvmrc), and use the Makefile: git clone https://github.com/fosrl/pangolin.git && cd pangolin && nvm use && make install && make dev. For Docker-based development, use docker-compose with Dockerfile.dev. Refer to docs.pangolin.net for detailed setup; config/config.example.yml shows required configuration structure.
Daily commands:
Development: make dev (referenced in Makefile); production: docker pull fosrl/pangolin && docker run ... with configuration mounted from config/config.example.yml. The .nvmrc file specifies Node version requirement. CLI commands accessible via cli/wrapper.sh for administrative operations (clearExitNodes, deleteClient, generateOrgCaKeys, etc.).
Map of the codebase
package.json— Root project manifest defining all Node.js dependencies, scripts, and build configuration for the entire Pangolin application.next.config.ts— Next.js build and runtime configuration that controls the entire web application's compilation, routing, and deployment behavior.install/main.go— Entry point for the installer CLI tool written in Go; orchestrates the interactive setup flow for new Pangolin deployments.docker-compose.yml— Primary Docker composition file defining all containerized services, networking, and volumes required for the full Pangolin stack.cli/index.ts— TypeScript CLI command dispatcher that routes administrative operations (license keys, security, credentials) to their respective handlers.drizzle.sqlite.config.ts— Database schema and ORM configuration for SQLite; defines the fundamental data model for Pangolin's identity and VPN state..github/workflows/cicd.yml— CI/CD pipeline automation that gates all commits with linting, testing, building, and deployment checks across the entire codebase.
How to make changes
Add a new CLI command
- Create a new command handler file following the naming pattern in cli/commands/ (e.g., cli/commands/myNewCommand.ts). (
cli/commands/generateOrgCaKeys.ts) - Export the command function and import it in the main CLI dispatcher. (
cli/index.ts) - Register the command with appropriate argument parsing and execution logic in the dispatcher. (
cli/index.ts)
Add a new UI localization language
- Create a new translation file in the messages/ directory following the BCP 47 naming convention (e.g., messages/ja-JP.json). (
messages/en-US.json) - Translate all string keys from the English source file (en-US.json) into the target language. (
messages/en-US.json) - Update crowdin.yml to include the new language in the Crowdin synchronization workflow. (
crowdin.yml)
Configure a new reverse proxy route in Traefik
- Define the route and service endpoints in config/traefik/dynamic_config.yml under the routers and services sections. (
config/traefik/dynamic_config.yml) - Add TLS certificates and middleware configuration if identity-aware access control or custom headers are needed. (
config/traefik/dynamic_config.yml) - Rebuild or restart the Traefik container via docker-compose to apply the new dynamic routing configuration. (
docker-compose.yml)
Extend the database schema
- Define the new table or column schema in the Drizzle config (e.g., drizzle.sqlite.config.ts). (
drizzle.sqlite.config.ts) - Generate a migration file by running Drizzle's migration command; review and adjust the generated SQL. (
drizzle.sqlite.config.ts) - Run migrations before starting the application to ensure schema consistency across deployments. (
docker-compose.yml)
Why these technologies
- WireGuard + Go installer — Lightweight, modern kernel-space VPN protocol with minimal overhead; Go installer provides cross-platform deployment automation.
- Next.js + TypeScript frontend — Full-stack type safety and server-side rendering for the identity-aware dashboard; enables seamless API integration.
- Traefik reverse proxy — Cloud-native, dynamic routing configuration; integrates identity-aware access control policies without redeployment.
- Drizzle ORM + SQLite — Type-safe SQL queries in TypeScript; SQLite provides zero-configuration persistence suitable for remote access deployments.
- CrowdSec integration — Behavioral threat detection and IP reputation blocking for the VPN gateway; community-driven security intelligence.
- Docker Compose orchestration — Simplified multi-service deployment; all components (VPN, auth, proxy, database) deploy as a single logical unit.
Trade-offs already made
-
Kernel-space WireGuard over userspace VPN implementations
- Why: Minimal attack surface and higher performance, but requires kernel module support on host OS.
- Consequence: Not all Linux distributions or containerized environments support WireGuard out-of-the-box; may require host kernel configuration.
-
Identity-aware reverse proxy routing instead of application-level auth checks
- Why: Centralized policy enforcement at the network boundary; users cannot bypass identity checks.
- Consequence: Requires that all access flows through Traefik; direct backend access circumvents identity policies.
-
SQLite for persistence instead of PostgreSQL by default
- Why: Zero-configuration, single-file database suitable for edge deployments and small-to-medium organizations.
- Consequence: Limited horizontal scaling; multiple instances cannot share a single SQLite database. PostgreSQL option exists (drizzle.pg.config.ts) but is not the default.
-
Interactive Go-based installer instead of Terraform/Helm
- Why: Guided setup flow with TUI prompts reduces deployment friction and configuration errors for non-DevOps users.
- Consequence: Installer is imperative, not declarative; harder to version-control or repeat deployments identically without scripting.
Non-goals (don't propose these)
- Does not provide a managed SaaS platform out-of-the-box; requires self-hosted deployment via Docker or direct binary installation.
- Does not support multi-node clustering for the core VPN gateway; designed as a single-instance remote access solution.
- Does not replace traditional firewalls or handle Layer 2 networking; operates at Layer 3 (IP) and above.
- Does not provide end-to-end encryption for traffic between VPN client and backend target; relies on TLS for in-transit encryption.
Traps & gotchas
- .nvmrc specifies exact Node version — use
nvm usebefore running any npm/make commands. 2) Traefik integration (config/traefik/) requires running Traefik sidecar for reverse proxy functionality; not self-contained. 3) WireGuard kernel module must be installed on host for tunnel functionality; Docker setup handles this but bare-metal deployments need linux-headers. 4) License split (AGPL-3 vs Fossorial Commercial) affects which code path you use; check LICENSE and enterprise edition gates before deploying commercially. 5) config/db/.gitignore suggests SQLite/local DB — ensure persistent volume mounts in Docker.
Architecture
Concepts to learn
- NAT Traversal — Core capability enabling access to networks behind restrictive firewalls without public IPs or open ports; critical for Pangolin's site connector feature
- WireGuard Protocol — Pangolin's foundational VPN layer providing cryptographic tunneling; understanding its key exchange and packet forwarding is essential for debugging connectivity
- Identity-Based Access Control (IBAC) — Pangolin's defining feature: access decisions based on user/device identity rather than network location; enables zero-trust network architecture
- Reverse Proxy Pattern — Used via Traefik integration (config/traefik/) to expose internal web apps through browser without VPN client; enables browser-based access to private resources
- Outbound Tunnels — Pangolin's site connectors use outbound-only connections (no inbound listening) for NAT-friendly network access; architectural pattern unique to identity-aware VPNs
- License Dual-Licensing (AGPL-3 + Commercial) — Pangolin splits Community Edition (open-source, copyleft AGPL-3) from Enterprise Edition (Fossorial License); affects what code can be modified and deployed
Related repos
gravitl/netmaker— Alternative open-source mesh VPN with identity-aware access control; solves similar decentralized network problems but with different protocol (Wireguard wrapper vs. native integration)tailscale/tailscale— Proprietary identity-aware VPN; Pangolin differentiates by being open-source and adding reverse proxy capabilities, but Tailscale is the market leader for comparisoncloudflare/warp— Zero-trust proxy from Cloudflare; conceptually similar (identity-based access with reverse proxy) but SaaS-only; helps understand Pangolin's self-host positioningtraefik/traefik— Traefik reverse proxy used by Pangolin (config/traefik/ directory); understanding Traefik's dynamic routing is essential for Pangolin's proxy layerwireguard/wireguard— The WireGuard protocol that Pangolin builds on; reference implementation and specification for the underlying tunnel protocol
PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add integration tests for CLI commands in cli/commands/
The CLI module has 6 command files (clearExitNodes.ts, clearLicenseKeys.ts, deleteClient.ts, etc.) but there's no visible test directory for CLI functionality. Given the criticality of these administrative commands (rotateServerSecret, setAdminCredentials, resetUserSecurityKeys), adding integration tests would prevent regressions and document expected behavior. This aligns with the existing test.yml workflow.
- [ ] Create cli/tests/ directory structure
- [ ] Add integration tests for each command in cli/commands/ using a test framework (Jest recommended given .eslintrc.json suggests Node/TS setup)
- [ ] Test error handling for commands like resetUserSecurityKeys.ts and rotateServerSecret.ts
- [ ] Add test coverage reporting to test.yml GitHub Actions workflow
- [ ] Document test execution in CONTRIBUTING.md
Add validation schema tests for config/config.example.yml
The repo includes config/config.example.yml and install/config/config.yml but no visible schema validation or tests. With YAML config files, parsing errors can silently fail or cause runtime issues. Adding schema validation (using a library like joi or zod) and corresponding tests would improve reliability for both development and production deployments.
- [ ] Create a config schema validator (e.g., src/config/schema.ts or similar)
- [ ] Add unit tests validating config/config.example.yml against the schema
- [ ] Add tests for invalid config scenarios (missing required fields, wrong types)
- [ ] Integrate schema validation into the application startup path
- [ ] Document configuration validation in docs or CONTRIBUTING.md
Add GitHub Actions workflow for dependency security audits
The repo has .github/dependabot.yml but no explicit security audit workflow. With Go dependencies (go.mod with charmbracelet, golang.org packages) and Node/TypeScript dependencies, adding an automated workflow for npm audit and go mod verify would catch vulnerabilities early. This complements the existing SECURITY.md file and demonstrates security commitment to users.
- [ ] Create .github/workflows/security-audit.yml
- [ ] Add npm audit step (for Node dependencies used in cli/)
- [ ] Add go mod verify and go list -json -m all | nancy sleuth step (for Go installer module)
- [ ] Configure workflow to run on: push to main, pull_request, and schedule (e.g., weekly)
- [ ] Add failure notifications and link to SECURITY.md reporting process
Good first issues
- Add TypeScript/Go integration tests for the cli/commands/deleteClient.ts flow (currently no test files visible in cli/commands/ directory); would improve safety of destructive operations.
- Document the config/config.example.yml schema with JSDoc comments and create a schema validator in Go backend to fail fast on invalid config — currently no visible validation layer.
- Create CLI help text generator in cli/wrapper.sh that auto-extracts command descriptions from cli/commands/*.ts files and outputs formatted --help output; currently manual.
Top contributors
- @oschwartz10612 — 81 commits
- @miloschwartz — 13 commits
- @Josh-Voyles — 4 commits
- @Blacks-Army — 1 commits
- @marcschaeferger — 1 commits
Recent commits
432dc81— Merge pull request #3006 from fosrl/dev (oschwartz10612)2ecf076— don't await second calculate func (miloschwartz)9b71c42— Merge pull request #3005 from fosrl/dev (oschwartz10612)e06dda2— dont wait rebuild (miloschwartz)18f6e0f— add subscribed check back (miloschwartz)3b232bc— set orgId to undefined (miloschwartz)c575bb7— Fix only using acme.json in dir (oschwartz10612)87e6c7b— Merge pull request #3003 from fosrl/dev (oschwartz10612)c8e7e0e— WAL off default ENABLE_SQLITE_WAL_MODE to enable (oschwartz10612)0e7aafd— Merge pull request #2998 from Josh-Voyles/mem-fix-2 (oschwartz10612)
Security observations
- High · Multiple Exposed Development Ports in Docker Compose —
docker-compose.yml - app service ports. The docker-compose.yml exposes ports 3000, 3001, 3002, and 3003 for the development service. While marked as dev environment, this configuration could be accidentally used in production, exposing the application to unauthorized access. Fix: Restrict exposed ports to only necessary ones. Use a separate production-grade docker-compose file with minimal port exposure. Consider using localhost binding only for development (127.0.0.1:3000). - High · Incomplete Dockerfile COPY Statement —
Dockerfile - runner stage final COPY command. The Dockerfile contains a truncated COPY statement 'COPY --from=builder /app/node_mod' without specifying the destination. This is syntactically invalid and will fail during build, indicating the file snippet is incomplete and potentially contains hidden instructions. Fix: Complete the COPY instruction with proper source and destination paths. Ensure all Dockerfile instructions are complete and valid. - High · Source Code Mounted as Writable Volumes in Development —
docker-compose.yml - app service volumes. Development docker-compose mounts multiple source directories (/src, /server, /public, /messages, /config, etc.) as writable volumes without specifying read-only permissions. This allows container processes to modify host source code. Fix: Mount source directories as read-only unless write access is explicitly needed: '- ./src:/app/src:ro'. Alternatively, use named volumes with restricted permissions for development artifacts. - Medium · MaxMind Database Placeholder Files Created in Docker Build —
Dockerfile - builder stage, RUN touch commands. The Dockerfile creates placeholder MaxMind GeoLite2 database files during build (touch command). Real licensed databases should not be baked into images, and placeholders may cause runtime failures if validation is not properly implemented. Fix: Mount GeoLite2 databases as secrets or volumes at runtime rather than creating placeholders. Implement proper initialization checks and fail gracefully if databases are unavailable. - Medium · No Resource Limits Defined in Docker Compose —
docker-compose.yml - app service configuration. The docker-compose.yml development service lacks memory and CPU resource limits, potentially allowing the container to consume all host resources. Fix: Add resource constraints: 'deploy: { resources: { limits: { cpus: '2', memory: '2G' }, reservations: { cpus: '1', memory: '1G' } } }' - Medium · Potential Hardcoded Configuration Pattern —
config directory structure, .gitignore. The presence of config/config.example.yml alongside config/.gitkeep suggests actual configuration files may exist. If .gitignore is not properly configured, sensitive credentials in config files could be committed to the repository. Fix: Verify .gitignore properly excludes all configuration files containing secrets. Use environment variables for sensitive configuration instead of files. Implement pre-commit hooks to detect secrets. - Medium · Node.js Version Pinned to Slim Base Without Digest —
Dockerfile - FROM statements (base and runner stages). Dockerfile uses 'node:24-slim' and 'public.ecr.aws/docker/library/node:24-slim' without image digest pinning. This makes the image vulnerable to supply chain attacks if the tag is reused. Fix: Pin images using SHA256 digests: 'FROM public.ecr.aws/docker/library/node:24-slim@sha256:...' to ensure reproducible and secure builds. - Medium · Development Dependencies Not Removed in Production Build —
Dockerfile - builder-dev vs builder stages. While the builder stage uses 'npm ci --omit=dev', the builder-dev stage builds all code including potential development-only dependencies. If the wrong stage is used in production, development tools could be exposed. Fix: Ensure production builds only use the 'builder' stage. Add build argument validation or CI/CD checks to prevent builder-dev artifacts from reaching production. - Low · Missing Security Headers Configuration —
undefined. No visible Content-Security-Policy, X-Frame-Options, or other security headers Fix: undefined
LLM-derived; treat as a starting point, not a security audit.
Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.