Complete Entrypoint Detection Re-Engineering Program (Sprints 0410-0415) and Sprint 3500.0002.0003 (Proof Replay + API)

Entrypoint Detection Program (100% complete):
- Sprint 0411: Semantic Entrypoint Engine - all 25 tasks DONE
- Sprint 0412: Temporal & Mesh Entrypoint - all 19 tasks DONE
- Sprint 0413: Speculative Execution Engine - all 19 tasks DONE
- Sprint 0414: Binary Intelligence - all 19 tasks DONE
- Sprint 0415: Predictive Risk Scoring - all tasks DONE

Key deliverables:
- SemanticEntrypoint schema with ApplicationIntent/CapabilityClass
- TemporalEntrypointGraph and MeshEntrypointGraph
- ShellSymbolicExecutor with PathEnumerator and PathConfidenceScorer
- CodeFingerprint index with symbol recovery
- RiskScore with multi-dimensional risk assessment

Sprint 3500.0002.0003 (Proof Replay + API):
- ManifestEndpoints with DSSE content negotiation
- Proof bundle endpoints by root hash
- IdempotencyMiddleware with RFC 9530 Content-Digest
- Rate limiting (100 req/hr per tenant)
- OpenAPI documentation updates

Tests: 357 EntryTrace tests pass, WebService tests blocked by pre-existing infrastructure issue
This commit is contained in:
StellaOps Bot
2025-12-20 17:46:27 +02:00
parent ce8cdcd23d
commit 3698ebf4a8
46 changed files with 4156 additions and 46 deletions

View File

@@ -0,0 +1,127 @@
// -----------------------------------------------------------------------------
// RateLimitingExtensions.cs
// Sprint: SPRINT_3500_0002_0003_proof_replay_api
// Task: T4 - Rate Limiting
// Description: Rate limiting configuration for proof replay endpoints
// -----------------------------------------------------------------------------
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.Scanner.WebService.Security;
namespace StellaOps.Scanner.WebService.Extensions;
/// <summary>
/// Extensions for configuring rate limiting on proof replay endpoints.
/// </summary>
public static class RateLimitingExtensions
{
/// <summary>
/// Policy name for proof replay rate limiting (100 req/hr per tenant).
/// </summary>
public const string ProofReplayPolicy = "ProofReplay";
/// <summary>
/// Policy name for scan manifest rate limiting (100 req/hr per tenant).
/// </summary>
public const string ManifestPolicy = "Manifest";
/// <summary>
/// Add rate limiting services for scanner endpoints (proof replay, manifest, etc.).
/// </summary>
public static IServiceCollection AddScannerRateLimiting(this IServiceCollection services)
{
services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
// Proof replay: 100 requests per hour per tenant
options.AddPolicy(ProofReplayPolicy, context =>
{
var tenantId = GetTenantId(context);
return RateLimitPartition.GetFixedWindowLimiter(
partitionKey: $"proof-replay:{tenantId}",
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 100,
Window = TimeSpan.FromHours(1),
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 0 // No queuing; immediate rejection
});
});
// Manifest: 100 requests per hour per tenant
options.AddPolicy(ManifestPolicy, context =>
{
var tenantId = GetTenantId(context);
return RateLimitPartition.GetFixedWindowLimiter(
partitionKey: $"manifest:{tenantId}",
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 100,
Window = TimeSpan.FromHours(1),
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 0
});
});
// Configure rejection response
options.OnRejected = async (context, cancellationToken) =>
{
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
context.HttpContext.Response.Headers.RetryAfter = "3600"; // 1 hour
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
{
context.HttpContext.Response.Headers.RetryAfter =
((int)retryAfter.TotalSeconds).ToString();
}
await context.HttpContext.Response.WriteAsJsonAsync(new
{
type = "https://stellaops.org/problems/rate-limit",
title = "Too Many Requests",
status = 429,
detail = "Rate limit exceeded. Please wait before making more requests.",
retryAfterSeconds = context.HttpContext.Response.Headers.RetryAfter.ToString()
}, cancellationToken);
};
});
return services;
}
/// <summary>
/// Extract tenant ID from the HTTP context for rate limiting partitioning.
/// </summary>
private static string GetTenantId(HttpContext context)
{
// Try to get tenant from claims
var tenantClaim = context.User?.FindFirst(ScannerClaims.TenantId);
if (tenantClaim is not null && !string.IsNullOrWhiteSpace(tenantClaim.Value))
{
return tenantClaim.Value;
}
// Fallback to tenant header
if (context.Request.Headers.TryGetValue("X-Tenant-Id", out var headerValue) &&
!string.IsNullOrWhiteSpace(headerValue))
{
return headerValue.ToString();
}
// Fallback to IP address for unauthenticated requests
return context.Connection.RemoteIpAddress?.ToString() ?? "unknown";
}
}
/// <summary>
/// Scanner claims constants.
/// </summary>
public static class ScannerClaims
{
public const string TenantId = "tenant_id";
}