feat: add security sink detection patterns for JavaScript/TypeScript

- Introduced `sink-detect.js` with various security sink detection patterns categorized by type (e.g., command injection, SQL injection, file operations).
- Implemented functions to build a lookup map for fast sink detection and to match sink calls against known patterns.
- Added `package-lock.json` for dependency management.
This commit is contained in:
StellaOps Bot
2025-12-22 23:21:21 +02:00
parent 3ba7157b00
commit 5146204f1b
529 changed files with 73579 additions and 5985 deletions

View File

@@ -0,0 +1,253 @@
// -----------------------------------------------------------------------------
// BudgetEndpoints.cs
// Sprint: SPRINT_4300_0002_0001 (Unknowns Budget Policy Integration)
// Task: BUDGET-014 - Create budget management API endpoints
// Description: API endpoints for managing unknown budget configurations.
// -----------------------------------------------------------------------------
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using StellaOps.Policy.Unknowns.Configuration;
using StellaOps.Policy.Unknowns.Models;
using StellaOps.Policy.Unknowns.Services;
namespace StellaOps.Policy.Engine.Endpoints;
/// <summary>
/// API endpoints for managing unknown budget configurations.
/// </summary>
internal static class BudgetEndpoints
{
public static IEndpointRouteBuilder MapBudgets(this IEndpointRouteBuilder endpoints)
{
var group = endpoints.MapGroup("/api/v1/policy/budgets")
.RequireAuthorization()
.WithTags("Unknown Budgets");
group.MapGet(string.Empty, ListBudgets)
.WithName("ListBudgets")
.WithSummary("List all configured unknown budgets.")
.Produces<BudgetsListResponse>(StatusCodes.Status200OK);
group.MapGet("/{environment}", GetBudget)
.WithName("GetBudget")
.WithSummary("Get budget for a specific environment.")
.Produces<BudgetResponse>(StatusCodes.Status200OK)
.Produces<ProblemHttpResult>(StatusCodes.Status404NotFound);
group.MapGet("/{environment}/status", GetBudgetStatus)
.WithName("GetBudgetStatus")
.WithSummary("Get current budget status for an environment.")
.Produces<BudgetStatusResponse>(StatusCodes.Status200OK);
group.MapPost("/{environment}/check", CheckBudget)
.WithName("CheckBudget")
.WithSummary("Check unknowns against a budget.")
.Produces<BudgetCheckResponse>(StatusCodes.Status200OK);
group.MapGet("/defaults", GetDefaultBudgets)
.WithName("GetDefaultBudgets")
.WithSummary("Get the default budget configurations.")
.Produces<DefaultBudgetsResponse>(StatusCodes.Status200OK);
return endpoints;
}
private static Ok<BudgetsListResponse> ListBudgets(
IOptions<UnknownBudgetOptions> options)
{
var budgets = options.Value.Budgets
.Select(kvp => ToBudgetDto(kvp.Key, kvp.Value))
.OrderBy(b => b.Environment)
.ToList();
return TypedResults.Ok(new BudgetsListResponse(
budgets,
budgets.Count,
options.Value.EnforceBudgets));
}
private static Results<Ok<BudgetResponse>, NotFound<ProblemDetails>> GetBudget(
string environment,
IUnknownBudgetService budgetService)
{
var budget = budgetService.GetBudgetForEnvironment(environment);
if (budget is null)
{
return TypedResults.NotFound(new ProblemDetails
{
Title = "Budget not found",
Detail = $"No budget configured for environment '{environment}'."
});
}
return TypedResults.Ok(new BudgetResponse(ToBudgetDto(environment, budget)));
}
private static async Task<Results<Ok<BudgetStatusResponse>, ProblemHttpResult>> GetBudgetStatus(
HttpContext httpContext,
string environment,
IUnknownBudgetService budgetService,
Unknowns.Repositories.IUnknownsRepository repository,
CancellationToken ct)
{
var tenantId = ResolveTenantId(httpContext);
if (tenantId == Guid.Empty)
{
return TypedResults.Problem("Tenant ID is required.", statusCode: StatusCodes.Status400BadRequest);
}
// Get all unknowns for the tenant
var unknowns = await repository.GetAllAsync(tenantId, limit: 10000, ct: ct);
var status = budgetService.GetBudgetStatus(environment, unknowns);
return TypedResults.Ok(new BudgetStatusResponse(
status.Environment,
status.TotalUnknowns,
status.TotalLimit,
status.PercentageUsed,
status.IsExceeded,
status.ViolationCount,
status.ByReasonCode.ToDictionary(
kvp => kvp.Key.ToString(),
kvp => kvp.Value)));
}
private static async Task<Results<Ok<BudgetCheckResponse>, ProblemHttpResult>> CheckBudget(
HttpContext httpContext,
string environment,
[FromBody] BudgetCheckRequest request,
IUnknownBudgetService budgetService,
Unknowns.Repositories.IUnknownsRepository repository,
CancellationToken ct)
{
var tenantId = ResolveTenantId(httpContext);
if (tenantId == Guid.Empty)
{
return TypedResults.Problem("Tenant ID is required.", statusCode: StatusCodes.Status400BadRequest);
}
// Get unknowns (either from request or repository)
IReadOnlyList<Unknown> unknowns;
if (request.UnknownIds is { Count: > 0 })
{
var allUnknowns = await repository.GetAllAsync(tenantId, limit: 10000, ct: ct);
unknowns = allUnknowns.Where(u => request.UnknownIds.Contains(u.Id)).ToList();
}
else
{
unknowns = await repository.GetAllAsync(tenantId, limit: 10000, ct: ct);
}
var result = budgetService.CheckBudget(environment, unknowns);
return TypedResults.Ok(new BudgetCheckResponse(
result.IsWithinBudget,
result.RecommendedAction.ToString().ToLowerInvariant(),
result.TotalUnknowns,
result.TotalLimit,
result.Message,
result.Violations.Select(kvp => new BudgetViolationDto(
kvp.Key.ToString(),
kvp.Value.Count,
kvp.Value.Limit)).ToList()));
}
private static Ok<DefaultBudgetsResponse> GetDefaultBudgets()
{
return TypedResults.Ok(new DefaultBudgetsResponse(
ToBudgetDto("production", DefaultBudgets.Production),
ToBudgetDto("staging", DefaultBudgets.Staging),
ToBudgetDto("development", DefaultBudgets.Development),
ToBudgetDto("default", DefaultBudgets.Default)));
}
private static Guid ResolveTenantId(HttpContext context)
{
if (context.Request.Headers.TryGetValue("X-Tenant-Id", out var tenantHeader) &&
!string.IsNullOrWhiteSpace(tenantHeader) &&
Guid.TryParse(tenantHeader.ToString(), out var headerTenantId))
{
return headerTenantId;
}
var tenantClaim = context.User?.FindFirst("tenant_id")?.Value;
if (!string.IsNullOrEmpty(tenantClaim) && Guid.TryParse(tenantClaim, out var claimTenantId))
{
return claimTenantId;
}
return Guid.Empty;
}
private static BudgetDto ToBudgetDto(string environment, UnknownBudget budget)
{
return new BudgetDto(
environment,
budget.TotalLimit,
budget.ReasonLimits.ToDictionary(
kvp => kvp.Key.ToString(),
kvp => kvp.Value),
budget.Action.ToString().ToLowerInvariant(),
budget.ExceededMessage);
}
}
#region DTOs
/// <summary>Budget data transfer object.</summary>
public sealed record BudgetDto(
string Environment,
int? TotalLimit,
IReadOnlyDictionary<string, int> ReasonLimits,
string Action,
string? ExceededMessage);
/// <summary>Response containing a list of budgets.</summary>
public sealed record BudgetsListResponse(
IReadOnlyList<BudgetDto> Budgets,
int TotalCount,
bool EnforcementEnabled);
/// <summary>Response containing a single budget.</summary>
public sealed record BudgetResponse(BudgetDto Budget);
/// <summary>Response containing budget status.</summary>
public sealed record BudgetStatusResponse(
string Environment,
int TotalUnknowns,
int? TotalLimit,
decimal PercentageUsed,
bool IsExceeded,
int ViolationCount,
IReadOnlyDictionary<string, int> ByReasonCode);
/// <summary>Request to check unknowns against a budget.</summary>
public sealed record BudgetCheckRequest(IReadOnlyList<Guid>? UnknownIds = null);
/// <summary>Response from budget check.</summary>
public sealed record BudgetCheckResponse(
bool IsWithinBudget,
string RecommendedAction,
int TotalUnknowns,
int? TotalLimit,
string? Message,
IReadOnlyList<BudgetViolationDto> Violations);
/// <summary>Budget violation details.</summary>
public sealed record BudgetViolationDto(
string ReasonCode,
int Count,
int Limit);
/// <summary>Response containing default budgets.</summary>
public sealed record DefaultBudgetsResponse(
BudgetDto Production,
BudgetDto Staging,
BudgetDto Development,
BudgetDto Default);
#endregion