3 Commits

Author SHA1 Message Date
Asger Weirsøe 39145e5887 ci: source release body from the annotated tag message
release / release (push) Successful in 15s
The release step created releases with no body. Read the tag's annotation
into RELEASE_NOTES.md and pass it via body_path so each release carries
purpose-written notes. Fetch full history so the annotated tag object is
available to the build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 21:28:57 +02:00
Asger Weirsøe a429456987 fetch: strip all origin/chain headers so nothing leaks to targets
release / release (push) Successful in 23s
The fetch service only stripped the four X-Weircon-* control headers, so
any forwarding header injected upstream (X-Forwarded-For, X-Real-IP, Via,
CDN client-IP headers, …) passed straight through to the target — leaking
the caller's IP and proxy chain.

- Replace stripWeircon with stripIdentifying: removes the control headers
  plus all standard forwarding/origin-IP headers, with a prefix sweep for
  any vendor-specific X-Forwarded-* variant.
- NPM advanced.conf clears the same headers (defense in depth).
- Add TestStripIdentifying covering removal + survival of legit headers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 21:23:01 +02:00
Asger Weirsøe ed90151a24 setup-container: chmod the binary and improve missing-binary hint
When downloading a release asset over HTTP the executable bit is lost,
so checking '-x' would always fail. Check existence instead and chmod
unconditionally. Update the error message to cover the three common
install paths.
2026-05-27 15:21:02 +02:00
5 changed files with 132 additions and 7 deletions
+6
View File
@@ -10,6 +10,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-go@v5
with:
@@ -41,6 +43,9 @@ jobs:
sha256sum "dist/${STAGE}.tar.gz" "dist/${STAGE}/weircon-random-proxy" > dist/SHA256SUMS
ls -lh dist/
- name: Extract release notes from tag
run: git tag -l --format='%(contents)' "${{ github.ref_name }}" > RELEASE_NOTES.md
- name: Upload release asset
uses: https://gitea.com/actions/gitea-release-action@v1
with:
@@ -48,6 +53,7 @@ jobs:
server_url: ${{ github.server_url }}
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
body_path: RELEASE_NOTES.md
files: |
dist/weircon-random-proxy-${{ github.ref_name }}-linux-amd64.tar.gz
dist/weircon-random-proxy-${{ github.ref_name }}-linux-amd64/weircon-random-proxy
+18 -3
View File
@@ -30,11 +30,26 @@ if ! command -v microsocks >/dev/null 2>&1; then
rm -rf "$tmp"
fi
# 3. Install fetch-service binary (forventes pushet ind af host-scriptet)
if [[ ! -x /usr/local/bin/weircon-random-proxy ]]; then
echo "ERR: /usr/local/bin/weircon-random-proxy mangler — push den fra host'en først" >&2
# 3. Install fetch-service binary.
# Forventes pushet/lagt på plads af brugeren før dette script køres
# (enten via `pct push` fra Proxmox-hosten, eller lokalt med `cp`).
BIN=/usr/local/bin/weircon-random-proxy
if [[ ! -f "$BIN" ]]; then
cat >&2 <<EOF
ERR: $BIN mangler.
Læg binæren på plads med én af:
- fra Proxmox-host: pct push <ctid> ./weircon-random-proxy $BIN --perms 0755
- fra inde i LXC: cp /path/to/weircon-random-proxy $BIN
chmod +x $BIN
- download release: curl -fLo $BIN https://<gitea>/<owner>/weircon-random-proxy/releases/download/<tag>/weircon-random-proxy
chmod +x $BIN
Kør derefter setup-container.sh igen.
EOF
exit 1
fi
chmod +x "$BIN"
# 4. Helper-scripts
install -Dm755 "$SCRIPT_DIR/netns-up.sh" /usr/local/sbin/weircon-netns-up
+12
View File
@@ -21,6 +21,18 @@ if ($weircon_auth_ok = 0) {
# 3. Strip the auth header before forwarding — backend should never see it.
proxy_set_header X-Weircon-Random-Ip "";
# 3b. Defense in depth: never forward origin/chain headers. The fetch service
# also strips these, but clearing them here means they never even reach it.
# In nginx, an empty value removes the header entirely.
proxy_set_header X-Forwarded-For "";
proxy_set_header X-Forwarded-Host "";
proxy_set_header X-Forwarded-Proto "";
proxy_set_header X-Forwarded-Scheme "";
proxy_set_header X-Forwarded-Port "";
proxy_set_header X-Real-IP "";
proxy_set_header Forwarded "";
proxy_set_header Via "";
# 4. Generous timeouts: upstream fetches can be slow.
proxy_connect_timeout 15s;
proxy_send_timeout 60s;
+38 -4
View File
@@ -145,17 +145,51 @@ func (p *poolTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return p.pool[rc.proxyID].RoundTrip(req)
}
var weirconHeaders = []string{
// stripHeaders are removed from the outbound request so nothing about the
// caller or the proxy chain can reach the target. It covers our own control
// headers plus every standard forwarding / client-IP header that a fronting
// reverse proxy (NPM, nginx, Cloudflare, …) might inject. Any other
// "X-Forwarded-*" header is swept by prefix in stripIdentifying.
var stripHeaders = []string{
// weircon control headers
"X-Weircon-Random-Ip",
"X-Weircon-Random-Ip-Redirect",
"X-Weircon-Proxy-Id",
"X-Weircon-Forward-Method",
// generic forwarding / origin-IP headers
"Forwarded",
"Via",
"X-Real-Ip",
"X-Original-Forwarded-For",
"X-Client-Ip",
"X-Cluster-Client-Ip",
"X-Original-Url",
"X-Original-Host",
"X-Rewrite-Url",
"X-Proxy-Id",
// CDN / vendor client-IP headers
"Cf-Connecting-Ip",
"Cf-Ipcountry",
"Cf-Ray",
"True-Client-Ip",
"Fastly-Client-Ip",
"Fly-Client-Ip",
"X-Appengine-User-Ip",
}
func stripWeircon(h http.Header) {
for _, k := range weirconHeaders {
// stripIdentifying removes every header that could reveal the caller or the
// proxy chain. The explicit list catches the common ones; the prefix sweep
// catches any vendor-specific X-Forwarded-* we didn't enumerate. Header keys
// in an http.Header are already canonicalized, so prefix matching is exact.
func stripIdentifying(h http.Header) {
for _, k := range stripHeaders {
h.Del(k)
}
for k := range h {
if strings.HasPrefix(k, "X-Forwarded") {
h.Del(k)
}
}
}
func pickProxyID(req *http.Request, n int) (int, error) {
@@ -212,7 +246,7 @@ func newHandler(cfg Config, pool []*http.Transport) http.Handler {
pr.Out.Host = rc.target.Host
pr.Out.Method = rc.method
pr.Out.RequestURI = ""
stripWeircon(pr.Out.Header)
stripIdentifying(pr.Out.Header)
},
Transport: &poolTransport{pool: pool},
ModifyResponse: func(resp *http.Response) error {
+58
View File
@@ -0,0 +1,58 @@
package main
import (
"net/http"
"testing"
)
func TestStripIdentifying(t *testing.T) {
h := http.Header{}
// Headers that MUST be removed before the request reaches the target.
removed := []string{
"X-Weircon-Random-Ip",
"X-Weircon-Proxy-Id",
"X-Forwarded-For",
"X-Forwarded-Host",
"X-Forwarded-Proto",
"X-Forwarded-Port",
"X-Forwarded-Custom-Vendor", // caught by prefix sweep
"X-Real-Ip",
"Forwarded",
"Via",
"Cf-Connecting-Ip",
"True-Client-Ip",
"Fastly-Client-Ip",
"X-Original-Forwarded-For",
"X-Client-Ip",
}
for _, k := range removed {
h.Set(k, "leak")
}
// Headers a crawler legitimately sets — these MUST survive untouched.
kept := map[string]string{
"User-Agent": "my-crawler/1.0",
"Cookie": "session=abc",
"Accept": "text/html",
"Accept-Language": "en-US",
"Referer": "https://example.com",
"X-Forward": "not-a-forwarding-header", // does not match "X-Forwarded" prefix
}
for k, v := range kept {
h.Set(k, v)
}
stripIdentifying(h)
for _, k := range removed {
if got := h.Get(k); got != "" {
t.Errorf("expected %q to be stripped, still present: %q", k, got)
}
}
for k, want := range kept {
if got := h.Get(k); got != want {
t.Errorf("expected %q to survive as %q, got %q", k, want, got)
}
}
}