Skip to content

Commit 3fac2a8

Browse files
authored
Merge pull request #15 from encryption4all/fix/validate-https-urls
fix: reject http:// URLs in PostGuardConfig to prevent plaintext credential leaks
2 parents 29acec5 + fe6d4eb commit 3fac2a8

5 files changed

Lines changed: 152 additions & 0 deletions

File tree

E4A.PostGuard.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
<Solution>
22
<Project Path="src/E4A.PostGuard.csproj" />
3+
<Project Path="tests/E4A.PostGuard.Tests/E4A.PostGuard.Tests.csproj" />
34
</Solution>

src/PostGuard.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ public class PostGuard
88

99
public PostGuard(PostGuardConfig config)
1010
{
11+
ArgumentNullException.ThrowIfNull(config);
12+
config.Validate();
1113
_config = config;
1214
}
1315

src/PostGuardConfig.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,34 @@ public class PostGuardConfig
55
public required string PkgUrl { get; init; }
66
public required string CryptifyUrl { get; init; }
77
public Dictionary<string, string>? Headers { get; init; }
8+
9+
/// <summary>
10+
/// Opt-in escape hatch to allow non-https URLs (e.g. http://localhost) for
11+
/// local development and testing. Defaults to false: the SDK refuses to
12+
/// send API keys and signing keys over plaintext connections.
13+
/// </summary>
14+
public bool AllowInsecureUrls { get; init; }
15+
16+
internal void Validate()
17+
{
18+
if (AllowInsecureUrls)
19+
{
20+
return;
21+
}
22+
23+
RequireHttps(PkgUrl, nameof(PkgUrl));
24+
RequireHttps(CryptifyUrl, nameof(CryptifyUrl));
25+
}
26+
27+
private static void RequireHttps(string url, string name)
28+
{
29+
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri) ||
30+
uri.Scheme != Uri.UriSchemeHttps)
31+
{
32+
throw new ArgumentException(
33+
$"{name} must be an absolute https:// URL (got '{url}'). " +
34+
"Set AllowInsecureUrls = true to opt in to plaintext URLs for local testing.",
35+
name);
36+
}
37+
}
838
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<IsPackable>false</IsPackable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="coverlet.collector" Version="6.0.4" />
12+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
13+
<PackageReference Include="xunit" Version="2.9.3" />
14+
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
15+
</ItemGroup>
16+
17+
<ItemGroup>
18+
<Using Include="Xunit" />
19+
</ItemGroup>
20+
21+
<ItemGroup>
22+
<ProjectReference Include="..\..\src\E4A.PostGuard.csproj" />
23+
</ItemGroup>
24+
25+
</Project>
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using E4A.PostGuard;
2+
3+
namespace E4A.PostGuard.Tests;
4+
5+
public class PostGuardConfigTests
6+
{
7+
private const string ValidPkg = "https://pkg.postguard.eu";
8+
private const string ValidCryptify = "https://cryptify.postguard.eu";
9+
10+
[Fact]
11+
public void Ctor_AcceptsHttpsUrls()
12+
{
13+
var config = new PostGuardConfig
14+
{
15+
PkgUrl = ValidPkg,
16+
CryptifyUrl = ValidCryptify,
17+
};
18+
19+
var pg = new PostGuard(config);
20+
21+
Assert.NotNull(pg);
22+
}
23+
24+
[Theory]
25+
[InlineData("http://pkg.postguard.eu")]
26+
[InlineData("http://localhost:8080")]
27+
[InlineData("ftp://pkg.postguard.eu")]
28+
[InlineData("pkg.postguard.eu")]
29+
[InlineData("/relative/path")]
30+
[InlineData("")]
31+
public void Ctor_RejectsNonHttpsPkgUrl(string badUrl)
32+
{
33+
var config = new PostGuardConfig
34+
{
35+
PkgUrl = badUrl,
36+
CryptifyUrl = ValidCryptify,
37+
};
38+
39+
var ex = Assert.Throws<ArgumentException>(() => new PostGuard(config));
40+
Assert.Equal("PkgUrl", ex.ParamName);
41+
}
42+
43+
[Theory]
44+
[InlineData("http://cryptify.postguard.eu")]
45+
[InlineData("ws://cryptify.postguard.eu")]
46+
[InlineData("cryptify.postguard.eu")]
47+
public void Ctor_RejectsNonHttpsCryptifyUrl(string badUrl)
48+
{
49+
var config = new PostGuardConfig
50+
{
51+
PkgUrl = ValidPkg,
52+
CryptifyUrl = badUrl,
53+
};
54+
55+
var ex = Assert.Throws<ArgumentException>(() => new PostGuard(config));
56+
Assert.Equal("CryptifyUrl", ex.ParamName);
57+
}
58+
59+
[Fact]
60+
public void Ctor_AllowInsecureUrls_AcceptsHttpLocalhost()
61+
{
62+
var config = new PostGuardConfig
63+
{
64+
PkgUrl = "http://localhost:8080",
65+
CryptifyUrl = "http://localhost:8081",
66+
AllowInsecureUrls = true,
67+
};
68+
69+
var pg = new PostGuard(config);
70+
71+
Assert.NotNull(pg);
72+
}
73+
74+
[Fact]
75+
public void Ctor_AllowInsecureUrls_StillAcceptsHttpsUrls()
76+
{
77+
var config = new PostGuardConfig
78+
{
79+
PkgUrl = ValidPkg,
80+
CryptifyUrl = ValidCryptify,
81+
AllowInsecureUrls = true,
82+
};
83+
84+
var pg = new PostGuard(config);
85+
86+
Assert.NotNull(pg);
87+
}
88+
89+
[Fact]
90+
public void Ctor_NullConfig_Throws()
91+
{
92+
Assert.Throws<ArgumentNullException>(() => new PostGuard(null!));
93+
}
94+
}

0 commit comments

Comments
 (0)