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:
@@ -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`). |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 });
|
||||
});
|
||||
});
|
||||
@@ -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>
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user