docs re-org, audit fixes, build fixes

This commit is contained in:
StellaOps Bot
2026-01-05 09:35:33 +02:00
parent eca4e964d3
commit dfab8a29c3
173 changed files with 1276 additions and 560 deletions

View File

@@ -26,7 +26,7 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
import { inject } from '@angular/core';
import { SecretAlertSettings, SecretAlertDestination } from '../../models';
import { SecretAlertSettings, SecretAlertDestination, SecretSeverity } from '../../models';
import { SecretDetectionSettingsService } from '../../services';
@Component({
@@ -403,7 +403,16 @@ export class AlertDestinationConfigComponent {
private readonly settingsService = inject(SecretDetectionSettingsService);
private readonly snackBar = inject(MatSnackBar);
@Input() settings: SecretAlertSettings = { enabled: false, destinations: [] };
@Input() settings: SecretAlertSettings = {
enabled: false,
destinations: [],
minimumAlertSeverity: 'High',
maxAlertsPerScan: 100,
deduplicationWindowHours: 24,
includeFilePath: true,
includeMaskedValue: false,
includeImageRef: true,
};
@Input() tenantId = '';
@Output() settingsChange = new EventEmitter<SecretAlertSettings>();
@@ -421,7 +430,7 @@ export class AlertDestinationConfigComponent {
this.emitChange();
}
onMinSeverityChange(severity: string): void {
onMinSeverityChange(severity: SecretSeverity): void {
this.settings = { ...this.settings, minimumSeverity: severity };
this.emitChange();
}
@@ -448,6 +457,8 @@ export class AlertDestinationConfigComponent {
const newDest: SecretAlertDestination = {
id: crypto.randomUUID(),
name: '',
channelType: 'Webhook',
channelId: '',
type: 'webhook',
enabled: true,
config: {},
@@ -466,7 +477,7 @@ export class AlertDestinationConfigComponent {
this.updateDestination(index, { name: input.value });
}
onDestTypeChange(index: number, type: string): void {
onDestTypeChange(index: number, type: 'webhook' | 'slack' | 'email' | 'teams' | 'pagerduty'): void {
this.updateDestination(index, { type, config: {} });
}
@@ -478,7 +489,7 @@ export class AlertDestinationConfigComponent {
});
}
onDestSeverityChange(index: number, severities: string[]): void {
onDestSeverityChange(index: number, severities: SecretSeverity[]): void {
this.updateDestination(index, { severityFilter: severities });
}

View File

@@ -25,7 +25,7 @@ import { MatCardModule } from '@angular/material/card';
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
import { SecretExceptionService } from '../../services';
import { SecretExceptionPattern } from '../../models';
import { SecretExceptionPattern, CreateExceptionRequest } from '../../models';
@Component({
selector: 'app-exception-manager',
@@ -398,7 +398,7 @@ export class ExceptionManagerComponent implements OnInit {
}
loadExceptions(): void {
this.exceptionService.loadExceptions(this.tenantId).subscribe();
this.exceptionService.listExceptions(this.tenantId).subscribe();
}
openCreateDialog(): void {
@@ -436,17 +436,18 @@ export class ExceptionManagerComponent implements OnInit {
const formValue = this.exceptionForm.value;
const existing = this.editingException();
const payload: Partial<SecretExceptionPattern> = {
name: formValue.name,
const payload: CreateExceptionRequest = {
name: formValue.name ?? '',
pattern: formValue.pattern ?? '',
reason: formValue.description ?? 'Exception created via UI',
matchType: formValue.matchType ?? 'literal',
target: formValue.target ?? 'value',
enabled: formValue.enabled ?? true,
description: formValue.description || undefined,
matchType: formValue.matchType,
pattern: formValue.pattern,
target: formValue.target,
enabled: formValue.enabled,
};
const operation = existing
? this.exceptionService.updateException(existing.id, payload)
? this.exceptionService.updateException(this.tenantId, existing.id, payload)
: this.exceptionService.createException(this.tenantId, payload);
operation.subscribe({
@@ -464,7 +465,7 @@ export class ExceptionManagerComponent implements OnInit {
}
toggleEnabled(exception: SecretExceptionPattern): void {
this.exceptionService.updateException(exception.id, {
this.exceptionService.updateException(this.tenantId, exception.id, {
enabled: !exception.enabled,
}).subscribe({
next: () => {
@@ -477,7 +478,7 @@ export class ExceptionManagerComponent implements OnInit {
deleteException(exception: SecretExceptionPattern): void {
if (!confirm(`Delete exception "${exception.name}"?`)) return;
this.exceptionService.deleteException(exception.id).subscribe({
this.exceptionService.deleteException(this.tenantId, exception.id).subscribe({
next: () => {
this.showSuccess('Exception deleted');
this.loadExceptions();

View File

@@ -121,6 +121,7 @@ export class MaskedValueDisplayComponent {
@Input() value = '';
@Input() findingId = '';
@Input() tenantId = '';
@Input() canReveal = false;
@Output() revealed = new EventEmitter<string>();
@@ -138,12 +139,12 @@ export class MaskedValueDisplayComponent {
};
reveal(): void {
if (!this.canReveal || !this.findingId) return;
if (!this.canReveal || !this.findingId || !this.tenantId) return;
this.revealing.set(true);
this.findingsService.revealValue(this.findingId).subscribe({
next: (value) => {
this.findingsService.revealValue(this.tenantId, this.findingId).subscribe({
next: (value: string) => {
this.revealedValue.set(value);
this.isRevealed.set(true);
this.revealing.set(false);

View File

@@ -26,8 +26,8 @@ import { MatTooltipModule } from '@angular/material/tooltip';
import { MatMenuModule } from '@angular/material/menu';
import { SecretFindingsService } from '../../services';
import { SecretFinding } from '../../models';
import { MaskedValueDisplayComponent } from './masked-value-display.component';
import { SecretFinding, SecretSeverity, SecretFindingStatus } from '../../models';
import { MaskedValueDisplayComponent } from './';
@Component({
selector: 'app-secret-findings-list',
@@ -426,7 +426,9 @@ export class SecretFindingsListComponent implements OnInit {
// Expose service signals
readonly findings = this.findingsService.findings;
readonly pagination = this.findingsService.pagination;
readonly page = this.findingsService.page;
readonly pageSize = this.findingsService.pageSize;
readonly total = this.findingsService.total;
readonly loading = this.findingsService.loading;
readonly error = this.findingsService.error;
@@ -459,14 +461,11 @@ export class SecretFindingsListComponent implements OnInit {
reload(): void {
const tid = this.tenantId();
if (tid) {
this.findingsService.loadFindings(tid, {
this.findingsService.listFindings(tid, {
page: 1,
pageSize: 25,
search: this.searchQuery(),
severity: this.severityFilter(),
status: this.statusFilter(),
sortBy: this.sortField(),
sortDirection: this.sortDirection(),
severity: this.severityFilter() as SecretSeverity[],
status: this.statusFilter() ? [this.statusFilter() as SecretFindingStatus] : undefined,
}).subscribe();
}
}
@@ -496,14 +495,11 @@ export class SecretFindingsListComponent implements OnInit {
onPageChange(event: PageEvent): void {
const tid = this.tenantId();
if (tid) {
this.findingsService.loadFindings(tid, {
this.findingsService.listFindings(tid, {
page: event.pageIndex + 1,
pageSize: event.pageSize,
search: this.searchQuery(),
severity: this.severityFilter(),
status: this.statusFilter(),
sortBy: this.sortField(),
sortDirection: this.sortDirection(),
severity: this.severityFilter() as SecretSeverity[],
status: this.statusFilter() ? [this.statusFilter() as SecretFindingStatus] : undefined,
}).subscribe();
}
}
@@ -527,7 +523,8 @@ export class SecretFindingsListComponent implements OnInit {
}
markResolved(finding: SecretFinding): void {
this.findingsService.updateStatus(finding.id, 'resolved').subscribe({
const tid = this.tenantId();
this.findingsService.updateStatus(tid, finding.id, 'Resolved').subscribe({
next: () => this.reload(),
});
}

View File

@@ -260,7 +260,14 @@ type RevelationMode = 'masked' | 'partial' | 'full' | 'redacted';
`]
})
export class RevelationPolicySelectorComponent {
@Input() config: RevelationPolicyConfig = { mode: 'masked' };
@Input() config: RevelationPolicyConfig = {
defaultPolicy: 'FullMask',
exportPolicy: 'FullMask',
logPolicy: 'FullMask',
fullRevealRoles: [],
partialRevealChars: 4,
mode: 'masked',
};
@Output() configChange = new EventEmitter<RevelationPolicyConfig>();
private readonly sampleSecret = 'ghp_abc123XYZ789secret';

View File

@@ -19,10 +19,10 @@ import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
import { ActivatedRoute } from '@angular/router';
import { SecretDetectionSettingsService } from '../../services';
import { RevelationPolicySelectorComponent } from '../settings/revelation-policy-selector.component';
import { RuleCategoryTogglesComponent } from '../settings/rule-category-toggles.component';
import { ExceptionManagerComponent } from '../exceptions/exception-manager.component';
import { AlertDestinationConfigComponent } from '../alerts/alert-destination-config.component';
import { RevelationPolicySelectorComponent } from './revelation-policy-selector.component';
import { RuleCategoryTogglesComponent } from './rule-category-toggles.component';
import { ExceptionManagerComponent } from '../exceptions';
import { AlertDestinationConfigComponent } from '../alerts';
import { RevelationPolicyConfig, SecretAlertSettings } from '../../models';
@Component({

View File

@@ -24,6 +24,11 @@ export interface SecretDetectionSettings {
*/
export type SecretRevelationPolicy = 'FullMask' | 'PartialReveal' | 'FullReveal';
/**
* Revelation mode for display settings.
*/
export type RevelationMode = 'masked' | 'partial' | 'full' | 'redacted';
/**
* Configuration for revelation policies by context.
*/
@@ -33,6 +38,18 @@ export interface RevelationPolicyConfig {
logPolicy: SecretRevelationPolicy;
fullRevealRoles: string[];
partialRevealChars: number;
/** Display mode for UI presentation */
mode?: RevelationMode;
/** Character used for masking */
maskChar?: string;
/** Length of the mask */
maskLength?: number;
/** Number of characters to reveal at start */
revealFirst?: number;
/** Number of characters to reveal at end */
revealLast?: number;
/** Required permission for full reveal */
requiredPermission?: string;
}
/**
@@ -47,6 +64,16 @@ export interface SecretExceptionPattern {
expiresAt?: string;
ruleIds?: string[];
pathFilter?: string;
/** Display name for the exception */
name?: string;
/** Description of why exception exists */
description?: string;
/** How the pattern is matched */
matchType?: 'literal' | 'regex' | 'glob' | 'prefix' | 'suffix';
/** What the pattern applies to */
target?: 'value' | 'path' | 'filename';
/** Whether the exception is active */
enabled?: boolean;
}
/**
@@ -62,6 +89,12 @@ export interface SecretAlertSettings {
includeMaskedValue: boolean;
includeImageRef: boolean;
alertMessagePrefix?: string;
/** Minimum severity to trigger alert (UI variant) */
minimumSeverity?: SecretSeverity;
/** Rate limit per hour */
rateLimitPerHour?: number;
/** Deduplication window in minutes (UI variant) */
deduplicationWindowMinutes?: number;
}
/**
@@ -74,6 +107,12 @@ export interface SecretAlertDestination {
channelId: string;
severityFilter?: SecretSeverity[];
ruleCategoryFilter?: string[];
/** Destination type (UI variant) */
type?: 'webhook' | 'slack' | 'email' | 'teams' | 'pagerduty';
/** Configuration options */
config?: Record<string, unknown>;
/** Whether destination is enabled */
enabled?: boolean;
}
/**
@@ -120,6 +159,8 @@ export interface SecretRuleCategory {
description: string;
ruleCount: number;
enabled: boolean;
/** Group for categorization */
group?: string;
}
/**
@@ -131,6 +172,16 @@ export interface CreateExceptionRequest {
expiresAt?: string;
ruleIds?: string[];
pathFilter?: string;
/** Display name for the exception */
name?: string;
/** Description of why exception exists */
description?: string;
/** How the pattern is matched */
matchType?: 'literal' | 'regex' | 'glob' | 'prefix' | 'suffix';
/** What the pattern applies to */
target?: 'value' | 'path' | 'filename';
/** Whether the exception is active */
enabled?: boolean;
}
/**

View File

@@ -7,7 +7,7 @@
import { Injectable, inject, signal } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { catchError, tap } from 'rxjs/operators';
import { catchError, tap, map } from 'rxjs/operators';
import { Observable, of } from 'rxjs';
import {
SecretExceptionPattern,
@@ -47,12 +47,10 @@ export class SecretExceptionService {
catchError(err => {
this._error.set(err.message || 'Failed to load exceptions');
this._loading.set(false);
return of({ items: [] });
return of({ items: [] as SecretExceptionPattern[] });
}),
// Transform response
tap(() => {}),
// Return just the items array
tap(response => this._exceptions.set(response.items))
// Map to array
map(response => response.items)
);
}

View File

@@ -7,7 +7,7 @@
import { Injectable, inject, signal, computed } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { catchError, tap } from 'rxjs/operators';
import { catchError, tap, map } from 'rxjs/operators';
import { Observable, of } from 'rxjs';
import {
SecretFinding,
@@ -157,6 +157,21 @@ export class SecretFindingsService {
);
}
/**
* Reveals the secret value for a finding (requires appropriate permissions).
*/
revealValue(tenantId: string, findingId: string): Observable<string> {
return this.http.get<{ value: string }>(
`${this.baseUrl}/${tenantId}/${findingId}/reveal`
).pipe(
map(response => response.value),
catchError(err => {
this._error.set(err.message || 'Failed to reveal secret value');
throw err;
})
);
}
/**
* Selects a finding for detail view.
*/