Add PHP Analyzer Plugin and Composer Lock Data Handling
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Implemented the PhpAnalyzerPlugin to analyze PHP projects.
- Created ComposerLockData class to represent data from composer.lock files.
- Developed ComposerLockReader to load and parse composer.lock files asynchronously.
- Introduced ComposerPackage class to encapsulate package details.
- Added PhpPackage class to represent PHP packages with metadata and evidence.
- Implemented PhpPackageCollector to gather packages from ComposerLockData.
- Created PhpLanguageAnalyzer to perform analysis and emit results.
- Added capability signals for known PHP frameworks and CMS.
- Developed unit tests for the PHP language analyzer and its components.
- Included sample composer.lock and expected output for testing.
- Updated project files for the new PHP analyzer library and tests.
This commit is contained in:
StellaOps Bot
2025-11-22 14:02:49 +02:00
parent a7f3c7869a
commit b6b9ffc050
158 changed files with 16272 additions and 809 deletions

View File

@@ -12,6 +12,7 @@ using StellaOps.Configuration;
using StellaOps.DependencyInjection;
using StellaOps.Findings.Ledger.Domain;
using StellaOps.Findings.Ledger.Infrastructure;
using StellaOps.Findings.Ledger.Infrastructure.AirGap;
using StellaOps.Findings.Ledger.Infrastructure.Merkle;
using StellaOps.Findings.Ledger.Infrastructure.Postgres;
using StellaOps.Findings.Ledger.Infrastructure.Projection;
@@ -140,6 +141,10 @@ builder.Services.AddSingleton<PolicyEngineEvaluationService>();
builder.Services.AddSingleton<IPolicyEvaluationService>(sp => sp.GetRequiredService<PolicyEngineEvaluationService>());
builder.Services.AddSingleton<ILedgerEventWriteService, LedgerEventWriteService>();
builder.Services.AddSingleton<IFindingWorkflowService, FindingWorkflowService>();
builder.Services.AddSingleton<IOrchestratorExportRepository, PostgresOrchestratorExportRepository>();
builder.Services.AddSingleton<OrchestratorExportService>();
builder.Services.AddSingleton<IAirgapImportRepository, PostgresAirgapImportRepository>();
builder.Services.AddSingleton<AirgapImportService>();
builder.Services.AddSingleton<IAttachmentEncryptionService, AttachmentEncryptionService>();
builder.Services.AddSingleton<IAttachmentUrlSigner, AttachmentUrlSigner>();
builder.Services.AddSingleton<IConsoleCsrfValidator, ConsoleCsrfValidator>();
@@ -300,6 +305,95 @@ app.MapGet("/ledger/export/sboms", () => TypedResults.Json(new ExportPage<SbomEx
.RequireAuthorization(LedgerExportPolicy)
.Produces(StatusCodes.Status200OK);
app.MapPost("/internal/ledger/orchestrator-export", async Task<Results<Accepted<OrchestratorExportResponse>, ProblemHttpResult>> (
HttpContext httpContext,
OrchestratorExportRequest request,
OrchestratorExportService service,
CancellationToken cancellationToken) =>
{
if (!httpContext.Request.Headers.TryGetValue("X-Stella-Tenant", out var tenantValues) || string.IsNullOrWhiteSpace(tenantValues))
{
return TypedResults.Problem(statusCode: StatusCodes.Status400BadRequest, title: "missing_tenant");
}
var tenantId = tenantValues.ToString();
var input = new OrchestratorExportInput(
tenantId,
request.RunId,
request.JobType,
request.ArtifactHash,
request.PolicyHash,
request.StartedAt,
request.CompletedAt,
request.Status,
request.ManifestPath,
request.LogsPath);
var record = await service.RecordAsync(input, cancellationToken).ConfigureAwait(false);
var response = new OrchestratorExportResponse(record.RunId, record.MerkleRoot);
return TypedResults.Accepted($"/internal/ledger/orchestrator-export/{record.RunId}", response);
})
.WithName("OrchestratorExportRecord")
.RequireAuthorization(LedgerWritePolicy)
.Produces(StatusCodes.Status202Accepted)
.ProducesProblem(StatusCodes.Status400BadRequest);
app.MapGet("/internal/ledger/orchestrator-export/{artifactHash}", async Task<Results<JsonHttpResult<IReadOnlyList<OrchestratorExportRecord>>, ProblemHttpResult>> (
HttpContext httpContext,
string artifactHash,
OrchestratorExportService service,
CancellationToken cancellationToken) =>
{
if (!httpContext.Request.Headers.TryGetValue("X-Stella-Tenant", out var tenantValues) || string.IsNullOrWhiteSpace(tenantValues))
{
return TypedResults.Problem(statusCode: StatusCodes.Status400BadRequest, title: "missing_tenant");
}
var records = await service.GetByArtifactAsync(tenantValues.ToString(), artifactHash, cancellationToken).ConfigureAwait(false);
return TypedResults.Json(records);
})
.WithName("OrchestratorExportQuery")
.RequireAuthorization(LedgerExportPolicy)
.Produces(StatusCodes.Status200OK)
.ProducesProblem(StatusCodes.Status400BadRequest);
app.MapPost("/internal/ledger/airgap-import", async Task<Results<Accepted<AirgapImportResponse>, ProblemHttpResult>> (
HttpContext httpContext,
AirgapImportRequest request,
AirgapImportService service,
CancellationToken cancellationToken) =>
{
if (!httpContext.Request.Headers.TryGetValue("X-Stella-Tenant", out var tenantValues) || string.IsNullOrWhiteSpace(tenantValues))
{
return TypedResults.Problem(statusCode: StatusCodes.Status400BadRequest, title: "missing_tenant");
}
var input = new AirgapImportInput(
tenantValues.ToString(),
request.BundleId,
request.MirrorGeneration,
request.MerkleRoot,
request.TimeAnchor,
request.Publisher,
request.HashAlgorithm,
request.Contents ?? Array.Empty<string>(),
request.ImportOperator);
var result = await service.RecordAsync(input, cancellationToken).ConfigureAwait(false);
if (!result.Success)
{
return TypedResults.Problem(statusCode: StatusCodes.Status409Conflict, title: "airgap_import_failed", detail: result.Error ?? "Failed to record air-gap import.");
}
var response = new AirgapImportResponse(result.ChainId, result.SequenceNumber, result.LedgerEventId, "accepted", null);
return TypedResults.Accepted($"/internal/ledger/airgap-import/{request.BundleId}", response);
})
.WithName("AirgapImportRecord")
.RequireAuthorization(LedgerWritePolicy)
.Produces(StatusCodes.Status202Accepted)
.ProducesProblem(StatusCodes.Status400BadRequest)
.ProducesProblem(StatusCodes.Status409Conflict);
app.Run();
static Created<LedgerEventResponse> CreateCreatedResponse(LedgerEventRecord record)