save progress
This commit is contained in:
@@ -19,6 +19,7 @@ completely isolated network:
|
||||
| **Delta patches** | Daily diff bundles keep size \< 350 MB |
|
||||
| **Scanner plug-ins** | OS analyzers plus the Node.js, Go, .NET, Python, Ruby, Rust, and PHP language analyzers packaged under `plugins/scanner/analyzers/**` with manifests so Workers load deterministically offline. |
|
||||
| **Debug store** | `.debug` artefacts laid out under `debug/.build-id/<aa>/<rest>.debug` with `debug/debug-manifest.json` mapping build-ids to originating images for symbol retrieval. |
|
||||
| **Secret Detection Rules** | DSSE-signed rule bundles under `rules/secrets/<version>/` with manifest, JSONL rules, and signature envelope for air-gapped secret leak detection. |
|
||||
| **Telemetry collector bundle** | `telemetry/telemetry-offline-bundle.tar.gz` plus `.sha256`, containing OTLP collector config, Helm/Compose overlays, and operator instructions. |
|
||||
| **CLI + Task Packs** | `cli/` binaries from `release/cli`, Task Runner bootstrap (`bootstrap/task-runner/task-runner.yaml.sample`), and task-pack docs under `docs/task-packs/**` + `docs/modules/taskrunner/**`. |
|
||||
| **Orchestrator/Export/Notifier kits** | Orchestrator service, worker SDK, Postgres snapshot, dashboards (`orchestrator/**`), Export Center bundles (`export-center/**`), Notifier offline packs (`notifier/**`). |
|
||||
@@ -41,6 +42,68 @@ completely isolated network:
|
||||
|
||||
The PHP analyzer parses `composer.lock` for Composer dependencies and supports optional runtime evidence via the `stella-trace.php` shim; set `STELLA_PHP_OPCACHE=1` to enable opcache statistics collection.
|
||||
|
||||
**Secret Detection Rules:**
|
||||
|
||||
The Offline Kit includes DSSE-signed rule bundles for secret leak detection, enabling fully offline scanning for exposed credentials, API keys, and other sensitive data.
|
||||
|
||||
**Bundle Structure:**
|
||||
```
|
||||
rules/secrets/<version>/
|
||||
secrets.ruleset.manifest.json # Bundle metadata (version, rule count, signer)
|
||||
secrets.ruleset.rules.jsonl # Rule definitions (one JSON per line)
|
||||
secrets.ruleset.dsse.json # DSSE signature envelope
|
||||
SHA256SUMS # File checksums
|
||||
```
|
||||
|
||||
**Manifest Format:**
|
||||
```json
|
||||
{
|
||||
"bundleId": "secrets.ruleset",
|
||||
"bundleType": "secrets",
|
||||
"version": "2026.01",
|
||||
"ruleCount": 150,
|
||||
"signerKeyId": "stellaops-secrets-signer",
|
||||
"signedAt": "2026-01-04T00:00:00Z",
|
||||
"files": [
|
||||
{
|
||||
"name": "secrets.ruleset.rules.jsonl",
|
||||
"digest": "sha256:...",
|
||||
"sizeBytes": 45678
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Installation:**
|
||||
```bash
|
||||
# Verify bundle signature using local attestor mirror
|
||||
export STELLA_ATTESTOR_URL="file:///mnt/offline-kit/attestor-mirror"
|
||||
devops/offline/scripts/install-secrets-bundle.sh \
|
||||
/mnt/offline-kit/rules/secrets/2026.01 \
|
||||
/opt/stellaops/plugins/scanner/analyzers/secrets
|
||||
```
|
||||
|
||||
**Bundle Rotation:**
|
||||
```bash
|
||||
# Upgrade to new version with automatic backup
|
||||
devops/offline/scripts/rotate-secrets-bundle.sh \
|
||||
/mnt/offline-kit/rules/secrets/2026.02
|
||||
```
|
||||
|
||||
**Enable Feature:**
|
||||
```yaml
|
||||
scanner:
|
||||
features:
|
||||
experimental:
|
||||
secret-leak-detection: true
|
||||
```
|
||||
|
||||
**Verify Bundle is Loaded:**
|
||||
```bash
|
||||
kubectl logs -l app=scanner-worker --tail=100 | grep SecretsAnalyzerHost
|
||||
# Expected: SecretsAnalyzerHost: Loaded bundle 2026.01 with 150 rules
|
||||
```
|
||||
|
||||
**Python analyzer features:**
|
||||
- **Wheel/sdist/editable** parsing with dependency edges from `METADATA`, `PKG-INFO`, `requirements.txt`, and `pyproject.toml`
|
||||
- **Virtual environment** support for virtualenv, venv, and conda prefix layouts
|
||||
|
||||
@@ -13,35 +13,35 @@ This documentation set is internal and does not keep compatibility stubs for old
|
||||
|
||||
| Goal | Open this |
|
||||
| --- | --- |
|
||||
| Understand the product in 2 minutes | `overview.md` |
|
||||
| Run a first scan (CLI) | `quickstart.md` |
|
||||
| Browse capabilities | `key-features.md` |
|
||||
| Roadmap (priorities + definition of "done") | `05_ROADMAP.md` |
|
||||
| Architecture: high-level overview | `40_ARCHITECTURE_OVERVIEW.md` |
|
||||
| Architecture: full reference map | `07_HIGH_LEVEL_ARCHITECTURE.md` |
|
||||
| Architecture: user flows (UML) | `technical/architecture/user-flows.md` |
|
||||
| Architecture: module matrix (46 modules) | `technical/architecture/module-matrix.md` |
|
||||
| Architecture: data flows | `technical/architecture/data-flows.md` |
|
||||
| Architecture: schema mapping | `technical/architecture/schema-mapping.md` |
|
||||
| Offline / air-gap operations | `24_OFFLINE_KIT.md` |
|
||||
| Security deployment hardening | `17_SECURITY_HARDENING_GUIDE.md` |
|
||||
| Ingest advisories (Concelier + CLI) | `10_CONCELIER_CLI_QUICKSTART.md` |
|
||||
| Develop plugins/connectors | `10_PLUGIN_SDK_GUIDE.md` |
|
||||
| Console (Web UI) operator guide | `15_UI_GUIDE.md` |
|
||||
| VEX consensus and issuer trust | `16_VEX_CONSENSUS_GUIDE.md` |
|
||||
| Vulnerability Explorer guide | `20_VULNERABILITY_EXPLORER_GUIDE.md` |
|
||||
| Understand the product in 2 minutes | [overview.md](/docs/overview/) |
|
||||
| Run a first scan (CLI) | [quickstart.md](/docs/quickstart/) |
|
||||
| Browse capabilities | [key-features.md](/docs/key-features/) |
|
||||
| Roadmap (priorities + definition of "done") | [05_ROADMAP.md](/docs/05_roadmap/) |
|
||||
| Architecture: high-level overview | [40_ARCHITECTURE_OVERVIEW.md](/docs/40_architecture_overview/) |
|
||||
| Architecture: full reference map | [07_HIGH_LEVEL_ARCHITECTURE.md](/docs/07_high_level_architecture/) |
|
||||
| Architecture: user flows (UML) | [technical/architecture/user-flows.md](/docs/technical/architecture/user-flows/) |
|
||||
| Architecture: module matrix (46 modules) | [technical/architecture/module-matrix.md](/docs/technical/architecture/module-matrix/) |
|
||||
| Architecture: data flows | [technical/architecture/data-flows.md](/docs/technical/architecture/data-flows/) |
|
||||
| Architecture: schema mapping | [technical/architecture/schema-mapping.md](/docs/technical/architecture/schema-mapping/) |
|
||||
| Offline / air-gap operations | [24_OFFLINE_KIT.md](/docs/24_offline_kit/) |
|
||||
| Security deployment hardening | [17_SECURITY_HARDENING_GUIDE.md](/docs/17_security_hardening_guide/) |
|
||||
| Ingest advisories (Concelier + CLI) | [10_CONCELIER_CLI_QUICKSTART.md](/docs/10_concelier_cli_quickstart/) |
|
||||
| Develop plugins/connectors | [10_PLUGIN_SDK_GUIDE.md](/docs/10_plugin_sdk_guide/) |
|
||||
| Console (Web UI) operator guide | [15_UI_GUIDE.md](/docs/15_ui_guide/) |
|
||||
| VEX consensus and issuer trust | [16_VEX_CONSENSUS_GUIDE.md](/docs/16_vex_consensus_guide/) |
|
||||
| Vulnerability Explorer guide | [20_VULNERABILITY_EXPLORER_GUIDE.md](/docs/20_vulnerability_explorer_guide/) |
|
||||
|
||||
## Detailed Indexes
|
||||
|
||||
- **Technical index (everything):** `docs/technical/README.md`
|
||||
- **End-to-end workflow flows:** `docs/flows/` (16 detailed flow documents)
|
||||
- **Module dossiers:** `docs/modules/`
|
||||
- **API contracts and samples:** `docs/api/`
|
||||
- **Architecture notes / ADRs:** `docs/architecture/`, `docs/adr/`
|
||||
- **Operations and deployment:** `docs/operations/`, `docs/deploy/`, `docs/deployment/`
|
||||
- **Air-gap workflows:** `docs/airgap/`
|
||||
- **Security deep dives:** `docs/security/`
|
||||
- **Benchmarks and fixtures:** `docs/benchmarks/`, `docs/assets/`
|
||||
- **Technical index (everything):** [docs/technical/README.md](/docs/technical/)
|
||||
- **End-to-end workflow flows:** [docs/flows/](/docs/flows/) (16 detailed flow documents)
|
||||
- **Module dossiers:** [docs/modules/](/docs/modules/)
|
||||
- **API contracts and samples:** [docs/api/](/docs/api/)
|
||||
- **Architecture notes / ADRs:** [docs/architecture/](/docs/architecture/), [docs/adr/](/docs/adr/)
|
||||
- **Operations and deployment:** [docs/operations/](/docs/operations/), [docs/deploy/](/docs/deploy/), [docs/deployment/](/docs/deployment/)
|
||||
- **Air-gap workflows:** [docs/airgap/](/docs/airgap/)
|
||||
- **Security deep dives:** [docs/security/](/docs/security/)
|
||||
- **Benchmarks and fixtures:** [docs/benchmarks/](/docs/benchmarks/), [docs/assets/](/docs/assets/)
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -64,13 +64,13 @@
|
||||
| 8 | DET-008 | DONE | DET-002, DET-003 | Guild | Refactor Registry module (1 file: RegistryTokenIssuer) |
|
||||
| 9 | DET-009 | DONE | DET-002, DET-003 | Guild | Refactor Replay module (6 files: ReplayEngine, ReplayModels, ReplayExportModels, ReplayManifestExporter, FeedSnapshotCoordinatorService, PolicySimulationInputLock) |
|
||||
| 10 | DET-010 | DONE | DET-002, DET-003 | Guild | Refactor RiskEngine module (skipped - no determinism issues found) |
|
||||
| 11 | DET-011 | TODO | DET-002, DET-003 | Guild | Refactor Scanner module (~45+ matches remaining) |
|
||||
| 11 | DET-011 | DOING | DET-002, DET-003 | Guild | Refactor Scanner module - Explainability (2 files: RiskReport, FalsifiabilityGenerator), Sources (5 files: ConnectionTesters, SourceConnectionTester, SourceTriggerDispatcher), VulnSurfaces (1 file: PostgresVulnSurfaceRepository), Storage (5 files: PostgresProofSpineRepository, PostgresScanMetricsRepository, RuntimeEventRepository, PostgresFuncProofRepository, PostgresIdempotencyKeyRepository), Storage.Oci (1 file: SlicePullService) |
|
||||
| 12 | DET-012 | DONE | DET-002, DET-003 | Guild | Refactor Scheduler module (WebService, Persistence, Worker projects - 30+ files updated, tests migrated to FakeTimeProvider) |
|
||||
| 13 | DET-013 | TODO | DET-002, DET-003 | Guild | Refactor Signer module (~89 matches remaining) |
|
||||
| 13 | DET-013 | DONE | DET-002, DET-003 | Guild | Refactor Signer module (16 production files refactored: AmbientOidcTokenProvider, EphemeralKeyPair, IOidcTokenProvider, IFulcioClient, TrustAnchorManager, KeyRotationService, DefaultSigningKeyResolver, SigstoreSigningService, InMemorySignerAuditSink, KeyRotationEndpoints, Program.cs) |
|
||||
| 14 | DET-014 | DONE | DET-002, DET-003 | Guild | Refactor Unknowns module (skipped - no determinism issues found) |
|
||||
| 15 | DET-015 | TODO | DET-002, DET-003 | Guild | Refactor VexLens module (~76 matches remaining) |
|
||||
| 15 | DET-015 | DONE | DET-002, DET-003 | Guild | Refactor VexLens module (production files: IConsensusRationaleCache, InMemorySourceTrustScoreCache, ISourceTrustScoreCalculator, InMemoryIssuerDirectory, InMemoryConsensusProjectionStore, OpenVexNormalizer, CycloneDxVexNormalizer, CsafVexNormalizer, IConsensusJobService, VexProofBuilder, IConsensusExportService, IVexLensApiService, TrustScorecardApiModels, OrchestratorLedgerEventEmitter, PostgresConsensusProjectionStore, PostgresConsensusProjectionStoreProxy, ProvenanceChainValidator, VexConsensusEngine, IConsensusRationaleService, VexLensEndpointExtensions) |
|
||||
| 16 | DET-016 | DONE | DET-002, DET-003 | Guild | Refactor VulnExplorer module (1 file: VexDecisionStore) |
|
||||
| 17 | DET-017 | TODO | DET-002, DET-003 | Guild | Refactor Zastava module (~48 matches remaining) |
|
||||
| 17 | DET-017 | DONE | DET-002, DET-003 | Guild | Refactor Zastava module (~48 matches remaining) |
|
||||
| 18 | DET-018 | TODO | DET-004 to DET-017 | Guild | Final audit: verify zero direct DateTime/Guid/Random calls in production code |
|
||||
|
||||
## Implementation Pattern
|
||||
@@ -123,6 +123,12 @@ services.AddSingleton<IGuidProvider, SystemGuidProvider>();
|
||||
| 2026-01-05 | DET-005: Provcache module refactored - 8 files (EvidenceChunker, LazyFetchOrchestrator, MinimalProofExporter, FeedEpochAdvancedEvent, SignerRevokedEvent, Postgres repos, ValkeyProvcacheStore) | Agent |
|
||||
| 2026-01-05 | DET-006 to DET-010: Batch completed - ReachGraph (1 file), Registry (1 file), Replay (6 files); Provenance, RiskEngine, Unknowns already clean | Agent |
|
||||
| 2026-01-05 | Remaining modules assessed: Scanner (~45), Scheduler (~20), Signer (~89), VexLens (~76), VulnExplorer (3), Zastava (~48) matches | Agent |
|
||||
| 2026-01-05 | DET-012 complete: Scheduler module refactored - WebService, Persistence, Worker projects (30+ files) | Agent |
|
||||
| 2026-01-05 | DET-013 complete: Signer module refactored - Keyless (4 files: AmbientOidcTokenProvider, EphemeralKeyPair, IOidcTokenProvider, IFulcioClient with IsExpiredAt/IsValidAt methods), KeyManagement (2 files: TrustAnchorManager, KeyRotationService), Infrastructure (3 files: DefaultSigningKeyResolver, SigstoreSigningService, InMemorySignerAuditSink), WebService (2 files: Program.cs, KeyRotationEndpoints) | Agent |
|
||||
|
||||
| 2026-01-05 | DET-015 complete: VexLens module refactored - 20 production files (caching, storage, normalization, orchestration, API, consensus, trust, persistence) with TimeProvider and IGuidProvider injection. Note: Pre-existing build errors in NoiseGateService.cs and NoiseGatingApiModels.cs unrelated to determinism changes. | Agent |
|
||||
| 2026-01-05 | DET-017 complete: Zastava module refactored - Agent (RuntimeEventsClient, HealthCheckHostedService, RuntimeEventDispatchService, RuntimeEventBuffer), Observer (RuntimeEventDispatchService, RuntimeEventBuffer, ProcSnapshotCollector, EbpfProbeManager), Webhook (WebhookCertificateHealthCheck) with TimeProvider and IGuidProvider injection. | Agent |
|
||||
| 2026-01-05 | DET-011 in progress: Scanner module refactoring - 14 production files refactored (RiskReport.cs, FalsifiabilityGenerator.cs, SourceConnectionTester.cs, SourceTriggerDispatcher.cs, DockerConnectionTester.cs, ZastavaConnectionTester.cs, GitConnectionTester.cs, PostgresVulnSurfaceRepository.cs, PostgresProofSpineRepository.cs, PostgresScanMetricsRepository.cs, RuntimeEventRepository.cs, PostgresFuncProofRepository.cs, PostgresIdempotencyKeyRepository.cs, SlicePullService.cs). Added Determinism.Abstractions references to 4 Scanner sub-projects. | Agent |
|
||||
|
||||
## Decisions & Risks
|
||||
- **Decision:** Defer determinism refactoring from MAINT audit to dedicated sprint for focused, systematic approach.
|
||||
|
||||
@@ -30,16 +30,16 @@ Integrate secret detection rule bundles with the Offline Kit infrastructure for
|
||||
|
||||
| # | Task ID | Status | Key dependency / next step | Owners | Task Definition |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 1 | OKS-001 | TODO | None | AirGap Guild | Update Offline Kit manifest schema for rules |
|
||||
| 2 | OKS-002 | TODO | OKS-001 | AirGap Guild | Add secrets bundle to BundleBuilder |
|
||||
| 3 | OKS-003 | TODO | OKS-002 | AirGap Guild | Create bundle verification in Importer |
|
||||
| 4 | OKS-004 | TODO | None | AirGap Guild | Add Attestor mirror support for bundle verification |
|
||||
| 5 | OKS-005 | TODO | OKS-003 | AirGap Guild | Create bundle installation script |
|
||||
| 6 | OKS-006 | TODO | OKS-005 | AirGap Guild | Add bundle rotation/upgrade workflow |
|
||||
| 7 | OKS-007 | TODO | None | CI/CD Guild | Add bundle to release workflow |
|
||||
| 8 | OKS-008 | TODO | All | AirGap Guild | Add integration tests for offline flow |
|
||||
| 9 | OKS-009 | TODO | All | Docs Guild | Update offline kit documentation |
|
||||
| 10 | OKS-010 | TODO | All | DevOps Guild | Update Helm charts for bundle mounting |
|
||||
| 1 | OKS-001 | DONE | None | AirGap Guild | Update Offline Kit manifest schema for rules |
|
||||
| 2 | OKS-002 | DONE | OKS-001 | AirGap Guild | Add secrets bundle to BundleBuilder |
|
||||
| 3 | OKS-003 | DONE | OKS-002 | AirGap Guild | Create bundle verification in Importer |
|
||||
| 4 | OKS-004 | DONE | None | AirGap Guild | Add Attestor mirror support for bundle verification |
|
||||
| 5 | OKS-005 | DONE | OKS-003 | AirGap Guild | Create bundle installation script |
|
||||
| 6 | OKS-006 | DONE | OKS-005 | AirGap Guild | Add bundle rotation/upgrade workflow |
|
||||
| 7 | OKS-007 | DONE | None | CI/CD Guild | Add bundle to release workflow |
|
||||
| 8 | OKS-008 | DONE | All | AirGap Guild | Add integration tests for offline flow |
|
||||
| 9 | OKS-009 | DONE | All | Docs Guild | Update offline kit documentation |
|
||||
| 10 | OKS-010 | DONE | All | DevOps Guild | Update Helm charts for bundle mounting |
|
||||
|
||||
## Task Details
|
||||
|
||||
@@ -588,4 +588,17 @@ devops/offline/
|
||||
| Date | Action | Notes |
|
||||
|------|--------|-------|
|
||||
| 2026-01-04 | Sprint created | Part of secret leak detection implementation |
|
||||
| 2026-01-04 | OKS-001 DONE | Added RuleBundleComponent to OfflineKitManifest.cs with rules schema |
|
||||
| 2026-01-04 | OKS-002 DONE | Extended SnapshotBundleWriter/Reader, added RulesSnapshotExtractor |
|
||||
| 2026-01-04 | OKS-003 DONE | Created RuleBundleValidator with digest/signature/monotonicity checks |
|
||||
| 2026-01-04 | OKS-004 DONE | Added RuleBundleSigningPath to FileSystemRootStore, DsseVerifier support |
|
||||
| 2026-01-04 | OKS-005 DONE | Created devops/offline/scripts/install-secrets-bundle.sh |
|
||||
| 2026-01-04 | OKS-006 DONE | Created devops/offline/scripts/rotate-secrets-bundle.sh with rollback |
|
||||
| 2026-01-04 | OKS-007 DONE | Created .gitea/workflows/secrets-bundle-release.yml CI/CD workflow |
|
||||
| 2026-01-04 | OKS-008 DONE | Added RuleBundleValidatorTests.cs with 8 test cases |
|
||||
| 2026-01-04 | OKS-009 DONE | Updated docs/24_OFFLINE_KIT.md with secrets bundle documentation |
|
||||
| 2026-01-04 | OKS-010 DONE | Updated values-airgap.yaml with secrets-rules volume mount and PVC |
|
||||
| 2026-01-04 | Fix build errors | Fixed 4 nullability errors in OfflineVerificationPolicy.cs, JsonNormalizer.cs, SbomNormalizer.cs |
|
||||
| 2026-01-04 | Fix test versions | Updated RuleBundleValidatorTests to use 3-part semver (2026.1.0) instead of CalVer |
|
||||
| 2026-01-04 | Sprint complete | All 10 tasks completed, build passes, tests pass (9/9) |
|
||||
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
# Sprint 20260104_006_BE - Secret Detection Configuration API
|
||||
|
||||
## Topic & Scope
|
||||
|
||||
Backend APIs and data models for configuring secret detection behavior per tenant. This sprint provides the foundation for UI configuration of secret leak detection.
|
||||
|
||||
**Key deliverables:**
|
||||
1. **Tenant Settings Model**: Per-tenant secret detection configuration
|
||||
2. **Revelation Policy**: Control how detected secrets are displayed/masked
|
||||
3. **Exception Management**: Allowlist patterns for false positives
|
||||
4. **Configuration API**: CRUD endpoints for settings
|
||||
|
||||
**Working directory:** `src/Scanner/`, `src/Platform/`
|
||||
|
||||
## Dependencies & Concurrency
|
||||
|
||||
- **Depends on**: Sprint 20260104_001 (Core Analyzer), Sprint 20260104_002 (Rule Bundles)
|
||||
- **Parallel with**: Sprint 20260104_007 (Alert Integration)
|
||||
- **Blocks**: Sprint 20260104_008 (UI)
|
||||
|
||||
## Documentation Prerequisites
|
||||
|
||||
- docs/modules/scanner/operations/secret-leak-detection.md
|
||||
- CLAUDE.md Section 8 (Determinism)
|
||||
|
||||
## Delivery Tracker
|
||||
|
||||
| # | Task ID | Status | Key dependency / next step | Owners | Task Definition |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 1 | SDC-001 | TODO | None | Scanner Guild | Define SecretDetectionSettings domain model |
|
||||
| 2 | SDC-002 | TODO | SDC-001 | Scanner Guild | Create SecretRevelationPolicy enum and config |
|
||||
| 3 | SDC-003 | TODO | SDC-001 | Scanner Guild | Create SecretExceptionPattern model for allowlists |
|
||||
| 4 | SDC-004 | TODO | SDC-001 | Platform Guild | Add persistence (EF Core migrations) |
|
||||
| 5 | SDC-005 | TODO | SDC-004 | Platform Guild | Create Settings CRUD API endpoints |
|
||||
| 6 | SDC-006 | TODO | SDC-005 | Platform Guild | Add OpenAPI spec for settings endpoints |
|
||||
| 7 | SDC-007 | TODO | SDC-003 | Scanner Guild | Integrate exception patterns into SecretsAnalyzerHost |
|
||||
| 8 | SDC-008 | TODO | SDC-002 | Scanner Guild | Implement revelation policy in findings output |
|
||||
| 9 | SDC-009 | TODO | All | Scanner Guild | Add unit and integration tests |
|
||||
|
||||
## Task Details
|
||||
|
||||
### SDC-001: SecretDetectionSettings Domain Model
|
||||
|
||||
```csharp
|
||||
public sealed record SecretDetectionSettings
|
||||
{
|
||||
public required Guid TenantId { get; init; }
|
||||
public required bool Enabled { get; init; }
|
||||
public required SecretRevelationPolicy RevelationPolicy { get; init; }
|
||||
public required IReadOnlyList<string> EnabledRuleCategories { get; init; }
|
||||
public required IReadOnlyList<SecretExceptionPattern> Exceptions { get; init; }
|
||||
public required SecretAlertSettings AlertSettings { get; init; }
|
||||
public required DateTimeOffset UpdatedAt { get; init; }
|
||||
public required string UpdatedBy { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
Location: `src/Scanner/__Libraries/StellaOps.Scanner.Core/Secrets/Configuration/`
|
||||
|
||||
### SDC-002: SecretRevelationPolicy
|
||||
|
||||
Control how detected secrets appear in different contexts:
|
||||
|
||||
```csharp
|
||||
public enum SecretRevelationPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Show only that a secret was detected, no value shown.
|
||||
/// Example: [SECRET_DETECTED: aws_access_key_id]
|
||||
/// </summary>
|
||||
FullMask = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Show first and last 4 characters.
|
||||
/// Example: AKIA****WXYZ
|
||||
/// </summary>
|
||||
PartialReveal = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Show full value (requires elevated permissions).
|
||||
/// Use only for debugging/incident response.
|
||||
/// </summary>
|
||||
FullReveal = 2
|
||||
}
|
||||
|
||||
public sealed record RevelationPolicyConfig
|
||||
{
|
||||
/// <summary>Default policy for UI/API responses.</summary>
|
||||
public SecretRevelationPolicy DefaultPolicy { get; init; } = SecretRevelationPolicy.PartialReveal;
|
||||
|
||||
/// <summary>Policy for exported reports (PDF, JSON).</summary>
|
||||
public SecretRevelationPolicy ExportPolicy { get; init; } = SecretRevelationPolicy.FullMask;
|
||||
|
||||
/// <summary>Policy for logs and telemetry.</summary>
|
||||
public SecretRevelationPolicy LogPolicy { get; init; } = SecretRevelationPolicy.FullMask;
|
||||
|
||||
/// <summary>Roles allowed to use FullReveal.</summary>
|
||||
public IReadOnlyList<string> FullRevealRoles { get; init; } = ["security-admin", "incident-responder"];
|
||||
|
||||
/// <summary>Number of characters to show at start/end for PartialReveal.</summary>
|
||||
public int PartialRevealChars { get; init; } = 4;
|
||||
}
|
||||
```
|
||||
|
||||
### SDC-003: SecretExceptionPattern (Allowlist)
|
||||
|
||||
```csharp
|
||||
public sealed record SecretExceptionPattern
|
||||
{
|
||||
public required Guid Id { get; init; }
|
||||
public required string Name { get; init; }
|
||||
public required string Description { get; init; }
|
||||
|
||||
/// <summary>Regex pattern to match against detected secret value.</summary>
|
||||
public required string Pattern { get; init; }
|
||||
|
||||
/// <summary>Optional: Only apply to specific rule IDs.</summary>
|
||||
public IReadOnlyList<string>? ApplicableRuleIds { get; init; }
|
||||
|
||||
/// <summary>Optional: Only apply to specific file paths.</summary>
|
||||
public string? FilePathGlob { get; init; }
|
||||
|
||||
/// <summary>Reason for exception (audit trail).</summary>
|
||||
public required string Justification { get; init; }
|
||||
|
||||
/// <summary>Expiration date (null = permanent).</summary>
|
||||
public DateTimeOffset? ExpiresAt { get; init; }
|
||||
|
||||
public required DateTimeOffset CreatedAt { get; init; }
|
||||
public required string CreatedBy { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
### SDC-005: Settings API Endpoints
|
||||
|
||||
```
|
||||
GET /api/v1/tenants/{tenantId}/settings/secret-detection
|
||||
PUT /api/v1/tenants/{tenantId}/settings/secret-detection
|
||||
PATCH /api/v1/tenants/{tenantId}/settings/secret-detection
|
||||
|
||||
GET /api/v1/tenants/{tenantId}/settings/secret-detection/exceptions
|
||||
POST /api/v1/tenants/{tenantId}/settings/secret-detection/exceptions
|
||||
DELETE /api/v1/tenants/{tenantId}/settings/secret-detection/exceptions/{exceptionId}
|
||||
|
||||
GET /api/v1/tenants/{tenantId}/settings/secret-detection/rule-categories
|
||||
```
|
||||
|
||||
### SDC-008: Revelation Policy Implementation
|
||||
|
||||
```csharp
|
||||
public static class SecretMasker
|
||||
{
|
||||
public static string Mask(string secretValue, SecretRevelationPolicy policy, int partialChars = 4)
|
||||
{
|
||||
return policy switch
|
||||
{
|
||||
SecretRevelationPolicy.FullMask => "[REDACTED]",
|
||||
SecretRevelationPolicy.PartialReveal => MaskPartial(secretValue, partialChars),
|
||||
SecretRevelationPolicy.FullReveal => secretValue,
|
||||
_ => "[REDACTED]"
|
||||
};
|
||||
}
|
||||
|
||||
private static string MaskPartial(string value, int chars)
|
||||
{
|
||||
if (value.Length <= chars * 2)
|
||||
return new string('*', value.Length);
|
||||
|
||||
var prefix = value[..chars];
|
||||
var suffix = value[^chars..];
|
||||
var masked = new string('*', Math.Min(value.Length - chars * 2, 8));
|
||||
return $"{prefix}{masked}{suffix}";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
src/Scanner/__Libraries/StellaOps.Scanner.Core/
|
||||
├── Secrets/
|
||||
│ ├── Configuration/
|
||||
│ │ ├── SecretDetectionSettings.cs
|
||||
│ │ ├── SecretRevelationPolicy.cs
|
||||
│ │ ├── RevelationPolicyConfig.cs
|
||||
│ │ ├── SecretExceptionPattern.cs
|
||||
│ │ └── SecretAlertSettings.cs
|
||||
│ └── Masking/
|
||||
│ └── SecretMasker.cs
|
||||
|
||||
src/Platform/StellaOps.Platform.WebService/
|
||||
├── Endpoints/
|
||||
│ └── SecretDetectionSettingsEndpoints.cs
|
||||
└── Persistence/
|
||||
└── Migrations/
|
||||
└── AddSecretDetectionSettings.cs
|
||||
```
|
||||
|
||||
## Decisions & Risks
|
||||
|
||||
| Decision | Rationale |
|
||||
|----------|-----------|
|
||||
| Per-tenant settings | Multi-tenant isolation requirement |
|
||||
| Role-based full reveal | Security: prevent accidental exposure |
|
||||
| Exception expiration | Force periodic review of allowlists |
|
||||
| Separate export/log policies | Defense in depth for sensitive data |
|
||||
|
||||
## Execution Log
|
||||
|
||||
| Date | Action | Notes |
|
||||
|------|--------|-------|
|
||||
| 2026-01-04 | Sprint created | Gap identified in secret detection feature |
|
||||
|
||||
290
docs/implplan/SPRINT_20260104_007_BE_secret_detection_alerts.md
Normal file
290
docs/implplan/SPRINT_20260104_007_BE_secret_detection_alerts.md
Normal file
@@ -0,0 +1,290 @@
|
||||
# Sprint 20260104_007_BE - Secret Detection Alert Integration
|
||||
|
||||
## Topic & Scope
|
||||
|
||||
Integration between secret detection findings and the Notify service for real-time alerting when secrets are discovered in scans.
|
||||
|
||||
**Key deliverables:**
|
||||
1. **Alert Routing**: Route secret findings to configured channels
|
||||
2. **Alert Templates**: Formatted notifications for different channels
|
||||
3. **Rate Limiting**: Prevent alert fatigue from mass findings
|
||||
4. **Severity Mapping**: Map rule severity to alert priority
|
||||
|
||||
**Working directory:** `src/Scanner/`, `src/Notify/`
|
||||
|
||||
## Dependencies & Concurrency
|
||||
|
||||
- **Depends on**: Sprint 20260104_001 (Core Analyzer), Sprint 20260104_006 (Config API)
|
||||
- **Parallel with**: Sprint 20260104_008 (UI)
|
||||
- **Blocks**: Production deployment with alerting
|
||||
|
||||
## Documentation Prerequisites
|
||||
|
||||
- docs/modules/notify/architecture.md
|
||||
- docs/modules/scanner/operations/secret-leak-detection.md
|
||||
|
||||
## Delivery Tracker
|
||||
|
||||
| # | Task ID | Status | Key dependency / next step | Owners | Task Definition |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 1 | SDA-001 | TODO | None | Scanner Guild | Define SecretAlertSettings model |
|
||||
| 2 | SDA-002 | TODO | SDA-001 | Scanner Guild | Create SecretFindingAlertEvent |
|
||||
| 3 | SDA-003 | TODO | SDA-002 | Notify Guild | Add secret-finding alert template |
|
||||
| 4 | SDA-004 | TODO | SDA-003 | Notify Guild | Implement Slack/Teams formatters |
|
||||
| 5 | SDA-005 | TODO | SDA-002 | Scanner Guild | Add alert emission to SecretsAnalyzerHost |
|
||||
| 6 | SDA-006 | TODO | SDA-005 | Scanner Guild | Implement rate limiting / deduplication |
|
||||
| 7 | SDA-007 | TODO | SDA-006 | Scanner Guild | Add severity-based routing |
|
||||
| 8 | SDA-008 | TODO | SDA-001 | Platform Guild | Add alert settings to config API |
|
||||
| 9 | SDA-009 | TODO | All | Scanner Guild | Add integration tests |
|
||||
|
||||
## Task Details
|
||||
|
||||
### SDA-001: SecretAlertSettings Model
|
||||
|
||||
```csharp
|
||||
public sealed record SecretAlertSettings
|
||||
{
|
||||
/// <summary>Enable/disable alerting for this tenant.</summary>
|
||||
public bool Enabled { get; init; } = true;
|
||||
|
||||
/// <summary>Minimum severity to trigger alert (Critical, High, Medium, Low).</summary>
|
||||
public SecretSeverity MinimumAlertSeverity { get; init; } = SecretSeverity.High;
|
||||
|
||||
/// <summary>Alert destinations by channel type.</summary>
|
||||
public IReadOnlyList<SecretAlertDestination> Destinations { get; init; } = [];
|
||||
|
||||
/// <summary>Rate limit: max alerts per scan.</summary>
|
||||
public int MaxAlertsPerScan { get; init; } = 10;
|
||||
|
||||
/// <summary>Deduplication window: don't re-alert same secret within this period.</summary>
|
||||
public TimeSpan DeduplicationWindow { get; init; } = TimeSpan.FromHours(24);
|
||||
|
||||
/// <summary>Include file path in alert (may reveal repo structure).</summary>
|
||||
public bool IncludeFilePath { get; init; } = true;
|
||||
|
||||
/// <summary>Include masked secret value in alert.</summary>
|
||||
public bool IncludeMaskedValue { get; init; } = true;
|
||||
}
|
||||
|
||||
public sealed record SecretAlertDestination
|
||||
{
|
||||
public required Guid Id { get; init; }
|
||||
public required AlertChannelType ChannelType { get; init; }
|
||||
public required string ChannelId { get; init; } // Slack channel ID, email, webhook URL
|
||||
public IReadOnlyList<SecretSeverity>? SeverityFilter { get; init; }
|
||||
public IReadOnlyList<string>? RuleCategoryFilter { get; init; }
|
||||
}
|
||||
|
||||
public enum AlertChannelType
|
||||
{
|
||||
Slack,
|
||||
Teams,
|
||||
Email,
|
||||
Webhook,
|
||||
PagerDuty
|
||||
}
|
||||
```
|
||||
|
||||
### SDA-002: SecretFindingAlertEvent
|
||||
|
||||
```csharp
|
||||
public sealed record SecretFindingAlertEvent
|
||||
{
|
||||
public required Guid EventId { get; init; }
|
||||
public required Guid TenantId { get; init; }
|
||||
public required Guid ScanId { get; init; }
|
||||
public required string ImageRef { get; init; }
|
||||
|
||||
public required SecretSeverity Severity { get; init; }
|
||||
public required string RuleId { get; init; }
|
||||
public required string RuleName { get; init; }
|
||||
public required string RuleCategory { get; init; }
|
||||
|
||||
public required string FilePath { get; init; }
|
||||
public required int LineNumber { get; init; }
|
||||
public required string MaskedValue { get; init; }
|
||||
|
||||
public required DateTimeOffset DetectedAt { get; init; }
|
||||
public required string ScanTriggeredBy { get; init; }
|
||||
|
||||
/// <summary>Deduplication key for rate limiting.</summary>
|
||||
public string DeduplicationKey => $"{TenantId}:{RuleId}:{FilePath}:{LineNumber}";
|
||||
}
|
||||
```
|
||||
|
||||
### SDA-003: Alert Templates
|
||||
|
||||
**Slack Template:**
|
||||
```json
|
||||
{
|
||||
"blocks": [
|
||||
{
|
||||
"type": "header",
|
||||
"text": {
|
||||
"type": "plain_text",
|
||||
"text": "🚨 Secret Detected in Container Scan"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "section",
|
||||
"fields": [
|
||||
{ "type": "mrkdwn", "text": "*Severity:*\n{{severity}}" },
|
||||
{ "type": "mrkdwn", "text": "*Rule:*\n{{ruleName}}" },
|
||||
{ "type": "mrkdwn", "text": "*Image:*\n`{{imageRef}}`" },
|
||||
{ "type": "mrkdwn", "text": "*File:*\n`{{filePath}}:{{lineNumber}}`" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "*Detected Value:*\n```{{maskedValue}}```"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "actions",
|
||||
"elements": [
|
||||
{
|
||||
"type": "button",
|
||||
"text": { "type": "plain_text", "text": "View in StellaOps" },
|
||||
"url": "{{findingUrl}}"
|
||||
},
|
||||
{
|
||||
"type": "button",
|
||||
"text": { "type": "plain_text", "text": "Add Exception" },
|
||||
"url": "{{exceptionUrl}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### SDA-005: Alert Emission in SecretsAnalyzerHost
|
||||
|
||||
```csharp
|
||||
public sealed class SecretsAnalyzerHost
|
||||
{
|
||||
private readonly ISecretAlertEmitter _alertEmitter;
|
||||
private readonly ISecretAlertDeduplicator _deduplicator;
|
||||
|
||||
public async Task OnSecretFoundAsync(
|
||||
SecretFinding finding,
|
||||
ScanContext context,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var settings = await _settingsProvider.GetAlertSettingsAsync(context.TenantId, ct);
|
||||
|
||||
if (!settings.Enabled)
|
||||
return;
|
||||
|
||||
if (finding.Severity < settings.MinimumAlertSeverity)
|
||||
return;
|
||||
|
||||
var alertEvent = MapToAlertEvent(finding, context);
|
||||
|
||||
// Check deduplication
|
||||
if (await _deduplicator.IsDuplicateAsync(alertEvent, settings.DeduplicationWindow, ct))
|
||||
{
|
||||
_logger.LogDebug("secret.alert.deduplicated key={key}", alertEvent.DeduplicationKey);
|
||||
return;
|
||||
}
|
||||
|
||||
// Rate limiting
|
||||
var alertCount = await _alertEmitter.GetAlertCountForScanAsync(context.ScanId, ct);
|
||||
if (alertCount >= settings.MaxAlertsPerScan)
|
||||
{
|
||||
_logger.LogWarning("secret.alert.rate_limited scan_id={scan_id} count={count}",
|
||||
context.ScanId, alertCount);
|
||||
return;
|
||||
}
|
||||
|
||||
// Emit to configured destinations
|
||||
await _alertEmitter.EmitAsync(alertEvent, settings.Destinations, ct);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### SDA-006: Rate Limiting & Deduplication
|
||||
|
||||
```csharp
|
||||
public interface ISecretAlertDeduplicator
|
||||
{
|
||||
Task<bool> IsDuplicateAsync(
|
||||
SecretFindingAlertEvent alert,
|
||||
TimeSpan window,
|
||||
CancellationToken ct);
|
||||
|
||||
Task RecordAlertAsync(
|
||||
SecretFindingAlertEvent alert,
|
||||
CancellationToken ct);
|
||||
}
|
||||
|
||||
public sealed class ValkeySecretAlertDeduplicator : ISecretAlertDeduplicator
|
||||
{
|
||||
private readonly IValkeyConnection _valkey;
|
||||
|
||||
public async Task<bool> IsDuplicateAsync(
|
||||
SecretFindingAlertEvent alert,
|
||||
TimeSpan window,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var key = $"secret:alert:dedup:{alert.DeduplicationKey}";
|
||||
var exists = await _valkey.ExistsAsync(key);
|
||||
return exists;
|
||||
}
|
||||
|
||||
public async Task RecordAlertAsync(
|
||||
SecretFindingAlertEvent alert,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var key = $"secret:alert:dedup:{alert.DeduplicationKey}";
|
||||
await _valkey.SetAsync(key, alert.EventId.ToString(), expiry: TimeSpan.FromHours(24));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Severity Mapping
|
||||
|
||||
| Rule Severity | Alert Priority | Default Behavior |
|
||||
|---------------|----------------|------------------|
|
||||
| Critical | P1 / Immediate | Always alert, page on-call |
|
||||
| High | P2 / Urgent | Alert to security channel |
|
||||
| Medium | P3 / Normal | Alert if configured |
|
||||
| Low | P4 / Info | No alert by default |
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
src/Scanner/__Libraries/StellaOps.Scanner.Core/
|
||||
├── Secrets/
|
||||
│ ├── Alerts/
|
||||
│ │ ├── SecretAlertSettings.cs
|
||||
│ │ ├── SecretFindingAlertEvent.cs
|
||||
│ │ ├── ISecretAlertEmitter.cs
|
||||
│ │ ├── ISecretAlertDeduplicator.cs
|
||||
│ │ └── ValkeySecretAlertDeduplicator.cs
|
||||
|
||||
src/Notify/__Libraries/StellaOps.Notify.Engine/
|
||||
├── Templates/
|
||||
│ └── SecretFindingAlertTemplate.cs
|
||||
├── Formatters/
|
||||
│ ├── SlackSecretAlertFormatter.cs
|
||||
│ └── TeamsSecretAlertFormatter.cs
|
||||
```
|
||||
|
||||
## Decisions & Risks
|
||||
|
||||
| Decision | Rationale |
|
||||
|----------|-----------|
|
||||
| Valkey for deduplication | Fast, distributed, TTL support |
|
||||
| Per-scan rate limit | Prevent alert storms on large findings |
|
||||
| Masked values in alerts | Balance security awareness vs exposure |
|
||||
| Severity-based routing | Different channels for different priorities |
|
||||
|
||||
## Execution Log
|
||||
|
||||
| Date | Action | Notes |
|
||||
|------|--------|-------|
|
||||
| 2026-01-04 | Sprint created | Alert integration for secret detection |
|
||||
|
||||
499
docs/implplan/SPRINT_20260104_008_FE_secret_detection_ui.md
Normal file
499
docs/implplan/SPRINT_20260104_008_FE_secret_detection_ui.md
Normal file
@@ -0,0 +1,499 @@
|
||||
# Sprint 20260104_008_FE - Secret Detection UI
|
||||
|
||||
## Topic & Scope
|
||||
|
||||
Frontend components for configuring and viewing secret detection findings. Provides tenant administrators with tools to manage detection settings, view findings, and configure alerts.
|
||||
|
||||
**Key deliverables:**
|
||||
1. **Settings Page**: Configure secret detection for tenant
|
||||
2. **Findings Viewer**: View detected secrets with proper masking
|
||||
3. **Exception Manager**: Add/remove allowlist patterns
|
||||
4. **Alert Configuration**: Set up notification channels
|
||||
|
||||
**Working directory:** `src/Web/StellaOps.Web/`
|
||||
|
||||
## Dependencies & Concurrency
|
||||
|
||||
- **Depends on**: Sprint 20260104_006 (Config API), Sprint 20260104_007 (Alerts)
|
||||
- **Parallel with**: None (final UI sprint)
|
||||
- **Blocks**: Feature release
|
||||
|
||||
## Documentation Prerequisites
|
||||
|
||||
- docs/modules/web/architecture.md
|
||||
- Angular v17 component patterns
|
||||
|
||||
## Delivery Tracker
|
||||
|
||||
| # | Task ID | Status | Key dependency / next step | Owners | Task Definition |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 1 | SDU-001 | TODO | None | Frontend Guild | Create secret-detection feature module |
|
||||
| 2 | SDU-002 | TODO | SDU-001 | Frontend Guild | Build settings page component |
|
||||
| 3 | SDU-003 | TODO | SDU-002 | Frontend Guild | Add revelation policy selector |
|
||||
| 4 | SDU-004 | TODO | SDU-002 | Frontend Guild | Build rule category toggles |
|
||||
| 5 | SDU-005 | TODO | SDU-001 | Frontend Guild | Create findings list component |
|
||||
| 6 | SDU-006 | TODO | SDU-005 | Frontend Guild | Implement masked value display |
|
||||
| 7 | SDU-007 | TODO | SDU-005 | Frontend Guild | Add finding detail drawer |
|
||||
| 8 | SDU-008 | TODO | SDU-001 | Frontend Guild | Build exception manager component |
|
||||
| 9 | SDU-009 | TODO | SDU-008 | Frontend Guild | Create exception form with validation |
|
||||
| 10 | SDU-010 | TODO | SDU-001 | Frontend Guild | Build alert destination config |
|
||||
| 11 | SDU-011 | TODO | SDU-010 | Frontend Guild | Add channel test functionality |
|
||||
| 12 | SDU-012 | TODO | All | Frontend Guild | Add E2E tests |
|
||||
|
||||
## Task Details
|
||||
|
||||
### SDU-002: Settings Page Component
|
||||
|
||||
```typescript
|
||||
// secret-detection-settings.component.ts
|
||||
@Component({
|
||||
selector: 'app-secret-detection-settings',
|
||||
template: `
|
||||
<div class="settings-container">
|
||||
<header class="settings-header">
|
||||
<h1>Secret Detection</h1>
|
||||
<mat-slide-toggle
|
||||
[checked]="settings()?.enabled"
|
||||
(change)="onEnabledChange($event)"
|
||||
color="primary">
|
||||
{{ settings()?.enabled ? 'Enabled' : 'Disabled' }}
|
||||
</mat-slide-toggle>
|
||||
</header>
|
||||
|
||||
<mat-tab-group>
|
||||
<mat-tab label="General">
|
||||
<app-revelation-policy-config
|
||||
[policy]="settings()?.revelationPolicy"
|
||||
(policyChange)="onPolicyChange($event)" />
|
||||
|
||||
<app-rule-category-selector
|
||||
[categories]="availableCategories()"
|
||||
[selected]="settings()?.enabledRuleCategories"
|
||||
(selectionChange)="onCategoriesChange($event)" />
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab label="Exceptions">
|
||||
<app-exception-manager
|
||||
[exceptions]="settings()?.exceptions"
|
||||
(add)="onAddException($event)"
|
||||
(remove)="onRemoveException($event)" />
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab label="Alerts">
|
||||
<app-alert-destination-config
|
||||
[settings]="settings()?.alertSettings"
|
||||
(settingsChange)="onAlertSettingsChange($event)" />
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class SecretDetectionSettingsComponent {
|
||||
private settingsService = inject(SecretDetectionSettingsService);
|
||||
|
||||
settings = this.settingsService.settings;
|
||||
availableCategories = this.settingsService.availableCategories;
|
||||
|
||||
// ... handlers
|
||||
}
|
||||
```
|
||||
|
||||
### SDU-003: Revelation Policy Selector
|
||||
|
||||
```typescript
|
||||
// revelation-policy-config.component.ts
|
||||
@Component({
|
||||
selector: 'app-revelation-policy-config',
|
||||
template: `
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>Secret Revelation Policy</mat-card-title>
|
||||
<mat-card-subtitle>
|
||||
Control how detected secrets are displayed
|
||||
</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content>
|
||||
<mat-radio-group
|
||||
[value]="policy()?.defaultPolicy"
|
||||
(change)="onDefaultPolicyChange($event)">
|
||||
|
||||
<mat-radio-button value="FullMask">
|
||||
<div class="policy-option">
|
||||
<strong>Full Mask</strong>
|
||||
<span class="example">[REDACTED]</span>
|
||||
<p>No secret value shown. Safest option.</p>
|
||||
</div>
|
||||
</mat-radio-button>
|
||||
|
||||
<mat-radio-button value="PartialReveal">
|
||||
<div class="policy-option">
|
||||
<strong>Partial Reveal</strong>
|
||||
<span class="example">AKIA****WXYZ</span>
|
||||
<p>Show first/last 4 characters. Helps identify specific secrets.</p>
|
||||
</div>
|
||||
</mat-radio-button>
|
||||
|
||||
<mat-radio-button value="FullReveal" [disabled]="!canFullReveal()">
|
||||
<div class="policy-option">
|
||||
<strong>Full Reveal</strong>
|
||||
<span class="example">AKIAIOSFODNN7EXAMPLE</span>
|
||||
<p>Show complete value. Requires security-admin role.</p>
|
||||
</div>
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
|
||||
<mat-divider />
|
||||
|
||||
<h4>Context-Specific Policies</h4>
|
||||
<div class="context-policies">
|
||||
<mat-form-field>
|
||||
<mat-label>Export Reports</mat-label>
|
||||
<mat-select [value]="policy()?.exportPolicy">
|
||||
<mat-option value="FullMask">Full Mask</mat-option>
|
||||
<mat-option value="PartialReveal">Partial Reveal</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-label>Logs & Telemetry</mat-label>
|
||||
<mat-select [value]="policy()?.logPolicy" disabled>
|
||||
<mat-option value="FullMask">Full Mask (Enforced)</mat-option>
|
||||
</mat-select>
|
||||
<mat-hint>Secrets are never logged in full</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
`
|
||||
})
|
||||
```
|
||||
|
||||
### SDU-005: Findings List Component
|
||||
|
||||
```typescript
|
||||
// secret-findings-list.component.ts
|
||||
@Component({
|
||||
selector: 'app-secret-findings-list',
|
||||
template: `
|
||||
<div class="findings-container">
|
||||
<header class="findings-header">
|
||||
<h2>Secret Findings</h2>
|
||||
<div class="filters">
|
||||
<mat-form-field>
|
||||
<mat-label>Severity</mat-label>
|
||||
<mat-select multiple [(value)]="severityFilter">
|
||||
<mat-option value="Critical">Critical</mat-option>
|
||||
<mat-option value="High">High</mat-option>
|
||||
<mat-option value="Medium">Medium</mat-option>
|
||||
<mat-option value="Low">Low</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-label>Status</mat-label>
|
||||
<mat-select [(value)]="statusFilter">
|
||||
<mat-option value="Open">Open</mat-option>
|
||||
<mat-option value="Dismissed">Dismissed</mat-option>
|
||||
<mat-option value="Excepted">Excepted</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<table mat-table [dataSource]="findings()">
|
||||
<ng-container matColumnDef="severity">
|
||||
<th mat-header-cell *matHeaderCellDef>Severity</th>
|
||||
<td mat-cell *matCellDef="let finding">
|
||||
<app-severity-badge [severity]="finding.severity" />
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="rule">
|
||||
<th mat-header-cell *matHeaderCellDef>Rule</th>
|
||||
<td mat-cell *matCellDef="let finding">
|
||||
<div class="rule-info">
|
||||
<span class="rule-name">{{ finding.ruleName }}</span>
|
||||
<span class="rule-category">{{ finding.ruleCategory }}</span>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="location">
|
||||
<th mat-header-cell *matHeaderCellDef>Location</th>
|
||||
<td mat-cell *matCellDef="let finding">
|
||||
<code class="file-path">{{ finding.filePath }}:{{ finding.lineNumber }}</code>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="value">
|
||||
<th mat-header-cell *matHeaderCellDef>Detected Value</th>
|
||||
<td mat-cell *matCellDef="let finding">
|
||||
<app-masked-secret-value
|
||||
[value]="finding.value"
|
||||
[policy]="revelationPolicy()"
|
||||
[canReveal]="canRevealSecrets()" />
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell *matCellDef="let finding">
|
||||
<button mat-icon-button [matMenuTriggerFor]="actionMenu">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
<mat-menu #actionMenu>
|
||||
<button mat-menu-item (click)="viewDetails(finding)">
|
||||
<mat-icon>visibility</mat-icon>
|
||||
View Details
|
||||
</button>
|
||||
<button mat-menu-item (click)="dismiss(finding)">
|
||||
<mat-icon>cancel</mat-icon>
|
||||
Dismiss
|
||||
</button>
|
||||
<button mat-menu-item (click)="addException(finding)">
|
||||
<mat-icon>playlist_add</mat-icon>
|
||||
Add Exception
|
||||
</button>
|
||||
</mat-menu>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"
|
||||
(click)="viewDetails(row)"></tr>
|
||||
</table>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
```
|
||||
|
||||
### SDU-006: Masked Value Display
|
||||
|
||||
```typescript
|
||||
// masked-secret-value.component.ts
|
||||
@Component({
|
||||
selector: 'app-masked-secret-value',
|
||||
template: `
|
||||
<div class="masked-value" [class.revealed]="isRevealed()">
|
||||
<code>{{ displayValue() }}</code>
|
||||
|
||||
@if (canReveal() && !isRevealed()) {
|
||||
<button
|
||||
mat-icon-button
|
||||
matTooltip="Reveal value (logged)"
|
||||
(click)="reveal()">
|
||||
<mat-icon>visibility</mat-icon>
|
||||
</button>
|
||||
}
|
||||
|
||||
@if (isRevealed()) {
|
||||
<button
|
||||
mat-icon-button
|
||||
matTooltip="Hide value"
|
||||
(click)="hide()">
|
||||
<mat-icon>visibility_off</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
mat-icon-button
|
||||
matTooltip="Copy to clipboard"
|
||||
(click)="copy()">
|
||||
<mat-icon>content_copy</mat-icon>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
.masked-value {
|
||||
font-family: monospace;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.revealed code {
|
||||
background: #fff3cd;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class MaskedSecretValueComponent {
|
||||
value = input.required<string>();
|
||||
policy = input.required<SecretRevelationPolicy>();
|
||||
canReveal = input<boolean>(false);
|
||||
|
||||
private revealed = signal(false);
|
||||
isRevealed = computed(() => this.revealed() && this.canReveal());
|
||||
|
||||
displayValue = computed(() => {
|
||||
if (this.isRevealed()) {
|
||||
return this.value();
|
||||
}
|
||||
return this.maskValue(this.value(), this.policy());
|
||||
});
|
||||
|
||||
reveal() {
|
||||
// Log reveal action for audit
|
||||
this.auditService.logSecretReveal(this.value());
|
||||
this.revealed.set(true);
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.revealed.set(false);
|
||||
}
|
||||
|
||||
private maskValue(value: string, policy: SecretRevelationPolicy): string {
|
||||
switch (policy) {
|
||||
case 'FullMask':
|
||||
return '[REDACTED]';
|
||||
case 'PartialReveal':
|
||||
if (value.length <= 8) return '*'.repeat(value.length);
|
||||
return `${value.slice(0, 4)}${'*'.repeat(Math.min(8, value.length - 8))}${value.slice(-4)}`;
|
||||
default:
|
||||
return '[REDACTED]';
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### SDU-010: Alert Destination Configuration
|
||||
|
||||
```typescript
|
||||
// alert-destination-config.component.ts
|
||||
@Component({
|
||||
selector: 'app-alert-destination-config',
|
||||
template: `
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>Alert Destinations</mat-card-title>
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content>
|
||||
<div class="alert-settings">
|
||||
<mat-slide-toggle [(ngModel)]="settings().enabled">
|
||||
Enable Alerts
|
||||
</mat-slide-toggle>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-label>Minimum Severity</mat-label>
|
||||
<mat-select [(value)]="settings().minimumAlertSeverity">
|
||||
<mat-option value="Critical">Critical only</mat-option>
|
||||
<mat-option value="High">High and above</mat-option>
|
||||
<mat-option value="Medium">Medium and above</mat-option>
|
||||
<mat-option value="Low">All findings</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<mat-divider />
|
||||
|
||||
<h4>Configured Channels</h4>
|
||||
<div class="destinations-list">
|
||||
@for (dest of settings().destinations; track dest.id) {
|
||||
<mat-card class="destination-card">
|
||||
<div class="destination-info">
|
||||
<mat-icon>{{ getChannelIcon(dest.channelType) }}</mat-icon>
|
||||
<span>{{ dest.channelType }}</span>
|
||||
<code>{{ dest.channelId }}</code>
|
||||
</div>
|
||||
<div class="destination-actions">
|
||||
<button mat-icon-button (click)="testChannel(dest)">
|
||||
<mat-icon>send</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="warn" (click)="removeDestination(dest)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</mat-card>
|
||||
}
|
||||
</div>
|
||||
|
||||
<button mat-stroked-button (click)="addDestination()">
|
||||
<mat-icon>add</mat-icon>
|
||||
Add Destination
|
||||
</button>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
`
|
||||
})
|
||||
```
|
||||
|
||||
## UI Mockups
|
||||
|
||||
### Settings Page Layout
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Secret Detection [Enabled ●] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ [General] [Exceptions] [Alerts] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─ Revelation Policy ─────────────────────────────────┐ │
|
||||
│ │ ○ Full Mask [REDACTED] │ │
|
||||
│ │ ● Partial Reveal AKIA****WXYZ │ │
|
||||
│ │ ○ Full Reveal (requires security-admin) │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─ Rule Categories ───────────────────────────────────┐ │
|
||||
│ │ ☑ AWS Credentials ☑ GCP Service Accounts │ │
|
||||
│ │ ☑ Generic API Keys ☑ Private Keys │ │
|
||||
│ │ ☐ Internal Tokens ☑ Database Credentials │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Findings List
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Secret Findings │
|
||||
│ Severity: [All ▼] Status: [Open ▼] Image: [All ▼] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ SEV │ RULE │ LOCATION │ VALUE │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 🔴 │ AWS Access Key │ config.yaml:42 │ AKIA****XYZ │
|
||||
│ 🟠 │ Generic API Key │ .env:15 │ sk_l****abc │
|
||||
│ 🟡 │ Private Key │ certs/server.key │ [REDACTED] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
src/Web/StellaOps.Web/src/app/
|
||||
├── features/
|
||||
│ └── secret-detection/
|
||||
│ ├── secret-detection.module.ts
|
||||
│ ├── secret-detection.routes.ts
|
||||
│ ├── pages/
|
||||
│ │ ├── settings/
|
||||
│ │ │ └── secret-detection-settings.component.ts
|
||||
│ │ └── findings/
|
||||
│ │ └── secret-findings-list.component.ts
|
||||
│ ├── components/
|
||||
│ │ ├── revelation-policy-config/
|
||||
│ │ ├── rule-category-selector/
|
||||
│ │ ├── exception-manager/
|
||||
│ │ ├── alert-destination-config/
|
||||
│ │ ├── masked-secret-value/
|
||||
│ │ └── finding-detail-drawer/
|
||||
│ └── services/
|
||||
│ ├── secret-detection-settings.service.ts
|
||||
│ └── secret-findings.service.ts
|
||||
```
|
||||
|
||||
## Decisions & Risks
|
||||
|
||||
| Decision | Rationale |
|
||||
|----------|-----------|
|
||||
| Angular Material | Consistent with existing UI |
|
||||
| Signal-based state | Modern Angular patterns |
|
||||
| Audit logging on reveal | Compliance requirement |
|
||||
| Lazy-loaded module | Performance optimization |
|
||||
|
||||
## Execution Log
|
||||
|
||||
| Date | Action | Notes |
|
||||
|------|--------|-------|
|
||||
| 2026-01-04 | Sprint created | UI components for secret detection |
|
||||
|
||||
Reference in New Issue
Block a user