Update: drop the API-key requirement from the update trigger
ci / build-test (push) Successful in 1m14s
ci / build-test (push) Successful in 1m14s
Owner's call: MeterVault__AllowInAppUpdate is now the whole gate. One click on the banner, no key, no prompt, and the REST endpoint no longer asks for one either. What that means, recorded so it is not rediscovered later: with the flag on, anything that can reach MeterVault can trigger a rebuild and restart. On the realistic threat model that is a repeatable denial of service — minutes of downtime and a pegged CPU per request — rather than code injection, because the build comes from the owner's own repository. It becomes remote code execution if that repository is ever compromised. The flag still defaults off, and that default is now the only thing between an upgrade and an open trigger, so UpdateRunnerTests pins it along with the fact that configuring API keys does not imply consent to rebuild the host. Kept one guard, which is not authentication: the REST endpoint requires an X-MeterVault-Update header. Without it any website could POST to the endpoint through the browser of someone on the network — a plain HTML form is enough, and no key means nothing else would stop it. A form cannot set a custom header and a cross-origin fetch that tries is stopped by a preflight nothing here answers, so this costs a deliberate caller one flag and costs the button nothing, since it runs over the Blazor circuit rather than HTTP. The confirmation dialog stays, now purely as a guard against a stray click costing several minutes of downtime. Every triggered update is logged as a warning: with no key there is no caller to attribute it to, and the restart discards anything held in memory. Claude-Session: https://claude.ai/code/session_01V6joyergfvVLFEizH1hJLd
This commit is contained in:
@@ -12,25 +12,21 @@ public sealed class ApiTests(TimescaleFixture fx)
|
||||
private sealed record ReadingPush(int MeterId, DateTimeOffset Time, double Value);
|
||||
|
||||
[Fact]
|
||||
public async Task Update_endpoint_is_shut_by_default_and_not_openable_anonymously()
|
||||
public async Task Update_endpoint_is_shut_unless_explicitly_enabled()
|
||||
{
|
||||
// Through the real pipeline, not just the runner: an install that never opted in must return
|
||||
// a refusal for this endpoint even when the caller presents a valid key, and even when the
|
||||
// API itself has been opened anonymously. 409 rather than 401 — the endpoint is disabled,
|
||||
// which is a different fact from the caller being unauthenticated.
|
||||
// Through the real pipeline, not just the runner. 409 rather than 401: the endpoint is
|
||||
// disabled, which is a different fact from the caller being unauthenticated — and the
|
||||
// default install must refuse regardless of what the caller presents.
|
||||
using var factory = new MeterVaultAppFactory(fx.ConnectionString, configureApiKey: true);
|
||||
using var client = factory.CreateClient();
|
||||
|
||||
using var withKey = new HttpRequestMessage(HttpMethod.Post, "/api/v1/system/update");
|
||||
withKey.Headers.Add("X-Api-Key", MeterVaultAppFactory.ApiKey);
|
||||
var authorised = await client.SendAsync(withKey);
|
||||
Assert.Equal(HttpStatusCode.Conflict, authorised.StatusCode);
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, "/api/v1/system/update");
|
||||
request.Headers.Add("X-Api-Key", MeterVaultAppFactory.ApiKey);
|
||||
request.Headers.Add(MeterVault.App.Api.ApiEndpoints.UpdateRequestHeader, "1");
|
||||
|
||||
// And without a key it is certainly not reachable.
|
||||
var anonymous = await client.PostAsync(new Uri("/api/v1/system/update", UriKind.Relative), content: null);
|
||||
Assert.True(
|
||||
anonymous.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Conflict,
|
||||
$"expected the update endpoint to refuse an unauthenticated caller, got {anonymous.StatusCode}.");
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.Conflict, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user