Fast Reverse Proxy (frp)
The Problem
Exposing a local server behind NAT or firewall to the internet is a common challenge in distributed systems. Traditional solutions like port forwarding or VPNs are often cumbersome, inflexible, or require changes to the network infrastructure. This is particularly problematic for temporary or dynamic services.
What This Does
frp is a fast reverse proxy that simplifies exposing local services to the internet. It achieves this by running a lightweight frp server in the public internet and a frp client on the local machine behind NAT/firewall. The client connects to the server and tunnels traffic back to the local service.
The project is forked from fatedier/frp, which has a significant user base and community support. The core functionality is implemented in Go (pkg/ directory) and supports multiple protocols including TCP, UDP, HTTP, and HTTPS. The web dashboard (web/ directory) provides a user interface for monitoring and managing proxies.
How It Is Wired
Execution starts from the main.go files in cmd/frpc/ and cmd/frps/ for client and server respectively. The client and server communicate over a control connection established in pkg/msg/conn.go. The pkg/config/ directory handles configuration parsing and validation, supporting both legacy INI and modern YAML/TOML formats.
Proxy management is handled by client/proxy/proxy_manager.go and client/visitor/visitor_manager.go on the client side, while the server side uses pkg/nathole/controller.go for tunnel management. The dashboard (web/frpc/) provides a Vue.js frontend interacting with the Go backend via REST APIs defined in client/http/controller.go.
How To Use It
- Install: Clone the repository and build using
make build. Alternatively, use the provided Dockerfiles (dockerfiles/Dockerfile-for-frpc and dockerfiles/Dockerfile-for-frps).
- Configure: - Edit
conf/frps.toml for server configuration. - Edit conf/frpc.toml for client configuration.
- Run: ```bash # Start server ./frps -c conf/frps.toml
# Start client ./frpc -c conf/frpc.toml
### Real-World Use
A practical use case is exposing a local web application to the internet. For example, running a local Node.js app on port `3000` behind a NAT can be exposed via frp:
```ini
# frpc.toml
[common]
server_addr = your-public-ip
server_port = 7000
[proxy]
name = "web"
type = "http"
local_port = 3000
custom_domains = ["yourdomain.com"]
Code Health & Issues
- High - No test coverage for
pkg/plugin/ which contains critical proxy plugins. (148 test files found, but none in pkg/plugin/) - Med -
pkg/config/legacy/ contains deprecated INI config support which may introduce complexity. - Low - Documentation in
doc/ is sparse for advanced use cases.
The Bottom Line
frp is a mature, well-structured solution for exposing local services to the internet. It's particularly useful for temporary or dynamic services. The codebase is clean and modular, but the test coverage could be improved for plugin functionality. If you need a reliable reverse proxy with minimal setup, frp is a solid choice.
What would stop me shipping this
Ranked by severity × confidence × production reach. Reach is the honest discriminator across a collection that is mostly other people's code: the same finding matters more in something that ships.
Pin third-party GitHub Actions to a commit SHAhigh6 occurrences
.github/workflows
docker/setup-qemu-action@v4, docker/setup-buildx-action@v4, docker/login-action@v4, docker/build-push-action@v7
A tag can be moved, so the action running with your token and secrets is whatever its owner last pushed; this is how tj-actions/changed-files leaked secrets from thousands of repos.
Fix: Replace each @vN with the 40-character commit SHA, keep # vN as a comment, and let Dependabot bump the SHAs.
Upgrade the pinned dependency carrying a published advisoryhigh2 occurrences
github.com/Azure/go-ntlmssp@0.1.0 CVE-2026-32952 (medium) +1 more
The pinned version is the one that installs, so a published advisory against it describes this deployment rather than a hypothetical one, and the advisory is also a public description of how to exploit it.
Fix: Upgrade to the fixed version named in the advisory, then commit the lockfile.
Declare least-privilege permissions for GITHUB_TOKENmedium
.github/workflows/goreleaser.yml
1 workflow(s) declare no permissions, 1 of them reference secrets
With no declaration the token inherits the repository default, so any injected step can push commits or mint releases from inside your own CI.
Fix: Add permissions: contents: read at the top of the workflow and widen per job only where needed.
Enable Dependabot or Renovatemedium
5 manifest(s), no update bot configured
Without a bot a published advisory sits unpatched until someone audits by hand, which across 1,322 repositories means never.
Fix: Commit .github/dependabot.yml covering the repo ecosystems plus github-actions.
Pin the container base image by digestmedium3 occurrences
dockerfiles/Dockerfile-for-frpc
node:22, golang:1.25, alpine:3
An untagged or mutable base means today's build and last month's contain different libc and a different CVE set, with no record of which shipped.
Fix: Use image:tag@sha256:<digest> and enable Dependabot's docker ecosystem.
Gate pull requests on a dependency vulnerability scanmedium
.github/workflows
no dependency scan in CI
This is the one gate that would catch a known-vulnerable package before it reaches a build, and no repository in the sample had it.
Fix: Add dependency-review-action on pull_request, or osv-scanner on push and a schedule.
Add a non-root USER to the imagemedium
dockerfiles/Dockerfile-for-frpc
CMD or ENTRYPOINT with no USER directive
A process running as root in the container is root against every mounted volume, and it turns any container escape or writable-mount mistake from a contained problem into a host one.
Fix: Create an unprivileged user, chown what it needs, and end the Dockerfile with USER.
Set timeout-minutes on the workflow jobslow4 occurrences
.github/workflows/build-and-push-image.yml
4 workflow(s) declare no job timeout
A wedged step runs to the six-hour platform default, which on a two-hourly schedule means three runs overlap behind it.
Fix: Add timeout-minutes with a realistic bound to each job.
Add the repository convention files this project lackslow3 occurrences
missing .editorconfig, .gitattributes, a formatter config
Without them one contributor's editor writes tabs into a Python file, a shell script commits with CRLF and fails in the container, and a notebook diff is unreviewable.
Fix: Add .editorconfig, .gitattributes with text=auto eol=lf, and a formatter config.
Checked deterministically against the repository tree and a bounded set of its files: committed credentials, unpinned actions and base images, missing lockfiles and update bots, workflows that discard failures, published advisories against the declared dependencies, runtime configuration, licensing and notebook reproducibility. No language model is involved in this section.