gitshark

Clone repository

git clone https://gitshark.de/git/workaround/Gitshark.git
git clone git@gitshark.de:workaround/Gitshark.git

← Commits

✨ feat(federation): dev-only insecure flag for local two-host trials

9d72b1d0f3cb49823b9d9eba9578f20fca775447 · Michael Hainz · 2026-06-21T17:14:34Z

Changes

5 files changed, +81 -4

MODIFY README.md +1 -0
diff --git a/README.md b/README.md
index 9f5237c..0cbdc54 100644
--- a/README.md
+++ b/README.md
@@ -70,6 +70,7 @@
70 70 | `GITSHARK_FEDERATION_BASE_URL` | — | Public HTTPS origin (e.g. `https://shark.example`); actor IDs derive from it and are permanent |
71 71 | `GITSHARK_FEDERATION_PEER_ALLOWLIST` | — (empty = deny all) | Comma-separated peer hosts allowed to send/receive federation traffic |
72 72 | `GITSHARK_FEDERATION_MAX_ATTEMPTS` | `8` | Max delivery attempts before a queued activity is dead-lettered |
73 +| `GITSHARK_FEDERATION_DEV_ALLOW_INSECURE` | `false` | **Dev/local only.** Lets the SSRF guard accept `http` + loopback/private targets so two instances can federate on one machine (peer allowlist still enforced). Never enable in production. |
73 74
74 75 > **TLS required in production:** personal access tokens travel as HTTP Basic credentials.
75 76 > Terminate TLS in front of the service; never expose plain HTTP publicly.
MODIFY src/main/java/de/workaround/federation/FederationConfig.java +13 -0
diff --git a/src/main/java/de/workaround/federation/FederationConfig.java b/src/main/java/de/workaround/federation/FederationConfig.java
index d5fc4de..cc9e8e3 100644
--- a/src/main/java/de/workaround/federation/FederationConfig.java
+++ b/src/main/java/de/workaround/federation/FederationConfig.java
@@ -31,6 +31,9 @@
31 31 @ConfigProperty(name = "gitshark.federation.delivery.max-attempts", defaultValue = "8")
32 32 int maxDeliveryAttempts;
33 33
34 + @ConfigProperty(name = "gitshark.federation.dev-allow-insecure", defaultValue = "false")
35 + boolean devAllowInsecure;
36 +
34 37 public boolean enabled()
35 38 {
36 39 return enabled;
@@ -72,6 +75,16 @@
72 75 return maxDeliveryAttempts;
73 76 }
74 77
78 + /**
79 + * Dev/local-only escape hatch: when true, the SSRF guard permits {@code http} and
80 + * loopback/private targets (the peer allowlist is still enforced). Never enable in production —
81 + * it exists so two instances can federate on one machine for testing.
82 + */
83 + public boolean devAllowInsecure()
84 + {
85 + return devAllowInsecure;
86 + }
87 +
75 88 /** Whether the given host is on the peer allowlist (case-insensitive). */
76 89 public boolean peerAllowed(String host)
77 90 {
MODIFY src/main/java/de/workaround/federation/RemoteUrlGuard.java +12 -4
diff --git a/src/main/java/de/workaround/federation/RemoteUrlGuard.java b/src/main/java/de/workaround/federation/RemoteUrlGuard.java
index e35926e..f425b6e 100644
--- a/src/main/java/de/workaround/federation/RemoteUrlGuard.java
+++ b/src/main/java/de/workaround/federation/RemoteUrlGuard.java
@@ -38,7 +38,11 @@
38 38 {
39 39 throw new UnsafeUrlException("Malformed URL: " + url);
40 40 }
41 - if (uri.getScheme() == null || !uri.getScheme().equalsIgnoreCase("https"))
41 + boolean insecure = config.devAllowInsecure();
42 + String scheme = uri.getScheme();
43 + boolean schemeOk = scheme != null
44 + && (scheme.equalsIgnoreCase("https") || (insecure && scheme.equalsIgnoreCase("http")));
45 + if (!schemeOk)
42 46 {
43 47 throw new UnsafeUrlException("Only https is allowed: " + url);
44 48 }
@@ -47,15 +51,19 @@
47 51 {
48 52 throw new UnsafeUrlException("Missing host: " + url);
49 53 }
54 + // The peer allowlist is always enforced, even in dev-insecure mode.
50 55 if (!config.peerAllowed(host))
51 56 {
52 57 throw new UnsafeUrlException("Host not on peer allowlist: " + host);
53 58 }
54 - for (InetAddress address : resolve(host))
59 + if (!insecure)
55 60 {
56 - if (isNonPublic(address))
61 + for (InetAddress address : resolve(host))
57 62 {
58 - throw new UnsafeUrlException("Host resolves to a non-public address: " + host);
63 + if (isNonPublic(address))
64 + {
65 + throw new UnsafeUrlException("Host resolves to a non-public address: " + host);
66 + }
59 67 }
60 68 }
61 69 return uri;
MODIFY src/main/resources/application.properties +2 -0
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index cd5448e..f01696e 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -38,6 +38,8 @@
38 38 gitshark.federation.base-url=${GITSHARK_FEDERATION_BASE_URL:}
39 39 gitshark.federation.peer-allowlist=${GITSHARK_FEDERATION_PEER_ALLOWLIST:}
40 40 gitshark.federation.delivery.max-attempts=${GITSHARK_FEDERATION_MAX_ATTEMPTS:8}
41 +# Dev/local ONLY: allow http + loopback/private federation targets (allowlist still enforced).
42 +gitshark.federation.dev-allow-insecure=${GITSHARK_FEDERATION_DEV_ALLOW_INSECURE:false}
41 43 %test.gitshark.federation.enabled=true
42 44 %test.gitshark.federation.base-url=https://shark.test
43 45 %test.gitshark.federation.peer-allowlist=peer.test,shark.test
ADD src/test/java/de/workaround/federation/RemoteUrlGuardInsecureTest.java +53 -0
diff --git a/src/test/java/de/workaround/federation/RemoteUrlGuardInsecureTest.java b/src/test/java/de/workaround/federation/RemoteUrlGuardInsecureTest.java
new file mode 100644
index 0000000..e1d3487
--- /dev/null
+++ b/src/test/java/de/workaround/federation/RemoteUrlGuardInsecureTest.java
@@ -0,0 +1,53 @@
1 +package de.workaround.federation;
2 +
3 +import java.util.Map;
4 +
5 +import org.junit.jupiter.api.Test;
6 +
7 +import io.quarkus.test.junit.QuarkusTest;
8 +import io.quarkus.test.junit.TestProfile;
9 +import io.quarkus.test.junit.QuarkusTestProfile;
10 +import jakarta.inject.Inject;
11 +
12 +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
13 +import static org.junit.jupiter.api.Assertions.assertThrows;
14 +
15 +/**
16 + * With the dev-only insecure flag on, the SSRF guard permits http + loopback targets (so two
17 + * instances can federate on one machine) but STILL enforces the peer allowlist.
18 + */
19 +@QuarkusTest
20 +@TestProfile(RemoteUrlGuardInsecureTest.InsecureProfile.class)
21 +class RemoteUrlGuardInsecureTest
22 +{
23 + public static class InsecureProfile implements QuarkusTestProfile
24 + {
25 + @Override
26 + public Map<String, String> getConfigOverrides()
27 + {
28 + return Map.of(
29 + "gitshark.federation.enabled", "true",
30 + "gitshark.federation.base-url", "https://shark.test",
31 + "gitshark.federation.dev-allow-insecure", "true",
32 + "gitshark.federation.peer-allowlist", "localhost,127.0.0.1,shark-b.local");
33 + }
34 + }
35 +
36 + @Inject
37 + RemoteUrlGuard guard;
38 +
39 + @Test
40 + void httpAndLoopbackAllowedWhenInsecureAndAllowlisted()
41 + {
42 + assertDoesNotThrow(() -> guard.requireSafe("http://localhost:8081/ap/instance/inbox"));
43 + assertDoesNotThrow(() -> guard.requireSafe("https://shark-b.local:8443/ap/instance/inbox"));
44 + }
45 +
46 + @Test
47 + void allowlistStillEnforcedInInsecureMode()
48 + {
49 + assertThrows(RemoteUrlGuard.UnsafeUrlException.class,
50 + () -> guard.requireSafe("http://evil.example/ap/users/x"));
51 + }
52 +
53 +}

Keyboard shortcuts

?Show this help
g hGo home
EscClose dialog