feat(zastava): add evidence locker plan and schema examples

- Introduced README.md for Zastava Evidence Locker Plan detailing artifacts to sign and post-signing steps.
- Added example JSON schemas for observer events and webhook admissions.
- Updated implementor guidelines with checklist for CI linting, determinism, secrets management, and schema control.
- Created alert rules for Vuln Explorer to monitor API latency and projection errors.
- Developed analytics ingestion plan for Vuln Explorer, focusing on telemetry and PII guardrails.
- Implemented Grafana dashboard configuration for Vuln Explorer metrics visualization.
- Added expected projection SHA256 for vulnerability events.
- Created k6 load testing script for Vuln Explorer API.
- Added sample projection and replay event data for testing.
- Implemented ReplayInputsLock for deterministic replay inputs management.
- Developed tests for ReplayInputsLock to ensure stable hash computation.
- Created SurfaceManifestDeterminismVerifier to validate manifest determinism and integrity.
- Added unit tests for SurfaceManifestDeterminismVerifier to ensure correct functionality.
- Implemented Angular tests for VulnerabilityHttpClient and VulnerabilityDetailComponent to verify API interactions and UI rendering.
This commit is contained in:
StellaOps Bot
2025-12-02 09:27:31 +02:00
parent 885ce86af4
commit 2d08f52715
74 changed files with 1690 additions and 131 deletions

View File

@@ -5,7 +5,7 @@
| WEB-AOC-19-002 | DONE (2025-11-30) | Added provenance builder, checksum utilities, and DSSE/CMS signature verification helpers with unit tests. |
| WEB-AOC-19-003 | DONE (2025-11-30) | Added client-side guard validator (forbidden/derived/unknown fields, provenance/signature checks) with unit fixtures. |
| WEB-CONSOLE-23-002 | DOING (2025-12-01) | Console status polling + SSE run stream client/store/UI added; tests pending once env fixed. |
| WEB-RISK-66-001 | DOING (2025-12-02) | Added risk gateway HTTP client (trace-id headers), store, `/risk` dashboard with filters and vuln link, auth guard; added `/vulnerabilities/:vulnId` detail; risk/vuln providers switch via quickstart; awaiting gateway endpoints/test harness. |
| WEB-RISK-66-001 | DOING (2025-12-02) | Added risk gateway HTTP client (trace-id headers), store, `/risk` dashboard with filters, empty state, vuln link, auth guard; added `/vulnerabilities/:vulnId` detail + specs; risk/vuln providers switch via quickstart; awaiting gateway endpoints/test harness. |
| WEB-EXC-25-001 | TODO | Exceptions workflow CRUD pending policy scopes. |
| WEB-TEN-47-CONTRACT | DONE (2025-12-01) | Gateway tenant auth/ABAC contract doc v1.0 published (`docs/api/gateway/tenant-auth.md`). |
| WEB-VULN-29-LEDGER-DOC | DONE (2025-12-01) | Findings Ledger proxy contract doc v1.0 with idempotency + retries (`docs/api/gateway/findings-ledger-proxy.md`). |

View File

@@ -18,7 +18,7 @@ export class RiskHttpClient implements RiskApi {
list(options: RiskQueryOptions): Observable<RiskResultPage> {
const tenant = this.resolveTenant(options.tenantId);
const traceId = options.traceId ?? this.generateTraceId();
const traceId = options.traceId ?? crypto.randomUUID?.() ?? this.generateTraceId();
const headers = this.buildHeaders(tenant, options.projectId, traceId);
let params = new HttpParams();
@@ -40,7 +40,7 @@ export class RiskHttpClient implements RiskApi {
stats(options: Pick<RiskQueryOptions, 'tenantId' | 'projectId' | 'traceId'>): Observable<RiskStats> {
const tenant = this.resolveTenant(options.tenantId);
const traceId = options.traceId ?? this.generateTraceId();
const traceId = options.traceId ?? crypto.randomUUID?.() ?? this.generateTraceId();
const headers = this.buildHeaders(tenant, options.projectId, traceId);
return this.http

View File

@@ -0,0 +1,53 @@
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { AuthSessionStore } from '../auth/auth-session.store';
import { VulnerabilityHttpClient, VULNERABILITY_API_BASE_URL } from './vulnerability-http.client';
import { VulnerabilitiesResponse } from './vulnerability.models';
class MockAuthSessionStore {
getActiveTenantId(): string | null {
return 'tenant-dev';
}
}
describe('VulnerabilityHttpClient', () => {
let client: VulnerabilityHttpClient;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
VulnerabilityHttpClient,
{ provide: VULNERABILITY_API_BASE_URL, useValue: 'https://api.example.local' },
{ provide: AuthSessionStore, useClass: MockAuthSessionStore },
],
});
client = TestBed.inject(VulnerabilityHttpClient);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => httpMock.verify());
it('adds tenant header when listing vulnerabilities', () => {
const stub: VulnerabilitiesResponse = { items: [], total: 0, page: 1, pageSize: 20 };
client.listVulnerabilities({ page: 1, pageSize: 5 }).subscribe((resp) => {
expect(resp.page).toBe(1);
});
const req = httpMock.expectOne('https://api.example.local/vuln?page=1&pageSize=5');
expect(req.request.headers.get('X-Stella-Tenant')).toBe('tenant-dev');
req.flush(stub);
});
it('adds project header when provided', () => {
client.listVulnerabilities({ page: 1, projectId: 'proj-ops' }).subscribe();
const req = httpMock.expectOne('https://api.example.local/vuln?page=1');
expect(req.request.headers.get('X-Stella-Project')).toBe('proj-ops');
req.flush({ items: [], total: 0, page: 1, pageSize: 20 });
});
});

View File

@@ -40,7 +40,7 @@
<button type="button" (click)="applyFilters()">Refresh</button>
</section>
<section class="risk-dashboard__table" *ngIf="list() as page">
<section class="risk-dashboard__table" *ngIf="list() as page; else riskEmpty">
<table>
<thead>
<tr>
@@ -67,4 +67,11 @@
</table>
<p class="meta">Showing {{ page.items.length }} of {{ page.total }} risks.</p>
</section>
<ng-template #riskEmpty>
<div class="empty" *ngIf="!loading(); else riskLoading">No risks found for current filters.</div>
<ng-template #riskLoading>
<div class="empty">Loading risks…</div>
</ng-template>
</ng-template>
</section>

View File

@@ -156,6 +156,13 @@ tr:last-child td {
color: #6b7280;
}
.empty {
padding: 1rem;
border: 1px dashed #d1d5db;
border-radius: 0.75rem;
color: #6b7280;
}
@media (max-width: 768px) {
.risk-dashboard__header { flex-direction: column; align-items: flex-start; }
table { display: block; overflow-x: auto; }

View File

@@ -0,0 +1,55 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute } from '@angular/router';
import { of } from 'rxjs';
import { VULNERABILITY_API } from '../../core/api/vulnerability.client';
import { Vulnerability } from '../../core/api/vulnerability.models';
import { VulnerabilityDetailComponent } from './vulnerability-detail.component';
const STUB_VULN: Vulnerability = {
vulnId: 'vuln-001',
cveId: 'CVE-2021-44228',
title: 'Log4Shell',
description: 'Test description',
severity: 'critical',
cvssScore: 10,
cvssVector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H',
status: 'open',
publishedAt: '2021-12-10T00:00:00Z',
modifiedAt: '2024-06-27T00:00:00Z',
affectedComponents: [],
references: [],
hasException: false,
};
class MockVulnApi {
getVulnerability() {
return of(STUB_VULN);
}
}
describe('VulnerabilityDetailComponent', () => {
let fixture: ComponentFixture<VulnerabilityDetailComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [VulnerabilityDetailComponent],
providers: [
{ provide: VULNERABILITY_API, useClass: MockVulnApi },
{
provide: ActivatedRoute,
useValue: { snapshot: { paramMap: new Map([['vulnId', 'vuln-001']]) } },
},
],
}).compileComponents();
fixture = TestBed.createComponent(VulnerabilityDetailComponent);
fixture.detectChanges();
});
it('renders vulnerability data', () => {
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.textContent).toContain('Log4Shell');
expect(compiled.textContent).toContain('CVE-2021-44228');
});
});

View File

@@ -10,12 +10,12 @@ import {
import { firstValueFrom } from 'rxjs';
import { VULNERABILITY_API, VulnerabilityApi } from '../../core/api/vulnerability.client';
import {
Vulnerability,
VulnerabilitySeverity,
VulnerabilityStats,
VulnerabilityStatus,
} from '../../core/api/vulnerability.models';
import {
Vulnerability,
VulnerabilitySeverity,
VulnerabilityStats,
VulnerabilityStatus,
} from '../../core/api/vulnerability.models';
import {
ExceptionDraftContext,
ExceptionDraftInlineComponent,