Add OpenSslLegacyShim to ensure OpenSSL 1.1 libraries are accessible on Linux
Some checks are pending
Docs CI / lint-and-preview (push) Waiting to run

This commit introduces the OpenSslLegacyShim class, which sets the LD_LIBRARY_PATH environment variable to include the directory containing OpenSSL 1.1 native libraries. This is necessary for Mongo2Go to function correctly on Linux platforms that do not ship these libraries by default. The shim checks if the current operating system is Linux and whether the required directory exists before modifying the environment variable.
This commit is contained in:
master
2025-11-02 21:41:03 +02:00
parent f98cea3bcf
commit 1d962ee6fc
71 changed files with 3675 additions and 1255 deletions

View File

@@ -368,7 +368,7 @@ Compliance checklist:
| --- | --- | --- |
| `Orch.Viewer` role | `orch:read` | Read-only access to Orchestrator dashboards, queues, and telemetry. |
| `Orch.Operator` role | `orch:read`, `orch:operate` | Issue short-lived tokens for control actions (pause/resume, retry, sync). Token requests **must** include `operator_reason` (≤256 chars) and `operator_ticket` (≤128 chars); Authority rejects requests missing either value and records both in audit events. |
| `Orch.Admin` role | `orch:read`, `orch:operate`, `orch:quota` | Manage tenant quotas/burst ceilings/backfill allowances. Tokens **must** include `quota_reason` (≤256 chars); optional `quota_ticket` (≤128 chars) is stored for audit trails. |
| `Orch.Admin` role | `orch:read`, `orch:operate`, `orch:quota`, `orch:backfill` | Manage tenant quotas, burst ceilings, and historical backfill allowances. Quota tokens **must** include `quota_reason` (≤256 chars) and may include `quota_ticket` (≤128 chars); backfill tokens **must** include both `backfill_reason` (≤256 chars) and `backfill_ticket` (≤128 chars). Authority records all values in audit trails. |
Token request example via client credentials:
@@ -397,6 +397,19 @@ curl -u orch-admin:s3cr3t! \
CLI automation should supply these values via `Authority.QuotaReason` / `Authority.QuotaTicket` (environment variables `STELLAOPS_ORCH_QUOTA_REASON` and `STELLAOPS_ORCH_QUOTA_TICKET`). Missing `quota_reason` yields `invalid_request`; when provided, both reason and ticket are captured in audit properties (`quota.reason`, `quota.ticket`).
Backfill run tokens extend the same pattern:
```bash
curl -u orch-admin:s3cr3t! \
-d 'grant_type=client_credentials' \
-d 'scope=orch:backfill' \
-d 'backfill_reason=rebuild historical findings for tenant-default' \
-d 'backfill_ticket=INC-9905' \
https://authority.example.com/token
```
CLI clients configure these values via `Authority.BackfillReason` / `Authority.BackfillTicket` (environment variables `STELLAOPS_ORCH_BACKFILL_REASON` and `STELLAOPS_ORCH_BACKFILL_TICKET`). Tokens missing either field are rejected with `invalid_request`; audit events store the supplied values as `backfill.reason` and `backfill.ticket`.
## 8. Offline & Sovereign Operation
- **No outbound dependencies:** Authority only contacts MongoDB and local plugins. Discovery and JWKS are cached by clients with offline tolerances (`AllowOfflineCacheFallback`, `OfflineCacheTolerance`). Operators should mirror these responses for air-gapped use.
- **Structured logging:** Every revocation export, signing rotation, bootstrap action, and token issuance emits structured logs with `traceId`, `client_id`, `subjectId`, and `network.remoteIp` where applicable. Mirror logs to your SIEM to retain audit trails without central connectivity.

View File

@@ -20,6 +20,11 @@
| DOCS-RUNBOOK-55-001 | TODO | Docs Guild, Ops Guild | DEVOPS-OBS-55-001, WEB-OBS-55-001 | Author `/docs/runbooks/incidents.md` describing incident mode activation, escalation steps, retention impact, verification checklist, and imposed rule banner. | Doc merged; runbook rehearsed; banner included; linked from alerts. |
| DOCS-SURFACE-01 | TODO | Docs Guild, Scanner Guild, Zastava Guild | SURFACE-FS-01, SURFACE-ENV-01, SURFACE-SECRETS-01 | Create `/docs/modules/scanner/scanner-engine.md` covering Surface.FS/Env/Secrets workflow between Scanner, Zastava, Scheduler, and Ops. | Guide merged; linked from scanner/zastava architecture dossiers and component map; diagrams committed. |
## Scanner Comparisons (Epic 62)
| ID | Status | Owner(s) | Depends on | Description | Exit Criteria |
|----|--------|----------|------------|-------------|---------------|
| DOCS-SCANNER-BENCH-62-001 | DONE (2025-11-02) | Docs Guild, Scanner Guild | — | Maintain scanner comparison docs for Trivy, Grype, and Snyk; refresh deep dives and ecosystem matrix with source-linked implementation notes. | Comparison docs updated; matrix synced; deep dives cite source paths and highlight coverage gaps. |
## Air-Gapped Mode (Epic 16)
| ID | Status | Owner(s) | Depends on | Description | Exit Criteria |
|----|--------|----------|------------|-------------|---------------|

View File

@@ -0,0 +1,63 @@
# Scanner Feature Comparison — StellaOps vs Grype
_Reference snapshot: Grype commit `6e746a546ecca3e2456316551673357e4a166d77` cloned 2025-11-02._
## TL;DR
- StellaOps runs as a multi-service platform with deterministic SBOM generation, attestation (DSSE + Rekor), and tenant-aware controls, whereas Grype is a single Go CLI that leans on Syft to build SBOMs before vulnerability matching.[1](#sources)[g1](#grype-sources)
- Grype covers a broad OS and language matrix via Syft catalogers and Anchores aggregated vulnerability database, but it lacks attestation, runtime usage context, and secret management features found in StellaOps Surface/Policy ecosystem.[1](#sources)[g2](#grype-sources)[g3](#grype-sources)
- Opportunity: position StellaOps strengths (evidence provenance, policy orchestration, offline kits) while tracking high-demand ecosystems that Grype already supports (e.g., Ruby, PHP) and could inform StellaOps backlog priorities.
## Comparison Matrix
| Dimension | StellaOps Scanner | Grype |
| --- | --- | --- |
| Architecture & deployment | WebService + Worker services, queue backbones, RustFS/S3 artifact store, Mongo catalog, Authority-issued OpToks, Surface libraries, restart-only analyzers.[1](#sources)[3](#sources)[4](#sources)[5](#sources) | Go CLI that invokes Syft to construct an SBOM from images/filesystems and feeds Syfts packages into Anchore matchers; optional SBOM ingest via `syft`/`sbom` inputs.[g1](#grype-sources) |
| Scan targets & coverage | Container images & filesystem captures; analyzers for APK/DPKG/RPM, Java/Node/Python/Go/.NET/Rust, native ELF, EntryTrace usage graph (PE/Mach-O roadmap).[1](#sources) | Images, directories, archives, and SBOMs; OS feeds include Alpine, Ubuntu, RHEL, SUSE, Wolfi, etc., and language support spans Ruby, Java, JavaScript, Python, .NET, Go, PHP, Rust.[g2](#grype-sources) |
| Evidence & outputs | CycloneDX JSON/Protobuf, SPDX 3.0.1, deterministic diffs, BOM-index sidecar, explain traces, DSSE-ready report metadata.[1](#sources)[2](#sources) | Outputs table, JSON, CycloneDX (XML/JSON), SARIF, and templated formats; evidence tied to Syft SBOM and JSON report (no deterministic replay artifacts).[g4](#grype-sources) |
| Attestation & supply chain | DSSE signing via Signer → Attestor → Rekor v2, OpenVEX-first modelling, policy overlays, provenance digests.[1](#sources) | Supports ingesting OpenVEX for filtering but ships no signing/attestation workflow; relies on external tooling for provenance.[g2](#grype-sources) |
| Policy & decisioning | Central Policy Engine (stella-dsl), VEX-first decisioning, lattice logic, API streaming of policy previews, fail-fast validation pipelines.[1](#sources)[7](#sources) | CLI gating via `--fail-on`, ignore rules, and allow/deny lists; no multi-tenant policy service.[g4](#grype-sources) |
| Offline & air-gap | Offline kits bundle artifacts, manifests, secrets; Surface.Env/Validation enforce prerequisites; RustFS supports air-gapped object storage.[3](#sources)[4](#sources)[6](#sources) | Local SQLite vulnerability DB auto-managed; supports offline by disabling auto-update and importing Anchore DB archives manually.[g3](#grype-sources) |
| Caching & performance | Layer CAS caches, queue leasing, EntryTrace reuse, deterministic sorting for replay.[1](#sources)[4](#sources) | SBOM caching handled by Syft; vulnerability DB stored under `$XDG_CACHE_HOME`; no layer caches or queue orchestration.[g3](#grype-sources) |
| Security & tenancy | OpTok enforcement (DPoP/mTLS), tenant-aware storage prefixes, Surface.Secrets providers, validation pipeline for misconfiguration, DSSE for tamper evidence.[1](#sources)[5](#sources)[6](#sources) | Single-user CLI; registry credentials handled via config secrets; no tenant isolation or secret provider abstraction.[g3](#grype-sources) |
| Extensibility & ecosystem | Analyzer plug-ins, BuildX SBOM generator, CLI/Worker integration, Surface libraries, attested exports.[1](#sources)[2](#sources) | Template-based reporters, external Maven lookup, GitHub Actions integrations; cataloging delegated to Syft (extensible separately).[g2](#grype-sources)[g4](#grype-sources) |
| Observability & ops | Structured logs, metrics, explain traces, offline manifests, runbooks.[1](#sources)[4](#sources)[6](#sources) | CLI logging and exit codes; no built-in metrics/traces beyond verbose logs.[g4](#grype-sources) |
## Ecosystem Deep Dives
- **Feature matrix overview** see [scanner/deep-dives/matrix.md](scanner/deep-dives/matrix.md).
- **OS package managers** see [scanner/deep-dives/os-packages.md](scanner/deep-dives/os-packages.md).
- **Node.js & package managers** see [scanner/deep-dives/nodejs.md](scanner/deep-dives/nodejs.md).
- **Python ecosystem** see [scanner/deep-dives/python.md](scanner/deep-dives/python.md).
- **Java / JVM artifacts** see [scanner/deep-dives/java.md](scanner/deep-dives/java.md).
- **Go modules & binaries** see [scanner/deep-dives/golang.md](scanner/deep-dives/golang.md).
- **.NET / NuGet** see [scanner/deep-dives/dotnet.md](scanner/deep-dives/dotnet.md).
- **Rust ecosystem** see [scanner/deep-dives/rust.md](scanner/deep-dives/rust.md).
- **SAST (application code)** see [scanner/deep-dives/sast.md](scanner/deep-dives/sast.md).
- **Secret handling posture** see [scanner/deep-dives/secrets.md](scanner/deep-dives/secrets.md).
## Observations
- Grypes reliance on Syft provides broad package coverage quickly, but it inherits Syfts metadata limits (no runtime usage or deterministic replay). StellaOps can continue emphasizing provenance and policy orchestration while considering backlog for ecosystems where Grype today has parity (Ruby/PHP).[g1](#grype-sources)[g5](#grype-sources)
- Anchores aggregated vulnerability DB offers fast setup, though it introduces dependency on Anchore services; StellaOps approach keeps attestation and evidence self-hostable, which remains a differentiator for regulated tenants.[1](#sources)[g3](#grype-sources)
- Secret detection, SAST, and runtime attestation remain gaps for both Trivy and Grype—opportunity for StellaOps to lead with Surface policies, recommend SAST partners, and emphasise DSSE/EntryTrace integration.
## Opportunities for StellaOps
1. Prioritize analyzers for ecosystems currently supported by Grype (Ruby, PHP, Composer) to close parity gaps without sacrificing determinism.[g5](#grype-sources)
2. Publish guidance on integrating Anchore DB feeds (where allowed) while keeping StellaOps provenance guarantees, or offer curated feeds with DSSE-backed provenance for air-gapped users.[g3](#grype-sources)
3. Evaluate optional syft/grype compatibility layers (importing Syft SBOMs) so customers can transition while retaining StellaOps attestation benefits.
---
### Sources
1. `docs/modules/scanner/architecture.md`
2. `docs/modules/scanner/implementation_plan.md`
3. `docs/modules/scanner/design/surface-env.md`
4. `docs/modules/scanner/design/surface-fs.md`
5. `docs/modules/scanner/design/surface-secrets.md`
6. `docs/modules/scanner/design/surface-validation.md`
7. `docs/modules/platform/architecture-overview.md`
#### Grype sources
- [g1] `grype/pkg/syft_provider.go`
- [g2] `grype/README.md` (Features list: OS & language coverage, external sources, integrations)
- [g3] `grype/README.md` (Vulnerability DB management, offline workflow)
- [g4] `grype/README.md` (Output formats, CLI controls)
- [g5] `grype/grype/matcher/javascript/matcher.go`

View File

@@ -0,0 +1,60 @@
# Scanner Feature Comparison — StellaOps vs Snyk CLI
_Reference snapshot: Snyk CLI commit `7ae3b11642d143b588016d4daef0a6ddaddb792b` cloned 2025-11-02._
## TL;DR
- StellaOps delivers a self-hosted, multi-service scanning plane with deterministic SBOMs, attestation (DSSE + Rekor), and tenant-aware Surface controls, while the Snyk CLI is a Node.js tool that authenticates against Snyks SaaS to analyse dependency graphs, containers, IaC, and code.[1](#sources)[s1](#snyk-sources)
- Snyks plugin ecosystem covers many package managers (npm, yarn, pnpm, Maven, Gradle, NuGet, Go modules, Composer, etc.) and routes scans through Snyks cloud for policy, reporting, and fix advice; however it lacks offline operation, deterministic evidence, and attestation workflows that StellaOps provides out of the box.[1](#sources)[s1](#snyk-sources)[s2](#snyk-sources)
- Opportunity: Lean on StellaOps strengths (offline parity, provenance, policy) while tracking Snyk-only ecosystems (for example, SwiftPM, CocoaPods) and SaaS conveniences (IaC, Snyk Code) that may influence backlog priorities.
## Comparison Matrix
| Dimension | StellaOps Scanner | Snyk CLI |
| --- | --- | --- |
| Architecture & deployment | WebService + Worker services, queue backbone, RustFS/S3 artifact store, Mongo catalog, Authority-issued OpToks, Surface libs, restart-only analyzers.[1](#sources)[3](#sources)[4](#sources)[5](#sources) | Node.js CLI; users authenticate (`snyk auth`) and run commands (`snyk test`, `snyk monitor`, `snyk container test`) that upload project metadata to Snyks SaaS for analysis.[s2](#snyk-sources) |
| Scan targets & coverage | Container images/filesystems, analyzers for APK/DPKG/RPM, Java/Node/Python/Go/.NET/Rust, native ELF, EntryTrace usage graph.[1](#sources) | Supports Snyk Open Source, Container, Code (SAST), and IaC; plugin loader dispatches npm/yarn/pnpm, Maven/Gradle/SBT, pip/poetry, Go modules, NuGet/Paket, Composer, CocoaPods, Hex, SwiftPM.[s1](#snyk-sources)[s2](#snyk-sources) |
| Evidence & outputs | CycloneDX JSON/Protobuf, SPDX 3.0.1, deterministic diffs, BOM-index sidecar, explain traces, DSSE-ready report metadata.[1](#sources)[2](#sources) | CLI prints human-readable tables and supports JSON/SARIF outputs for Snyk Open Source/Snyk Code; results originate from cloud analysis, not deterministic SBOM fragments.[s3](#snyk-sources) |
| Attestation & supply chain | DSSE signing via Signer → Attestor → Rekor v2, OpenVEX-first modelling, policy overlays, provenance digests.[1](#sources) | No DSSE/attestation workflow; remediation guidance and monitors live in Snyk SaaS.[s2](#snyk-sources) |
| Policy & decisioning | Central Policy Engine (stella-dsl), lattice logic, VEX-first decisioning, API streaming of policy previews.[1](#sources)[7](#sources) | Policy controls managed in Snyk platform (org/project settings); CLI can gate on severity (`--severity-threshold`) and push projects for monitoring.[s2](#snyk-sources) |
| Offline & air-gap | Offline kits with Surface manifests, secrets bundles, RustFS; no external connectivity required after provisioning.[3](#sources)[4](#sources)[6](#sources) | Requires internet connectivity for authentication and analysis; no offline mode documented.[s2](#snyk-sources) |
| Caching & performance | Layer CAS caches, queue leasing, EntryTrace reuse, deterministic ordering.[1](#sources)[4](#sources) | Dependency graphs are resolved locally, but vulnerability analysis happens in the cloud; no local cache beyond CLI conveniences.[s1](#snyk-sources)[s2](#snyk-sources) |
| Security & tenancy | OpTok enforcement (DPoP/mTLS), tenant-aware storage, Surface.Secrets providers, validation pipeline.[1](#sources)[5](#sources)[6](#sources) | Authentication scoped per Snyk org; registry credentials handled via config/secret stores but no tenant isolation inside the CLI itself.[s2](#snyk-sources) |
| Extensibility & ecosystem | Analyzer plug-ins, BuildX SBOM generator, CLI/Worker integration, attested exports.[1](#sources)[2](#sources) | Plugin architecture for package managers (Node.js legacy + external plugins); integrations for CI/IDE rely on Snyk platform APIs.[s1](#snyk-sources) |
| Observability & ops | Structured logs, metrics, explain traces, offline manifests, runbooks.[1](#sources)[4](#sources)[6](#sources) | CLI logs to stdout/stderr; deeper analytics available via Snyk SaaS dashboards rather than local instrumentation.[s2](#snyk-sources) |
## Ecosystem Deep Dives
- **Feature matrix overview** see [scanner/deep-dives/matrix.md](scanner/deep-dives/matrix.md).
- **OS package managers** see [scanner/deep-dives/os-packages.md](scanner/deep-dives/os-packages.md).
- **Node.js & package managers** see [scanner/deep-dives/nodejs.md](scanner/deep-dives/nodejs.md).
- **Python ecosystem** see [scanner/deep-dives/python.md](scanner/deep-dives/python.md).
- **Java / JVM artifacts** see [scanner/deep-dives/java.md](scanner/deep-dives/java.md).
- **Go modules & binaries** see [scanner/deep-dives/golang.md](scanner/deep-dives/golang.md).
- **.NET / NuGet** see [scanner/deep-dives/dotnet.md](scanner/deep-dives/dotnet.md).
- **Secret handling posture** see [scanner/deep-dives/secrets.md](scanner/deep-dives/secrets.md).
- **SAST (application code)** see [scanner/deep-dives/sast.md](scanner/deep-dives/sast.md).
## Observations
- Snyks cloud-first workflow simplifies policy management and monitoring for hosted users, but it prevents fully offline operation—StellaOps should continue emphasising sovereign/offline parity while documenting bridge options (e.g., exporting SBOMs for Snyk ingestion).[s2](#snyk-sources)
- Plugin breadth (SwiftPM, CocoaPods, Hex) exceeds current StellaOps coverage; backlog items for these ecosystems may become higher priority if customer demand aligns.[s1](#snyk-sources)
- Secret detection and SAST are available via Snyk Code, yet require uploading code; StellaOps can differentiate with deterministic, self-hosted evidence plus attestation.
## Opportunities for StellaOps
1. Evaluate demand for additional package managers (SwiftPM, CocoaPods, Hex) supported by Snyk plugins and scope analyzer roadmap accordingly.[s1](#snyk-sources)
2. Provide guidance on integrating with external SaaS tools (including Snyk) using StellaOps SBOM exports for hybrid workflows.
3. Continue to highlight DSSE/attestation, offline kits, and tenant isolation as differentiators versus cloud-only scanners.
---
### Sources
1. `docs/modules/scanner/architecture.md`
2. `docs/modules/scanner/implementation_plan.md`
3. `docs/modules/scanner/design/surface-env.md`
4. `docs/modules/scanner/design/surface-fs.md`
5. `docs/modules/scanner/design/surface-secrets.md`
6. `docs/modules/scanner/design/surface-validation.md`
7. `docs/modules/platform/architecture-overview.md`
#### Snyk sources
- [s1] `/tmp/snyk-cli/src/lib/plugins/index.ts`
- [s2] `/tmp/snyk-cli/README.md`
- [s3] `/tmp/snyk-cli/src/lib/plugins/sast/format/output-format.ts`

View File

@@ -0,0 +1,90 @@
# Scanner Feature Comparison — StellaOps vs Trivy
_Reference snapshot: Trivy commit `012f3d75359e019df1eb2602460146d43cb59715`, cloned 2025-11-02._
## TL;DR
- StellaOps Scanner stays focused on deterministic, tenant-scoped SBOM production with signed evidence, policy hand-offs, and Surface primitives that keep offline deployments first-class.[1](#sources)
- Trivy delivers broad, single-binary coverage (images, filesystems, repos, VMs, Kubernetes, SBOM input) with multiple scanners (vuln, misconfig, secret, license) and a rich plugin ecosystem, but it leaves provenance, signing, and multi-tenant controls to downstream tooling.[8](#sources)
- Opportunity: highlight StellaOps differentiators (attested evidence, Surface reuse, policy integration) while identifying where Trivys breadth suggests roadmap checkpoints (e.g., wider target ingestion or optional plugin bridges) without compromising determinism.
## Comparison Matrix
| Dimension | StellaOps Scanner | Trivy |
| --- | --- | --- |
| Architecture & deployment | WebService + Worker services with queue abstraction (Redis Streams/NATS), RustFS/S3 artifact store, Mongo catalog, Authority-issued DPoP tokens, Surface.* libraries for env/fs/secrets, restart-only analyzer plugins.[1](#sources)[3](#sources)[4](#sources)[5](#sources) | Single Go binary CLI with optional server that centralises vulnerability DB updates; client/server mode streams scan queries while misconfig/secret scanning stays client-side; relies on local cache directories.[8](#sources)[15](#sources) |
| Scan targets & coverage | Container images & filesystem snapshots; analyser families:<br>• OS: APK, DPKG, RPM with layer fragments.<br>• Languages: Java, Node, Python, Go, .NET, Rust (installed metadata only).<br>• Native: ELF today (PE/Mach-O M2 roadmap).<br>• EntryTrace usage graph for runtime focus.<br>Outputs paired inventory/usage SBOMs plus BOM-index sidecar; no direct repo/VM/K8s scanning.[1](#sources) | Container images, rootfs, local filesystems, git repositories, VM images, Kubernetes clusters, and standalone SBOMs. Language portfolio spans Ruby, Python, PHP, Node.js, .NET, Java, Go, Rust, C/C++, Elixir, Dart, Swift, Julia across pre/post-build contexts. OS coverage includes Alpine, RHEL/Alma/Rocky, Debian/Ubuntu, SUSE, Amazon, Bottlerocket, etc. Secret and misconfiguration scanners run alongside vulnerability analysis.[8](#sources)[9](#sources)[10](#sources)[18](#sources)[19](#sources) |
| Evidence & outputs | CycloneDX (JSON + protobuf) and SPDX 3.0.1 exports, three-way diffs, DSSE-ready report metadata, BOM-index sidecar, deterministic manifests, explain traces for policy consumers.[1](#sources)[2](#sources) | Human-readable, JSON, CycloneDX, SPDX outputs; can both generate SBOMs and rescan existing SBOM artefacts; no built-in DSSE or attestation pipeline documented—signing left to external workflows.[8](#sources)[10](#sources) |
| Attestation & supply chain | DSSE signing via Signer → Attestor → Rekor v2, OpenVEX-first modelling, lattice logic for exploitability, provenance-bound digests, optional Rekor transparency, policy overlays.[1](#sources) | Experimental VEX repository consumption (`--vex repo`) pulling statements from VEX Hub or custom feeds; relies on external OCI registries for DB artefacts, but does not ship an attestation/signing workflow.[11](#sources)[14](#sources) |
| Policy & decisioning | Scanner reports facts; Policy Engine, Concelier, and Excititor consume SBOM + VEX to emit explainable verdicts; WebService streams policy previews and report links with tenant enforcement.[1](#sources)[7](#sources) | Built-in misconfiguration checks (OPA/Rego bundles) and severity filtering, but no integrated policy decisioning layer; consumers interpret results or bridge to Aqua commercial offerings.[8](#sources) |
| Offline & air-gap | Offline kits bundle Surface manifests, SBOM artefacts, secrets vault, and configuration; Surface.Env/Validation enforce deterministic prerequisites before work executes; RustFS supports air-gapped object storage.[3](#sources)[4](#sources)[6](#sources) | Supports offline scans by mirroring OCI-hosted vulnerability/Java/check databases, manual cache seeding, `--offline-scan`, and self-hosting VEX repositories; requires operators to keep mirrors fresh.[12](#sources)[13](#sources)[14](#sources) |
| Caching & performance | Layer CAS caching, Content-addressed Surface manifests, queue leasing with retries/dead-letter, EntryTrace reuse, analyzer restart-only plugins for reproducible hot-swaps.[1](#sources)[4](#sources) | Scan cache backends for filesystem, in-memory, or Redis (experimental); caches vulnerability/Java/misconfig bundles locally; BoltDB backend limits concurrent writers.[12](#sources) |
| Security & tenancy | Authority-scoped OpToks (DPoP/mTLS), tenant-aware storage prefixes, secret providers, validation pipeline preventing misconfiguration, DSSE signing for tamper evidence.[1](#sources)[3](#sources)[5](#sources)[6](#sources) | CLI/server intended for single-tenant use; docs emphasise network hardening but do not describe built-in tenant isolation or authenticated server endpoints—deployments rely on surrounding controls.[8](#sources)[15](#sources) |
| Extensibility & ecosystem | Analyzer plug-ins (restart-time), Surface shared libraries, BuildX SBOM generator, CLI orchestration, integration contracts with Scheduler, Export Center, Policy, Notify.[1](#sources)[2](#sources) | CLI plugin framework (`trivy plugin`), rich ecosystem integrations (GitHub Actions, Kubernetes operator, IDE plugins), community plugin index for custom commands.[8](#sources)[16](#sources) |
| Observability & ops | Structured logs, metrics for queue/cache/validation, policy preview traces, runbooks and offline manifest documentation embedded in module docs.[1](#sources)[4](#sources)[6](#sources) | CLI-/server-level logging; documentation focuses on usage rather than metrics/trace emission—operators layer external tooling as needed.[8](#sources) |
| Licensing | AGPL-3.0-or-later with sovereign/offline obligations (per project charter).[StellaOps LICENSE](../../LICENSE) | Apache-2.0; permissive for redistribution and derivative tooling.[17](#sources) |
## Coverage Deep Dive
### StellaOps Scanner
- **Targets**: container images and curated filesystem captures; workers read OCI layers via queue-driven jobs. Other asset types (git repos, clusters, VMs) stay outside current scope to preserve deterministic replay guarantees.[1](#sources)
- **OS ecosystems**: APK (Alpine/Wolfi), DPKG/APT (Debian/Ubuntu), RPM (RHEL, Fedora derivatives) analyzers extract authoritative package DB entries, with fragments persisted per layer for SBOM/diff assembly.[1](#sources)
- **Language analyzers**: Java (`pom.properties`, manifest), Node (`package.json`), Python (`*.dist-info`), Go (binary buildinfo), .NET (`*.deps.json`), Rust (embedded metadata/registry traces) recorded only when install evidence exists, avoiding lockfile speculation.[1](#sources)
- **Native & usage context**: ELF link graph, `bin:{sha256}` fallbacks, and EntryTrace shell/launcher resolution feed usage SBOM views and `usedByEntrypoint` bitmaps for downstream policy joins.[1](#sources)
- **Secrets posture**: Surface.Secrets provides tenant-scoped retrieval for CAS credentials, registry tokens, TLS material via pluggable providers (Kubernetes, file, inline) with validation/rotation controls; Scanner itself does not perform secret discovery within target artefacts.[5](#sources)
### Trivy
- **Targets**: CLI scans container images, rootfs exports, arbitrary filesystems, remote git repos, OCI and VM images, Kubernetes clusters, and accepts CycloneDX/SPDX SBOMs as inputs.[8](#sources)[10](#sources)
- **Language ecosystems**: Pre-build lockfiles and post-build package metadata across Ruby, Python, PHP, Node.js, .NET, Java, Go, Rust, C/C++, Elixir, Dart, Swift, Julia; availability varies by target type (image/rootfs/filesystem/repo).[9](#sources)
- **OS coverage**: Supports Alpine/Wolfi/Chainguard, RHEL/CentOS/Alma/Rocky, Debian/Ubuntu, SUSE family, Amazon/Azure Linux, Bottlerocket, and others with appropriate package manager adapters.[18](#sources)
- **Secret scanning**: Pattern-driven engine (builtin + custom regex rules) inspects plaintext files and `.pyc` bytecode, with allow/deny lists, custom rule packs, and options to limit scope for performance.[19](#sources)
- **Complementary scanners**: Misconfiguration (OPA/Rego bundles) and license analysis operate alongside vulnerability scanning, widening coverage beyond SBOM generation.[8](#sources)
### Secret Detection vs Secret Management
- **StellaOps**: Treats secrets as operational inputs. Surface.Secrets resolves tenant-scoped credentials from Kubernetes, file, or inline providers, caches handles securely, enforces validation/rotation, and packages encrypted archives for offline kits.[5](#sources) The scanner deliberately omits secret discovery in analysed artefacts to maintain deterministic SBOM focus.
- **Trivy**: Embeds secret detection within every scan, shipping extensive builtin rules (AWS/GCP/GitHub keys, private keys, etc.), allowlists, and configuration knobs (`--secret-config`, `enable-builtin-rules`). Secrets are reported with file/line context and severity, and features can be scoped or disabled per workload.[19](#sources)
## Ecosystem Deep Dives
- **Feature matrix overview** see [scanner/deep-dives/matrix.md](scanner/deep-dives/matrix.md).
- **OS package managers** see [scanner/deep-dives/os-packages.md](scanner/deep-dives/os-packages.md).
- **Node.js & package managers** see [scanner/deep-dives/nodejs.md](scanner/deep-dives/nodejs.md).
- **Python ecosystem** see [scanner/deep-dives/python.md](scanner/deep-dives/python.md).
- **Java / JVM artifacts** see [scanner/deep-dives/java.md](scanner/deep-dives/java.md).
- **Go modules & binaries** see [scanner/deep-dives/golang.md](scanner/deep-dives/golang.md).
- **.NET / NuGet** see [scanner/deep-dives/dotnet.md](scanner/deep-dives/dotnet.md).
- **Rust ecosystem** see [scanner/deep-dives/rust.md](scanner/deep-dives/rust.md).
- **SAST (application code)** see [scanner/deep-dives/sast.md](scanner/deep-dives/sast.md).
- **Secret handling posture** see [scanner/deep-dives/secrets.md](scanner/deep-dives/secrets.md).
## Observations
- StellaOps DSSE + Rekor attestation chain, Surface libraries, and tenant-aware controls are clear differentiators for regulated or offline-first estates; highlight these when positioning against broad scanners.
- Trivys breadth (targets, scanners, plugins) sets user expectations for “one tool covers everything.” Decide which scenarios (e.g., file system repos, IaC misconfig) we must support directly versus document integration paths.
- VEX remains a common thread: StellaOps emphasises OpenVEX + lattice logic; Trivys VEX Hub is experimental. Opportunity to showcase production-ready workflows and governance around VEX ingestion/waivers.
- Trivy ships no built-in SAST; Snyk remains the only compared scanner offering code analysis, so we should continue framing StellaOps around deterministic evidence and document recommended SAST partners for customers needing both.
## Opportunities for StellaOps
1. Publish a concise decision guide contrasting deterministic evidence (StellaOps) with generalist coverage (Trivy) for buyers evaluating scanners.
2. Evaluate lightweight adapters (or documentation) that re-use Trivy SBOM output when a customer already mandates it, while keeping StellaOps as the source of attested truth.
3. Track Trivy ecosystem additions (e.g., new target types) to inform backlog triage—capture deltas in this comparison doc when significant features land.
---
### Sources
1. `docs/modules/scanner/architecture.md`
2. `docs/modules/scanner/implementation_plan.md`
3. `docs/modules/scanner/design/surface-env.md`
4. `docs/modules/scanner/design/surface-fs.md`
5. `docs/modules/scanner/design/surface-secrets.md`
6. `docs/modules/scanner/design/surface-validation.md`
7. `docs/modules/platform/architecture-overview.md`
8. Trivy README — <https://github.com/aquasecurity/trivy/blob/012f3d75359e019df1eb2602460146d43cb59715/README.md>
9. Scanning coverage — <https://github.com/aquasecurity/trivy/blob/012f3d75359e019df1eb2602460146d43cb59715/docs/docs/coverage/index.md>
10. SBOM scanning — <https://github.com/aquasecurity/trivy/blob/012f3d75359e019df1eb2602460146d43cb59715/docs/docs/target/sbom.md>
11. VEX repository — <https://github.com/aquasecurity/trivy/blob/012f3d75359e019df1eb2602460146d43cb59715/docs/docs/supply-chain/vex/repo.md>
12. Cache configuration — <https://github.com/aquasecurity/trivy/blob/012f3d75359e019df1eb2602460146d43cb59715/docs/docs/configuration/cache.md>
13. Air-gap guidance — <https://github.com/aquasecurity/trivy/blob/012f3d75359e019df1eb2602460146d43cb59715/docs/docs/advanced/air-gap.md>
14. Self-hosting databases — <https://github.com/aquasecurity/trivy/blob/012f3d75359e019df1eb2602460146d43cb59715/docs/docs/advanced/self-hosting.md>
15. Client/server mode — <https://github.com/aquasecurity/trivy/blob/012f3d75359e019df1eb2602460146d43cb59715/docs/docs/references/modes/client-server.md>
16. Plugin system — <https://github.com/aquasecurity/trivy/blob/012f3d75359e019df1eb2602460146d43cb59715/docs/docs/plugin/index.md>
17. Trivy LICENSE — <https://github.com/aquasecurity/trivy/blob/012f3d75359e019df1eb2602460146d43cb59715/LICENSE>
18. OS coverage — <https://github.com/aquasecurity/trivy/blob/012f3d75359e019df1eb2602460146d43cb59715/docs/docs/coverage/os/index.md>
19. Secret scanning — <https://github.com/aquasecurity/trivy/blob/012f3d75359e019df1eb2602460146d43cb59715/docs/docs/scanner/secret.md>

View File

@@ -0,0 +1,31 @@
# .NET / NuGet Ecosystem
## StellaOps implementation
- `src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.DotNet/DotNetLanguageAnalyzer.cs` orchestrates package collection through `Internal/DotNetDependencyCollector.cs`.
- Aggregates data from `*.deps.json`, `*.runtimeconfig.json`, and assemblies, merging multiple sources via `DotNetPackageAggregator`.
- Metadata includes RID lists, serviceable flags, target frameworks, runtimeconfig origins, and authenticode signer details (via optional `IDotNetAuthenticodeInspector`).
- Evidence records SHA512 hashes, file paths, and runtime config references; EntryTrace hints mark assemblies tied to runtime entrypoints.
## Trivy implementation
- `pkg/fanal/analyzer/language/dotnet/nuget/nuget.go` parses `packages.lock.json` and `packages.config`, populating `types.Application` via parsers in `pkg/dependency/parser/nuget`.
- Optionally resolves licenses by inspecting local `*.nuspec` files if the NuGet packages directory is present.
- Focuses on dependency graph reconstruction (direct vs transitive) but does not analyse runtime asset maps or signing metadata.
## Snyk implementation
- `src/lib/plugins/index.ts` routes NuGet and Paket projects to `snyk-nuget-plugin`.[s1]
- CLI scans require authenticated access to Snyks SaaS backend; results reflect dependency tree analysis without runtimeconfig/signing metadata.[s2]
- No offline mode or per-assembly evidence is provided beyond the vulnerability list returned.
## Grype implementation
- Syft collects .NET packages and assemblies before matching (`grype/pkg/syft_provider.go`).
- The .NET matcher targets `syftPkg.DotnetPkg` entries and invokes ecosystem/CPE matching (`grype/matcher/dotnet/matcher.go`); no authenticode or RID enrichment is propagated.
- Outputs mirror SBOM inventory without runtimeconfig correlation or signing metadata.
## Key differences
- **Runtime-aware metadata**: StellaOps correlates deps.json, runtimeconfig, and authenticode signatures; Trivy, Snyk, and Grype limit themselves to dependency catalogs.
- **Evidence richness**: StellaOps stores per-assembly hashes and source file paths; Trivy, Snyk, and Grype output dependency lists without file-level provenance.
- **Usage insights**: StellaOps applies EntryTrace flags to mark runtime use; Trivy, Snyk, and Grype do not differentiate runtime vs build-time packages.
### References
- [s1] `/tmp/snyk-cli/src/lib/plugins/index.ts`
- [s2] `/tmp/snyk-cli/README.md`

View File

@@ -0,0 +1,31 @@
# Go Modules & Binaries
## StellaOps implementation
- `src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Go/GoLanguageAnalyzer.cs` scans executables for Go build info (`module.Version`, `build Settings`) and reads DWARF metadata when available.
- Captures full module graphs (main + dependencies) with PURLs; for stripped binaries, falls back to hashed component keys via `GoBinaryScanner.TryClassifyStrippedBinary`.
- Metadata records Go toolchain settings, build IDs, and replacements; evidence references binary paths and entrypoint usage (`LanguageAnalyzerContext.UsageHints`).
- Ensures deterministic ordering and retains hashed fallback components for binaries lacking Go metadata.
## Trivy implementation
- Analyzer registry includes `pkg/fanal/analyzer/language/golang/binary/binary.go` (Go binaries) and `.../mod/mod.go` (`go.mod` + `go.sum`).
- Binary parser utilises `pkg/dependency/parser/golang/binary` to extract module data; non-Go or unrecognised binaries return nil without fallback components.
- Module analyzer parses dependency requirements and classifies direct vs indirect modules but does not capture runtime usage or binary hashes.
## Snyk implementation
- `src/lib/plugins/index.ts` dispatches Go projects to `snyk-go-plugin` (golangdep, gomodules, govendor).[s1]
- CLI scans require authenticated connectivity and send dependency graphs to Snyks SaaS backend (`snyk test`, `snyk monitor`) for analysis; binaries are not inspected locally.[s2]
- No fallback hashing or runtime usage metadata accompanies the results.
## Grype implementation
- SBOM generation via Syft feeds Go modules/binaries into Grype (`grype/pkg/syft_provider.go`).
- The Go matcher consumes `syftPkg.GoModulePkg`, applying ecosystem+/-CPE logic and skipping binaries with pseudo-version metadata when build info is insufficient (`grype/matcher/golang/matcher.go`).
- No fallback hashing beyond Syfts catalog; runtime usage and DWARF metadata are not surfaced.
## Key differences
- **Fallback handling**: StellaOps emits explicit hashed components when build info is missing; Trivy and Grype skip binaries without usable metadata.
- **Runtime linkage**: StellaOps ties modules to binary paths and EntryTrace usage; Trivy, Snyk, and Grype output inventory without execution context.
- **Build metadata**: StellaOps records DWARF/VCS settings; Trivy, Snyk, and Grype rely on module info exposed by their parsers without extended metadata.
### References
- [s1] `/tmp/snyk-cli/src/lib/plugins/index.ts`
- [s2] `/tmp/snyk-cli/README.md`

View File

@@ -0,0 +1,32 @@
# Java / JVM Ecosystem
## StellaOps implementation
- `src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Java/JavaLanguageAnalyzer.cs` normalises JAR/WAR/EAR/PAR archives within the scan root.
- Extracts `META-INF/maven/**/pom.properties` alongside `MANIFEST.MF`, building Maven PURLs and capturing manifest metadata (implementation info, main class) per component.
- Evidence locators encode archive-relative paths (`archive.jar!META-INF/...`) and include cryptographic hashes; EntryTrace usage hints mark archives referenced by runtime entrypoints.
- Focuses on installed artifacts; build-time descriptors (Gradle/SBT) are out of scope for the core analyzer.
## Trivy implementation
- `pkg/fanal/analyzer/language/java/jar/jar.go` registers a post-analyzer parsing archives via `pkg/dependency/parser/java/jar`, leveraging the Java DB client for supplemental metadata.
- Additional analyzers handle Gradle lockfiles, Maven `pom.xml`, and SBT lockfiles, expanding pre-build coverage.
- Results populate `types.Application` with dependency graphs and optional license data; relies on external Java DB availability.
- Does not track runtime usage or per-layer provenance.
## Snyk implementation
- Java projects are handled by plugins such as `snyk-mvn-plugin`, `snyk-gradle-plugin`, and `snyk-sbt-plugin` loaded via `src/lib/plugins/index.ts`.[s1]
- CLI commands (`snyk test`, `snyk monitor`) require authentication and network connectivity; dependency graphs are evaluated by Snyks SaaS service.[s2]
- Archive-level hashes or EntryTrace usage are not exposed—results focus on vulnerability listings returned by the service.
## Grype implementation
- Syft builds the SBOM before matching (`grype/pkg/syft_provider.go`), supplying Java packages and metadata.
- The Java matcher accepts `syftPkg.JavaPkg` and Jenkins plugin packages; it can query Maven by SHA when POM metadata is missing and falls back to ecosystem/CPE matching (`grype/matcher/java/matcher.go`).
- No deterministic archive hashing or EntryTrace linkage; provenance mirrors Syfts SBOM plus Anchores upstream lookups.
## Key differences
- **Archive evidence**: StellaOps captures archive path, hashes, and manifest data for deterministic attestation; Trivy, Snyk, and Grype derive dependency graphs without preserving per-entry evidence.
- **Build-tool coverage**: Trivy supports Gradle/SBT lockfiles; Snyk uses dedicated Maven/Gradle/SBT plugins; Grype augments matches via Maven SHA searches; StellaOps intentionally scopes to observed artifacts.
- **Usage insights**: StellaOps records EntryTrace linkage from executables to archives; Trivy, Snyk, and Grype omit runtime usage metadata.
### References
- [s1] `/tmp/snyk-cli/src/lib/plugins/index.ts`
- [s2] `/tmp/snyk-cli/README.md`

View File

@@ -0,0 +1,28 @@
# Ecosystem Feature Matrix — StellaOps vs Trivy / Grype / Snyk
| Lang / Ecosystem | Feature | Winner | StellaOps gaps | Trivy gaps | Grype gaps | Snyk gaps | Backlog follow-up (suggested) | Doc |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| .NET | Dependency retrieval | Snyk | No pre-build lock/config ingestion (installed `deps.json` only). | No runtime graph; ignores `runtimeconfig`/installed assemblies. | Relies on Syft `deps.json` catalogs; no layer-aware runtime context. | Requires authenticated SaaS analysis; projects often need restore/build before scanning. | Evaluate adding lockfile analyzer parity (track via Scanner .NET guild tasks). | [dotnet.md](dotnet.md) |
| .NET | Runtime metadata & signing | StellaOps | Authenticode inspection optional; Windows-only coverage pending. | Does not capture signer metadata or assembly hashes. | No authenticode or RID metadata captured; package fields only. | No runtimeconfig/authenticode data; focuses on dependency manifests. | Harden Authenticode integration & document Windows variants. | [dotnet.md](dotnet.md) |
| Node.js | Workspace & pnpm resolution | Tie (StellaOps / Snyk) | Lack of pnpm lock validator tooling for CLI users. | pnpm virtual store resolved only via lockfile semantics; skips actual installs. | Depends on Syft catalogers; lacks pnpm workspace policy controls or dedupe tuning. | Manifest-based plugins (npm/yarn/pnpm) send dependency graphs to Snyk API; offline unsupported. | Add pnpm validator CLI story; share results with Surface Env guild. | [nodejs.md](nodejs.md) |
| Node.js | Usage tracking | StellaOps | EntryTrace launcher catalog requires regular updates. | No runtime usage model; inventory-only. | No runtime usage modelling; reports inventory only. | No runtime usage modelling (inventory only). | Establish cadence for launcher catalog review (EntryTrace TASKS). | [nodejs.md](nodejs.md) |
| Python | Evidence source | Tie (StellaOps / Trivy) | Build-only repos need supplemental workflow. | Accepts stale lockfiles; installed evidence optional. | Leverages Syft-installed metadata; build-only projects need external flow. | Requires language environment & build; manifest graph sent to Snyk service. | Scope CLI guidance for build-only repos in docs backlog. | [python.md](python.md) |
| Python | Usage awareness | StellaOps | EntryTrace hints dependent on shell heuristic coverage. | Missing runtime usage context entirely. | No runtime usage awareness. | No runtime usage metadata. | Expand EntryTrace shell heuristic coverage. | [python.md](python.md) |
| Java | Archive evidence | Tie (StellaOps / Snyk) | Gradle/SBT lockfiles out of scope; relies on observed archives. | No archive hash locators; depends on Java DB availability. | Relies on Syft archive metadata without manifest hashing/attestation. | Relies on Maven/Gradle plugins; no archive hashing or offline support. | Track Gradle/SBT ingestion feasibility (Java analyzer task board). | [java.md](java.md) |
| Go | Stripped binaries | StellaOps | Fallback components limited to hash + binary metadata. | Drops binaries lacking build info; no fallback reporting. | Skips pseudo-version binaries without build info; no hashed fallback. | Go plugin inspects modules via manifests; binaries without modules not analysed. | Investigate richer fallback metadata (Go analyzer backlog). | [golang.md](golang.md) |
| Rust | Binary heuristics | StellaOps | Fingerprint coverage incomplete for niche toolchains. | Unmatched binaries ignored; no fallback crates. | No fallback for binaries lacking Cargo metadata; depends on Syft crate data. | No Rust/Cargo support in CLI plugins. | Expand fingerprint signatures; note in Rust analyzer tasks. | [rust.md](rust.md) |
| OS packages | Linux distro coverage & provenance | Tie (StellaOps / Grype) | Requires RustFS/object store deployment for full replay; Windows packaging still out of scope. | No per-layer fragment storage; provenance limited; Windows support likewise minimal. | No per-layer provenance; shares Syft catalog and Anchore DB only. | Snyk Container scanning depends on SaaS API; no per-layer provenance. | Document RustFS dependency & offline alternatives in ops backlog; evaluate Windows pkg roadmap. | [os-packages.md](os-packages.md) |
| OS packages | Linux flavor support (Alpine/Wolfi/Chainguard, Debian/Ubuntu, RHEL/Alma/Rocky, SUSE, Amazon/Bottlerocket) | Tie (Trivy / Snyk) | Windows/macOS package ecosystems still pending. | Coverage relies on package DB adapters; per-distro nuances (e.g., Chainguard signatures) not attested. | Supports major Linux feeds but no Windows/macOS package analyzers. | Supports documented distro list via Snyk Container but requires cloud connectivity. | Track demand for non-Linux package analyzers; document distro mapping in os-packages deep dive. | [os-packages.md](os-packages.md) |
| OS packages | Windows/macOS coverage | — | No Windows/macOS analyzer; backlog item for offline parity. | Coverage docs enumerate Linux distributions only; Windows/macOS packages unsupported. | Syft matchers focus on Linux ecosystems; Windows/macOS packages unsupported. | Coverage depends on Snyks SaaS service; no offline assurance for Windows/macOS packages. | Capture demand for Windows/macOS analyzers and scope feasibility. | [os-packages.md](os-packages.md) |
| Secrets | Handling posture | StellaOps | No leak scanning by design; Surface.Secrets manages retrieval/rotation with tenant scopes. | Leak detections lack governance hooks; operators must track rule updates. | No secret management abstraction; credentials configured manually. | Requires SaaS backend for secret scanning; no offline posture or secret storage guidance. | Document governance patterns for Surface.Secrets users and recommended companion tooling. | [secrets.md](secrets.md) |
| Secrets | Detection technique | Trivy | No content scanning; relies on Surface.Secrets integrations. | Regex/entropy detectors with configurable allow/deny lists across files/bytecode. | No detector available; Syft/Grype skip leak scanning entirely. | Snyk Code/Snyk secrets require uploading code to SaaS; offline detection unavailable. | Provide guidance on pairing Surface with third-party leak scanners; evaluate optional plugin strategy. | [secrets.md](secrets.md) |
| EntryTrace | Runtime command resolution | StellaOps | Shell/language launcher coverage needs continuous tuning. | Not supported. | Not available. | Not available. | Continue EntryTrace backlog (SURFACE-ENTRYTRACE stories). | — |
| DSSE / Rekor | Attested SBOM/report signing | StellaOps | Rekor v2 adoption requires operator enablement guidance. | Not supported. | No attestation or transparency log integration. | No attestation workflow. | Add operator playbook updates in Export Center backlog. | — |
| Ruby | Language analyzer parity | Snyk | No Ruby analyzer implementation yet. | Lacks runtime usage/EntryTrace integration. | Supports Ruby via matcher but lacks runtime usage/attestation. | Supported through rubygems plugin (SaaS dependency graph). | Prioritise Ruby analyzer work (see `src/Scanner/StellaOps.Scanner.Analyzers.Lang.Ruby/TASKS.md`). | — |
| PHP | Language analyzer parity | Snyk | No PHP analyzer implementation yet. | No usage or evidence beyond lockfiles. | Composer handled via generic matcher; no runtime evidence. | Supported through PHP Composer plugin (requires Snyk API). | Track PHP analyzer backlog (`...Lang.Php/TASKS.md`). | — |
| Deno | Language analyzer parity | Trivy | Analyzer not yet implemented (tasks pending). | None (lockfile support limited but present). | No Deno support. | No Deno plugin. | Execute Deno analyzer epics in `...Lang.Deno/TASKS.md`. | — |
| Dart | Language analyzer parity | Trivy | No Dart support. | Provides Dart lockfile parsing. | No Dart support. | No Dart plugin. | Create backlog item for Dart coverage feasibility study. | — |
| Swift | Language analyzer parity | Snyk | No Swift support today. | Supports Package.resolved parsing but no runtime usage. | No Swift support. | Supported via swift plugin but SaaS-only. | Evaluate need for Swift analyzer based on customer demand. | — |
| SAST | Application code analysis | Snyk | No built-in SAST engine. | No SAST engine (focus on vuln & config). | No SAST support (SBOM matching only). | Requires SaaS upload of code; privacy considerations. | Evaluate integration points with existing SAST tooling / document partner options. | [sast.md](sast.md) |
| IaC / Misconfiguration | Built-in scanning | Snyk | No misconfiguration analyzer (policy engine focuses on runtime evidence). | Ships IaC scanning but lacks deterministic replay. | No IaC or misconfiguration scanners (vulnerability-only). | Handled via Snyk IaC (`snyk iac test`) with SaaS policy engine. | Coordinate with Policy/Surface guild on IaC roadmap assessment. | — |
| Kubernetes / VM targets | Target coverage breadth | Tie (Trivy / Snyk) | Scanner limited to images/filesystems; relies on other modules for runtime posture. | Supported but lacks attestation pipeline. | Scans images/filesystems; no live cluster or VM state analysis. | Snyk Container/K8s scanning available but cloud-managed; no offline runtime attestation. | Document complementary modules (Zastava/Runtime) in comparison appendix. | — |

View File

@@ -0,0 +1,33 @@
# Node.js & Package Managers
## StellaOps implementation
- `src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Node/NodeLanguageAnalyzer.cs` drives analysis, delegating filesystem traversal to `Internal/NodePackageCollector.cs`.
- Workspace-aware: respects monorepo manifests and PNPM virtual store layout while de-duplicating package paths.
- Emits components only when concrete `package.json` evidence is present, sorted deterministically and annotated with usage hints from EntryTrace (via `LanguageAnalyzerContext`).
- Metadata captures resolved workspace membership, install locations, lock-derived provenance, and npm package type; evidence records exact file locators for `package.json` entries.
## Trivy implementation
- Post-analyzers under `pkg/fanal/analyzer/language/nodejs` cover npm, Yarn, pnpm, and Bun lockfiles.
- `.../npm/npm.go` parses `package-lock.json`, optionally harvesting license data by walking `node_modules` and re-reading `package.json`.
- Each analyzer registers via `analyzer.RegisterPostAnalyzer`, translating lockfile dependency graphs into `types.Application` without runtime usage linkage.
- License attribution depends on `node_modules` material being present (`npm install`/`pnpm install` executed) but does not emit per-file evidence.
## Snyk implementation
- The CLI selects language plugins (legacy Node.js, `snyk-nodejs-plugin`) from `src/lib/plugins/index.ts`, covering npm, yarn, and pnpm manifests via dedicated parsers.[s1]
- Dependency trees are resolved locally (lockfile/workspace parsing) then sent to Snyks SaaS API (`snyk test`, `snyk monitor`) for vulnerability assessment; CLI requires authenticated network access.[s2]
- No per-file evidence or runtime usage information is emitted beyond the dependency graph returned by the API.
## Grype implementation
- SBOM creation is handled by Syft before matching; `grype/pkg/syft_provider.go` retrieves sources and constructs the catalog used for scanning.
- The JavaScript matcher advertises support for `syftPkg.NpmPkg` and performs ecosystem/CPE matching via Anchores vulnerability provider (`grype/matcher/javascript/matcher.go`).
- No workspace- or usage-aware enrichment beyond what Syft records in the SBOM; pnpm/yarn nuances depend on Syft catalogers.
## Key differences
- **Evidence vs lockfiles**: StellaOps grounds every component in observed filesystem evidence, while Trivy prioritises lockfile graphs (falling back to installed metadata for licensing only).
- **Usage awareness**: StellaOps propagates EntryTrace hints to mark packages used by runtime entrypoints; Trivy and Snyk/Grype do not track usage.
- **PNPM handling**: StellaOps crawls the virtual store to resolve flattened packages; Trivy and Snyk lean on pnpm lockfile semantics supplied by their parsers.
- **Output richness**: StellaOps records workspace context and per-file evidence suitable for deterministic replay, whereas Trivy, Snyk, and Grype surface inventory-only dependency lists.
### References
- [s1] `/tmp/snyk-cli/src/lib/plugins/index.ts`
- [s2] `/tmp/snyk-cli/README.md`

View File

@@ -0,0 +1,35 @@
# OS Package Managers (APK / DPKG / RPM)
## StellaOps implementation
- Analyzer implementations reside under `src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.OS.*` (e.g., `...OS.Dpkg/DpkgPackageAnalyzer.cs`, `...OS.Rpm/RpmPackageAnalyzer.cs`, `...OS.Apk/ApkPackageAnalyzer.cs`).
- Each analyzer reads authoritative package databases (`/var/lib/dpkg/status`, RPMDB headers, `lib/apk/db/installed`) and emits content-addressed fragments per layer.
- Metadata includes source package, maintainer, vendor fields, dependencies/provides, license strings, and CVE hint extraction; file evidence captures configuration files and checksums.
- Results feed SBOM assembly with deterministic ordering and layer attribution, enabling replayable diff/manifests and provenance binding.
- Windows/macOS package ecosystems remain out of scope; analyzer backlog tracks feasibility for future releases.
## Trivy implementation
- Package analyzers live under `pkg/fanal/analyzer/pkg` (apk, dpkg, rpm). Parsers translate distro databases into `types.Package` records for vulnerability matching.
- RPM analyzers leverage distro-specific metadata (e.g., Red Hat content manifests) when available; dpkg/apk analyzers enumerate package lists and file paths for licensing purposes.
- Layer attribution is implicit via artifact walkers; outputs prioritise package inventories for scanning rather than deterministic fragment storage.
- Supported operating systems are catalogued in `docs/docs/coverage/os/index.md`, which maps Alpine/Wolfi/Chainguard/MinimOS (apk), RHEL and rebuilds (dnf/yum/rpm), SUSE families (zypper), Photon/Azure/Amazon variants, Debian/Ubuntu, Bottlerocket, and Conda-based images to their package managers with per-distro version ranges (e.g., `docs/docs/coverage/os/rhel.md`, `.../ubuntu.md`, `.../suse.md`, `.../amazon.md`, `.../bottlerocket.md`).[t1]
- Container image profiles such as Google Distroless and Bitnami are tracked in the same coverage table, highlighting Trivys ability to repurpose cached feeds across common base images.[t1]
## Snyk implementation
- The CLI exposes container scanning (`snyk container test`) that uploads image metadata to Snyks SaaS backend for analysis; supported distribution lists are maintained in Snyk SaaS documentation rather than the CLI itself.[s1]
- Requires authentication and network connectivity; no per-layer fragments or offline workflow beyond SaaS-managed results, and coverage expands as the SaaS service updates its advisory backends rather than local catalogers.[s1]
## Grype implementation
- Source enumeration flows through Syft (`grype/pkg/syft_provider.go`), after which distro-specific matchers operate (e.g., `grype/matcher/apk/matcher.go`, `grype/matcher/dpkg/matcher.go`).
- Matchers combine ecosystem lookups, upstream package indirection, and Anchores vulnerability DB; fixes and NAK handling (e.g., Alpine SecDB) are embedded in matcher logic.
- OS support mirrors Syfts catalogers: dedicated matchers exist for APK (Alpine/Wolfi/Chainguard), DPKG/APT (Debian/Ubuntu), RPM (RHEL/Alma/Rocky/Amazon/SUSE), Portage, Bitnami stacks, and stock package inventories, all under `grype/matcher/**`. The matcher set delegates to Anchores feed service to stay current with distro advisories.[g1]
- Per-layer provenance is not retained; results align with Syfts catalog and Anchores aggregated feeds.
## Key differences
- **Layer fragments**: StellaOps persists per-layer fragments tied to content-addressed storage, whereas Trivy, Snyk, and Grype maintain package inventories without explicit fragment artifacts.
- **Metadata depth**: StellaOps records extensive vendor metadata and file evidence for replay; Trivy, Snyk, and Grype focus on match-relevant fields from their feeds.
- **Provenance**: StellaOps outputs integrate directly with attestation/diff pipelines, while Trivy, Snyk, and Grype assume downstream tools consume package lists without replay requirements.
### References
- [t1] `/tmp/trivy-src/docs/docs/coverage/os/index.md`
- [s1] `/tmp/snyk-cli/README.md`
- [g1] `/tmp/grype-data/grype/matcher/apk/matcher.go`, `/tmp/grype-data/grype/matcher/dpkg/matcher.go`, `/tmp/grype-data/grype/matcher/rpm/matcher.go`

View File

@@ -0,0 +1,32 @@
# Python Ecosystem
## StellaOps implementation
- `src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Python/PythonLanguageAnalyzer.cs` enumerates `*.dist-info` directories (using deterministic traversal options) and loads metadata through `PythonDistributionLoader`.
- Components are emitted only when authoritative installed metadata is available (`METADATA`, `RECORD`, `entry_points.txt`), guaranteeing observed-state accuracy across layers and virtual environments.
- EntryTrace usage hints flag distributions whose scripts appear in runtime paths, enabling “usage vs inventory” SBOM differentiation.
- Evidence payloads capture file locators (path + hash) so downstream diff/attestation pipelines can replay findings deterministically.
## Trivy implementation
- Multiple analyzers under `pkg/fanal/analyzer/language/python` target packaging formats (`packaging`, `egg`, `wheel`) and lockfiles (`pip`, `poetry`, `pipenv`, `uv`).
- Lockfile analyzers parse dependency graphs via language parsers (e.g., `pkg/dependency/parser/python/poetry`), classifying direct vs transitive packages for vulnerability matching.
- Packaging analyzers walk archives to populate `types.Application`, enriching license data by inspecting declared license files.
- Outputs prioritise dependency relationships; runtime usage and per-file evidence are not tracked.
## Snyk implementation
- The CLI selects `snyk-python-plugin` for `pip` and `poetry` projects (`src/lib/plugins/index.ts`).[s1]
- Users must authenticate and often build or prepare their Python environment before running `snyk test`; dependency graphs are uploaded to Snyks SaaS backend for analysis.[s2]
- No runtime usage metadata or per-file evidence is surfaced beyond the API response.
## Grype implementation
- SBOM extraction relies on Syft (`grype/pkg/syft_provider.go`), which catalogs Python packages from `dist-info`/egg metadata.
- The Python matcher accepts `syftPkg.PythonPkg` entries and performs ecosystem/CPE matching via Anchores provider (`grype/matcher/python/matcher.go`).
- No runtime or usage annotations beyond Syfts inventory; lockfile vs installed distinctions follow Syfts behavior.
## Key differences
- **Source of truth**: StellaOps confines itself to installed artefacts for determinism; Trivy, Snyk, and Grype accommodate SBOM/lockfile inventory generated by their parsers.
- **Usage metadata**: StellaOps propagates EntryTrace usage, supporting policy decisions on runtime reach; Trivy, Snyk, and Grype emit inventory without runtime context.
- **Evidence granularity**: StellaOps stores per-file evidence hashes; Trivy, Snyk, and Grype focus on dependency graphs and CPE matching, not replay artefacts.
### References
- [s1] `/tmp/snyk-cli/src/lib/plugins/index.ts`
- [s2] `/tmp/snyk-cli/README.md`

View File

@@ -0,0 +1,29 @@
# Rust Ecosystem
## StellaOps implementation
- `src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Rust/RustLanguageAnalyzer.cs` coordinates collection via `Internal/RustAnalyzerCollector.cs`.
- Collector ingests Cargo.lock, cargo fingerprint caches, and compiled binaries, merging data into three record sets: crate (authoritative), heuristic (fingerprints), and fallback (hashed binaries).
- License metadata is sourced through `RustLicenseScanner`; binaries retain SHA256 hashes and usage indicators derived from EntryTrace.
- Results are deduplicated and sorted to guarantee deterministic replay; each record includes evidence pointing to lockfile paths or binary locations.
## Trivy implementation
- `pkg/fanal/analyzer/language/rust/cargo/cargo.go` parses Cargo.lock/TOML to classify direct, workspace, and transitive dependencies, removing dev dependencies where possible.
- `pkg/fanal/analyzer/language/rust/binary/binary.go` inspects ELF binaries, returning nil when crate metadata is missing; no hashed fallback component is emitted.
- License attribution relies on Cargo metadata; runtime usage is not tracked.
## Snyk implementation
- The plugin dispatcher in `src/lib/plugins/index.ts` does not list a Rust/Cargo plugin, so Rust projects are currently unsupported by the Snyk CLI.[s1]
- Users must resort to other tooling or Snyk SaaS capabilities outside the CLI for Rust coverage.
## Grype implementation
- Syft provides Rust packages for Grypes matchers (`grype/pkg/syft_provider.go`).
- The Rust matcher consumes `syftPkg.RustPkg` and matches via ecosystem/CPE lookups (`grype/matcher/rust/matcher.go`); there is no fallback for binaries without Cargo metadata.
- Runtime usage, fingerprinting, or hashed binary fallbacks are not emitted.
## Key differences
- **Fallback strategy**: StellaOps preserves components for binaries lacking metadata; Trivy and Grype drop or ignore binaries without Cargo linkage, while Snyk lacks Rust support entirely.
- **Evidence breadth**: StellaOps aggregates lockfile, fingerprint, and binary evidence into a deterministic record; Trivy and Grype primarily consume Cargo descriptors, and Snyk provides no CLI coverage.
- **Usage insight**: StellaOps propagates EntryTrace usage flags; Trivy and Grype outputs are inventory-only; Snyk has no runtime data due to lack of support.
### References
- [s1] `/tmp/snyk-cli/src/lib/plugins/index.ts`

View File

@@ -0,0 +1,21 @@
# SAST (Application Code Scanning)
## StellaOps implementation
- No first-party SAST engine; platform focuses on deterministic SBOM, vulnerability ingestion, and policy evaluation rather than static code analysis.
## Trivy implementation
- Trivy does not ship a SAST analyzer; scanning targets container images, filesystems, SBOMs, and misconfiguration/secret detection only.
## Grype implementation
- Grype is limited to SBOM-driven vulnerability matching; static code analysis is out of scope.
## Snyk implementation
- The CLI routes SAST requests through Snyk Code (`snyk code test`), leveraging the SAST plugin stack under `src/lib/plugins/sast` and emitting SARIF/JSON outputs.[s1]
- Code is uploaded to Snyks SaaS backend for analysis, producing issue listings, severities, and remediation guidance; offline execution is unsupported.[s2]
## Key differences
- Only Snyk provides integrated SAST capabilities via Snyk Code; StellaOps, Trivy, and Grype would require external tooling for static analysis.
### References
- [s1] `/tmp/snyk-cli/src/lib/plugins/sast`
- [s2] `/tmp/snyk-cli/README.md`

View File

@@ -0,0 +1,32 @@
# Secret Handling
## StellaOps approach
- Secrets treated as operational inputs delivered through Surface.Secrets (`src/Scanner/__Libraries/StellaOps.Scanner.Surface.Secrets` and documented in `docs/modules/scanner/design/surface-secrets.md`).
- Providers support Kubernetes Secrets, file-based bundles, and inline definitions; configuration resolved via Surface.Env with validation gates from Surface.Validation.
- Secret placeholders (`secret://type/name`) are resolved before analyzers execute, with results wrapped in secure handles and rotation metadata checked at startup.
- Scanner deliberately avoids scanning artefacts for secret disclosure to preserve deterministic SBOM pipelines and avoid exposing sensitive payloads.
## Trivy approach
- Secret scanning integrated as an analyzer under `pkg/fanal/secret`, applying regex-based detectors across plaintext files and certain bytecode (e.g., `.pyc`).
- Builtin rule sets detect common credentials (AWS, GCP, GitHub PATs, private keys) with allow/deny configurations (`pkg/fanal/secret/builtin-allow-rules.go`) and support custom rule packs via `trivy-secret.yaml`.
- Findings include file path, line number, severity, and match; operators fine-tune via CLI options (`--secret-config`, `--skip-dirs`, `enable-builtin-rules`).
- Secret storage/rotation is outside Trivy scope; deployments manage credentials independently.
## Snyk approach
- Secret detection is available through Snyk Code scans (`snyk code test`), invoked from the CLI via the Snyk Code plugin stack (`src/lib/plugins/sast`).[s1]
- CLI usage requires authentication and uploads code to Snyks SaaS backend (per README guidance), so offline workflows are not supported.[s2]
- Results focus on issue listings from the service; operational secret management is not part of the CLI.
## Grype approach
- Grype does not ship a secret-scanning analyzer; secrets surface only as credentials for registry access (`cmd/grype/cli/options/secret.go`, README sections on mounting Kubernetes secrets).
- Operators must rely on external tooling for leak detection while Grype focuses exclusively on vulnerability matching.[g1]
## Key differences
- **Purpose**: StellaOps focuses on secure retrieval/validation of operational secrets; Trivy and Snyk Code detect leaked secrets, whereas Grype omits secret detection entirely.
- **Workflow**: StellaOps secret lifecycle is pre-scan configuration; Trivy and Snyk analyse content at scan time (Snyk requiring SaaS connectivity), and Grype requires external tooling for leak detection.
- **Determinism**: StellaOps avoids non-deterministic leak scans; Trivy and Snyks detectors may evolve with rule updates; Grype remains deterministic by not attempting secret discovery.
### References
- [s1] `/tmp/snyk-cli/src/lib/plugins/sast`
- [s2] `/tmp/snyk-cli/README.md`
- [g1] `/tmp/grype/README.md`

View File

@@ -48,11 +48,18 @@ Follow the sprint files below in order. Update task status in both `SPRINTS` and
> 2025-11-02: AUTH-OBS-50-001 moved to DOING (Authority Core & Security Guild) defining observability scopes and updating discovery/offline defaults.
> 2025-11-02: AUTH-OBS-52-001 moved to DOING (Authority Core & Security Guild) rolling observability scopes through resource server policies and audit wiring.
> 2025-11-02: AUTH-OBS-55-001 marked DONE (Authority Core & Security Guild, Ops Guild) incident-mode tokens now require fresh auth, audit records expose `incident.reason`, and `/authority/audit/incident` verification path documented.
> 2025-11-02: AUTH-ORCH-34-001 marked DONE (Authority Core & Security Guild) `orch:backfill` scope enforced with reason/ticket metadata, Authority + CLI updated, docs/config refreshed for Orchestrator admins.
> 2025-11-02: AUTH-PACKS-41-001 moved to DOING (Authority Core & Security Guild) defining packs scope catalogue, issuer templates, and offline defaults.
> 2025-11-02: AUTH-PACKS-41-001 added shared OpenSSL 1.1 test libs so Authority & Signals Mongo2Go suites run on OpenSSL 3.
> 2025-11-02: ENTRYTRACE-SURFACE-02 moved to DOING (EntryTrace Guild) replacing direct env/secret access with Surface.Secrets provider for EntryTrace runs.
> 2025-11-02: ENTRYTRACE-SURFACE-01 marked DONE (EntryTrace Guild) Surface.Validation + Surface.FS cache now drive EntryTrace reuse with regression tests.
> 2025-11-02: ENTRYTRACE-SURFACE-02 marked DONE (EntryTrace Guild) EntryTrace environment placeholders resolved via Surface.Secrets with updated docs/tests.
> 2025-11-02: SCANNER-ENTRYTRACE-18-506 marked DONE (EntryTrace Guild, Scanner WebService Guild) EntryTrace graph surfaced via WebService and CLI with confidence metadata.
> 2025-11-02: SCANNER-ENTRYTRACE-18-509 moved to DOING (EntryTrace Guild, QA Guild) adding regression coverage for EntryTrace surfaces and NDJSON hashing.
> 2025-11-02: SCANNER-ENTRYTRACE-18-509 marked DONE (EntryTrace Guild, QA Guild) regression coverage landed for result store/WebService/CLI with NDJSON hashing snapshot.
> 2025-11-02: SCANNER-ENTRYTRACE-18-507 marked DONE (EntryTrace Guild) fallback candidate discovery now covers history, supervisor configs, service directories, and entrypoint scripts with tests.
> 2025-11-02: SCANNER-ENTRYTRACE-18-508 marked DONE (EntryTrace Guild) wrapper catalogue expanded for bundle, docker-php-entrypoint, npm, yarn, pipenv, and poetry with wrapper metadata assertions.
> 2025-11-02: CONCELIER-WEB-OAS-61-001 moved to DOING (Concelier WebService Guild) implementing discovery endpoint for `.well-known/openapi` with version metadata and ETag.
> 2025-11-02: CONCELIER-WEB-OAS-61-001 marked DONE (Concelier WebService Guild) discovery endpoint now serves signed OpenAPI 3.1 document with ETag support.
> 2025-11-02: DOCS-SCANNER-BENCH-62-001 moved to DOING (Docs Guild, Scanner Guild) refreshing Trivy/Grype/Snyk comparison docs and ecosystem matrix with source-linked coverage.
> 2025-11-02: DOCS-SCANNER-BENCH-62-001 marked DONE (Docs Guild, Scanner Guild) matrix updated with Windows/macOS coverage row and secret detection techniques; deep dives cite Trivy/Grype/Snyk sources.

View File

@@ -12,13 +12,14 @@ ATTEST-TYPES-72-001 | DONE | Draft JSON Schemas for BuildProvenance v1, SBOMAtte
ATTEST-TYPES-72-002 | DONE | Generate Go/TS models from schemas with validation helpers and canonical JSON serialization. Dependencies: ATTEST-TYPES-72-001. | Attestation Payloads Guild (src/Attestor/StellaOps.Attestor.Types/TASKS.md)
ATTEST-TYPES-73-001 | DONE | Create golden payload samples for each type; integrate into tests and documentation. Dependencies: ATTEST-TYPES-72-002. | Attestation Payloads Guild (src/Attestor/StellaOps.Attestor.Types/TASKS.md)
ATTEST-TYPES-73-002 | DONE | Publish schema reference docs (`/docs/modules/attestor/payloads.md`) with annotated JSON examples. Dependencies: ATTEST-TYPES-73-001. | Attestation Payloads Guild, Docs Guild (src/Attestor/StellaOps.Attestor.Types/TASKS.md)
ATTEST-VERIFY-73-001 | DONE | Implement verification engine: policy evaluation, issuer trust resolution, freshness, signature count, transparency checks; produce structured reports. | Verification Guild, Policy Guild (src/Attestor/StellaOps.Attestor.Verify/TASKS.md)
ATTEST-VERIFY-73-001 | DONE | Implement verification engine: policy evaluation, issuer trust resolution, freshness, signature count, transparency checks; produce structured reports. Dependencies: VERPOL-73-001, ATTESTOR-73-002. | Verification Guild, Policy Guild (src/Attestor/StellaOps.Attestor.Verify/TASKS.md)
ATTEST-VERIFY-73-002 | DONE | Add caching layer keyed by `(subject, envelope_id, policy_version)` with TTL and invalidation on new evidence. Dependencies: ATTEST-VERIFY-73-001. | Verification Guild (src/Attestor/StellaOps.Attestor.Verify/TASKS.md)
ATTEST-VERIFY-74-001 | DONE | Emit telemetry (spans/metrics) tagged by subject, issuer, policy, result; integrate with dashboards. Dependencies: ATTEST-VERIFY-73-002. | Verification Guild, Observability Guild (src/Attestor/StellaOps.Attestor.Verify/TASKS.md)
ATTEST-VERIFY-74-002 | DONE (2025-11-01) | Document verification report schema and explainability in `/docs/modules/attestor/workflows.md`. Dependencies: ATTEST-VERIFY-74-001. | Verification Guild, Docs Guild (src/Attestor/StellaOps.Attestor.Verify/TASKS.md)
ATTESTOR-72-001 | DONE | Scaffold service (REST API skeleton, storage interfaces, KMS integration stubs) and DSSE validation pipeline. | Attestor Service Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
ATTEST-VERIFY-74-001 | DONE | Emit telemetry (spans/metrics) tagged by subject, issuer, policy, result; integrate with dashboards. Dependencies: ATTEST-VERIFY-73-001. | Verification Guild, Observability Guild (src/Attestor/StellaOps.Attestor.Verify/TASKS.md)
ATTEST-VERIFY-74-002 | DONE (2025-11-01) | Document verification report schema and explainability in `/docs/modules/attestor/workflows.md`. Dependencies: ATTEST-VERIFY-73-001. | Verification Guild, Docs Guild (src/Attestor/StellaOps.Attestor.Verify/TASKS.md)
ATTESTOR-72-001 | DONE | Scaffold service (REST API skeleton, storage interfaces, KMS integration stubs) and DSSE validation pipeline. Dependencies: ATTEST-ENVELOPE-72-001. | Attestor Service Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
ATTESTOR-72-002 | DONE | Implement attestation store (DB tables, object storage integration), CRUD, and indexing strategies. Dependencies: ATTESTOR-72-001. | Attestor Service Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
ATTESTOR-73-001 | DONE (2025-11-01) | Implement signing endpoint with Ed25519/ECDSA support, KMS integration, and audit logging. Dependencies: ATTESTOR-72-002. | Attestor Service Guild, KMS Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
ATTESTOR-72-003 | BLOCKED | Validate attestation store TTL against production-like Mongo/Redis stack; capture logs and remediation plan. Dependencies: ATTESTOR-72-002. | Attestor Service Guild, QA Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
ATTESTOR-73-001 | DONE (2025-11-01) | Implement signing endpoint with Ed25519/ECDSA support, KMS integration, and audit logging. Dependencies: ATTESTOR-72-002, KMS-72-001. | Attestor Service Guild, KMS Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
[Identity & Signing] 100.A) Attestor.II
@@ -26,12 +27,12 @@ Depends on: Sprint 100.A - Attestor.I
Summary: Identity & Signing focus on Attestor (phase II).
Task ID | State | Task description | Owners (Source)
--- | --- | --- | ---
ATTESTOR-73-002 | DONE (2025-11-01) | Build verification pipeline evaluating DSSE signatures, issuer trust, and verification policies; persist reports. Dependencies: ATTESTOR-73-001. | Attestor Service Guild, Policy Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
ATTESTOR-73-002 | DONE (2025-11-01) | Build verification pipeline evaluating DSSE signatures, issuer trust, and verification policies; persist reports. Dependencies: ATTESTOR-73-001, VERPOL-73-001. | Attestor Service Guild, Policy Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
ATTESTOR-73-003 | DONE | Implement listing/fetch APIs with filters (subject, type, issuer, scope, date). Dependencies: ATTESTOR-73-002. | Attestor Service Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
ATTESTOR-74-001 | DONE (2025-11-02) | Integrate transparency witness client, inclusion proof verification, and caching. Dependencies: ATTESTOR-73-003. | Attestor Service Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
ATTESTOR-74-001 | DONE (2025-11-02) | Integrate transparency witness client, inclusion proof verification, and caching. Dependencies: ATTESTOR-73-002, TRANSP-74-001. | Attestor Service Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
ATTESTOR-74-002 | DONE | Implement bulk verification worker + API with progress tracking, rate limits, and caching. Dependencies: ATTESTOR-74-001. | Attestor Service Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
ATTESTOR-75-001 | DONE | Add export/import flows for attestation bundles and offline verification mode. Dependencies: ATTESTOR-74-002. | Attestor Service Guild, Export Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
ATTESTOR-75-002 | DONE | Harden APIs with rate limits, auth scopes, threat model mitigations, and fuzz testing. Dependencies: ATTESTOR-75-001. | Attestor Service Guild, Security Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
ATTESTOR-75-001 | DONE | Add export/import flows for attestation bundles and offline verification mode. Dependencies: ATTESTOR-74-002, EXPORT-ATTEST-74-001. | Attestor Service Guild, Export Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
ATTESTOR-75-002 | DONE | Harden APIs with rate limits, auth scopes, threat model mitigations, and fuzz testing. Dependencies: ATTESTOR-73-002. | Attestor Service Guild, Security Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
> 2025-11-01: ATTESTOR-73-002 completed — verification endpoints emit structured reports, cache hits, and telemetry; Attestor verification test suites cover success, failure, and cached paths. Transparency witness integration continues under ATTESTOR-74-001.
> 2025-11-02: ATTESTOR-74-001 completed — witness client wired into proof refresh, repository model stores witness statements, and verification warns on missing endorsements. Tests updated for witness refresh, bundle export/import, and signing stubs.
@@ -41,29 +42,32 @@ ATTESTOR-75-002 | DONE | Harden APIs with rate limits, auth scopes, threat model
Summary: Identity & Signing focus on Authority (phase I).
Task ID | State | Task description | Owners (Source)
--- | --- | --- | ---
AUTH-AIAI-31-001 | DONE (2025-11-01) | Define Advisory AI scopes (`advisory-ai:view`, `advisory-ai:operate`, `advisory-ai:admin`) and remote inference toggles; update discovery metadata/offline defaults. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-AIAI-31-002 | DONE (2025-11-01) | Enforce anonymized prompt logging, tenant consent for remote inference, and audit logging of assistant tasks. Dependencies: AUTH-AIAI-31-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-AIRGAP-56-001 | DOING (2025-11-01) | Provision new scopes (`airgap:seal`, `airgap:import`, `airgap:status:read`) in configuration metadata, offline kit defaults, and issuer templates. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-AIRGAP-56-002 | DOING (2025-11-01) | Audit import actions with actor, tenant, bundle ID, and trace ID; expose `/authority/audit/airgap` endpoint. Dependencies: AUTH-AIRGAP-56-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-AIRGAP-57-001 | BLOCKED (2025-11-01) | Enforce sealed-mode CI gating by refusing token issuance when declared sealed install lacks sealing confirmation. Dependencies: AUTH-AIRGAP-56-002. | Authority Core & Security Guild, DevOps Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-AIAI-31-001 | DONE (2025-11-01) | Define Advisory AI scopes (`advisory-ai:view`, `advisory-ai:operate`, `advisory-ai:admin`) and remote inference toggles; update discovery metadata/offline defaults. Dependencies: AUTH-VULN-29-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-AIAI-31-002 | DONE (2025-11-01) | Enforce anonymized prompt logging, tenant consent for remote inference, and audit logging of assistant tasks. Dependencies: AUTH-AIAI-31-001, AIAI-31-006. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-AIRGAP-56-001 | DOING (2025-11-01) | Provision new scopes (`airgap:seal`, `airgap:import`, `airgap:status:read`) in configuration metadata, offline kit defaults, and issuer templates. Dependencies: AIRGAP-CTL-56-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-AIRGAP-56-002 | DOING (2025-11-01) | Audit import actions with actor, tenant, bundle ID, and trace ID; expose `/authority/audit/airgap` endpoint. Dependencies: AUTH-AIRGAP-56-001, AIRGAP-IMP-58-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-AIRGAP-57-001 | BLOCKED (2025-11-01) | Enforce sealed-mode CI gating by refusing token issuance when declared sealed install lacks sealing confirmation. Dependencies: AUTH-AIRGAP-56-001, DEVOPS-AIRGAP-57-002. | Authority Core & Security Guild, DevOps Guild (src/Authority/StellaOps.Authority/TASKS.md)
> 2025-11-01: AUTH-AIRGAP-57-001 blocked pending definition of sealed-confirmation evidence and configuration shape before gating (Authority Core & Security Guild, DevOps Guild).
AUTH-NOTIFY-38-001 | DONE (2025-11-01) | Define `Notify.Viewer`, `Notify.Operator`, `Notify.Admin` scopes/roles, update discovery metadata, offline defaults, and issuer templates. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
> 2025-11-01: AUTH-NOTIFY-38-001 completed—Notify scope catalog, discovery metadata, docs, configuration samples, and service tests updated for new roles.
AUTH-NOTIFY-40-001 | DONE (2025-11-02) | Implement signed ack token key rotation, webhook allowlists, admin-only escalation settings, and audit logging of ack actions. Dependencies: AUTH-NOTIFY-38-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-NOTIFY-40-001 | DONE (2025-11-02) | Implement signed ack token key rotation, webhook allowlists, admin-only escalation settings, and audit logging of ack actions. Dependencies: AUTH-NOTIFY-38-001, WEB-NOTIFY-40-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
> 2025-11-02: `/notify/ack-tokens/rotate` (notify.admin) now rotates DSSE keys with audit coverage and integration tests. Webhook allowlist + escalation scope enforcement verified.
AUTH-OAS-62-001 | DONE (2025-11-02) | Provide SDK helpers for OAuth2/PAT flows, tenancy override header; add integration tests. | Authority Core & Security Guild, SDK Generator Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-OAS-62-001 | DONE (2025-11-02) | Provide SDK helpers for OAuth2/PAT flows, tenancy override header; add integration tests. Dependencies: AUTH-OAS-61-001, SDKGEN-63-001. | Authority Core & Security Guild, SDK Generator Guild (src/Authority/StellaOps.Authority/TASKS.md)
> 2025-11-02: Added HttpClient auth helper (OAuth2 + PAT) with tenant header support, plus coverage in `StellaOps.Auth.Client.Tests`.
AUTH-OAS-63-001 | DONE (2025-11-02) | Emit deprecation headers and notifications for legacy auth endpoints. Dependencies: AUTH-OAS-62-001. | Authority Core & Security Guild, API Governance Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-OAS-63-001 | DONE (2025-11-02) | Emit deprecation headers and notifications for legacy auth endpoints. Dependencies: AUTH-OAS-62-001, APIGOV-63-001. | Authority Core & Security Guild, API Governance Guild (src/Authority/StellaOps.Authority/TASKS.md)
> 2025-11-02: AUTH-OAS-63-001 marked DONE — legacy `/oauth/*` shims now emit Deprecation/Sunset/Warning headers, audit events (`authority.api.legacy_endpoint`) validated by tests, and migration guide `docs/api/authority-legacy-auth-endpoints.md` published (Authority Core & Security Guild, API Governance Guild).
AUTH-OBS-50-001 | DONE (2025-11-02) | Introduce scopes `obs:read`, `timeline:read`, `timeline:write`, `evidence:create`, `evidence:read`, `evidence:hold`, `attest:read`, and `obs:incident` (all tenant-scoped). Update discovery metadata, offline defaults, and scope grammar docs. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-OBS-50-001 | DONE (2025-11-02) | Introduce scopes `obs:read`, `timeline:read`, `timeline:write`, `evidence:create`, `evidence:read`, `evidence:hold`, `attest:read`, and `obs:incident` (all tenant-scoped). Update discovery metadata, offline defaults, and scope grammar docs. Dependencies: AUTH-AOC-19-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
> 2025-11-02: Observability scope bundle published in discovery metadata, OpenAPI, docs, and offline configs; issuer templates + roles updated with deterministic scope ordering and tests refreshed.
AUTH-OBS-52-001 | DONE (2025-11-02) | Configure resource server policies for Timeline Indexer, Evidence Locker, Exporter, and Observability APIs enforcing new scopes + tenant claims. Emit audit events including scope usage and trace IDs. Dependencies: AUTH-OBS-50-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-OBS-52-001 | DONE (2025-11-02) | Configure resource server policies for Timeline Indexer, Evidence Locker, Exporter, and Observability APIs enforcing new scopes + tenant claims. Emit audit events including scope usage and trace IDs. Dependencies: AUTH-OBS-50-001, TIMELINE-OBS-52-003, EVID-OBS-53-003. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
> 2025-11-02: Timeline/Evidence/Export resource servers now register observability policies, enforce tenant claims, and emit enriched authorization audit events; config samples + tests updated.
AUTH-OBS-55-001 | DONE (2025-11-02) | Harden incident mode authorization: require `obs:incident` scope + fresh auth, log activation reason, and expose verification endpoint for auditors. Update docs/runbooks. Dependencies: AUTH-OBS-52-001. | Authority Core & Security Guild, Ops Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-OBS-55-001 | DONE (2025-11-02) | Harden incident mode authorization: require `obs:incident` scope + fresh auth, log activation reason, and expose verification endpoint for auditors. Update docs/runbooks. Dependencies: AUTH-OBS-50-001, WEB-OBS-55-001. | Authority Core & Security Guild, Ops Guild (src/Authority/StellaOps.Authority/TASKS.md)
> 2025-11-02: Resource servers now enforce a five-minute fresh-auth window for `obs:incident`, incident reasons are stamped into authorization audits and `/authority/audit/incident`, and sample configs/tests updated to require tenant headers across observability endpoints.
AUTH-ORCH-34-001 | DOING (2025-11-02) | Introduce `Orch.Admin` role with quota/backfill scopes, enforce audit reason on quota changes, and update offline defaults/docs. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-PACKS-41-001 | TODO | Define CLI SSO profiles and pack scopes (`Packs.Read`, `Packs.Write`, `Packs.Run`, `Packs.Approve`), update discovery metadata, offline defaults, and issuer templates. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-PACKS-43-001 | BLOCKED (2025-10-27) | Enforce pack signing policies, approval RBAC checks, CLI CI token scopes, and audit logging for approvals. Dependencies: AUTH-PACKS-41-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-ORCH-34-001 | DONE (2025-11-02) | Introduce `Orch.Admin` role with quota/backfill scopes, enforce audit reason on quota changes, and update offline defaults/docs. Dependencies: AUTH-ORCH-33-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
> 2025-11-02: Added `orch:backfill` scope with required `backfill_reason`/`backfill_ticket`, tightened Authority handlers/tests, updated CLI configuration/env vars, and refreshed docs + samples for Orchestrator admins.
AUTH-PACKS-41-001 | DOING (2025-11-02) | Define CLI SSO profiles and pack scopes (`Packs.Read`, `Packs.Write`, `Packs.Run`, `Packs.Approve`), update discovery metadata, offline defaults, and issuer templates. Dependencies: AUTH-AOC-19-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
> 2025-11-02: Pack scope policies added, Authority samples/roles refreshed, and CLI SSO profiles documented for packs operators/publishers/approvers.
> 2025-11-02: Shared OpenSSL 1.1 shim now feeds Mongo2Go for Authority & Signals tests, keeping pack scope regressions and other Mongo flows working on OpenSSL 3 hosts.
AUTH-PACKS-43-001 | BLOCKED (2025-10-27) | Enforce pack signing policies, approval RBAC checks, CLI CI token scopes, and audit logging for approvals. Dependencies: AUTH-PACKS-41-001, TASKRUN-42-001, ORCH-SVC-42-101. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
[Identity & Signing] 100.B) Authority.II
@@ -71,14 +75,14 @@ Depends on: Sprint 100.B - Authority.I
Summary: Identity & Signing focus on Authority (phase II).
Task ID | State | Task description | Owners (Source)
--- | --- | --- | ---
AUTH-POLICY-23-002 | BLOCKED (2025-10-29) | Implement optional two-person rule for activation: require two distinct `policy:activate` approvals when configured; emit audit logs. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-POLICY-23-003 | BLOCKED (2025-10-29) | Update documentation and sample configs for policy roles, approval workflow, and signing requirements. Dependencies: AUTH-POLICY-23-002. | Authority Core & Docs Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-POLICY-27-002 | TODO | Provide attestation signing service bindings (OIDC token exchange, cosign integration) and enforce publish/promote scope checks, fresh-auth requirements, and audit logging. Dependencies: AUTH-POLICY-23-003. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-POLICY-27-003 | TODO | Update Authority configuration/docs for Policy Studio roles, signing policies, approval workflows, and CLI integration; include compliance checklist. Dependencies: AUTH-POLICY-27-002. | Authority Core & Docs Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-TEN-49-001 | TODO | Implement service accounts & delegation tokens (`act` chain), per-tenant quotas, audit stream of auth decisions, and revocation APIs. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-VULN-29-001 | TODO | Define Vuln Explorer scopes/roles (`vuln:view`, `vuln:investigate`, `vuln:operate`, `vuln:audit`) with ABAC attributes (env, owner, business_tier) and update discovery metadata/offline kit defaults. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-VULN-29-002 | TODO | Enforce CSRF/anti-forgery tokens for workflow actions, sign attachment tokens, and record audit logs with ledger event hashes. Dependencies: AUTH-VULN-29-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-VULN-29-003 | TODO | Update security docs/config samples for Vuln Explorer roles, ABAC policies, attachment signing, and ledger verification guidance. Dependencies: AUTH-VULN-29-002. | Authority Core & Docs Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-POLICY-23-002 | BLOCKED (2025-10-29) | Implement optional two-person rule for activation: require two distinct `policy:activate` approvals when configured; emit audit logs. Dependencies: AUTH-POLICY-23-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-POLICY-23-003 | BLOCKED (2025-10-29) | Update documentation and sample configs for policy roles, approval workflow, and signing requirements. Dependencies: AUTH-POLICY-23-001. | Authority Core & Docs Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-POLICY-27-002 | TODO | Provide attestation signing service bindings (OIDC token exchange, cosign integration) and enforce publish/promote scope checks, fresh-auth requirements, and audit logging. Dependencies: AUTH-POLICY-27-001, REGISTRY-API-27-007. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-POLICY-27-003 | TODO | Update Authority configuration/docs for Policy Studio roles, signing policies, approval workflows, and CLI integration; include compliance checklist. Dependencies: AUTH-POLICY-27-001, AUTH-POLICY-27-002. | Authority Core & Docs Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-TEN-49-001 | TODO | Implement service accounts & delegation tokens (`act` chain), per-tenant quotas, audit stream of auth decisions, and revocation APIs. Dependencies: AUTH-TEN-47-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-VULN-29-001 | TODO | Define Vuln Explorer scopes/roles (`vuln:view`, `vuln:investigate`, `vuln:operate`, `vuln:audit`) with ABAC attributes (env, owner, business_tier) and update discovery metadata/offline kit defaults. Dependencies: AUTH-POLICY-27-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-VULN-29-002 | TODO | Enforce CSRF/anti-forgery tokens for workflow actions, sign attachment tokens, and record audit logs with ledger event hashes. Dependencies: AUTH-VULN-29-001, LEDGER-29-002. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
AUTH-VULN-29-003 | TODO | Update security docs/config samples for Vuln Explorer roles, ABAC policies, attachment signing, and ledger verification guidance. Dependencies: AUTH-VULN-29-001..002. | Authority Core & Docs Guild (src/Authority/StellaOps.Authority/TASKS.md)
PLG4-6.CAPABILITIES | BLOCKED (2025-10-12) | Finalise capability metadata exposure, config validation, and developer guide updates; remaining action is Docs polish/diagram export. | BE-Auth Plugin, Docs Guild (src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/TASKS.md)
PLG6.DIAGRAM | TODO | Export final sequence/component diagrams for the developer guide and add offline-friendly assets under `docs/assets/authority`. | Docs Guild (src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/TASKS.md)
PLG7.RFC | REVIEW | Socialize LDAP plugin RFC (`docs/rfcs/authority-plugin-ldap.md`) and capture guild feedback. | BE-Auth Plugin, Security Guild (src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/TASKS.md)
@@ -93,17 +97,17 @@ Task ID | State | Task description | Owners (Source)
--- | --- | --- | ---
ISSUER-30-001 | DONE (2025-11-01) | Implement issuer CRUD API with RBAC, audit logging, and tenant scoping; seed CSAF publisher metadata. | Issuer Directory Guild (src/IssuerDirectory/StellaOps.IssuerDirectory/TASKS.md)
ISSUER-30-002 | DONE (2025-11-01) | Implement key management endpoints (add/rotate/revoke keys), enforce expiry, validate formats (Ed25519, X.509, DSSE). Dependencies: ISSUER-30-001. | Issuer Directory Guild, Security Guild (src/IssuerDirectory/StellaOps.IssuerDirectory/TASKS.md)
ISSUER-30-003 | DOING | Provide trust weight APIs and tenant overrides with validation (+/- bounds) and audit trails. Dependencies: ISSUER-30-002. | Issuer Directory Guild, Policy Guild (src/IssuerDirectory/StellaOps.IssuerDirectory/TASKS.md)
ISSUER-30-004 | DONE (2025-11-01) | Integrate with VEX Lens and Excitator signature verification (client SDK, caching, retries). Dependencies: ISSUER-30-003. | Issuer Directory Guild, VEX Lens Guild (src/IssuerDirectory/StellaOps.IssuerDirectory/TASKS.md)
ISSUER-30-005 | DONE (2025-11-01) | Instrument metrics/logs (issuer changes, key rotation, verification failures) and dashboards/alerts. Dependencies: ISSUER-30-004. | Issuer Directory Guild, Observability Guild (src/IssuerDirectory/StellaOps.IssuerDirectory/TASKS.md)
ISSUER-30-006 | TODO | Provide deployment manifests, backup/restore, secure secret storage, and offline kit instructions. Dependencies: ISSUER-30-005. | Issuer Directory Guild, DevOps Guild (src/IssuerDirectory/StellaOps.IssuerDirectory/TASKS.md)
ISSUER-30-003 | DOING | Provide trust weight APIs and tenant overrides with validation (+/- bounds) and audit trails. Dependencies: ISSUER-30-001. | Issuer Directory Guild, Policy Guild (src/IssuerDirectory/StellaOps.IssuerDirectory/TASKS.md)
ISSUER-30-004 | DONE (2025-11-01) | Integrate with VEX Lens and Excitator signature verification (client SDK, caching, retries). Dependencies: ISSUER-30-001..003. | Issuer Directory Guild, VEX Lens Guild (src/IssuerDirectory/StellaOps.IssuerDirectory/TASKS.md)
ISSUER-30-005 | DONE (2025-11-01) | Instrument metrics/logs (issuer changes, key rotation, verification failures) and dashboards/alerts. Dependencies: ISSUER-30-001..004. | Issuer Directory Guild, Observability Guild (src/IssuerDirectory/StellaOps.IssuerDirectory/TASKS.md)
ISSUER-30-006 | TODO | Provide deployment manifests, backup/restore, secure secret storage, and offline kit instructions. Dependencies: ISSUER-30-001..005. | Issuer Directory Guild, DevOps Guild (src/IssuerDirectory/StellaOps.IssuerDirectory/TASKS.md)
[Identity & Signing] 100.D) __Libraries
Summary: Identity & Signing focus on Libraries.
Task ID | State | Task description | Owners (Source)
--- | --- | --- | ---
KMS-73-001 | TODO | Add cloud KMS driver (e.g., AWS KMS, GCP KMS) with signing and key metadata retrieval. | KMS Guild (src/__Libraries/StellaOps.Cryptography.Kms/TASKS.md)
KMS-73-001 | TODO | Add cloud KMS driver (e.g., AWS KMS, GCP KMS) with signing and key metadata retrieval. Dependencies: KMS-72-001. | KMS Guild (src/__Libraries/StellaOps.Cryptography.Kms/TASKS.md)
KMS-73-002 | TODO | Implement PKCS#11/HSM driver plus FIDO2 signing support for high assurance workflows. Dependencies: KMS-73-001. | KMS Guild (src/__Libraries/StellaOps.Cryptography.Kms/TASKS.md)

View File

@@ -2,18 +2,18 @@
[Ingestion & Evidence] 110.A) AdvisoryAI
Depends on: Sprint 100.A - Attestor
Summary: Ingestion & Evidence focus on AdvisoryAI).
Summary: Ingestion & Evidence focus on AdvisoryAI.
Task ID | State | Task description | Owners (Source)
--- | --- | --- | ---
AIAI-31-001 | TODO | Implement structured and vector retrievers for advisories/VEX with paragraph anchors and citation metadata. | Advisory AI Guild (src/AdvisoryAI/StellaOps.AdvisoryAI/TASKS.md)
AIAI-31-002 | TODO | Build SBOM context retriever (purl version timelines, dependency paths, env flags, blast radius estimator). Dependencies: AIAI-31-001. | Advisory AI Guild, SBOM Service Guild (src/AdvisoryAI/StellaOps.AdvisoryAI/TASKS.md)
AIAI-31-003 | TODO | Implement deterministic toolset (version comparators, range checks, dependency analysis, policy lookup) exposed via orchestrator. Dependencies: AIAI-31-002. | Advisory AI Guild (src/AdvisoryAI/StellaOps.AdvisoryAI/TASKS.md)
AIAI-31-004 | TODO | Build orchestration pipeline for Summary/Conflict/Remediation tasks (prompt templates, tool calls, token budgets, caching). Dependencies: AIAI-31-003. | Advisory AI Guild (src/AdvisoryAI/StellaOps.AdvisoryAI/TASKS.md)
AIAI-31-001 | DOING (2025-11-02) | Implement structured and vector retrievers for advisories/VEX with paragraph anchors and citation metadata. Dependencies: CONCELIER-VULN-29-001, EXCITITOR-VULN-29-001. | Advisory AI Guild (src/AdvisoryAI/StellaOps.AdvisoryAI/TASKS.md)
AIAI-31-002 | TODO | Build SBOM context retriever (purl version timelines, dependency paths, env flags, blast radius estimator). Dependencies: SBOM-VULN-29-001. | Advisory AI Guild, SBOM Service Guild (src/AdvisoryAI/StellaOps.AdvisoryAI/TASKS.md)
AIAI-31-003 | TODO | Implement deterministic toolset (version comparators, range checks, dependency analysis, policy lookup) exposed via orchestrator. Dependencies: AIAI-31-001..002. | Advisory AI Guild (src/AdvisoryAI/StellaOps.AdvisoryAI/TASKS.md)
AIAI-31-004 | TODO | Build orchestration pipeline for Summary/Conflict/Remediation tasks (prompt templates, tool calls, token budgets, caching). Dependencies: AIAI-31-001..003, AUTH-VULN-29-001. | Advisory AI Guild (src/AdvisoryAI/StellaOps.AdvisoryAI/TASKS.md)
AIAI-31-005 | TODO | Implement guardrails (redaction, injection defense, output validation, citation enforcement) and fail-safe handling. Dependencies: AIAI-31-004. | Advisory AI Guild, Security Guild (src/AdvisoryAI/StellaOps.AdvisoryAI/TASKS.md)
AIAI-31-006 | TODO | Expose REST API endpoints (`/advisory/ai/*`) with RBAC, rate limits, OpenAPI schemas, and batching support. Dependencies: AIAI-31-005. | Advisory AI Guild (src/AdvisoryAI/StellaOps.AdvisoryAI/TASKS.md)
AIAI-31-007 | TODO | Instrument metrics (`advisory_ai_latency`, `guardrail_blocks`, `validation_failures`, `citation_coverage`), logs, and traces; publish dashboards/alerts. Dependencies: AIAI-31-006. | Advisory AI Guild, Observability Guild (src/AdvisoryAI/StellaOps.AdvisoryAI/TASKS.md)
AIAI-31-008 | TODO | Package inference on-prem container, remote inference toggle, Helm/Compose manifests, scaling guidance, offline kit instructions. Dependencies: AIAI-31-007. | Advisory AI Guild, DevOps Guild (src/AdvisoryAI/StellaOps.AdvisoryAI/TASKS.md)
AIAI-31-009 | TODO | Develop unit/golden/property/perf tests, injection harness, and regression suite; ensure determinism with seeded caches. Dependencies: AIAI-31-008. | Advisory AI Guild, QA Guild (src/AdvisoryAI/StellaOps.AdvisoryAI/TASKS.md)
AIAI-31-006 | TODO | Expose REST API endpoints (`/advisory/ai/*`) with RBAC, rate limits, OpenAPI schemas, and batching support. Dependencies: AIAI-31-004..005. | Advisory AI Guild (src/AdvisoryAI/StellaOps.AdvisoryAI/TASKS.md)
AIAI-31-007 | TODO | Instrument metrics (`advisory_ai_latency`, `guardrail_blocks`, `validation_failures`, `citation_coverage`), logs, and traces; publish dashboards/alerts. Dependencies: AIAI-31-004..006. | Advisory AI Guild, Observability Guild (src/AdvisoryAI/StellaOps.AdvisoryAI/TASKS.md)
AIAI-31-008 | TODO | Package inference on-prem container, remote inference toggle, Helm/Compose manifests, scaling guidance, offline kit instructions. Dependencies: AIAI-31-006..007. | Advisory AI Guild, DevOps Guild (src/AdvisoryAI/StellaOps.AdvisoryAI/TASKS.md)
AIAI-31-009 | TODO | Develop unit/golden/property/perf tests, injection harness, and regression suite; ensure determinism with seeded caches. Dependencies: AIAI-31-001..006. | Advisory AI Guild, QA Guild (src/AdvisoryAI/StellaOps.AdvisoryAI/TASKS.md)
[Ingestion & Evidence] 110.B) Concelier.I
@@ -23,19 +23,19 @@ Task ID | State | Task description | Owners (Source)
--- | --- | --- | ---
CONCELIER-AIAI-31-001 `Paragraph anchors` | TODO | Expose advisory chunk API returning paragraph anchors, section metadata, and token-safe text for Advisory AI retrieval. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
CONCELIER-AIAI-31-002 `Structured fields` | TODO | Ensure observation APIs expose upstream workaround/fix/CVSS fields with provenance; add caching for summary queries. Dependencies: CONCELIER-AIAI-31-001. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
CONCELIER-AIAI-31-003 `Advisory AI telemetry` | TODO | Emit metrics/logs for chunk requests, cache hits, and guardrail blocks triggered by advisory payloads. Dependencies: CONCELIER-AIAI-31-002. | Concelier WebService Guild, Observability Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
CONCELIER-AIRGAP-56-001 `Mirror ingestion adapters` | TODO | Add mirror source adapters reading advisories from imported bundles, preserving source metadata and bundle IDs. Ensure ingestion remains append-only. | Concelier Core Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-AIRGAP-56-002 `Bundle catalog linking` | TODO | Persist `bundle_id`, `merkle_root`, and time anchor references on observations/linksets for provenance. Dependencies: CONCELIER-AIRGAP-56-001. | Concelier Core Guild, AirGap Importer Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-AIRGAP-57-001 `Sealed-mode source restrictions` | TODO | Enforce sealed-mode egress rules by disallowing non-mirror connectors and surfacing remediation errors. Dependencies: CONCELIER-AIRGAP-56-002. | Concelier Core Guild, AirGap Policy Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-AIRGAP-57-002 `Staleness annotations` | TODO | Compute staleness metadata for advisories per bundle and expose via API for Console/CLI badges. Dependencies: CONCELIER-AIRGAP-57-001. | Concelier Core Guild, AirGap Time Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-AIRGAP-58-001 `Portable advisory evidence` | TODO | Package advisory evidence fragments into portable evidence bundles for cross-domain transfer. Dependencies: CONCELIER-AIRGAP-57-002. | Concelier Core Guild, Evidence Locker Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-ATTEST-73-001 `ScanResults attestation inputs` | TODO | Provide observation artifacts and linkset digests needed for ScanResults attestations (raw data + provenance, no merge outputs). | Concelier Core Guild, Attestor Service Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-AIAI-31-003 `Advisory AI telemetry` | TODO | Emit metrics/logs for chunk requests, cache hits, and guardrail blocks triggered by advisory payloads. Dependencies: CONCELIER-AIAI-31-001. | Concelier WebService Guild, Observability Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
CONCELIER-AIRGAP-56-001 `Mirror ingestion adapters` | TODO | Add mirror source adapters reading advisories from imported bundles, preserving source metadata and bundle IDs. Ensure ingestion remains append-only. Dependencies: AIRGAP-IMP-57-002, MIRROR-CRT-56-001. | Concelier Core Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-AIRGAP-56-002 `Bundle catalog linking` | TODO | Persist `bundle_id`, `merkle_root`, and time anchor references on observations/linksets for provenance. Dependencies: CONCELIER-AIRGAP-56-001, AIRGAP-IMP-57-001. | Concelier Core Guild, AirGap Importer Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-AIRGAP-57-001 `Sealed-mode source restrictions` | TODO | Enforce sealed-mode egress rules by disallowing non-mirror connectors and surfacing remediation errors. Dependencies: CONCELIER-AIRGAP-56-001, AIRGAP-POL-56-001. | Concelier Core Guild, AirGap Policy Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-AIRGAP-57-002 `Staleness annotations` | TODO | Compute staleness metadata for advisories per bundle and expose via API for Console/CLI badges. Dependencies: CONCELIER-AIRGAP-56-002, AIRGAP-TIME-58-001. | Concelier Core Guild, AirGap Time Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-AIRGAP-58-001 `Portable advisory evidence` | TODO | Package advisory evidence fragments into portable evidence bundles for cross-domain transfer. Dependencies: CONCELIER-OBS-53-001, EVID-OBS-54-001. | Concelier Core Guild, Evidence Locker Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-ATTEST-73-001 `ScanResults attestation inputs` | TODO | Provide observation artifacts and linkset digests needed for ScanResults attestations (raw data + provenance, no merge outputs). Dependencies: ATTEST-TYPES-72-001. | Concelier Core Guild, Attestor Service Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-ATTEST-73-002 `Transparency metadata` | TODO | Ensure Conseiller exposes source digests for transparency proofs and explainability. Dependencies: CONCELIER-ATTEST-73-001. | Concelier Core Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-CONSOLE-23-001 `Advisory aggregation views` | TODO | Expose `/console/advisories` endpoints returning aggregation groups (per linkset) with source chips, provider-reported severity columns (no local consensus), and provenance metadata for Console list + dashboard cards. Support filters by source, ecosystem, published/modified window, tenant enforcement. | Concelier WebService Guild, BE-Base Platform Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
CONCELIER-CONSOLE-23-002 `Dashboard deltas API` | TODO | Provide aggregated advisory delta counts (new, modified, conflicting) for Console dashboard + live status ticker; emit structured events for queue lag metrics. Ensure deterministic counts across repeated queries. Dependencies: CONCELIER-CONSOLE-23-001. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
CONCELIER-CONSOLE-23-003 `Search fan-out helpers` | TODO | Deliver fast lookup endpoints for CVE/GHSA/purl search (linksets, observations) returning evidence fragments for Console global search; implement caching + scope guards. Dependencies: CONCELIER-CONSOLE-23-002. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
CONCELIER-CORE-AOC-19-004 `Remove ingestion normalization` | DOING (2025-10-28) | Strip normalization/dedup/severity logic from ingestion pipelines, delegate derived computations to Policy Engine, and update exporters/tests to consume raw documents only.<br>2025-10-29 19:05Z: Audit completed for `AdvisoryRawService`/Mongo repo to confirm alias order/dedup removal persists; identified remaining normalization in observation/linkset factory that will be revised to surface raw duplicates for Policy ingestion. Change sketch + regression matrix drafted under `docs/dev/aoc-normalization-removal-notes.md` (pending commit).<br>2025-10-31 20:45Z: Added raw linkset projection to observations/storage, exposing canonical+raw views, refreshed fixtures/tests, and documented behaviour in models/doc factory.<br>2025-10-31 21:10Z: Coordinated with Policy Engine (POLICY-ENGINE-20-003) on adoption timeline; backfill + consumer readiness tracked in `docs/dev/raw-linkset-backfill-plan.md`. | Concelier Core Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-CORE-AOC-19-013 `Authority tenant scope smoke coverage` | TODO | Extend Concelier smoke/e2e fixtures to configure `requiredTenants` and assert cross-tenant rejection with updated Authority tokens. Dependencies: CONCELIER-CORE-AOC-19-004. | Concelier Core Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-CONSOLE-23-001 `Advisory aggregation views` | TODO | Expose `/console/advisories` endpoints returning aggregation groups (per linkset) with source chips, provider-reported severity columns (no local consensus), and provenance metadata for Console list + dashboard cards. Support filters by source, ecosystem, published/modified window, tenant enforcement. Dependencies: CONCELIER-LNM-21-201, CONCELIER-LNM-21-202. | Concelier WebService Guild, BE-Base Platform Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
CONCELIER-CONSOLE-23-002 `Dashboard deltas API` | TODO | Provide aggregated advisory delta counts (new, modified, conflicting) for Console dashboard + live status ticker; emit structured events for queue lag metrics. Ensure deterministic counts across repeated queries. Dependencies: CONCELIER-CONSOLE-23-001, CONCELIER-LNM-21-203. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
CONCELIER-CONSOLE-23-003 `Search fan-out helpers` | TODO | Deliver fast lookup endpoints for CVE/GHSA/purl search (linksets, observations) returning evidence fragments for Console global search; implement caching + scope guards. Dependencies: CONCELIER-CONSOLE-23-001. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
CONCELIER-CORE-AOC-19-004 `Remove ingestion normalization` | DOING (2025-10-28) | Strip normalization/dedup/severity logic from ingestion pipelines, delegate derived computations to Policy Engine, and update exporters/tests to consume raw documents only.<br>2025-10-29 19:05Z: Audit completed for `AdvisoryRawService`/Mongo repo to confirm alias order/dedup removal persists; identified remaining normalization in observation/linkset factory that will be revised to surface raw duplicates for Policy ingestion. Change sketch + regression matrix drafted under `docs/dev/aoc-normalization-removal-notes.md` (pending commit).<br>2025-10-31 20:45Z: Added raw linkset projection to observations/storage, exposing canonical+raw views, refreshed fixtures/tests, and documented behaviour in models/doc factory.<br>2025-10-31 21:10Z: Coordinated with Policy Engine (POLICY-ENGINE-20-003) on adoption timeline; backfill + consumer readiness tracked in `docs/dev/raw-linkset-backfill-plan.md`. Dependencies: CONCELIER-CORE-AOC-19-002, POLICY-AOC-19-003. | Concelier Core Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-CORE-AOC-19-013 `Authority tenant scope smoke coverage` | TODO | Extend Concelier smoke/e2e fixtures to configure `requiredTenants` and assert cross-tenant rejection with updated Authority tokens. Dependencies: AUTH-AOC-19-002. | Concelier Core Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
[Ingestion & Evidence] 110.B) Concelier.II
@@ -91,17 +91,17 @@ CONCELIER-POLICY-20-002 `Linkset enrichment for policy` | TODO | Strengthen link
CONCELIER-POLICY-20-003 `Selection cursors` | TODO | Add advisory/vex selection cursors (per policy run) with change stream checkpoints, indexes, and offline migration scripts to support incremental evaluations. Dependencies: CONCELIER-POLICY-20-002. | Concelier Storage Guild (src/Concelier/__Libraries/StellaOps.Concelier.Storage.Mongo/TASKS.md)
CONCELIER-POLICY-23-001 `Evidence indexes` | TODO | Add secondary indexes/materialized views to accelerate policy lookups (alias, provider severity per observation, correlation confidence). Document query contracts for runtime. Dependencies: CONCELIER-POLICY-20-003. | Concelier Core Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-POLICY-23-002 `Event guarantees` | TODO | Ensure `advisory.linkset.updated` emits at-least-once with idempotent keys and include policy-relevant metadata (confidence, conflict summary). Dependencies: CONCELIER-POLICY-23-001. | Concelier Core Guild, Platform Events Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-RISK-66-001 `CVSS/KEV providers` | TODO | Expose CVSS, KEV, fix availability data via provider APIs with source metadata preserved. | Concelier Core Guild, Risk Engine Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-RISK-66-001 `CVSS/KEV providers` | TODO | Expose CVSS, KEV, fix availability data via provider APIs with source metadata preserved. Dependencies: RISK-ENGINE-67-001. | Concelier Core Guild, Risk Engine Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-RISK-66-002 `Fix availability signals` | TODO | Provide structured fix availability and release metadata consumable by risk engine; document provenance. Dependencies: CONCELIER-RISK-66-001. | Concelier Core Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-RISK-67-001 `Source coverage metrics` | TODO | Add per-source coverage metrics for linked advisories (observation counts, conflicting statuses) without computing consensus scores; ensure explainability includes source digests. Dependencies: CONCELIER-RISK-66-002. | Concelier Core Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-RISK-68-001 `Policy Studio integration` | TODO | Surface advisory fields in Policy Studio profile editor (signal pickers, reducers). Dependencies: CONCELIER-RISK-67-001. | Concelier Core Guild, Policy Studio Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-RISK-69-001 `Notification hooks` | TODO | Emit events when advisory signals change impacting risk scores (e.g., fix available). Dependencies: CONCELIER-RISK-68-001. | Concelier Core Guild, Notifications Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-SIG-26-001 `Vulnerable symbol exposure` | TODO | Expose advisory metadata (affected symbols/functions) via API to enrich reachability scoring; update fixtures. | Concelier Core Guild, Signals Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-STORE-AOC-19-005 `Raw linkset backfill` | TODO (2025-11-04) | Plan and execute advisory_observations `rawLinkset` backfill (online + Offline Kit bundles), supply migration scripts + rehearse rollback. Follow the coordination plan in `docs/dev/raw-linkset-backfill-plan.md`. | Concelier Storage Guild, DevOps Guild (src/Concelier/__Libraries/StellaOps.Concelier.Storage.Mongo/TASKS.md)
CONCELIER-TEN-48-001 `Tenant-aware linking` | TODO | Ensure advisory normalization/linking runs per tenant with RLS enforcing isolation; emit capability endpoint reporting `merge=false`; update events with tenant context. | Concelier Core Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-VEXLENS-30-001 `Advisory rationale bridges` | TODO | Guarantee advisory key consistency and cross-links for consensus rationale; Label: VEX-Lens. | Concelier WebService Guild, VEX Lens Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
CONCELIER-VULN-29-001 `Advisory key canonicalization` | TODO | Canonicalize (lossless) advisory identifiers (CVE/GHSA/vendor) into `advisory_key`, persist `links[]`, expose raw payload snapshots for Explorer evidence tabs; AOC-compliant: no merge, no derived fields, no suppression. Include migration/backfill scripts. | Concelier WebService Guild, Data Integrity Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
CONCELIER-VULN-29-002 `Evidence retrieval API` | TODO | Provide `/vuln/evidence/advisories/{advisory_key}` returning raw advisory docs with provenance, filtering by tenant and source. Dependencies: CONCELIER-VULN-29-001. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
CONCELIER-RISK-67-001 `Source coverage metrics` | TODO | Add per-source coverage metrics for linked advisories (observation counts, conflicting statuses) without computing consensus scores; ensure explainability includes source digests. Dependencies: CONCELIER-RISK-66-001. | Concelier Core Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-RISK-68-001 `Policy Studio integration` | TODO | Surface advisory fields in Policy Studio profile editor (signal pickers, reducers). Dependencies: POLICY-RISK-68-001. | Concelier Core Guild, Policy Studio Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-RISK-69-001 `Notification hooks` | TODO | Emit events when advisory signals change impacting risk scores (e.g., fix available). Dependencies: CONCELIER-RISK-66-002. | Concelier Core Guild, Notifications Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-SIG-26-001 `Vulnerable symbol exposure` | TODO | Expose advisory metadata (affected symbols/functions) via API to enrich reachability scoring; update fixtures. Dependencies: SIGNALS-24-002. | Concelier Core Guild, Signals Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-STORE-AOC-19-005 `Raw linkset backfill` | TODO (2025-11-04) | Plan and execute advisory_observations `rawLinkset` backfill (online + Offline Kit bundles), supply migration scripts + rehearse rollback. Follow the coordination plan in `docs/dev/raw-linkset-backfill-plan.md`. Dependencies: CONCELIER-CORE-AOC-19-004. | Concelier Storage Guild, DevOps Guild (src/Concelier/__Libraries/StellaOps.Concelier.Storage.Mongo/TASKS.md)
CONCELIER-TEN-48-001 `Tenant-aware linking` | TODO | Ensure advisory normalization/linking runs per tenant with RLS enforcing isolation; emit capability endpoint reporting `merge=false`; update events with tenant context. Dependencies: AUTH-TEN-47-001. | Concelier Core Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
CONCELIER-VEXLENS-30-001 `Advisory rationale bridges` | TODO | Guarantee advisory key consistency and cross-links for consensus rationale; Label: VEX-Lens. Dependencies: CONCELIER-VULN-29-001, VEXLENS-30-005. | Concelier WebService Guild, VEX Lens Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
CONCELIER-VULN-29-001 `Advisory key canonicalization` | TODO | Canonicalize (lossless) advisory identifiers (CVE/GHSA/vendor) into `advisory_key`, persist `links[]`, expose raw payload snapshots for Explorer evidence tabs; AOC-compliant: no merge, no derived fields, no suppression. Include migration/backfill scripts. Dependencies: CONCELIER-LNM-21-001. | Concelier WebService Guild, Data Integrity Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
CONCELIER-VULN-29-002 `Evidence retrieval API` | TODO | Provide `/vuln/evidence/advisories/{advisory_key}` returning raw advisory docs with provenance, filtering by tenant and source. Dependencies: CONCELIER-VULN-29-001, VULN-API-29-003. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
[Ingestion & Evidence] 110.B) Concelier.V
@@ -109,7 +109,7 @@ Depends on: Sprint 110.B - Concelier.IV
Summary: Ingestion & Evidence focus on Concelier (phase V).
Task ID | State | Task description | Owners (Source)
--- | --- | --- | ---
CONCELIER-VULN-29-004 `Observability enhancements` | TODO | Instrument metrics/logs for observation + linkset pipelines (identifier collisions, withdrawn flags) and emit events consumed by Vuln Explorer resolver. Dependencies: CONCELIER-VULN-29-002. | Concelier WebService Guild, Observability Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
CONCELIER-VULN-29-004 `Observability enhancements` | TODO | Instrument metrics/logs for observation + linkset pipelines (identifier collisions, withdrawn flags) and emit events consumed by Vuln Explorer resolver. Dependencies: CONCELIER-VULN-29-001. | Concelier WebService Guild, Observability Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
CONCELIER-WEB-AIRGAP-56-001 `Mirror import APIs` | TODO | Extend ingestion endpoints to register mirror bundle sources, expose bundle catalog queries, and block external feed URLs in sealed mode. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
CONCELIER-WEB-AIRGAP-56-002 `Airgap status surfaces` | TODO | Add staleness metadata and bundle provenance to advisory APIs (`/advisories/observations`, `/advisories/linksets`). Dependencies: CONCELIER-WEB-AIRGAP-56-001. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
CONCELIER-WEB-AIRGAP-57-001 `Error remediation` | TODO | Map sealed-mode violations to `AIRGAP_EGRESS_BLOCKED` responses with user guidance. Dependencies: CONCELIER-WEB-AIRGAP-56-002. | Concelier WebService Guild, AirGap Policy Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
@@ -186,9 +186,9 @@ Task ID | State | Task description | Owners (Source)
--- | --- | --- | ---
EXCITITOR-CONN-SUSE-01-003 Trust metadata provenance | Team Excititor Connectors SUSE | TODO Emit provider trust configuration (signer fingerprints, trust tier notes) into the raw provenance envelope so downstream VEX Lens/Policy components can weigh issuers. Connector must not apply weighting or consensus inside ingestion. | EXCITITOR-CONN-SUSE-01-002, EXCITITOR-POLICY-01-001 (src/Excititor/__Libraries/StellaOps.Excititor.Connectors.SUSE.RancherVEXHub/TASKS.md)
EXCITITOR-CONN-UBUNTU-01-003 Trust provenance enrichment | Team Excititor Connectors Ubuntu | TODO Emit Ubuntu signing metadata (GPG fingerprints, issuer trust tier) inside raw provenance artifacts so downstream Policy/VEX Lens consumers can weigh issuers. Connector must remain aggregation-only with no inline weighting. | EXCITITOR-CONN-UBUNTU-01-002, EXCITITOR-POLICY-01-001 (src/Excititor/__Libraries/StellaOps.Excititor.Connectors.Ubuntu.CSAF/TASKS.md)
EXCITITOR-CONSOLE-23-001 `VEX aggregation views` | TODO | Expose `/console/vex` endpoints returning grouped VEX statements per advisory/component with status chips, justification metadata, precedence trace pointers, and tenant-scoped filters for Console explorer. | Excititor WebService Guild, BE-Base Platform Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
EXCITITOR-CONSOLE-23-002 `Dashboard VEX deltas` | TODO | Provide aggregated counts for VEX overrides (new, not_affected, revoked) powering Console dashboard + live status ticker; emit metrics for policy explain integration. Dependencies: EXCITITOR-CONSOLE-23-001. | Excititor WebService Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
EXCITITOR-CONSOLE-23-003 `VEX search helpers` | TODO | Deliver rapid lookup endpoints of VEX by advisory/component for Console global search; ensure response includes provenance and precedence context; include caching and RBAC. Dependencies: EXCITITOR-CONSOLE-23-002. | Excititor WebService Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
EXCITITOR-CONSOLE-23-001 `VEX aggregation views` | TODO | Expose `/console/vex` endpoints returning grouped VEX statements per advisory/component with status chips, justification metadata, precedence trace pointers, and tenant-scoped filters for Console explorer. Dependencies: EXCITITOR-LNM-21-201, EXCITITOR-LNM-21-202. | Excititor WebService Guild, BE-Base Platform Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
EXCITITOR-CONSOLE-23-002 `Dashboard VEX deltas` | TODO | Provide aggregated counts for VEX overrides (new, not_affected, revoked) powering Console dashboard + live status ticker; emit metrics for policy explain integration. Dependencies: EXCITITOR-CONSOLE-23-001, EXCITITOR-LNM-21-203. | Excititor WebService Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
EXCITITOR-CONSOLE-23-003 `VEX search helpers` | TODO | Deliver rapid lookup endpoints of VEX by advisory/component for Console global search; ensure response includes provenance and precedence context; include caching and RBAC. Dependencies: EXCITITOR-CONSOLE-23-001. | Excititor WebService Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
EXCITITOR-CORE-AOC-19-002 `VEX linkset extraction` | TODO | Implement deterministic extraction of advisory IDs, component PURLs, and references into `linkset`, capturing reconciled-from metadata for traceability. | Excititor Core Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
EXCITITOR-CORE-AOC-19-003 `Idempotent VEX raw upsert` | TODO | Enforce `(vendor, upstreamId, contentHash, tenant)` uniqueness, generate supersedes chains, and ensure append-only versioning of raw VEX documents. Dependencies: EXCITITOR-CORE-AOC-19-002. | Excititor Core Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
EXCITITOR-CORE-AOC-19-004 `Remove ingestion consensus` | TODO | Excise consensus/merge/severity logic from Excititor ingestion paths, updating exports/tests to rely on Policy Engine materializations instead. Dependencies: EXCITITOR-CORE-AOC-19-003. | Excititor Core Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
@@ -291,15 +291,15 @@ EXCITITOR-WEB-OBS-55-001 `Incident mode toggles` | TODO | Provide incident mode
[Ingestion & Evidence] 110.D) Mirror
Depends on: Sprint 100.A - Attestor
Summary: Ingestion & Evidence focus on Mirror).
Summary: Ingestion & Evidence focus on Mirror.
Task ID | State | Task description | Owners (Source)
--- | --- | --- | ---
MIRROR-CRT-56-001 | TODO | Implement deterministic bundle assembler supporting advisories, VEX, policy packs with Zstandard compression and manifest generation. | Mirror Creator Guild (src/Mirror/StellaOps.Mirror.Creator/TASKS.md)
MIRROR-CRT-56-002 | TODO | Integrate DSSE signing and TUF metadata generation (`root`, `snapshot`, `timestamp`, `targets`). Dependencies: MIRROR-CRT-56-001. | Mirror Creator Guild, Security Guild (src/Mirror/StellaOps.Mirror.Creator/TASKS.md)
MIRROR-CRT-57-001 | TODO | Add optional OCI image collection producing oci-archive layout with digests recorded in manifest. Dependencies: MIRROR-CRT-56-002. | Mirror Creator Guild, DevOps Guild (src/Mirror/StellaOps.Mirror.Creator/TASKS.md)
MIRROR-CRT-57-002 | TODO | Embed signed time anchor metadata (`meta/time-anchor.json`) sourced from trusted authority. Dependencies: MIRROR-CRT-57-001. | Mirror Creator Guild, AirGap Time Guild (src/Mirror/StellaOps.Mirror.Creator/TASKS.md)
MIRROR-CRT-58-001 | TODO | Deliver CLI `stella mirror create. Dependencies: MIRROR-CRT-57-002. | Mirror Creator Guild, CLI Guild (src/Mirror/StellaOps.Mirror.Creator/TASKS.md)
MIRROR-CRT-58-002 | TODO | Integrate with Export Center scheduling to automate mirror bundle creation with audit logs. Dependencies: MIRROR-CRT-58-001. | Mirror Creator Guild, Exporter Guild (src/Mirror/StellaOps.Mirror.Creator/TASKS.md)
MIRROR-CRT-56-001 | TODO | Implement deterministic bundle assembler supporting advisories, VEX, policy packs with Zstandard compression and manifest generation. Dependencies: EXPORT-OBS-51-001. | Mirror Creator Guild (src/Mirror/StellaOps.Mirror.Creator/TASKS.md)
MIRROR-CRT-56-002 | TODO | Integrate DSSE signing and TUF metadata generation (`root`, `snapshot`, `timestamp`, `targets`). Dependencies: MIRROR-CRT-56-001, PROV-OBS-53-001. | Mirror Creator Guild, Security Guild (src/Mirror/StellaOps.Mirror.Creator/TASKS.md)
MIRROR-CRT-57-001 | TODO | Add optional OCI image collection producing oci-archive layout with digests recorded in manifest. Dependencies: MIRROR-CRT-56-001. | Mirror Creator Guild, DevOps Guild (src/Mirror/StellaOps.Mirror.Creator/TASKS.md)
MIRROR-CRT-57-002 | TODO | Embed signed time anchor metadata (`meta/time-anchor.json`) sourced from trusted authority. Dependencies: MIRROR-CRT-56-002, AIRGAP-TIME-57-001. | Mirror Creator Guild, AirGap Time Guild (src/Mirror/StellaOps.Mirror.Creator/TASKS.md)
MIRROR-CRT-58-001 | TODO | Deliver CLI `stella mirror create|verify` commands with content selection flags, delta mode, and dry-run verification. Dependencies: MIRROR-CRT-56-002, CLI-AIRGAP-56-001. | Mirror Creator Guild, CLI Guild (src/Mirror/StellaOps.Mirror.Creator/TASKS.md)
MIRROR-CRT-58-002 | TODO | Integrate with Export Center scheduling to automate mirror bundle creation with audit logs. Dependencies: MIRROR-CRT-56-002, EXPORT-OBS-54-001. | Mirror Creator Guild, Exporter Guild (src/Mirror/StellaOps.Mirror.Creator/TASKS.md)
If all tasks are done - read next sprint section - SPRINT_120_policy_reasoning.md

View File

@@ -10,8 +10,8 @@ Task ID | State | Task description | Owners (Source)
ENTRYTRACE-SURFACE-01 | DONE (2025-11-02) | Run Surface.Validation prereq checks and resolve cached entry fragments via Surface.FS to avoid duplicate parsing. | EntryTrace Guild (src/Scanner/__Libraries/StellaOps.Scanner.EntryTrace/TASKS.md)
ENTRYTRACE-SURFACE-02 | DONE (2025-11-02) | Replace direct env/secret access with Surface.Secrets provider when tracing runtime configs. Dependencies: ENTRYTRACE-SURFACE-01. | EntryTrace Guild (src/Scanner/__Libraries/StellaOps.Scanner.EntryTrace/TASKS.md)
SCANNER-ENTRYTRACE-18-509 | DONE (2025-11-02) | Add regression coverage for EntryTrace surfaces (result store, WebService endpoint, CLI renderer) and NDJSON hashing. | EntryTrace Guild, QA Guild (src/Scanner/__Libraries/StellaOps.Scanner.EntryTrace/TASKS.md)
SCANNER-ENTRYTRACE-18-507 | DOING (2025-11-02) | Expand candidate discovery beyond ENTRYPOINT/CMD by scanning Docker history metadata and default service directories (`/etc/services/**`, `/s6/**`, `/etc/supervisor/*.conf`, `/usr/local/bin/*-entrypoint`) when explicit commands are absent. Dependencies: SCANNER-ENTRYTRACE-18-509. | EntryTrace Guild (src/Scanner/__Libraries/StellaOps.Scanner.EntryTrace/TASKS.md)
SCANNER-ENTRYTRACE-18-508 | DOING (2025-11-02) | Extend wrapper catalogue to collapse language/package launchers (`bundle`, `bundle exec`, `docker-php-entrypoint`, `npm`, `yarn node`, `pipenv`, `poetry run`) and vendor init scripts before terminal classification. Dependencies: SCANNER-ENTRYTRACE-18-507. | EntryTrace Guild (src/Scanner/__Libraries/StellaOps.Scanner.EntryTrace/TASKS.md)
SCANNER-ENTRYTRACE-18-507 | DONE (2025-11-02) | Expand candidate discovery beyond ENTRYPOINT/CMD by scanning Docker history metadata and default service directories (`/etc/services/**`, `/s6/**`, `/etc/supervisor/*.conf`, `/usr/local/bin/*-entrypoint`) when explicit commands are absent. Dependencies: SCANNER-ENTRYTRACE-18-509. | EntryTrace Guild (src/Scanner/__Libraries/StellaOps.Scanner.EntryTrace/TASKS.md)
SCANNER-ENTRYTRACE-18-508 | DONE (2025-11-02) | Extend wrapper catalogue to collapse language/package launchers (`bundle`, `bundle exec`, `docker-php-entrypoint`, `npm`, `yarn node`, `pipenv`, `poetry run`) and vendor init scripts before terminal classification. Dependencies: SCANNER-ENTRYTRACE-18-507. | EntryTrace Guild (src/Scanner/__Libraries/StellaOps.Scanner.EntryTrace/TASKS.md)
LANG-SURFACE-01 | TODO | Invoke Surface.Validation checks (env/cache/secrets) before analyzer execution to ensure consistent prerequisites. | Language Analyzer Guild (src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang/TASKS.md)
LANG-SURFACE-02 | TODO | Consume Surface.FS APIs for layer/source caching (instead of bespoke caches) to improve determinism. Dependencies: LANG-SURFACE-01. | Language Analyzer Guild (src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang/TASKS.md)
LANG-SURFACE-03 | TODO | Replace direct secret/env reads with Surface.Secrets references when fetching package feeds or registry creds. Dependencies: LANG-SURFACE-02. | Language Analyzer Guild (src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang/TASKS.md)

View File

@@ -205,6 +205,7 @@ Task ID | State | Task description | Owners (Source)
--- | --- | --- | ---
DOCS-SIG-26-008 | TODO | Write `/docs/migration/enable-reachability.md` guiding rollout, fallbacks, monitoring. Dependencies: DOCS-SIG-26-007. | Docs Guild, DevOps Guild (docs/TASKS.md)
DOCS-SURFACE-01 | TODO | Create `/docs/modules/scanner/scanner-engine.md` covering Surface.FS/Env/Secrets workflow between Scanner, Zastava, Scheduler, and Ops. | Docs Guild, Scanner Guild, Zastava Guild (docs/TASKS.md)
DOCS-SCANNER-BENCH-62-001 | DONE (2025-11-02) | Refresh scanner comparison docs (Trivy/Grype/Snyk) and keep ecosystem matrix aligned with source implementations. | Docs Guild, Scanner Guild (docs/TASKS.md)
DOCS-TEN-47-001 | TODO | Publish `/docs/security/tenancy-overview.md` and `/docs/security/scopes-and-roles.md` outlining scope grammar, tenant model, imposed rule reminder. | Docs Guild, Authority Core (docs/TASKS.md)
DOCS-TEN-48-001 | TODO | Publish `/docs/operations/multi-tenancy.md`, `/docs/operations/rls-and-data-isolation.md`, `/docs/console/admin-tenants.md`. Dependencies: DOCS-TEN-47-001. | Docs Guild, Platform Ops (docs/TASKS.md)
DOCS-TEN-49-001 | TODO | Publish `/docs/modules/cli/guides/authentication.md`, `/docs/api/authentication.md`, `/docs/policy/examples/abac-overlays.md`, update `/docs/install/configuration-reference.md` with new env vars, all ending with imposed rule line. Dependencies: DOCS-TEN-48-001. | Docs & DevEx Guilds (docs/TASKS.md)

View File

@@ -289,6 +289,7 @@ Additional notes:
- [Architecture overview](../../platform/architecture-overview.md)
- [Console AOC dashboard](../../../ui/console.md)
- [Authority scopes](../../authority/architecture.md)
- [Task Pack CLI profiles](./packs-profiles.md)
---
@@ -303,7 +304,7 @@ Additional notes:
---
*Last updated: 2025-10-29 (Sprint24).*
*Last updated: 2025-11-02 (Sprint100).*
## 13. Authority configuration quick reference
@@ -313,6 +314,9 @@ Additional notes:
| `StellaOps:Authority:OperatorTicket` | Change/incident ticket reference paired with orchestrator control actions. | CLI flag `--Authority:OperatorTicket=...` or env `STELLAOPS_ORCH_TICKET`. |
| `StellaOps:Authority:QuotaReason` | Required justification recorded with `orch:quota` tokens. | CLI flag `--Authority:QuotaReason=...` or env `STELLAOPS_ORCH_QUOTA_REASON`. |
| `StellaOps:Authority:QuotaTicket` | Optional change ticket/reference accompanying quota adjustments. | CLI flag `--Authority:QuotaTicket=...` or env `STELLAOPS_ORCH_QUOTA_TICKET`. |
| `StellaOps:Authority:BackfillReason` | Required justification recorded with `orch:backfill` tokens. | CLI flag `--Authority:BackfillReason=...` or env `STELLAOPS_ORCH_BACKFILL_REASON`. |
| `StellaOps:Authority:BackfillTicket` | Required ticket/reference accompanying historical backfill runs. | CLI flag `--Authority:BackfillTicket=...` or env `STELLAOPS_ORCH_BACKFILL_TICKET`. |
| `StellaOps:Authority:Scope` | Default scope string requested during `stella auth login`. | CLI flag `--Authority:Scope=\"packs.read packs.run\"` or env `STELLAOPS_AUTHORITY_SCOPE`; see `docs/modules/cli/guides/packs-profiles.md` for common Task Pack profiles. |
> Tokens requesting `orch:operate` fail with `invalid_request` unless both operator values are present. `orch:quota` tokens require `quota_reason` (≤256 chars) and accept an optional `quota_ticket` (≤128 chars). Avoid embedding secrets in either field.
> Tokens requesting `orch:operate` fail with `invalid_request` unless both operator values are present. `orch:quota` tokens require `quota_reason` (≤256 chars) and accept an optional `quota_ticket` (≤128 chars). `orch:backfill` tokens require both `backfill_reason` (≤256 chars) and `backfill_ticket` (≤128 chars). Avoid embedding secrets in any value.

View File

@@ -0,0 +1,54 @@
# CLI Task Pack SSO Profiles
Task Pack workflows rely on purpose-scoped Authority clients. To streamline local logins and CI/CD automation, define StellaOps CLI profiles under `~/.stellaops/profiles` so `stella auth login` automatically requests the correct scopes.
Profiles are simple YAML files that map onto the CLI configuration schema. Set `STELLA_PROFILE=<name>` (or pass `--profile <name>` once the CLI exposes the switch) before invoking `stella` to load the profile.
## Example profiles
### Packs operator (`~/.stellaops/profiles/packs-operator.yaml`)
```yaml
StellaOps:
Authority:
Url: https://authority.example.com
ClientId: pack-operator
ClientSecretFile: ~/.stellaops/secrets/pack-operator.secret
Scope: "packs.read packs.run"
TokenCacheDirectory: ~/.stellaops/tokens
BackendUrl: https://task-runner.example.com
```
### Packs publisher (`~/.stellaops/profiles/packs-publisher.yaml`)
```yaml
StellaOps:
Authority:
Url: https://authority.example.com
ClientId: packs-registry
ClientSecretFile: ~/.stellaops/secrets/packs-registry.secret
Scope: "packs.read packs.write"
TokenCacheDirectory: ~/.stellaops/tokens
BackendUrl: https://packs-registry.example.com
```
### Packs approver (`~/.stellaops/profiles/packs-approver.yaml`)
```yaml
StellaOps:
Authority:
Url: https://authority.example.com
ClientId: pack-approver
ClientSecretFile: ~/.stellaops/secrets/pack-approver.secret
Scope: "packs.read packs.approve"
TokenCacheDirectory: ~/.stellaops/tokens
BackendUrl: https://task-runner.example.com
```
## Usage
1. Create the profile file under `~/.stellaops/profiles/<name>.yaml`.
2. Store the matching client secret in the referenced path (or set `ClientSecret` for development).
3. Export `STELLA_PROFILE=<name>` before running `stella auth login` or individual pack commands.
The CLI reads the profile, applies the Authority configuration, and requests the listed scopes so the resulting tokens satisfy Task Runner and Packs Registry expectations.

View File

@@ -22,7 +22,7 @@
- Quotas defined per tenant/profile (`maxActive`, `maxPerHour`, `burst`). Stored in `quotas` and enforced before leasing.
- Dynamic throttles allow ops to pause specific sources (`pauseSource`, `resumeSource`) or reduce concurrency.
- Circuit breakers automatically pause job types when failure rate > configured threshold; incidents generated via Notify and Observability stack.
- Control plane quota updates require Authority scope `orch:quota` (issued via `Orch.Admin` role). Token requests include `quota_reason` (mandatory) and optional `quota_ticket`; Authority persists both values for audit replay.
- Control plane quota updates require Authority scope `orch:quota` (issued via `Orch.Admin` role). Historical rebuilds/backfills additionally require `orch:backfill` and must supply `backfill_reason` and `backfill_ticket` alongside the operator metadata. Authority persists all four fields (`quota_reason`, `quota_ticket`, `backfill_reason`, `backfill_ticket`) for audit replay.
## 4) APIs

View File

@@ -1,9 +1,10 @@
# Task board — Scanner
> Local tasks should link back to ./AGENTS.md and mirror status updates into ../../TASKS.md when applicable.
| ID | Status | Owner(s) | Description | Notes |
|----|--------|----------|-------------|-------|
| SCANNER-DOCS-0001 | DOING (2025-10-29) | Docs Guild | Validate that ./README.md aligns with the latest release notes. | See ./AGENTS.md |
| SCANNER-OPS-0001 | TODO | Ops Guild | Review runbooks/observability assets after next sprint demo. | Sync outcomes back to ../../TASKS.md |
| SCANNER-ENG-0001 | TODO | Module Team | Cross-check implementation plan milestones against ../../implplan/SPRINTS.md. | Update status via ./AGENTS.md workflow |
# Task board — Scanner
> Local tasks should link back to ./AGENTS.md and mirror status updates into ../../TASKS.md when applicable.
| ID | Status | Owner(s) | Description | Notes |
|----|--------|----------|-------------|-------|
| SCANNER-DOCS-0001 | DOING (2025-10-29) | Docs Guild | Validate that ./README.md aligns with the latest release notes. | See ./AGENTS.md |
| SCANNER-DOCS-0002 | DONE (2025-11-02) | Docs Guild | Keep scanner benchmark comparisons (Trivy/Grype/Snyk) and deep-dive matrix current with source references. | Coordinate with docs/benchmarks owners |
| SCANNER-OPS-0001 | TODO | Ops Guild | Review runbooks/observability assets after next sprint demo. | Sync outcomes back to ../../TASKS.md |
| SCANNER-ENG-0001 | TODO | Module Team | Cross-check implementation plan milestones against ../../implplan/SPRINTS.md. | Update status via ./AGENTS.md workflow |

View File

@@ -140,19 +140,18 @@ Compose the runtime argv as `Entrypoint ++ Cmd`, honouring shell-form vs exec-fo
## 4) Wrapper catalogue
> _Roadmap note_: extended package/tool runners land with **SCANNER-ENTRYTRACE-18-508**; today the catalogue covers init/user-switch/environment/supervisor wrappers listed above.
Collapse known wrappers before analysing the target command so the terminal reflects the real runtime binary. Sprint130.A ships the extended catalogue from **SCANNER-ENTRYTRACE-18-508**, covering init/user-switch/environment/supervisor wrappers as well as package and language launchers such as `bundle exec`, `docker-php-entrypoint`, `npm exec`, `yarn node`, `pipenv run`, and `poetry run`.
Collapse known wrappers before analysing the target command:
- Init shims: `tini`, `dumb-init`, `s6-svscan`, `runit`, `supervisord`.
- Privilege droppers: `gosu`, `su-exec`, `chpst`.
- Shells: `sh`, `bash`, `dash`, BusyBox variants.
- Package runners: `npm`, `yarn`, `pnpm`, `pip`, `pipenv`, `poetry`, `bundle`, `rake`.
- Init shims: `tini`, `dumb-init`, `s6-svscan`, `runit`, `supervisord`.
- Privilege droppers: `gosu`, `su-exec`, `chpst`.
- Shells: `sh`, `bash`, `dash`, BusyBox variants.
- Package runners: `npm`, `yarn`, `pnpm`, `pip`, `pipenv`, `poetry`, `bundle`, `rake`.
Rules:
- If wrapper contains a `--` sentinel (`tini -- app …`) drop the wrapper and record a reduction edge.
- `gosu user cmd …` → collapse to `cmd …`.
- For shell wrappers, delegate to the ShellFlow analyser (see separate guide).
- If wrapper contains a `--` sentinel (`tini -- app …`) drop the wrapper and record a reduction edge.
- `gosu user cmd …` → collapse to `cmd …`.
- For shell wrappers, delegate to the ShellFlow analyser (see separate guide).
- When a wrapper delegates to an interpreter (`python`, `node`, `java -jar`), continue into the interpreter handlers so a terminal classification is still recorded even if the script/module cannot be resolved.
## 5) ShellFlow integration

View File

@@ -82,7 +82,7 @@ Authority issues short-lived tokens bound to tenants and scopes. Sprint19 int
- **`role/ui-console-admin`** → `ui.read`, `authority:tenants.read`, `authority:roles.read`, `authority:tokens.read`, `authority:clients.read` (paired with write scopes where required).
- **`role/orch-viewer`** *(Authority role: `Orch.Viewer`)*`orch:read`.
- **`role/orch-operator`** *(Authority role: `Orch.Operator`)*`orch:read`, `orch:operate`.
- **`role/orch-admin`** *(Authority role: `Orch.Admin`)*`orch:read`, `orch:operate`, `orch:quota`.
- **`role/orch-admin`** *(Authority role: `Orch.Admin`)*`orch:read`, `orch:operate`, `orch:quota`, `orch:backfill`.
- **`role/packs-runner`** → `packs.read`, `packs.run`.
- **`role/packs-publisher`** → `packs.read`, `packs.write`.
- **`role/packs-approver`** → `packs.read`, `packs.approve`.
@@ -133,6 +133,16 @@ tenants:
scopes: [policy:operate, policy:run, policy:activate, policy:read, policy:simulate, findings:read]
policy-auditor:
scopes: [policy:audit, policy:read, policy:simulate, findings:read]
pack-viewer:
scopes: [packs.read]
pack-operator:
scopes: [packs.read, packs.run]
pack-publisher:
scopes: [packs.read, packs.write]
pack-approver:
scopes: [packs.read, packs.approve]
pack-admin:
scopes: [packs.read, packs.write, packs.run, packs.approve]
policy-engine:
scopes: [effective:write, findings:read]
exceptions-service:

View File

@@ -52,7 +52,8 @@ The console client is registered in Authority as `console-ui` with scopes:
| Policy approvals | `policy:read`, `policy:review`, `policy:approve`, `policy:operate`, `policy:simulate` | `policy:operate` (promote/activate/run) requires fresh-auth. |
| Observability panes (status ticker, telemetry) | `ui.telemetry`, `scheduler:runs.read`, `advisory:read`, `vex:read` | `ui.telemetry` drives OTLP export toggles. |
| Orchestrator dashboard (queues, workers, rate limits) | `orch:read` | Provision via `Orch.Viewer` role; read-only access to job state and telemetry. |
| Orchestrator control actions (pause/resume, retry, sync-now, backfill) | `orch:operate` (plus `orch:read`) | CLI/Console must request tokens with `operator_reason` and `operator_ticket`; Authority denies issuance when either value is missing. |
| Orchestrator control actions (pause/resume, retry, sync-now) | `orch:operate` (plus `orch:read`) | CLI/Console must request tokens with `operator_reason` and `operator_ticket`; Authority denies issuance when either value is missing. |
| Orchestrator backfill runs | `orch:backfill` (plus `orch:read`, `orch:operate`) | Backfill tokens require `backfill_reason` (≤256 chars) and `backfill_ticket` (≤128 chars); Authority stores both alongside operator metadata in audit events. |
| Orchestrator quota & burst controls | `orch:quota` (plus `orch:read`, `orch:operate`) | Tokens must include `quota_reason` (≤256 chars); optional `quota_ticket` (≤128 chars) is captured for audit. |
| Downloads parity (SBOM, attestation) | `downloads:read`, `attestation:verify`, `sbom:export` | Console surfaces digests only; download links require CLI parity for write operations. |
@@ -60,8 +61,8 @@ Guidance:
- **Role mapping**: Provision Authority role `role/ui-console-admin` encapsulating the admin scopes above.
- **Orchestrator viewers**: Assign Authority role `role/orch-viewer` (Authority role string `Orch.Viewer`) to consoles that require read-only access to Orchestrator telemetry.
- **Orchestrator operators**: Assign Authority role `role/orch-operator` (Authority role string `Orch.Operator`) to identities allowed to pause/resume or backfill. Tokens must include `operator_reason` (≤256 chars) and `operator_ticket` (≤128 chars); Authority records the values in audit logs.
- **Orchestrator admins**: Assign Authority role `role/orch-admin` (Authority role string `Orch.Admin`) to the handful of identities permitted to raise/lower quotas or trigger bulk backfills. Tokens must include `quota_reason` (≤256 chars); provide `quota_ticket` (≤128 chars) when available so Authority audit streams capture the change record.
- **Orchestrator operators**: Assign Authority role `role/orch-operator` (Authority role string `Orch.Operator`) to identities allowed to pause/resume jobs. Tokens must include `operator_reason` (≤256 chars) and `operator_ticket` (≤128 chars); Authority records the values in audit logs.
- **Orchestrator admins**: Assign Authority role `role/orch-admin` (Authority role string `Orch.Admin`) to the handful of identities permitted to raise/lower quotas or trigger backfills. Tokens must include `quota_reason` (≤256 chars) and `backfill_reason` (≤256 chars), plus the corresponding ticket fields (`quota_ticket`, `backfill_ticket`, ≤128 chars each) so audit streams capture the change record.
- **Tenant enforcement**: Gateway injects `X-Stella-Tenant` from token claims. Requests missing the header must be rejected by downstream services (Concelier, Excititor, Policy Engine) and logged.
- **Separation of duties**: Never grant `ui.admin` and `policy:approve`/`policy:operate` to the same human role without SOC sign-off; automation accounts should use least-privilege dedicated clients.

View File

@@ -2,10 +2,11 @@
**What changed**
- Introduced new `orch:quota` scope and `Orch.Admin` role for Orchestrator quota and burst adjustments.
- Introduced new `orch:quota` scope and expanded `Orch.Admin` role for Orchestrator quota, burst, and historical backfill adjustments.
- Client credential requests for `orch:quota` now require `quota_reason` (≤256 chars) and accept optional `quota_ticket` (≤128 chars). Authority records both values under `quota.reason` / `quota.ticket` audit properties.
- Tokens embedding `orch:quota` expose the reason/ticket claims so downstream services and audit tooling can trace quota increases or emergency backfills.
- Console, CLI, and configuration samples include the new role plus environment variables (`STELLAOPS_ORCH_QUOTA_REASON`, `STELLAOPS_ORCH_QUOTA_TICKET`) for automation.
- Added dedicated `orch:backfill` scope. Tokens must include `backfill_reason` (≤256 chars) and `backfill_ticket` (≤128 chars); Authority persists them as `backfill.reason` / `backfill.ticket` claims and audit properties alongside operator metadata.
- Tokens embedding `orch:quota` or `orch:backfill` expose the corresponding reason/ticket claims so downstream services and audit tooling can trace quota increases or emergency backfills.
- Console, CLI, and configuration samples include the updated role plus environment variables (`STELLAOPS_ORCH_QUOTA_REASON`, `STELLAOPS_ORCH_QUOTA_TICKET`, `STELLAOPS_ORCH_BACKFILL_REASON`, `STELLAOPS_ORCH_BACKFILL_TICKET`) for automation.
**Why**
@@ -14,5 +15,5 @@ Quotas and replay backfills materially affect tenant isolation and platform capa
**Actions**
1. Update Authority configuration/offline bundles to seed `Orch.Admin` role for the handful of ops identities that manage quotas.
2. Adjust automation to pass `quota_reason`/`quota_ticket` when exchanging tokens for `orch:quota`.
3. Monitor `authority.client_credentials.grant` records for the new `quota.*` audit properties when reviewing change windows.
2. Adjust automation to pass `quota_reason`/`quota_ticket` when exchanging tokens for `orch:quota` and `backfill_reason`/`backfill_ticket` for `orch:backfill`.
3. Monitor `authority.client_credentials.grant` records for the new `quota.*` and `backfill.*` audit properties when reviewing change windows.

View File

@@ -0,0 +1,18 @@
# 2025-11-02 · Pack scope catalogue & CLI profiles
**What changed**
- Authority configuration samples (`etc/authority.yaml.sample`) now seed Pack roles (`pack-viewer`, `pack-operator`, `pack-publisher`, `pack-approver`, `pack-admin`) with deterministic scope bundles.
- Added `AddPacksResourcePolicies` helper in `StellaOps.Auth.ServerIntegration` so Packs Registry/Task Runner services can register consistent authorization policies; accompanying unit tests validate the policy catalogue.
- Documented Task Pack CLI profiles (`docs/modules/cli/guides/packs-profiles.md`) and added quick-reference guidance in the CLI manual for setting `StellaOps:Authority:Scope` via profiles or environment variables.
- Updated Authority scope docs and samples to reflect the new roles, keeping offline/air-gap defaults aligned.
**Why**
Task Pack rollout requires explicit RBAC and short-lived tokens per workflow (publish, run, approve). Providing ready-to-use roles, policies, and CLI profiles removes guesswork for operators and ensures tokens carry the correct scopes by default.
**Actions**
1. Refresh Authority configuration in each environment from the updated sample (or add the roles manually) so Pack clients can request tokens.
2. Roll out the CLI profiles or equivalent configuration in automation (`STELLA_PROFILE=packs-operator`, etc.) before enabling pack workflows.
3. Update Task Runner/Packs Registry services to call `AddPacksResourcePolicies()` when wiring authorization.

View File

@@ -158,7 +158,7 @@ tenants:
orch-operator:
scopes: [ "orch:read", "orch:operate" ]
orch-admin:
scopes: [ "orch:read", "orch:operate", "orch:quota" ]
scopes: [ "orch:read", "orch:operate", "orch:quota", "orch:backfill" ]
export-viewer:
scopes: [ "export.viewer" ]
export-operator:
@@ -175,6 +175,16 @@ tenants:
scopes: [ "policy:operate", "policy:run", "policy:activate", "policy:read", "policy:simulate", "findings:read" ]
policy-auditor:
scopes: [ "policy:audit", "policy:read", "policy:simulate", "findings:read" ]
pack-viewer:
scopes: [ "packs.read" ]
pack-operator:
scopes: [ "packs.read", "packs.run" ]
pack-publisher:
scopes: [ "packs.read", "packs.write" ]
pack-approver:
scopes: [ "packs.read", "packs.approve" ]
pack-admin:
scopes: [ "packs.read", "packs.write", "packs.run", "packs.approve" ]
advisory-ai-viewer:
scopes: [ "advisory-ai:view" ]
advisory-ai-operator:

View File

@@ -395,7 +395,7 @@ tenants:
orch-operator:
scopes: [ "orch:read", "orch:operate" ]
orch-admin:
scopes: [ "orch:read", "orch:operate", "orch:quota" ]
scopes: [ "orch:read", "orch:operate", "orch:quota", "orch:backfill" ]
policy-author:
scopes: [ "policy:author", "policy:read", "policy:simulate", "findings:read" ]
policy-reviewer:
@@ -406,6 +406,16 @@ tenants:
scopes: [ "policy:operate", "policy:run", "policy:activate", "policy:read", "policy:simulate", "findings:read" ]
policy-auditor:
scopes: [ "policy:audit", "policy:read", "policy:simulate", "findings:read" ]
pack-viewer:
scopes: [ "packs.read" ]
pack-operator:
scopes: [ "packs.read", "packs.run" ]
pack-publisher:
scopes: [ "packs.read", "packs.write" ]
pack-approver:
scopes: [ "packs.read", "packs.approve" ]
pack-admin:
scopes: [ "packs.read", "packs.write", "packs.run", "packs.approve" ]
export-viewer:
scopes: [ "export.viewer" ]
export-operator:

View File

@@ -0,0 +1,13 @@
namespace StellaOps.AdvisoryAI.Abstractions;
public sealed record AdvisoryRetrievalRequest(
string AdvisoryKey,
IReadOnlyCollection<string>? PreferredSections = null,
int? MaxChunks = null)
{
public string AdvisoryKey { get; } = AdvisoryKey ?? throw new ArgumentNullException(nameof(AdvisoryKey));
public IReadOnlyCollection<string>? PreferredSections { get; } = PreferredSections;
public int? MaxChunks { get; } = MaxChunks;
}

View File

@@ -0,0 +1,38 @@
using System.Collections.Immutable;
using StellaOps.AdvisoryAI.Documents;
namespace StellaOps.AdvisoryAI.Abstractions;
public sealed class AdvisoryRetrievalResult
{
private AdvisoryRetrievalResult(
string advisoryKey,
IReadOnlyList<AdvisoryChunk> chunks,
IReadOnlyDictionary<string, string> metadata)
{
AdvisoryKey = advisoryKey;
Chunks = chunks;
Metadata = metadata;
}
public string AdvisoryKey { get; }
public IReadOnlyList<AdvisoryChunk> Chunks { get; }
public IReadOnlyDictionary<string, string> Metadata { get; }
public static AdvisoryRetrievalResult Create(
string advisoryKey,
IEnumerable<AdvisoryChunk> chunks,
IReadOnlyDictionary<string, string>? metadata = null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(advisoryKey);
ArgumentNullException.ThrowIfNull(chunks);
var chunkList = chunks.ToImmutableArray();
return new AdvisoryRetrievalResult(
advisoryKey,
chunkList,
metadata is null ? ImmutableDictionary<string, string>.Empty : metadata.ToImmutableDictionary(StringComparer.Ordinal));
}
}

View File

@@ -0,0 +1,8 @@
using StellaOps.AdvisoryAI.Documents;
namespace StellaOps.AdvisoryAI.Abstractions;
public interface IAdvisoryDocumentProvider
{
Task<IReadOnlyList<AdvisoryDocument>> GetDocumentsAsync(string advisoryKey, CancellationToken cancellationToken);
}

View File

@@ -0,0 +1,6 @@
namespace StellaOps.AdvisoryAI.Abstractions;
public interface IAdvisoryStructuredRetriever
{
Task<AdvisoryRetrievalResult> RetrieveAsync(AdvisoryRetrievalRequest request, CancellationToken cancellationToken);
}

View File

@@ -0,0 +1,25 @@
namespace StellaOps.AdvisoryAI.Abstractions;
public interface IAdvisoryVectorRetriever
{
Task<IReadOnlyList<VectorRetrievalMatch>> SearchAsync(VectorRetrievalRequest request, CancellationToken cancellationToken);
}
public sealed record VectorRetrievalRequest(
AdvisoryRetrievalRequest Retrieval,
string Query,
int TopK = 5)
{
public AdvisoryRetrievalRequest Retrieval { get; } = Retrieval ?? throw new ArgumentNullException(nameof(Retrieval));
public string Query { get; } = Query ?? throw new ArgumentNullException(nameof(Query));
public int TopK { get; } = TopK;
}
public sealed record VectorRetrievalMatch(
string DocumentId,
string ChunkId,
string Text,
double Score,
IReadOnlyDictionary<string, string> Metadata);

View File

@@ -0,0 +1,26 @@
using StellaOps.AdvisoryAI.Documents;
namespace StellaOps.AdvisoryAI.Chunking;
internal sealed class DocumentChunkerFactory
{
private readonly IReadOnlyList<IDocumentChunker> _chunkers;
public DocumentChunkerFactory(IEnumerable<IDocumentChunker> chunkers)
{
_chunkers = chunkers.ToList();
}
public IDocumentChunker Resolve(DocumentFormat format)
{
foreach (var chunker in _chunkers)
{
if (chunker.CanHandle(format))
{
return chunker;
}
}
throw new NotSupportedException($"No chunker registered for format {format}.");
}
}

View File

@@ -0,0 +1,10 @@
using StellaOps.AdvisoryAI.Documents;
namespace StellaOps.AdvisoryAI.Chunking;
internal interface IDocumentChunker
{
bool CanHandle(DocumentFormat format);
IEnumerable<AdvisoryChunk> Chunk(AdvisoryDocument document);
}

View File

@@ -0,0 +1,64 @@
using System.Collections.Immutable;
namespace StellaOps.AdvisoryAI.Documents;
public sealed class AdvisoryChunk
{
private AdvisoryChunk(
string documentId,
string chunkId,
string section,
string paragraphId,
string text,
IReadOnlyDictionary<string, string>? metadata,
float[]? embedding)
{
DocumentId = documentId;
ChunkId = chunkId;
Section = section;
ParagraphId = paragraphId;
Text = text;
Metadata = metadata is null
? ImmutableDictionary<string, string>.Empty
: metadata.ToImmutableDictionary(StringComparer.Ordinal);
Embedding = embedding;
}
public string DocumentId { get; }
public string ChunkId { get; }
public string Section { get; }
public string ParagraphId { get; }
public string Text { get; }
public IReadOnlyDictionary<string, string> Metadata { get; }
public float[]? Embedding { get; private set; }
public AdvisoryChunk WithEmbedding(float[] embedding)
{
ArgumentNullException.ThrowIfNull(embedding);
Embedding = embedding;
return this;
}
public static AdvisoryChunk Create(
string documentId,
string chunkId,
string section,
string paragraphId,
string text,
IReadOnlyDictionary<string, string>? metadata = null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(documentId);
ArgumentException.ThrowIfNullOrWhiteSpace(chunkId);
ArgumentException.ThrowIfNullOrWhiteSpace(section);
ArgumentException.ThrowIfNullOrWhiteSpace(paragraphId);
ArgumentNullException.ThrowIfNull(text);
return new AdvisoryChunk(documentId, chunkId, section, paragraphId, text, metadata, embedding: null);
}
}

View File

@@ -0,0 +1,46 @@
using System.Collections.Immutable;
namespace StellaOps.AdvisoryAI.Documents;
public sealed class AdvisoryDocument
{
private AdvisoryDocument(
string documentId,
DocumentFormat format,
string source,
string content,
IReadOnlyDictionary<string, string>? metadata)
{
DocumentId = documentId;
Format = format;
Source = source;
Content = content;
Metadata = metadata is null
? ImmutableDictionary<string, string>.Empty
: metadata.ToImmutableDictionary(StringComparer.Ordinal);
}
public string DocumentId { get; }
public DocumentFormat Format { get; }
public string Source { get; }
public string Content { get; }
public IReadOnlyDictionary<string, string> Metadata { get; }
public static AdvisoryDocument Create(
string documentId,
DocumentFormat format,
string source,
string content,
IReadOnlyDictionary<string, string>? metadata = null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(documentId);
ArgumentException.ThrowIfNullOrWhiteSpace(source);
ArgumentNullException.ThrowIfNull(content);
return new AdvisoryDocument(documentId, format, source, content, metadata);
}
}

View File

@@ -0,0 +1,9 @@
namespace StellaOps.AdvisoryAI.Documents;
public enum DocumentFormat
{
Unknown = 0,
Csaf,
Osv,
Markdown,
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="System.Text.Json" Version="10.0.0-rc.2.25502.2" />
</ItemGroup>
</Project>

View File

@@ -1,7 +1,7 @@
# Advisory AI Task Board — Epic 8
| ID | Status | Owner(s) | Depends on | Description | Exit Criteria |
|----|--------|----------|------------|-------------|---------------|
| AIAI-31-001 | TODO | Advisory AI Guild | CONCELIER-VULN-29-001, EXCITITOR-VULN-29-001 | Implement structured and vector retrievers for advisories/VEX with paragraph anchors and citation metadata. | Retrievers return deterministic chunks with source IDs/sections; unit tests cover CSAF/OSV/vendor formats. |
| AIAI-31-001 | DOING (2025-11-02) | Advisory AI Guild | CONCELIER-VULN-29-001, EXCITITOR-VULN-29-001 | Implement structured and vector retrievers for advisories/VEX with paragraph anchors and citation metadata. | Retrievers return deterministic chunks with source IDs/sections; unit tests cover CSAF/OSV/vendor formats. |
| AIAI-31-002 | TODO | Advisory AI Guild, SBOM Service Guild | SBOM-VULN-29-001 | Build SBOM context retriever (purl version timelines, dependency paths, env flags, blast radius estimator). | Retriever returns paths/metrics under SLA; tests cover ecosystems. |
| AIAI-31-003 | TODO | Advisory AI Guild | AIAI-31-001..002 | Implement deterministic toolset (version comparators, range checks, dependency analysis, policy lookup) exposed via orchestrator. | Tools validated with property tests; outputs cached; docs updated. |
| AIAI-31-004 | TODO | Advisory AI Guild | AIAI-31-001..003, AUTH-VULN-29-001 | Build orchestration pipeline for Summary/Conflict/Remediation tasks (prompt templates, tool calls, token budgets, caching). | Pipeline executes tasks deterministically; caches keyed by tuple+policy; integration tests cover tasks. |

View File

@@ -28,6 +28,19 @@ public class StellaOpsResourceServerPoliciesTests
AssertPolicy(options, StellaOpsResourceServerPolicies.ExportAdmin, StellaOpsScopes.ExportAdmin);
}
[Fact]
public void AddPacksResourcePolicies_RegistersExpectedPolicies()
{
var options = new AuthorizationOptions();
options.AddPacksResourcePolicies();
AssertPolicy(options, StellaOpsResourceServerPolicies.PacksRead, StellaOpsScopes.PacksRead);
AssertPolicy(options, StellaOpsResourceServerPolicies.PacksWrite, StellaOpsScopes.PacksWrite);
AssertPolicy(options, StellaOpsResourceServerPolicies.PacksRun, StellaOpsScopes.PacksRun);
AssertPolicy(options, StellaOpsResourceServerPolicies.PacksApprove, StellaOpsScopes.PacksApprove);
}
private static void AssertPolicy(AuthorizationOptions options, string policyName, string expectedScope)
{
var policy = options.GetPolicy(policyName);

View File

@@ -290,7 +290,70 @@ public class StellaOpsScopeAuthorizationHandlerTests
Assert.Equal(freshAuthTime.ToString("o", CultureInfo.InvariantCulture), GetPropertyValue(record, "incident.auth_time"));
Assert.Equal("Sev1 drill", GetPropertyValue(record, "incident.reason"));
}
[Fact]
public async Task HandleRequirement_Fails_WhenBackfillMetadataMissing()
{
var optionsMonitor = CreateOptionsMonitor(options =>
{
options.Authority = "https://authority.example";
options.RequiredTenants.Add("tenant-alpha");
options.Validate();
});
var (handler, accessor, sink) = CreateHandler(optionsMonitor, IPAddress.Parse("10.0.0.77"));
var requirement = new StellaOpsScopeRequirement(new[] { StellaOpsScopes.OrchBackfill });
var principal = new StellaOpsPrincipalBuilder()
.WithSubject("orch-admin")
.WithClientId("orch-control")
.WithTenant("tenant-alpha")
.WithScopes(new[] { StellaOpsScopes.OrchBackfill })
.Build();
var context = new AuthorizationHandlerContext(new[] { requirement }, principal, accessor.HttpContext);
await handler.HandleAsync(context);
Assert.False(context.HasSucceeded);
var record = Assert.Single(sink.Records);
Assert.Equal(AuthEventOutcome.Failure, record.Outcome);
Assert.Equal("Backfill scope requires reason and ticket.", record.Reason);
Assert.Equal("false", GetPropertyValue(record, "backfill.metadata_satisfied"));
}
[Fact]
public async Task HandleRequirement_Succeeds_WhenBackfillMetadataPresent()
{
var optionsMonitor = CreateOptionsMonitor(options =>
{
options.Authority = "https://authority.example";
options.RequiredTenants.Add("tenant-alpha");
options.Validate();
});
var (handler, accessor, sink) = CreateHandler(optionsMonitor, IPAddress.Parse("10.0.0.88"));
var requirement = new StellaOpsScopeRequirement(new[] { StellaOpsScopes.OrchBackfill });
var principal = new StellaOpsPrincipalBuilder()
.WithSubject("orch-admin")
.WithClientId("orch-control")
.WithTenant("tenant-alpha")
.WithScopes(new[] { StellaOpsScopes.OrchBackfill })
.AddClaim(StellaOpsClaimTypes.BackfillReason, "Quota recovery backfill")
.AddClaim(StellaOpsClaimTypes.BackfillTicket, "INC-741")
.Build();
var context = new AuthorizationHandlerContext(new[] { requirement }, principal, accessor.HttpContext);
await handler.HandleAsync(context);
Assert.True(context.HasSucceeded);
var record = Assert.Single(sink.Records);
Assert.Equal(AuthEventOutcome.Success, record.Outcome);
Assert.Equal("true", GetPropertyValue(record, "backfill.metadata_satisfied"));
Assert.Equal("Quota recovery backfill", GetPropertyValue(record, "backfill.reason"));
Assert.Equal("INC-741", GetPropertyValue(record, "backfill.ticket"));
}
private static (StellaOpsScopeAuthorizationHandler Handler, IHttpContextAccessor Accessor, RecordingAuthEventSink Sink) CreateHandler(IOptionsMonitor<StellaOpsResourceServerOptions> optionsMonitor, IPAddress remoteAddress, TimeProvider? timeProvider = null)
{
var accessor = new HttpContextAccessor();

View File

@@ -64,6 +64,26 @@ public static class StellaOpsResourceServerPolicies
/// </summary>
public const string ExportAdmin = StellaOpsScopes.ExportAdmin;
/// <summary>
/// Pack read policy name.
/// </summary>
public const string PacksRead = StellaOpsScopes.PacksRead;
/// <summary>
/// Pack write policy name.
/// </summary>
public const string PacksWrite = StellaOpsScopes.PacksWrite;
/// <summary>
/// Pack run policy name.
/// </summary>
public const string PacksRun = StellaOpsScopes.PacksRun;
/// <summary>
/// Pack approval policy name.
/// </summary>
public const string PacksApprove = StellaOpsScopes.PacksApprove;
/// <summary>
/// Registers all observability, timeline, evidence, attestation, and export authorization policies.
/// </summary>
@@ -83,4 +103,18 @@ public static class StellaOpsResourceServerPolicies
options.AddStellaOpsScopePolicy(ExportOperator, StellaOpsScopes.ExportOperator);
options.AddStellaOpsScopePolicy(ExportAdmin, StellaOpsScopes.ExportAdmin);
}
/// <summary>
/// Registers Task Pack registry, execution, and approval authorization policies.
/// </summary>
/// <param name="options">The authorization options to update.</param>
public static void AddPacksResourcePolicies(this AuthorizationOptions options)
{
ArgumentNullException.ThrowIfNull(options);
options.AddStellaOpsScopePolicy(PacksRead, StellaOpsScopes.PacksRead);
options.AddStellaOpsScopePolicy(PacksWrite, StellaOpsScopes.PacksWrite);
options.AddStellaOpsScopePolicy(PacksRun, StellaOpsScopes.PacksRun);
options.AddStellaOpsScopePolicy(PacksApprove, StellaOpsScopes.PacksApprove);
}
}

View File

@@ -98,10 +98,19 @@ internal sealed class StellaOpsScopeAuthorizationHandler : AuthorizationHandler<
string? incidentReasonClaim = null;
DateTimeOffset? incidentAuthTime = null;
string? incidentFailureReason = null;
var backfillMetadataRequired = combinedScopes.Contains(StellaOpsScopes.OrchBackfill);
var backfillMetadataSatisfied = true;
string? backfillReasonClaim = null;
string? backfillTicketClaim = null;
string? backfillFailureReason = null;
if (principalAuthenticated)
{
incidentReasonClaim = principal!.FindFirstValue(StellaOpsClaimTypes.IncidentReason);
backfillReasonClaim = principal!.FindFirstValue(StellaOpsClaimTypes.BackfillReason);
backfillTicketClaim = principal!.FindFirstValue(StellaOpsClaimTypes.BackfillTicket);
backfillReasonClaim = backfillReasonClaim?.Trim();
backfillTicketClaim = backfillTicketClaim?.Trim();
}
if (principalAuthenticated && allScopesSatisfied)
@@ -119,6 +128,15 @@ internal sealed class StellaOpsScopeAuthorizationHandler : AuthorizationHandler<
out incidentFailureReason);
}
if (principalAuthenticated && tenantAllowed && allScopesSatisfied && backfillMetadataRequired)
{
if (string.IsNullOrWhiteSpace(backfillReasonClaim) || string.IsNullOrWhiteSpace(backfillTicketClaim))
{
backfillMetadataSatisfied = false;
backfillFailureReason = "Backfill scope requires reason and ticket.";
}
}
var bypassed = false;
if ((!principalAuthenticated || !allScopesSatisfied || !tenantAllowed || !incidentFreshAuthSatisfied) &&
@@ -133,10 +151,12 @@ internal sealed class StellaOpsScopeAuthorizationHandler : AuthorizationHandler<
incidentFreshAuthSatisfied = true;
incidentFailureReason = null;
incidentAuthTime = null;
backfillMetadataSatisfied = true;
backfillFailureReason = null;
bypassed = true;
}
if (tenantAllowed && allScopesSatisfied && incidentFreshAuthSatisfied)
if (tenantAllowed && allScopesSatisfied && incidentFreshAuthSatisfied && backfillMetadataSatisfied)
{
context.Succeed(requirement);
}
@@ -181,9 +201,18 @@ internal sealed class StellaOpsScopeAuthorizationHandler : AuthorizationHandler<
ObservabilityIncidentFreshAuthWindow,
httpContext?.Connection.RemoteIpAddress);
}
if (backfillMetadataRequired && !backfillMetadataSatisfied)
{
logger.LogDebug(
"Backfill scope metadata requirement not satisfied. ReasonPresent={ReasonPresent}; TicketPresent={TicketPresent}; Remote={Remote}",
!string.IsNullOrWhiteSpace(backfillReasonClaim),
!string.IsNullOrWhiteSpace(backfillTicketClaim),
httpContext?.Connection.RemoteIpAddress);
}
}
var reason = incidentFailureReason ?? DetermineFailureReason(
var reason = backfillFailureReason ?? incidentFailureReason ?? DetermineFailureReason(
principalAuthenticated,
allScopesSatisfied,
anyScopeMatched,
@@ -202,7 +231,7 @@ internal sealed class StellaOpsScopeAuthorizationHandler : AuthorizationHandler<
resourceOptions,
normalizedTenant,
missingScopes,
tenantAllowed && allScopesSatisfied && incidentFreshAuthSatisfied,
tenantAllowed && allScopesSatisfied && incidentFreshAuthSatisfied && backfillMetadataSatisfied,
bypassed,
reason,
principalAuthenticated,
@@ -212,7 +241,11 @@ internal sealed class StellaOpsScopeAuthorizationHandler : AuthorizationHandler<
incidentFreshAuthRequired,
incidentFreshAuthSatisfied,
incidentReasonClaim,
incidentAuthTime).ConfigureAwait(false);
incidentAuthTime,
backfillMetadataRequired,
backfillMetadataSatisfied,
backfillReasonClaim,
backfillTicketClaim).ConfigureAwait(false);
}
private static string? DetermineFailureReason(
@@ -293,7 +326,11 @@ internal sealed class StellaOpsScopeAuthorizationHandler : AuthorizationHandler<
bool incidentFreshAuthRequired,
bool incidentFreshAuthSatisfied,
string? incidentReason,
DateTimeOffset? incidentAuthTime)
DateTimeOffset? incidentAuthTime,
bool backfillMetadataRequired,
bool backfillMetadataSatisfied,
string? backfillReason,
string? backfillTicket)
{
if (!auditSinks.Any())
{
@@ -320,7 +357,11 @@ internal sealed class StellaOpsScopeAuthorizationHandler : AuthorizationHandler<
incidentFreshAuthRequired,
incidentFreshAuthSatisfied,
incidentReason,
incidentAuthTime);
incidentAuthTime,
backfillMetadataRequired,
backfillMetadataSatisfied,
backfillReason,
backfillTicket);
var cancellationToken = httpContext?.RequestAborted ?? CancellationToken.None;
@@ -353,7 +394,11 @@ internal sealed class StellaOpsScopeAuthorizationHandler : AuthorizationHandler<
bool incidentFreshAuthRequired,
bool incidentFreshAuthSatisfied,
string? incidentReason,
DateTimeOffset? incidentAuthTime)
DateTimeOffset? incidentAuthTime,
bool backfillMetadataRequired,
bool backfillMetadataSatisfied,
string? backfillReason,
string? backfillTicket)
{
var correlationId = ResolveCorrelationId(httpContext);
var subject = BuildSubject(principal);
@@ -373,7 +418,11 @@ internal sealed class StellaOpsScopeAuthorizationHandler : AuthorizationHandler<
incidentFreshAuthRequired,
incidentFreshAuthSatisfied,
incidentReason,
incidentAuthTime);
incidentAuthTime,
backfillMetadataRequired,
backfillMetadataSatisfied,
backfillReason,
backfillTicket);
return new AuthEventRecord
{
@@ -403,7 +452,11 @@ internal sealed class StellaOpsScopeAuthorizationHandler : AuthorizationHandler<
bool incidentFreshAuthRequired,
bool incidentFreshAuthSatisfied,
string? incidentReason,
DateTimeOffset? incidentAuthTime)
DateTimeOffset? incidentAuthTime,
bool backfillMetadataRequired,
bool backfillMetadataSatisfied,
string? backfillReason,
string? backfillTicket)
{
var properties = new List<AuthEventProperty>();
@@ -507,6 +560,33 @@ internal sealed class StellaOpsScopeAuthorizationHandler : AuthorizationHandler<
}
}
if (backfillMetadataRequired)
{
properties.Add(new AuthEventProperty
{
Name = "backfill.metadata_satisfied",
Value = ClassifiedString.Public(backfillMetadataSatisfied ? "true" : "false")
});
if (!string.IsNullOrWhiteSpace(backfillReason))
{
properties.Add(new AuthEventProperty
{
Name = "backfill.reason",
Value = ClassifiedString.Sensitive(backfillReason!)
});
}
if (!string.IsNullOrWhiteSpace(backfillTicket))
{
properties.Add(new AuthEventProperty
{
Name = "backfill.ticket",
Value = ClassifiedString.Sensitive(backfillTicket!)
});
}
}
return properties;
}

View File

@@ -26,7 +26,7 @@ public sealed class AuthorityWebApplicationFactory : WebApplicationFactory<Progr
public AuthorityWebApplicationFactory()
{
mongoRunner = MongoDbRunner.Start(singleNodeReplSet: true);
mongoRunner = MongoDbRunner.Start(singleNodeReplSet: true, singleNodeReplSetWaitTimeout: 120);
tempContentRoot = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "stellaops-authority-tests", Guid.NewGuid().ToString("N"));
System.IO.Directory.CreateDirectory(tempContentRoot);
@@ -105,30 +105,32 @@ public sealed class AuthorityWebApplicationFactory : WebApplicationFactory<Progr
throw new InvalidOperationException("Failed to locate repository root for Authority tests.");
}
public Task DisposeAsync()
{
mongoRunner.Dispose();
Environment.SetEnvironmentVariable(IssuerKey, null);
Environment.SetEnvironmentVariable(SchemaVersionKey, null);
Environment.SetEnvironmentVariable(StorageConnectionKey, null);
Environment.SetEnvironmentVariable(StorageDatabaseKey, null);
Environment.SetEnvironmentVariable(SigningEnabledKey, null);
Environment.SetEnvironmentVariable(AckTokensEnabledKey, null);
Environment.SetEnvironmentVariable(WebhooksEnabledKey, null);
try
{
if (System.IO.Directory.Exists(tempContentRoot))
{
System.IO.Directory.Delete(tempContentRoot, recursive: true);
}
}
catch
{
// ignore cleanup failures
}
return Task.CompletedTask;
}
public override async ValueTask DisposeAsync()
{
mongoRunner.Dispose();
Environment.SetEnvironmentVariable(IssuerKey, null);
Environment.SetEnvironmentVariable(SchemaVersionKey, null);
Environment.SetEnvironmentVariable(StorageConnectionKey, null);
Environment.SetEnvironmentVariable(StorageDatabaseKey, null);
Environment.SetEnvironmentVariable(SigningEnabledKey, null);
Environment.SetEnvironmentVariable(AckTokensEnabledKey, null);
Environment.SetEnvironmentVariable(WebhooksEnabledKey, null);
try
{
if (System.IO.Directory.Exists(tempContentRoot))
{
System.IO.Directory.Delete(tempContentRoot, recursive: true);
}
}
catch
{
// ignore cleanup failures
}
await base.DisposeAsync().ConfigureAwait(false);
}
Task IAsyncLifetime.DisposeAsync() => DisposeAsync().AsTask();
}

View File

@@ -63,7 +63,7 @@ public sealed class NotifyAckTokenRotationEndpointTests : IClassFixture<Authorit
});
});
host.ConfigureTestServices(services =>
host.ConfigureServices(services =>
{
services.RemoveAll<IAuthEventSink>();
services.AddSingleton<IAuthEventSink>(sink);
@@ -138,7 +138,7 @@ public sealed class NotifyAckTokenRotationEndpointTests : IClassFixture<Authorit
});
});
host.ConfigureTestServices(services =>
host.ConfigureServices(services =>
{
services.RemoveAll<IAuthEventSink>();
services.AddSingleton<IAuthEventSink>(sink);

View File

@@ -666,6 +666,225 @@ public class ClientCredentialsHandlersTests
Assert.Equal("Operator actions require 'operator_ticket'.", context.ErrorDescription);
}
[Fact]
public async Task ValidateClientCredentials_RejectsOrchBackfill_WhenTenantMissing()
{
var clientDocument = CreateClient(
clientId: "orch-admin",
secret: "s3cr3t!",
allowedGrantTypes: "client_credentials",
allowedScopes: "orch:backfill orch:read");
var registry = CreateRegistry(withClientProvisioning: true, clientDescriptor: CreateDescriptor(clientDocument));
var options = TestHelpers.CreateAuthorityOptions();
var handler = new ValidateClientCredentialsHandler(
new TestClientStore(clientDocument),
registry,
TestActivitySource,
new TestAuthEventSink(),
new TestRateLimiterMetadataAccessor(),
TimeProvider.System,
new NoopCertificateValidator(),
new HttpContextAccessor(),
options,
NullLogger<ValidateClientCredentialsHandler>.Instance);
var transaction = CreateTokenTransaction(clientDocument.ClientId, "s3cr3t!", scope: "orch:backfill");
transaction.Request?.SetParameter(AuthorityOpenIddictConstants.BackfillReasonParameterName, "Backfill drift repair");
transaction.Request?.SetParameter(AuthorityOpenIddictConstants.BackfillTicketParameterName, "INC-9981");
var context = new OpenIddictServerEvents.ValidateTokenRequestContext(transaction);
await handler.HandleAsync(context);
Assert.True(context.IsRejected);
Assert.Equal(OpenIddictConstants.Errors.InvalidClient, context.Error);
Assert.Equal("Orchestrator scopes require a tenant assignment.", context.ErrorDescription);
}
[Fact]
public async Task ValidateClientCredentials_RejectsOrchBackfill_WhenReasonMissing()
{
var clientDocument = CreateClient(
clientId: "orch-admin",
secret: "s3cr3t!",
allowedGrantTypes: "client_credentials",
allowedScopes: "orch:backfill orch:read",
tenant: "tenant-default");
var registry = CreateRegistry(withClientProvisioning: true, clientDescriptor: CreateDescriptor(clientDocument));
var options = TestHelpers.CreateAuthorityOptions();
var handler = new ValidateClientCredentialsHandler(
new TestClientStore(clientDocument),
registry,
TestActivitySource,
new TestAuthEventSink(),
new TestRateLimiterMetadataAccessor(),
TimeProvider.System,
new NoopCertificateValidator(),
new HttpContextAccessor(),
options,
NullLogger<ValidateClientCredentialsHandler>.Instance);
var transaction = CreateTokenTransaction(clientDocument.ClientId, "s3cr3t!", scope: "orch:backfill");
transaction.Request?.SetParameter(AuthorityOpenIddictConstants.BackfillTicketParameterName, "INC-9981");
var context = new OpenIddictServerEvents.ValidateTokenRequestContext(transaction);
await handler.HandleAsync(context);
Assert.True(context.IsRejected);
Assert.Equal(OpenIddictConstants.Errors.InvalidRequest, context.Error);
Assert.Equal("Backfill actions require 'backfill_reason'.", context.ErrorDescription);
}
[Fact]
public async Task ValidateClientCredentials_RejectsOrchBackfill_WhenTicketMissing()
{
var clientDocument = CreateClient(
clientId: "orch-admin",
secret: "s3cr3t!",
allowedGrantTypes: "client_credentials",
allowedScopes: "orch:backfill orch:read",
tenant: "tenant-default");
var registry = CreateRegistry(withClientProvisioning: true, clientDescriptor: CreateDescriptor(clientDocument));
var options = TestHelpers.CreateAuthorityOptions();
var handler = new ValidateClientCredentialsHandler(
new TestClientStore(clientDocument),
registry,
TestActivitySource,
new TestAuthEventSink(),
new TestRateLimiterMetadataAccessor(),
TimeProvider.System,
new NoopCertificateValidator(),
new HttpContextAccessor(),
options,
NullLogger<ValidateClientCredentialsHandler>.Instance);
var transaction = CreateTokenTransaction(clientDocument.ClientId, "s3cr3t!", scope: "orch:backfill");
transaction.Request?.SetParameter(AuthorityOpenIddictConstants.BackfillReasonParameterName, "Backfill drift repair");
var context = new OpenIddictServerEvents.ValidateTokenRequestContext(transaction);
await handler.HandleAsync(context);
Assert.True(context.IsRejected);
Assert.Equal(OpenIddictConstants.Errors.InvalidRequest, context.Error);
Assert.Equal("Backfill actions require 'backfill_ticket'.", context.ErrorDescription);
}
[Fact]
public async Task ValidateClientCredentials_RejectsOrchBackfill_WhenReasonTooLong()
{
var clientDocument = CreateClient(
clientId: "orch-admin",
secret: "s3cr3t!",
allowedGrantTypes: "client_credentials",
allowedScopes: "orch:backfill",
tenant: "tenant-default");
var registry = CreateRegistry(withClientProvisioning: true, clientDescriptor: CreateDescriptor(clientDocument));
var options = TestHelpers.CreateAuthorityOptions();
var handler = new ValidateClientCredentialsHandler(
new TestClientStore(clientDocument),
registry,
TestActivitySource,
new TestAuthEventSink(),
new TestRateLimiterMetadataAccessor(),
TimeProvider.System,
new NoopCertificateValidator(),
new HttpContextAccessor(),
options,
NullLogger<ValidateClientCredentialsHandler>.Instance);
var longReason = new string('a', 257);
var transaction = CreateTokenTransaction(clientDocument.ClientId, "s3cr3t!", scope: "orch:backfill");
transaction.Request?.SetParameter(AuthorityOpenIddictConstants.BackfillReasonParameterName, longReason);
transaction.Request?.SetParameter(AuthorityOpenIddictConstants.BackfillTicketParameterName, "INC-9981");
var context = new OpenIddictServerEvents.ValidateTokenRequestContext(transaction);
await handler.HandleAsync(context);
Assert.True(context.IsRejected);
Assert.Equal(OpenIddictConstants.Errors.InvalidRequest, context.Error);
Assert.Equal("Backfill reason must not exceed 256 characters.", context.ErrorDescription);
}
[Fact]
public async Task ValidateClientCredentials_RejectsOrchBackfill_WhenTicketTooLong()
{
var clientDocument = CreateClient(
clientId: "orch-admin",
secret: "s3cr3t!",
allowedGrantTypes: "client_credentials",
allowedScopes: "orch:backfill",
tenant: "tenant-default");
var registry = CreateRegistry(withClientProvisioning: true, clientDescriptor: CreateDescriptor(clientDocument));
var options = TestHelpers.CreateAuthorityOptions();
var handler = new ValidateClientCredentialsHandler(
new TestClientStore(clientDocument),
registry,
TestActivitySource,
new TestAuthEventSink(),
new TestRateLimiterMetadataAccessor(),
TimeProvider.System,
new NoopCertificateValidator(),
new HttpContextAccessor(),
options,
NullLogger<ValidateClientCredentialsHandler>.Instance);
var longTicket = new string('b', 129);
var transaction = CreateTokenTransaction(clientDocument.ClientId, "s3cr3t!", scope: "orch:backfill");
transaction.Request?.SetParameter(AuthorityOpenIddictConstants.BackfillReasonParameterName, "Backfill drift repair");
transaction.Request?.SetParameter(AuthorityOpenIddictConstants.BackfillTicketParameterName, longTicket);
var context = new OpenIddictServerEvents.ValidateTokenRequestContext(transaction);
await handler.HandleAsync(context);
Assert.True(context.IsRejected);
Assert.Equal(OpenIddictConstants.Errors.InvalidRequest, context.Error);
Assert.Equal("Backfill ticket must not exceed 128 characters.", context.ErrorDescription);
}
[Fact]
public async Task ValidateClientCredentials_PopulatesBackfillMetadata_OnSuccess()
{
var clientDocument = CreateClient(
clientId: "orch-admin",
secret: "s3cr3t!",
allowedGrantTypes: "client_credentials",
allowedScopes: "orch:backfill orch:read",
tenant: "tenant-default");
var registry = CreateRegistry(withClientProvisioning: true, clientDescriptor: CreateDescriptor(clientDocument));
var options = TestHelpers.CreateAuthorityOptions();
var handler = new ValidateClientCredentialsHandler(
new TestClientStore(clientDocument),
registry,
TestActivitySource,
new TestAuthEventSink(),
new TestRateLimiterMetadataAccessor(),
TimeProvider.System,
new NoopCertificateValidator(),
new HttpContextAccessor(),
options,
NullLogger<ValidateClientCredentialsHandler>.Instance);
var transaction = CreateTokenTransaction(clientDocument.ClientId, "s3cr3t!", scope: "orch:backfill");
transaction.Request?.SetParameter(AuthorityOpenIddictConstants.BackfillReasonParameterName, "Backfill drift repair");
transaction.Request?.SetParameter(AuthorityOpenIddictConstants.BackfillTicketParameterName, "INC-9981");
var context = new OpenIddictServerEvents.ValidateTokenRequestContext(transaction);
await handler.HandleAsync(context);
Assert.False(context.IsRejected, $"Rejected: {context.Error} - {context.ErrorDescription}");
var grantedScopes = Assert.IsType<string[]>(context.Transaction.Properties[AuthorityOpenIddictConstants.ClientGrantedScopesProperty]);
Assert.Equal(new[] { "orch:backfill" }, grantedScopes);
var reason = Assert.IsType<string>(context.Transaction.Properties[AuthorityOpenIddictConstants.BackfillReasonProperty]);
Assert.Equal("Backfill drift repair", reason);
var ticket = Assert.IsType<string>(context.Transaction.Properties[AuthorityOpenIddictConstants.BackfillTicketProperty]);
Assert.Equal("INC-9981", ticket);
}
[Fact]
public async Task ValidateClientCredentials_AllowsOrchOperate_WithReasonAndTicket()
{

View File

@@ -14,4 +14,8 @@
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="3.5.0" />
</ItemGroup>
</Project>
<ItemGroup>
<Compile Include="../../../tests/shared/OpenSslLegacyShim.cs" Link="Infrastructure/OpenSslLegacyShim.cs" />
<None Include="../../../tests/native/openssl-1.1/linux-x64/*" Link="native/linux-x64/%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View File

@@ -1,11 +1,14 @@
using System;
using System.Runtime.CompilerServices;
using StellaOps.Testing;
internal static class TestEnvironment
{
[ModuleInitializer]
public static void Initialize()
{
OpenSslLegacyShim.EnsureOpenSsl11();
Environment.SetEnvironmentVariable("STELLAOPS_AUTHORITY_ISSUER", "https://authority.test");
Environment.SetEnvironmentVariable("STELLAOPS_AUTHORITY_STORAGE__CONNECTIONSTRING", "mongodb://localhost/authority");
Environment.SetEnvironmentVariable("STELLAOPS_AUTHORITY_SIGNING__ENABLED", "false");

View File

@@ -42,4 +42,8 @@ internal static class AuthorityOpenIddictConstants
internal const string QuotaTicketProperty = "authority:quota_ticket";
internal const string QuotaReasonParameterName = "quota_reason";
internal const string QuotaTicketParameterName = "quota_ticket";
internal const string BackfillReasonProperty = "authority:backfill_reason";
internal const string BackfillTicketProperty = "authority:backfill_ticket";
internal const string BackfillReasonParameterName = "backfill_reason";
internal const string BackfillTicketParameterName = "backfill_ticket";
}

View File

@@ -289,6 +289,7 @@ internal sealed class ValidateClientCredentialsHandler : IOpenIddictServerHandle
var hasOrchRead = grantedScopes.Length > 0 && Array.IndexOf(grantedScopes, StellaOpsScopes.OrchRead) >= 0;
var hasOrchOperate = grantedScopes.Length > 0 && Array.IndexOf(grantedScopes, StellaOpsScopes.OrchOperate) >= 0;
var hasOrchQuota = grantedScopes.Length > 0 && Array.IndexOf(grantedScopes, StellaOpsScopes.OrchQuota) >= 0;
var hasOrchBackfill = grantedScopes.Length > 0 && Array.IndexOf(grantedScopes, StellaOpsScopes.OrchBackfill) >= 0;
var hasExportViewer = grantedScopes.Length > 0 && Array.IndexOf(grantedScopes, StellaOpsScopes.ExportViewer) >= 0;
var hasExportOperator = grantedScopes.Length > 0 && Array.IndexOf(grantedScopes, StellaOpsScopes.ExportOperator) >= 0;
var hasExportAdmin = grantedScopes.Length > 0 && Array.IndexOf(grantedScopes, StellaOpsScopes.ExportAdmin) >= 0;
@@ -437,13 +438,15 @@ internal sealed class ValidateClientCredentialsHandler : IOpenIddictServerHandle
return;
}
if ((hasOrchRead || hasOrchOperate || hasOrchQuota) && !EnsureTenantAssigned())
if ((hasOrchRead || hasOrchOperate || hasOrchQuota || hasOrchBackfill) && !EnsureTenantAssigned())
{
var invalidScope = hasOrchQuota
? StellaOpsScopes.OrchQuota
: hasOrchOperate
? StellaOpsScopes.OrchOperate
: StellaOpsScopes.OrchRead;
: hasOrchBackfill
? StellaOpsScopes.OrchBackfill
: hasOrchOperate
? StellaOpsScopes.OrchOperate
: StellaOpsScopes.OrchRead;
context.Transaction.Properties[AuthorityOpenIddictConstants.AuditInvalidScopeProperty] = invalidScope;
context.Reject(OpenIddictConstants.Errors.InvalidClient, "Orchestrator scopes require a tenant assignment.");
logger.LogWarning(
@@ -529,6 +532,46 @@ internal sealed class ValidateClientCredentialsHandler : IOpenIddictServerHandle
}
}
if (hasOrchBackfill)
{
var backfillReasonRaw = context.Request.GetParameter(AuthorityOpenIddictConstants.BackfillReasonParameterName)?.Value?.ToString();
var backfillReason = NormalizeMetadata(backfillReasonRaw);
if (string.IsNullOrWhiteSpace(backfillReason))
{
context.Reject(OpenIddictConstants.Errors.InvalidRequest, "Backfill actions require 'backfill_reason'.");
logger.LogWarning("Client credentials validation failed for {ClientId}: backfill_reason missing.", document.ClientId);
return;
}
if (backfillReason.Length > 256)
{
context.Reject(OpenIddictConstants.Errors.InvalidRequest, "Backfill reason must not exceed 256 characters.");
logger.LogWarning("Client credentials validation failed for {ClientId}: backfill_reason exceeded length limit.", document.ClientId);
return;
}
var backfillTicketRaw = context.Request.GetParameter(AuthorityOpenIddictConstants.BackfillTicketParameterName)?.Value?.ToString();
var backfillTicket = NormalizeMetadata(backfillTicketRaw);
if (string.IsNullOrWhiteSpace(backfillTicket))
{
context.Reject(OpenIddictConstants.Errors.InvalidRequest, "Backfill actions require 'backfill_ticket'.");
logger.LogWarning("Client credentials validation failed for {ClientId}: backfill_ticket missing.", document.ClientId);
return;
}
if (backfillTicket.Length > 128)
{
context.Reject(OpenIddictConstants.Errors.InvalidRequest, "Backfill ticket must not exceed 128 characters.");
logger.LogWarning("Client credentials validation failed for {ClientId}: backfill_ticket exceeded length limit.", document.ClientId);
return;
}
context.Transaction.Properties[AuthorityOpenIddictConstants.BackfillReasonProperty] = backfillReason;
context.Transaction.Properties[AuthorityOpenIddictConstants.BackfillTicketProperty] = backfillTicket;
activity?.SetTag("authority.backfill_reason_present", true);
activity?.SetTag("authority.backfill_ticket_present", true);
}
if (hasExportAdmin)
{
var reasonRaw = context.Request.GetParameter(AuthorityOpenIddictConstants.ExportAdminReasonParameterName)?.Value?.ToString();
@@ -789,7 +832,7 @@ internal sealed class ValidateClientCredentialsHandler : IOpenIddictServerHandle
}
if (context.Transaction.Properties.TryGetValue(AuthorityOpenIddictConstants.QuotaTicketProperty, out var quotaTicketObj) &&
quotaTicketObj is string quotaTicket &&
quotaTicketObj is string quotaTicket &&
!string.IsNullOrWhiteSpace(quotaTicket))
{
extraProperties.Add(new AuthEventProperty
@@ -799,6 +842,28 @@ internal sealed class ValidateClientCredentialsHandler : IOpenIddictServerHandle
});
}
if (context.Transaction.Properties.TryGetValue(AuthorityOpenIddictConstants.BackfillReasonProperty, out var backfillReasonObj) &&
backfillReasonObj is string backfillReason &&
!string.IsNullOrWhiteSpace(backfillReason))
{
extraProperties.Add(new AuthEventProperty
{
Name = "backfill.reason",
Value = ClassifiedString.Sensitive(backfillReason)
});
}
if (context.Transaction.Properties.TryGetValue(AuthorityOpenIddictConstants.BackfillTicketProperty, out var backfillTicketObj) &&
backfillTicketObj is string backfillTicket &&
!string.IsNullOrWhiteSpace(backfillTicket))
{
extraProperties.Add(new AuthEventProperty
{
Name = "backfill.ticket",
Value = ClassifiedString.Sensitive(backfillTicket)
});
}
var record = ClientCredentialsAuditHelper.CreateRecord(
timeProvider,
context.Transaction,
@@ -1073,6 +1138,20 @@ internal sealed class HandleClientCredentialsHandler : IOpenIddictServerHandler<
identity.SetClaim(StellaOpsClaimTypes.QuotaTicket, quotaTicketValueString);
}
if (context.Transaction.Properties.TryGetValue(AuthorityOpenIddictConstants.BackfillReasonProperty, out var backfillReasonValue) &&
backfillReasonValue is string backfillReasonValueString &&
!string.IsNullOrWhiteSpace(backfillReasonValueString))
{
identity.SetClaim(StellaOpsClaimTypes.BackfillReason, backfillReasonValueString);
}
if (context.Transaction.Properties.TryGetValue(AuthorityOpenIddictConstants.BackfillTicketProperty, out var backfillTicketValue) &&
backfillTicketValue is string backfillTicketValueString &&
!string.IsNullOrWhiteSpace(backfillTicketValueString))
{
identity.SetClaim(StellaOpsClaimTypes.BackfillTicket, backfillTicketValueString);
}
var (providerHandle, descriptor) = await ResolveProviderAsync(context, document).ConfigureAwait(false);
if (context.IsRejected)
{

View File

@@ -1265,7 +1265,18 @@ if (authorityOptions.Bootstrap.Enabled)
try
{
var result = ackManager.Rotate(request);
request.KeyId = trimmedKeyId;
request.Location = trimmedLocation;
logger.LogDebug(
"Attempting ack token rotation with keyId='{KeyId}', location='{Location}', provider='{Provider}', source='{Source}', algorithm='{Algorithm}'",
trimmedKeyId,
trimmedLocation,
request.Provider ?? ackOptions.Provider,
request.Source ?? ackOptions.KeySource,
request.Algorithm ?? ackOptions.Algorithm);
var result = ackManager.Rotate(request);
ackLogger.LogInformation("Ack token key rotation completed. Active key {KeyId}.", result.ActiveKeyId);
return Results.Ok(new
@@ -1374,138 +1385,197 @@ app.MapPost("/permalinks/vuln", async (
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(StellaOpsScopes.VulnRead))
.WithName("CreateVulnPermalink");
app.MapPost("/notify/ack-tokens/rotate", async (
HttpContext context,
SigningRotationRequest? request,
AuthorityAckTokenKeyManager ackManager,
IOptions<StellaOpsAuthorityOptions> optionsAccessor,
IAuthEventSink auditSink,
TimeProvider timeProvider,
ILogger<AuthorityAckTokenKeyManager> logger,
CancellationToken cancellationToken) =>
{
var scopes = ExtractScopes(context.User);
if (request is null)
{
const string message = "Request payload is required.";
logger.LogWarning("Ack token rotation request payload missing.");
await WriteAckRotationAuditAsync(
context,
auditSink,
timeProvider,
AuthEventOutcome.Failure,
null,
null,
null,
null,
null,
message,
scopes,
cancellationToken).ConfigureAwait(false);
return Results.BadRequest(new { error = "invalid_request", message });
}
var notifications = optionsAccessor.Value.Notifications ?? throw new InvalidOperationException("Authority notifications configuration is missing.");
var ackOptions = notifications.AckTokens ?? throw new InvalidOperationException("Ack token configuration is missing.");
if (!ackOptions.Enabled)
{
const string message = "Ack tokens are disabled. Enable notifications.ackTokens before rotating keys.";
logger.LogWarning("Ack token rotation attempted while ack tokens are disabled.");
await WriteAckRotationAuditAsync(
context,
auditSink,
timeProvider,
AuthEventOutcome.Failure,
request.KeyId,
null,
null,
null,
null,
message,
scopes,
cancellationToken).ConfigureAwait(false);
return Results.BadRequest(new { error = "ack_tokens_disabled", message });
}
try
{
var result = ackManager.Rotate(request);
logger.LogInformation("Ack token key rotation completed. Active key {KeyId}.", result.ActiveKeyId);
await WriteAckRotationAuditAsync(
context,
auditSink,
timeProvider,
AuthEventOutcome.Success,
result.ActiveKeyId,
result.PreviousKeyId,
result.RetiredKeyIds,
result.ActiveProvider,
result.ActiveSource,
reason: null,
scopes,
cancellationToken).ConfigureAwait(false);
return Results.Ok(new
{
activeKeyId = result.ActiveKeyId,
provider = result.ActiveProvider,
source = result.ActiveSource,
location = result.ActiveLocation,
previousKeyId = result.PreviousKeyId,
retiredKeyIds = result.RetiredKeyIds
});
}
catch (InvalidOperationException ex)
{
logger.LogWarning(ex, "Ack token rotation failed due to invalid input.");
await WriteAckRotationAuditAsync(
context,
auditSink,
timeProvider,
AuthEventOutcome.Failure,
request.KeyId,
null,
null,
null,
null,
ex.Message,
scopes,
cancellationToken).ConfigureAwait(false);
return Results.BadRequest(new { error = "rotation_failed", message = ex.Message });
}
catch (Exception ex)
{
logger.LogError(ex, "Unexpected failure rotating ack token key.");
const string message = "Unexpected failure rotating ack token key.";
await WriteAckRotationAuditAsync(
context,
auditSink,
timeProvider,
AuthEventOutcome.Failure,
request.KeyId,
null,
null,
null,
null,
message,
scopes,
cancellationToken).ConfigureAwait(false);
return Results.Problem("Failed to rotate ack token key.");
}
})
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(StellaOpsScopes.NotifyAdmin))
.WithName("RotateNotifyAckTokenKey");
app.MapPost("/notify/ack-tokens/issue", async (
app.MapPost("/notify/ack-tokens/rotate", async (
HttpContext context,
SigningRotationRequest? request,
AuthorityAckTokenKeyManager ackManager,
IOptions<StellaOpsAuthorityOptions> optionsAccessor,
IAuthEventSink auditSink,
TimeProvider timeProvider,
ILogger<AuthorityAckTokenKeyManager> logger,
CancellationToken cancellationToken) =>
{
var scopes = ExtractScopes(context.User);
if (request is null)
{
const string message = "Request payload is required.";
logger.LogWarning("Ack token rotation request payload missing.");
await WriteAckRotationAuditAsync(
context,
auditSink,
timeProvider,
AuthEventOutcome.Failure,
null,
null,
null,
null,
null,
message,
scopes,
cancellationToken).ConfigureAwait(false);
return Results.BadRequest(new { error = "invalid_request", message });
}
logger.LogDebug(
"Ack token rotation request received. keyId='{KeyId}', location='{Location}', provider='{Provider}', source='{Source}'",
request.KeyId,
request.Location,
request.Provider,
request.Source);
var notifications = optionsAccessor.Value.Notifications ?? throw new InvalidOperationException("Authority notifications configuration is missing.");
var ackOptions = notifications.AckTokens ?? throw new InvalidOperationException("Ack token configuration is missing.");
var keyId = request.KeyId?.Trim();
if (string.IsNullOrWhiteSpace(keyId))
{
const string message = "Ack token key rotation requires a keyId.";
logger.LogWarning("Ack token rotation rejected: missing keyId.");
await WriteAckRotationAuditAsync(
context,
auditSink,
timeProvider,
AuthEventOutcome.Failure,
activeKeyId: null,
previousKeyId: null,
retiredKeyIds: null,
provider: null,
source: null,
reason: message,
scopes,
cancellationToken).ConfigureAwait(false);
return Results.BadRequest(new { error = "invalid_request", message });
}
var location = request.Location?.Trim();
if (string.IsNullOrWhiteSpace(location))
{
const string message = "Ack token key rotation requires a key path/location.";
logger.LogWarning("Ack token rotation rejected: missing key path/location.");
await WriteAckRotationAuditAsync(
context,
auditSink,
timeProvider,
AuthEventOutcome.Failure,
activeKeyId: keyId,
previousKeyId: null,
retiredKeyIds: null,
provider: null,
source: null,
reason: message,
scopes,
cancellationToken).ConfigureAwait(false);
return Results.BadRequest(new { error = "invalid_request", message });
}
var trimmedKeyId = keyId!;
var trimmedLocation = location!;
if (!ackOptions.Enabled)
{
const string message = "Ack tokens are disabled. Enable notifications.ackTokens before rotating keys.";
logger.LogWarning("Ack token rotation attempted while ack tokens are disabled.");
await WriteAckRotationAuditAsync(
context,
auditSink,
timeProvider,
AuthEventOutcome.Failure,
trimmedKeyId,
null,
null,
null,
null,
message,
scopes,
cancellationToken).ConfigureAwait(false);
return Results.BadRequest(new { error = "ack_tokens_disabled", message });
}
try
{
request.KeyId = trimmedKeyId;
request.Location = trimmedLocation;
var result = ackManager.Rotate(request);
logger.LogInformation("Ack token key rotation completed. Active key {KeyId}.", result.ActiveKeyId);
await WriteAckRotationAuditAsync(
context,
auditSink,
timeProvider,
AuthEventOutcome.Success,
result.ActiveKeyId,
result.PreviousKeyId,
result.RetiredKeyIds,
result.ActiveProvider,
result.ActiveSource,
reason: null,
scopes,
cancellationToken).ConfigureAwait(false);
return Results.Ok(new
{
activeKeyId = result.ActiveKeyId,
provider = result.ActiveProvider,
source = result.ActiveSource,
location = result.ActiveLocation,
previousKeyId = result.PreviousKeyId,
retiredKeyIds = result.RetiredKeyIds
});
}
catch (InvalidOperationException ex)
{
logger.LogWarning(ex, "Ack token rotation failed due to invalid input.");
await WriteAckRotationAuditAsync(
context,
auditSink,
timeProvider,
AuthEventOutcome.Failure,
request.KeyId,
null,
null,
null,
null,
ex.Message,
scopes,
cancellationToken).ConfigureAwait(false);
return Results.BadRequest(new { error = "rotation_failed", message = ex.Message });
}
catch (Exception ex)
{
logger.LogError(ex, "Unexpected failure rotating ack token key.");
const string message = "Unexpected failure rotating ack token key.";
await WriteAckRotationAuditAsync(
context,
auditSink,
timeProvider,
AuthEventOutcome.Failure,
request.KeyId,
null,
null,
null,
null,
message,
scopes,
cancellationToken).ConfigureAwait(false);
return Results.Problem("Failed to rotate ack token key.");
}
})
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(StellaOpsScopes.NotifyAdmin))
.WithName("RotateNotifyAckTokenKey");
app.MapPost("/notify/ack-tokens/issue", async (
HttpContext httpContext,
AckTokenIssueRequest request,
AuthorityAckTokenIssuer issuer,
@@ -1775,92 +1845,92 @@ static AuthEventClient? BuildClientContext(ClaimsPrincipal principal)
};
}
static async Task WriteAckRotationAuditAsync(
HttpContext context,
IAuthEventSink auditSink,
TimeProvider timeProvider,
AuthEventOutcome outcome,
string? activeKeyId,
string? previousKeyId,
IReadOnlyCollection<string>? retiredKeyIds,
string? provider,
string? source,
string? reason,
IReadOnlyList<string> scopes,
CancellationToken cancellationToken)
{
var eventType = outcome == AuthEventOutcome.Success
? "notify.ack.key_rotated"
: "notify.ack.key_rotation_failed";
var properties = new List<AuthEventProperty>();
if (!string.IsNullOrWhiteSpace(activeKeyId))
{
properties.Add(new AuthEventProperty
{
Name = "notify.ack.key_id",
Value = ClassifiedString.Public(activeKeyId)
});
}
if (!string.IsNullOrWhiteSpace(previousKeyId))
{
properties.Add(new AuthEventProperty
{
Name = "notify.ack.previous_key_id",
Value = ClassifiedString.Public(previousKeyId)
});
}
if (!string.IsNullOrWhiteSpace(provider))
{
properties.Add(new AuthEventProperty
{
Name = "notify.ack.provider",
Value = ClassifiedString.Public(provider)
});
}
if (!string.IsNullOrWhiteSpace(source))
{
properties.Add(new AuthEventProperty
{
Name = "notify.ack.source",
Value = ClassifiedString.Public(source)
});
}
if (retiredKeyIds is { Count: > 0 })
{
properties.Add(new AuthEventProperty
{
Name = "notify.ack.retired_key_ids",
Value = ClassifiedString.Public(string.Join(",", retiredKeyIds))
});
}
var record = new AuthEventRecord
{
EventType = eventType,
OccurredAt = timeProvider.GetUtcNow(),
CorrelationId = Activity.Current?.TraceId.ToString() ?? context.TraceIdentifier,
Outcome = outcome,
Reason = reason,
Client = BuildClientContext(context.User),
Tenant = ClassifiedString.Empty,
Scopes = scopes,
Network = BuildNetwork(context),
Properties = properties
};
await auditSink.WriteAsync(record, cancellationToken).ConfigureAwait(false);
}
static async Task WriteAckAuditAsync(
HttpContext context,
IAuthEventSink auditSink,
TimeProvider timeProvider,
static async Task WriteAckRotationAuditAsync(
HttpContext context,
IAuthEventSink auditSink,
TimeProvider timeProvider,
AuthEventOutcome outcome,
string? activeKeyId,
string? previousKeyId,
IReadOnlyCollection<string>? retiredKeyIds,
string? provider,
string? source,
string? reason,
IReadOnlyList<string> scopes,
CancellationToken cancellationToken)
{
var eventType = outcome == AuthEventOutcome.Success
? "notify.ack.key_rotated"
: "notify.ack.key_rotation_failed";
var properties = new List<AuthEventProperty>();
if (!string.IsNullOrWhiteSpace(activeKeyId))
{
properties.Add(new AuthEventProperty
{
Name = "notify.ack.key_id",
Value = ClassifiedString.Public(activeKeyId)
});
}
if (!string.IsNullOrWhiteSpace(previousKeyId))
{
properties.Add(new AuthEventProperty
{
Name = "notify.ack.previous_key_id",
Value = ClassifiedString.Public(previousKeyId)
});
}
if (!string.IsNullOrWhiteSpace(provider))
{
properties.Add(new AuthEventProperty
{
Name = "notify.ack.provider",
Value = ClassifiedString.Public(provider)
});
}
if (!string.IsNullOrWhiteSpace(source))
{
properties.Add(new AuthEventProperty
{
Name = "notify.ack.source",
Value = ClassifiedString.Public(source)
});
}
if (retiredKeyIds is { Count: > 0 })
{
properties.Add(new AuthEventProperty
{
Name = "notify.ack.retired_key_ids",
Value = ClassifiedString.Public(string.Join(",", retiredKeyIds))
});
}
var record = new AuthEventRecord
{
EventType = eventType,
OccurredAt = timeProvider.GetUtcNow(),
CorrelationId = Activity.Current?.TraceId.ToString() ?? context.TraceIdentifier,
Outcome = outcome,
Reason = reason,
Client = BuildClientContext(context.User),
Tenant = ClassifiedString.Empty,
Scopes = scopes,
Network = BuildNetwork(context),
Properties = properties
};
await auditSink.WriteAsync(record, cancellationToken).ConfigureAwait(false);
}
static async Task WriteAckAuditAsync(
HttpContext context,
IAuthEventSink auditSink,
TimeProvider timeProvider,
string eventType,
AuthEventOutcome outcome,
AckTokenPayload payload,

View File

@@ -51,7 +51,8 @@
> 2025-10-27: Added `orch:operate` scope, enforced `operator_reason`/`operator_ticket` on token issuance, updated Authority configs/docs, and captured audit metadata for control actions.
> 2025-10-28: Policy gateway + scanner now pass the expanded token client signature (`null` metadata by default), test stubs capture the optional parameters, and Policy Gateway/Scanner suites are green after fixing the Concelier storage build break.
> 2025-10-28: Authority password-grant tests now hit the new constructors but still need updates to drop obsolete `IOptions` arguments before the suite can pass.
| AUTH-ORCH-34-001 | DOING (2025-11-02) | Authority Core & Security Guild | AUTH-ORCH-33-001 | Introduce `Orch.Admin` role with quota/backfill scopes, enforce audit reason on quota changes, and update offline defaults/docs. | Admin role available; quotas/backfills require scope + reason; tests confirm tenant isolation; documentation updated. |
| AUTH-ORCH-34-001 | DONE (2025-11-02) | Authority Core & Security Guild | AUTH-ORCH-33-001 | Introduce `Orch.Admin` role with quota/backfill scopes, enforce audit reason on quota changes, and update offline defaults/docs. | Admin role available; quotas/backfills require scope + reason; tests confirm tenant isolation; documentation updated. |
> 2025-11-02: `orch:backfill` scope added with mandatory `backfill_reason`/`backfill_ticket`, client-credential validation and resource authorization paths emit audit fields, CLI picks up new configuration/env vars, and Authority docs/config samples updated for `Orch.Admin`.
## StellaOps Console (Sprint 23)
@@ -114,7 +115,9 @@
## CLI Parity & Task Packs
| ID | Status | Owner(s) | Depends on | Description | Exit Criteria |
|----|--------|----------|------------|-------------|---------------|
| AUTH-PACKS-41-001 | TODO | Authority Core & Security Guild | AUTH-AOC-19-001 | Define CLI SSO profiles and pack scopes (`Packs.Read`, `Packs.Write`, `Packs.Run`, `Packs.Approve`), update discovery metadata, offline defaults, and issuer templates. | Scopes available; metadata updated; tests ensure enforcement; offline kit templates refreshed. |
| AUTH-PACKS-41-001 | DOING (2025-11-02) | Authority Core & Security Guild | AUTH-AOC-19-001 | Define CLI SSO profiles and pack scopes (`Packs.Read`, `Packs.Write`, `Packs.Run`, `Packs.Approve`), update discovery metadata, offline defaults, and issuer templates. | Scopes available; metadata updated; tests ensure enforcement; offline kit templates refreshed. |
> 2025-11-02: Added Pack scope policies, Authority role defaults, and CLI profile guidance covering operator/publisher/approver flows.
> 2025-11-02: Shared OpenSSL 1.1 shim feeds Authority & Signals Mongo2Go harnesses so pack scope coverage keeps running on OpenSSL 3 hosts (AUTH-PACKS-41-001).
| AUTH-PACKS-43-001 | BLOCKED (2025-10-27) | Authority Core & Security Guild | AUTH-PACKS-41-001, TASKRUN-42-001, ORCH-SVC-42-101 | Enforce pack signing policies, approval RBAC checks, CLI CI token scopes, and audit logging for approvals. | Signing policies enforced; approvals require correct roles; CI token scope tests pass; audit logs recorded. |
> Blocked: Pack scopes (`AUTH-PACKS-41-001`) and Task Runner pack approvals (`ORCH-SVC-42-101`, `TASKRUN-42-001`) are still TODO. Authority lacks baseline `Packs.*` scope definitions and approval/audit endpoints to enforce policies. Revisit once dependent teams deliver scope catalog + Task Runner approval API.

View File

@@ -33,22 +33,32 @@ internal static class AuthorityTokenUtilities
var cacheKey = $"{options.Authority.Url}|{credential}|{scope}";
if (!string.IsNullOrWhiteSpace(scope) && scope.Contains("orch:operate", StringComparison.OrdinalIgnoreCase))
{
var reasonHash = HashOperatorMetadata(options.Authority.OperatorReason);
var ticketHash = HashOperatorMetadata(options.Authority.OperatorTicket);
cacheKey = $"{cacheKey}|op_reason:{reasonHash}|op_ticket:{ticketHash}";
}
return cacheKey;
}
private static string HashOperatorMetadata(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return "none";
}
if (!string.IsNullOrWhiteSpace(scope))
{
if (scope.Contains("orch:operate", StringComparison.OrdinalIgnoreCase))
{
var reasonHash = HashMetadata(options.Authority.OperatorReason);
var ticketHash = HashMetadata(options.Authority.OperatorTicket);
cacheKey = $"{cacheKey}|op_reason:{reasonHash}|op_ticket:{ticketHash}";
}
if (scope.Contains("orch:backfill", StringComparison.OrdinalIgnoreCase))
{
var reasonHash = HashMetadata(options.Authority.BackfillReason);
var ticketHash = HashMetadata(options.Authority.BackfillTicket);
cacheKey = $"{cacheKey}|bf_reason:{reasonHash}|bf_ticket:{ticketHash}";
}
}
return cacheKey;
}
private static string HashMetadata(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return "none";
}
var trimmed = value.Trim();
var bytes = Encoding.UTF8.GetBytes(trimmed);

View File

@@ -111,28 +111,44 @@ public static class CliBootstrapper
"StellaOps:Authority:OperatorReason",
"Authority:OperatorReason");
authority.OperatorTicket = ResolveWithFallback(
authority.OperatorTicket,
configuration,
"STELLAOPS_ORCH_TICKET",
"StellaOps:Authority:OperatorTicket",
"Authority:OperatorTicket");
authority.TokenCacheDirectory = ResolveWithFallback(
authority.TokenCacheDirectory,
configuration,
"STELLAOPS_AUTHORITY_TOKEN_CACHE_DIR",
"StellaOps:Authority:TokenCacheDirectory",
authority.OperatorTicket = ResolveWithFallback(
authority.OperatorTicket,
configuration,
"STELLAOPS_ORCH_TICKET",
"StellaOps:Authority:OperatorTicket",
"Authority:OperatorTicket");
authority.BackfillReason = ResolveWithFallback(
authority.BackfillReason,
configuration,
"STELLAOPS_ORCH_BACKFILL_REASON",
"StellaOps:Authority:BackfillReason",
"Authority:BackfillReason");
authority.BackfillTicket = ResolveWithFallback(
authority.BackfillTicket,
configuration,
"STELLAOPS_ORCH_BACKFILL_TICKET",
"StellaOps:Authority:BackfillTicket",
"Authority:BackfillTicket");
authority.TokenCacheDirectory = ResolveWithFallback(
authority.TokenCacheDirectory,
configuration,
"STELLAOPS_AUTHORITY_TOKEN_CACHE_DIR",
"StellaOps:Authority:TokenCacheDirectory",
"Authority:TokenCacheDirectory");
authority.Url = authority.Url?.Trim() ?? string.Empty;
authority.ClientId = authority.ClientId?.Trim() ?? string.Empty;
authority.ClientSecret = string.IsNullOrWhiteSpace(authority.ClientSecret) ? null : authority.ClientSecret.Trim();
authority.Username = authority.Username?.Trim() ?? string.Empty;
authority.Password = string.IsNullOrWhiteSpace(authority.Password) ? null : authority.Password.Trim();
authority.Scope = string.IsNullOrWhiteSpace(authority.Scope) ? StellaOpsScopes.ConcelierJobsTrigger : authority.Scope.Trim();
authority.OperatorReason = authority.OperatorReason?.Trim() ?? string.Empty;
authority.OperatorTicket = authority.OperatorTicket?.Trim() ?? string.Empty;
authority.Password = string.IsNullOrWhiteSpace(authority.Password) ? null : authority.Password.Trim();
authority.Scope = string.IsNullOrWhiteSpace(authority.Scope) ? StellaOpsScopes.ConcelierJobsTrigger : authority.Scope.Trim();
authority.OperatorReason = authority.OperatorReason?.Trim() ?? string.Empty;
authority.OperatorTicket = authority.OperatorTicket?.Trim() ?? string.Empty;
authority.BackfillReason = authority.BackfillReason?.Trim() ?? string.Empty;
authority.BackfillTicket = authority.BackfillTicket?.Trim() ?? string.Empty;
authority.Resilience ??= new StellaOpsCliAuthorityResilienceOptions();
authority.Resilience.RetryDelays ??= new List<TimeSpan>();

View File

@@ -46,11 +46,15 @@ public sealed class StellaOpsCliAuthorityOptions
public string Scope { get; set; } = StellaOpsScopes.ConcelierJobsTrigger;
public string OperatorReason { get; set; } = string.Empty;
public string OperatorTicket { get; set; } = string.Empty;
public string TokenCacheDirectory { get; set; } = string.Empty;
public string OperatorReason { get; set; } = string.Empty;
public string OperatorTicket { get; set; } = string.Empty;
public string BackfillReason { get; set; } = string.Empty;
public string BackfillTicket { get; set; } = string.Empty;
public string TokenCacheDirectory { get; set; } = string.Empty;
public StellaOpsCliAuthorityResilienceOptions Resilience { get; set; } = new();
}

View File

@@ -31,6 +31,8 @@ internal sealed class BackendOperationsClient : IBackendOperationsClient
private const string OperatorReasonParameterName = "operator_reason";
private const string OperatorTicketParameterName = "operator_ticket";
private const string BackfillReasonParameterName = "backfill_reason";
private const string BackfillTicketParameterName = "backfill_ticket";
private readonly HttpClient _httpClient;
private readonly StellaOpsCliOptions _options;
@@ -1745,26 +1747,52 @@ internal sealed class BackendOperationsClient : IBackendOperationsClient
}
}
private IReadOnlyDictionary<string, string>? ResolveOperatorMetadataIfNeeded(string? scope)
private IReadOnlyDictionary<string, string>? ResolveOrchestratorMetadataIfNeeded(string? scope)
{
if (string.IsNullOrWhiteSpace(scope) || !scope.Contains("orch:operate", StringComparison.OrdinalIgnoreCase))
if (string.IsNullOrWhiteSpace(scope))
{
return null;
}
var reason = _options.Authority.OperatorReason?.Trim();
var ticket = _options.Authority.OperatorTicket?.Trim();
var requiresOperate = scope.Contains("orch:operate", StringComparison.OrdinalIgnoreCase);
var requiresBackfill = scope.Contains("orch:backfill", StringComparison.OrdinalIgnoreCase);
if (string.IsNullOrWhiteSpace(reason) || string.IsNullOrWhiteSpace(ticket))
if (!requiresOperate && !requiresBackfill)
{
throw new InvalidOperationException("Authority.OperatorReason and Authority.OperatorTicket must be configured when requesting orch:operate tokens. Set STELLAOPS_ORCH_REASON and STELLAOPS_ORCH_TICKET or the corresponding configuration values.");
return null;
}
return new Dictionary<string, string>(StringComparer.Ordinal)
var metadata = new Dictionary<string, string>(StringComparer.Ordinal);
if (requiresOperate)
{
[OperatorReasonParameterName] = reason,
[OperatorTicketParameterName] = ticket
};
var reason = _options.Authority.OperatorReason?.Trim();
var ticket = _options.Authority.OperatorTicket?.Trim();
if (string.IsNullOrWhiteSpace(reason) || string.IsNullOrWhiteSpace(ticket))
{
throw new InvalidOperationException("Authority.OperatorReason and Authority.OperatorTicket must be configured when requesting orch:operate tokens. Set STELLAOPS_ORCH_REASON and STELLAOPS_ORCH_TICKET or the corresponding configuration values.");
}
metadata[OperatorReasonParameterName] = reason;
metadata[OperatorTicketParameterName] = ticket;
}
if (requiresBackfill)
{
var reason = _options.Authority.BackfillReason?.Trim();
var ticket = _options.Authority.BackfillTicket?.Trim();
if (string.IsNullOrWhiteSpace(reason) || string.IsNullOrWhiteSpace(ticket))
{
throw new InvalidOperationException("Authority.BackfillReason and Authority.BackfillTicket must be configured when requesting orch:backfill tokens. Set STELLAOPS_ORCH_BACKFILL_REASON and STELLAOPS_ORCH_BACKFILL_TICKET or the corresponding configuration values.");
}
metadata[BackfillReasonParameterName] = reason;
metadata[BackfillTicketParameterName] = ticket;
}
return metadata;
}
private async Task<string?> ResolveAccessTokenAsync(CancellationToken cancellationToken)
@@ -1802,7 +1830,7 @@ internal sealed class BackendOperationsClient : IBackendOperationsClient
}
var scope = AuthorityTokenUtilities.ResolveScope(_options);
var operatorMetadata = ResolveOperatorMetadataIfNeeded(scope);
var orchestratorMetadata = ResolveOrchestratorMetadataIfNeeded(scope);
StellaOpsTokenResult token;
if (!string.IsNullOrWhiteSpace(_options.Authority.Username))
@@ -1821,7 +1849,7 @@ internal sealed class BackendOperationsClient : IBackendOperationsClient
}
else
{
token = await _tokenClient.RequestClientCredentialsTokenAsync(scope, operatorMetadata, cancellationToken).ConfigureAwait(false);
token = await _tokenClient.RequestClientCredentialsTokenAsync(scope, orchestratorMetadata, cancellationToken).ConfigureAwait(false);
}
await _tokenClient.CacheTokenAsync(cacheKey, token.ToCacheEntry(), cancellationToken).ConfigureAwait(false);

View File

@@ -0,0 +1,86 @@
using System;
using System.Security.Cryptography;
using System.Text;
using StellaOps.Cli.Configuration;
using Xunit;
namespace StellaOps.Cli.Tests.Configuration;
public static class AuthorityTokenUtilitiesTests
{
[Fact]
public static void BuildCacheKey_AppendsOperateMetadataHashes_WhenScopeRequiresOperate()
{
var options = new StellaOpsCliOptions
{
Authority = new StellaOpsCliAuthorityOptions
{
Url = "https://authority.example",
ClientId = "cli",
Scope = "orch:operate",
OperatorReason = "Resume service",
OperatorTicket = "INC-2001"
}
};
var key = AuthorityTokenUtilities.BuildCacheKey(options);
var expectedReasonHash = ComputeHash("Resume service");
var expectedTicketHash = ComputeHash("INC-2001");
Assert.Contains($"|op_reason:{expectedReasonHash}|op_ticket:{expectedTicketHash}", key, StringComparison.Ordinal);
}
[Fact]
public static void BuildCacheKey_AppendsBackfillMetadataHashes_WhenScopeRequiresBackfill()
{
var options = new StellaOpsCliOptions
{
Authority = new StellaOpsCliAuthorityOptions
{
Url = "https://authority.example",
ClientId = "cli",
Scope = "orch:backfill",
BackfillReason = "Rebuild historical findings",
BackfillTicket = "INC-3003"
}
};
var key = AuthorityTokenUtilities.BuildCacheKey(options);
var expectedReasonHash = ComputeHash("Rebuild historical findings");
var expectedTicketHash = ComputeHash("INC-3003");
Assert.Contains($"|bf_reason:{expectedReasonHash}|bf_ticket:{expectedTicketHash}", key, StringComparison.Ordinal);
}
[Fact]
public static void BuildCacheKey_AppendsBothOperateAndBackfillHashes_WhenScopeRequiresBoth()
{
var options = new StellaOpsCliOptions
{
Authority = new StellaOpsCliAuthorityOptions
{
Url = "https://authority.example",
ClientId = "cli",
Scope = "orch:operate orch:backfill",
OperatorReason = "Adjust schedules",
OperatorTicket = "INC-4004",
BackfillReason = "Historical rebuild",
BackfillTicket = "INC-5005"
}
};
var key = AuthorityTokenUtilities.BuildCacheKey(options);
Assert.Contains($"|op_reason:{ComputeHash("Adjust schedules")}|op_ticket:{ComputeHash("INC-4004")}", key, StringComparison.Ordinal);
Assert.Contains($"|bf_reason:{ComputeHash("Historical rebuild")}|bf_ticket:{ComputeHash("INC-5005")}", key, StringComparison.Ordinal);
}
private static string ComputeHash(string value)
{
var bytes = Encoding.UTF8.GetBytes(value.Trim());
var hash = SHA256.HashData(bytes);
return Convert.ToHexString(hash).ToLowerInvariant();
}
}

View File

@@ -422,11 +422,11 @@ public sealed class BackendOperationsClientTests
}
[Fact]
public async Task TriggerJobAsync_UsesAuthorityTokenWhenConfigured()
{
using var temp = new TempDirectory();
var handler = new StubHttpMessageHandler((request, _) =>
public async Task TriggerJobAsync_UsesAuthorityTokenWhenConfigured()
{
using var temp = new TempDirectory();
var handler = new StubHttpMessageHandler((request, _) =>
{
Assert.NotNull(request.Headers.Authorization);
Assert.Equal("Bearer", request.Headers.Authorization!.Scheme);
@@ -471,10 +471,116 @@ public sealed class BackendOperationsClientTests
var result = await client.TriggerJobAsync("test", new Dictionary<string, object?>(), CancellationToken.None);
Assert.True(result.Success);
Assert.Equal("Accepted", result.Message);
Assert.True(tokenClient.Requests > 0);
}
Assert.Equal("Accepted", result.Message);
Assert.True(tokenClient.Requests > 0);
}
[Fact]
public async Task TriggerJobAsync_ThrowsWhenBackfillMetadataMissing()
{
using var temp = new TempDirectory();
var handler = new StubHttpMessageHandler((request, _) =>
{
return new HttpResponseMessage(HttpStatusCode.Accepted)
{
RequestMessage = request,
Content = JsonContent.Create(new JobRunResponse
{
RunId = Guid.NewGuid(),
Kind = "test",
Status = "Pending",
Trigger = "cli",
CreatedAt = DateTimeOffset.UtcNow
})
};
});
var httpClient = new HttpClient(handler)
{
BaseAddress = new Uri("https://concelier.example")
};
var options = new StellaOpsCliOptions
{
BackendUrl = "https://concelier.example",
Authority =
{
Url = "https://authority.example",
ClientId = "cli",
ClientSecret = "secret",
Scope = "orch:backfill",
TokenCacheDirectory = temp.Path
}
};
var tokenClient = new StubTokenClient();
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), tokenClient);
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => client.TriggerJobAsync("test", new Dictionary<string, object?>(), CancellationToken.None));
Assert.Contains("Authority.BackfillReason", exception.Message, StringComparison.Ordinal);
Assert.Equal(0, tokenClient.Requests);
}
[Fact]
public async Task TriggerJobAsync_RequestsOperateAndBackfillMetadata()
{
using var temp = new TempDirectory();
var handler = new StubHttpMessageHandler((request, _) =>
{
Assert.NotNull(request.Headers.Authorization);
return new HttpResponseMessage(HttpStatusCode.Accepted)
{
RequestMessage = request,
Content = JsonContent.Create(new JobRunResponse
{
RunId = Guid.NewGuid(),
Kind = "test",
Status = "Pending",
Trigger = "cli",
CreatedAt = DateTimeOffset.UtcNow
})
};
});
var httpClient = new HttpClient(handler)
{
BaseAddress = new Uri("https://concelier.example")
};
var options = new StellaOpsCliOptions
{
BackendUrl = "https://concelier.example",
Authority =
{
Url = "https://authority.example",
ClientId = "cli",
ClientSecret = "secret",
Scope = "orch:operate orch:backfill",
TokenCacheDirectory = temp.Path,
OperatorReason = "Resume operations",
OperatorTicket = "INC-6006",
BackfillReason = "Historical rebuild",
BackfillTicket = "INC-7007"
}
};
var tokenClient = new StubTokenClient();
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), tokenClient);
var result = await client.TriggerJobAsync("test", new Dictionary<string, object?>(), CancellationToken.None);
Assert.True(result.Success);
var metadata = Assert.NotNull(tokenClient.LastAdditionalParameters);
Assert.Equal("Resume operations", metadata["operator_reason"]);
Assert.Equal("INC-6006", metadata["operator_ticket"]);
Assert.Equal("Historical rebuild", metadata["backfill_reason"]);
Assert.Equal("INC-7007", metadata["backfill_ticket"]);
}
[Fact]
public async Task EvaluateRuntimePolicyAsync_ParsesDecisionPayload()
{

View File

@@ -29,7 +29,15 @@ public sealed class EntryTraceRuntimeReconciler
"bash",
"dash",
"ash",
"env"
"env",
"bundle",
"docker-php-entrypoint",
"npm",
"npx",
"yarn",
"yarnpkg",
"pipenv",
"poetry"
};
public EntryTraceGraph Reconcile(EntryTraceGraph graph, ProcGraph? procGraph)

View File

@@ -7,8 +7,8 @@
| SCANNER-ENTRYTRACE-18-504 | DONE (2025-11-01) | EntryTrace Guild | SCANNER-ENTRYTRACE-18-503 | Emit EntryTrace AOC NDJSON (`entrytrace.entry/node/edge/target/warning/capability`) and wire CLI/service streaming outputs. | NDJSON writer passes determinism tests, CLI/service endpoints stream ordered observations, and diagnostics integrate new warning codes for dynamic eval/glob limits/windows shims. |
| SCANNER-ENTRYTRACE-18-505 | DONE (2025-11-02) | EntryTrace Guild | SCANNER-ENTRYTRACE-18-504 | Implement process-tree replay (ProcGraph) to reconcile `/proc` exec chains with static EntryTrace results, collapsing wrappers (tini/gosu/supervisord) and emitting agreement/conflict diagnostics. | Runtime harness walks `/proc` (tests + fixture containers), merges ProcGraph with static graph, records High/Medium/Low confidence outcomes, and adds coverage to integration tests. |
| SCANNER-ENTRYTRACE-18-506 | DONE (2025-11-02) | EntryTrace Guild, Scanner WebService Guild | SCANNER-ENTRYTRACE-18-505 | Surface EntryTrace graph + confidence via Scanner.WebService and CLI (REST + streaming), including target summary in scan reports and policy payloads. | WebService exposes `/scans/{id}/entrytrace` + CLI verb, responses include chain/terminal/confidence/evidence, golden fixtures updated, and Policy/Export contracts documented. |
| SCANNER-ENTRYTRACE-18-507 | DOING (2025-11-02) | EntryTrace Guild | SCANNER-ENTRYTRACE-18-503 | Expand candidate discovery beyond ENTRYPOINT/CMD by scanning Docker history metadata and default service directories (`/etc/services/**`, `/s6/**`, `/etc/supervisor/*.conf`, `/usr/local/bin/*-entrypoint`) when explicit commands are absent. | Analyzer produces deterministic fallback candidates with evidence per discovery source, golden fixtures cover supervisor/service directories, and diagnostics distinguish inferred vs declared entrypoints. |
| SCANNER-ENTRYTRACE-18-508 | DOING (2025-11-02) | EntryTrace Guild | SCANNER-ENTRYTRACE-18-503 | Extend wrapper catalogue to collapse language/package launchers (`bundle`, `bundle exec`, `docker-php-entrypoint`, `npm`, `yarn node`, `pipenv`, `poetry run`) and vendor init scripts before terminal classification. | Wrapper detection table includes the new aliases with metadata, analyzer unwraps them into underlying commands, and fixture scripts assert metadata for runtime/package managers. |
| SCANNER-ENTRYTRACE-18-507 | DONE (2025-11-02) | EntryTrace Guild | SCANNER-ENTRYTRACE-18-503 | Expand candidate discovery beyond ENTRYPOINT/CMD by scanning Docker history metadata and default service directories (`/etc/services/**`, `/s6/**`, `/etc/supervisor/*.conf`, `/usr/local/bin/*-entrypoint`) when explicit commands are absent. | Analyzer produces deterministic fallback candidates with evidence per discovery source, golden fixtures cover supervisor/service directories, and diagnostics distinguish inferred vs declared entrypoints. |
| SCANNER-ENTRYTRACE-18-508 | DONE (2025-11-02) | EntryTrace Guild | SCANNER-ENTRYTRACE-18-503 | Extend wrapper catalogue to collapse language/package launchers (`bundle`, `bundle exec`, `docker-php-entrypoint`, `npm`, `yarn node`, `pipenv`, `poetry run`) and vendor init scripts before terminal classification. | Wrapper detection table includes the new aliases with metadata, analyzer unwraps them into underlying commands, and fixture scripts assert metadata for runtime/package managers. |
| SCANNER-ENTRYTRACE-18-509 | DONE (2025-11-02) | EntryTrace Guild, QA Guild | SCANNER-ENTRYTRACE-18-506 | Add regression coverage for persisted EntryTrace surfaces (result store, WebService endpoint, CLI renderer) and NDJSON payload hashing. | Unit/integration tests cover result retrieval (store/WebService), CLI rendering (`scan entrytrace`), and NDJSON hash stability with fixture snapshots. |
| ENTRYTRACE-SURFACE-01 | DONE (2025-11-02) | EntryTrace Guild | SURFACE-VAL-02, SURFACE-FS-02 | Run Surface.Validation prereq checks and resolve cached entry fragments via Surface.FS to avoid duplicate parsing. | EntryTrace performance metrics show reuse; regression tests updated; validation errors surfaced consistently. |
| ENTRYTRACE-SURFACE-02 | DONE (2025-11-02) | EntryTrace Guild | SURFACE-SECRETS-02 | Replace direct env/secret access with Surface.Secrets provider when tracing runtime configs. | Shared provider used; failure modes covered; documentation refreshed. |

View File

@@ -20,6 +20,239 @@ public sealed class EntryTraceAnalyzerTests
_output = output;
}
[Fact]
public async Task ResolveAsync_CollapsesBundleExecWrapper()
{
var fs = new TestRootFileSystem();
fs.AddBinaryFile("/usr/bin/bundle", CreateGoBinary(), executable: true);
fs.AddBinaryFile("/usr/local/bin/puma", CreateGoBinary(), executable: true);
fs.AddFile("/config.rb", "port 3000\n", executable: false);
var context = new EntryTraceContext(
fs,
ImmutableDictionary<string, string>.Empty,
ImmutableArray.Create("/usr/bin", "/usr/local/bin"),
"/",
"app",
"sha256:bundle-image",
"scan-bundle",
NullLogger.Instance);
var spec = EntrypointSpecification.FromExecForm(
new[] { "bundle", "exec", "puma", "-C", "config.rb" },
null);
var analyzer = CreateAnalyzer();
var result = await analyzer.ResolveAsync(spec, context);
Assert.Equal(EntryTraceOutcome.Resolved, result.Outcome);
var terminal = Assert.Single(result.Terminals);
Assert.Equal("/usr/local/bin/puma", terminal.Path);
var bundleNode = Assert.Single(result.Nodes.Where(n => n.DisplayName == "bundle"));
Assert.Equal("language-launcher", bundleNode.Metadata?["wrapper.category"]);
Assert.Equal("bundle exec", bundleNode.Metadata?["wrapper.name"]);
Assert.Contains(result.Edges, edge => edge.Relationship == "wrapper" && edge.FromNodeId == bundleNode.Id);
}
[Fact]
public async Task ResolveAsync_CollapsesDockerPhpEntrypointWrapper()
{
var fs = new TestRootFileSystem();
fs.AddFile("/usr/local/bin/docker-php-entrypoint", "#!/bin/sh\nexec \"$@\"\n", executable: true);
fs.AddBinaryFile("/usr/local/sbin/php-fpm", CreateGoBinary(), executable: true);
fs.AddFile("/usr/local/etc/php-fpm.conf", "include=/etc/php-fpm.d/*.conf\n", executable: false);
var context = new EntryTraceContext(
fs,
ImmutableDictionary<string, string>.Empty,
ImmutableArray.Create("/usr/local/bin", "/usr/local/sbin"),
"/app",
"www-data",
"sha256:php-image",
"scan-php",
NullLogger.Instance);
var spec = EntrypointSpecification.FromExecForm(
new[] { "/usr/local/bin/docker-php-entrypoint", "php-fpm", "-y", "/usr/local/etc/php-fpm.conf" },
null);
var analyzer = CreateAnalyzer();
var result = await analyzer.ResolveAsync(spec, context);
Assert.Equal(EntryTraceOutcome.Resolved, result.Outcome);
var terminal = Assert.Single(result.Terminals);
Assert.Equal("/usr/local/sbin/php-fpm", terminal.Path);
var wrapperNode = Assert.Single(result.Nodes.Where(n => n.DisplayName.Contains("docker-php-entrypoint", StringComparison.OrdinalIgnoreCase)));
Assert.Equal("language-launcher", wrapperNode.Metadata?["wrapper.category"]);
Assert.Equal("docker-php-entrypoint", wrapperNode.Metadata?["wrapper.name"]);
}
[Fact]
public async Task ResolveAsync_CollapsesNpmExecWrapper()
{
var fs = new TestRootFileSystem();
fs.AddBinaryFile("/usr/bin/npm", CreateGoBinary(), executable: true);
fs.AddBinaryFile("/usr/bin/node", CreateGoBinary(), executable: true);
fs.AddFile("/server.js", "console.log('hello');", executable: false);
var context = new EntryTraceContext(
fs,
ImmutableDictionary<string, string>.Empty,
ImmutableArray.Create("/usr/bin"),
"/srv",
"node",
"sha256:npm-image",
"scan-npm",
NullLogger.Instance);
var spec = EntrypointSpecification.FromExecForm(
new[] { "npm", "exec", "--yes", "node", "server.js" },
null);
var analyzer = CreateAnalyzer();
var result = await analyzer.ResolveAsync(spec, context);
Assert.True(
result.Diagnostics.All(d => d.Severity != EntryTraceDiagnosticSeverity.Warning),
string.Join(", ", result.Diagnostics.Select(d => $"{d.Severity}:{d.Reason}:{d.Message}")));
if (result.Outcome != EntryTraceOutcome.Resolved)
{
throw new XunitException("Outcome: " + result.Outcome + " Diagnostics: " + string.Join(", ", result.Diagnostics.Select(d => $"{d.Severity}:{d.Reason}:{d.Message}")));
}
if (result.Terminals.Length == 0)
{
throw new XunitException("Terminals missing; diagnostics=" + string.Join(", ", result.Diagnostics.Select(d => $"{d.Severity}:{d.Reason}:{d.Message}")) +
"; nodes=" + string.Join(", ", result.Nodes.Select(n => $"{n.Kind}:{n.DisplayName}")));
}
var terminal = Assert.Single(result.Terminals);
Assert.Equal("/usr/bin/node", terminal.Path);
var npmNode = Assert.Single(result.Nodes.Where(n => n.DisplayName.Contains("npm", StringComparison.OrdinalIgnoreCase)));
Assert.Equal("language-launcher", npmNode.Metadata?["wrapper.category"]);
Assert.Equal("npm", npmNode.Metadata?["wrapper.name"]);
}
[Fact]
public async Task ResolveAsync_CollapsesYarnNodeWrapper()
{
var fs = new TestRootFileSystem();
fs.AddBinaryFile("/usr/local/bin/yarn", CreateGoBinary(), executable: true);
fs.AddBinaryFile("/usr/bin/node", CreateGoBinary(), executable: true);
fs.AddFile("/app.js", "console.log('app');", executable: false);
var context = new EntryTraceContext(
fs,
ImmutableDictionary<string, string>.Empty,
ImmutableArray.Create("/usr/local/bin", "/usr/bin"),
"/workspace",
"node",
"sha256:yarn-image",
"scan-yarn",
NullLogger.Instance);
var spec = EntrypointSpecification.FromExecForm(
new[] { "/usr/local/bin/yarn", "node", "app.js" },
null);
var analyzer = CreateAnalyzer();
var result = await analyzer.ResolveAsync(spec, context);
Assert.Equal(EntryTraceOutcome.Resolved, result.Outcome);
if (result.Terminals.Length == 0)
{
throw new XunitException("Terminals missing; diagnostics=" + string.Join(", ", result.Diagnostics.Select(d => $"{d.Severity}:{d.Reason}:{d.Message}")) +
"; nodes=" + string.Join(", ", result.Nodes.Select(n => $"{n.Kind}:{n.DisplayName}")));
}
var terminal = Assert.Single(result.Terminals);
Assert.Equal("/usr/bin/node", terminal.Path);
var yarnNode = Assert.Single(result.Nodes.Where(n => n.DisplayName.Contains("yarn", StringComparison.OrdinalIgnoreCase)));
Assert.Equal("language-launcher", yarnNode.Metadata?["wrapper.category"]);
Assert.Equal("yarn node", yarnNode.Metadata?["wrapper.name"]);
}
[Fact]
public async Task ResolveAsync_CollapsesPipenvRunWrapper()
{
var fs = new TestRootFileSystem();
fs.AddBinaryFile("/usr/local/bin/pipenv", CreateGoBinary(), executable: true);
fs.AddBinaryFile("/usr/bin/python", CreateGoBinary(), executable: true);
fs.AddFile("/app.py", "print('ok')\n", executable: false);
var context = new EntryTraceContext(
fs,
ImmutableDictionary<string, string>.Empty,
ImmutableArray.Create("/usr/local/bin", "/usr/bin"),
"/service",
"python",
"sha256:pipenv-image",
"scan-pipenv",
NullLogger.Instance);
var spec = EntrypointSpecification.FromExecForm(
new[] { "pipenv", "run", "python", "app.py" },
null);
var analyzer = CreateAnalyzer();
var result = await analyzer.ResolveAsync(spec, context);
Assert.Equal(EntryTraceOutcome.Resolved, result.Outcome);
if (result.Terminals.Length == 0)
{
throw new XunitException("Terminals missing; diagnostics=" + string.Join(", ", result.Diagnostics.Select(d => $"{d.Severity}:{d.Reason}:{d.Message}")) +
"; nodes=" + string.Join(", ", result.Nodes.Select(n => $"{n.Kind}:{n.DisplayName}")));
}
var terminal = Assert.Single(result.Terminals);
Assert.Equal("/usr/bin/python", terminal.Path);
var pipenvNode = Assert.Single(result.Nodes.Where(n => n.DisplayName.Contains("pipenv", StringComparison.OrdinalIgnoreCase)));
Assert.Equal("language-launcher", pipenvNode.Metadata?["wrapper.category"]);
Assert.Equal("pipenv run", pipenvNode.Metadata?["wrapper.name"]);
}
[Fact]
public async Task ResolveAsync_CollapsesPoetryRunWrapper()
{
var fs = new TestRootFileSystem();
fs.AddBinaryFile("/usr/local/bin/poetry", CreateGoBinary(), executable: true);
fs.AddBinaryFile("/usr/bin/python", CreateGoBinary(), executable: true);
fs.AddFile("/manage.py", "print('manage')\n", executable: false);
var context = new EntryTraceContext(
fs,
ImmutableDictionary<string, string>.Empty,
ImmutableArray.Create("/usr/local/bin", "/usr/bin"),
"/srv/app",
"python",
"sha256:poetry-image",
"scan-poetry",
NullLogger.Instance);
var spec = EntrypointSpecification.FromExecForm(
new[] { "poetry", "run", "python", "manage.py" },
null);
var analyzer = CreateAnalyzer();
var result = await analyzer.ResolveAsync(spec, context);
Assert.Equal(EntryTraceOutcome.Resolved, result.Outcome);
if (result.Terminals.Length == 0)
{
throw new XunitException("Terminals missing; diagnostics=" + string.Join(", ", result.Diagnostics.Select(d => $"{d.Severity}:{d.Reason}:{d.Message}")) +
"; nodes=" + string.Join(", ", result.Nodes.Select(n => $"{n.Kind}:{n.DisplayName}")));
}
var terminal = Assert.Single(result.Terminals);
Assert.Equal("/usr/bin/python", terminal.Path);
var poetryNode = Assert.Single(result.Nodes.Where(n => n.DisplayName.Contains("poetry", StringComparison.OrdinalIgnoreCase)));
Assert.Equal("language-launcher", poetryNode.Metadata?["wrapper.category"]);
Assert.Equal("poetry run", poetryNode.Metadata?["wrapper.name"]);
}
private static EntryTraceAnalyzer CreateAnalyzer()
{
var options = Options.Create(new EntryTraceAnalyzerOptions
@@ -269,6 +502,7 @@ public sealed class EntryTraceAnalyzerTests
{
var fs = new TestRootFileSystem();
fs.AddBinaryFile("/usr/bin/node", CreateGoBinary(), executable: true);
fs.AddFile("/app/server.js", "console.log('server');\n", executable: false);
var config = new OciImageConfig
{
@@ -284,6 +518,11 @@ public sealed class EntryTraceAnalyzerTests
"scan-history",
NullLogger.Instance);
var candidates = imageContext.Context.Candidates;
var historyCandidate = Assert.Single(candidates);
Assert.Equal("history", historyCandidate.Source);
Assert.True(historyCandidate.Command.SequenceEqual(new[] { "/usr/bin/node", "/app/server.js" }));
var analyzer = CreateAnalyzer();
var result = await analyzer.ResolveAsync(imageContext.Entrypoint, imageContext.Context);
@@ -291,7 +530,7 @@ public sealed class EntryTraceAnalyzerTests
Assert.Contains(result.Diagnostics, diagnostic => diagnostic.Reason == EntryTraceUnknownReason.InferredEntrypointFromHistory);
var terminal = Assert.Single(result.Terminals);
Assert.Equal("/usr/bin/node", terminal.Path);
Assert.Contains("/usr/bin/node", terminal.Arguments[0]);
Assert.Contains("/app/server.js", terminal.Arguments);
}
[Fact]
@@ -315,6 +554,11 @@ public sealed class EntryTraceAnalyzerTests
"scan-supervisor",
NullLogger.Instance);
var candidates = imageContext.Context.Candidates;
var supervisorCandidate = Assert.Single(candidates);
Assert.Equal("supervisor", supervisorCandidate.Source);
Assert.True(supervisorCandidate.Command.SequenceEqual(new[] { "gunicorn", "app:app" }));
var analyzer = CreateAnalyzer();
var result = await analyzer.ResolveAsync(imageContext.Entrypoint, imageContext.Context);
@@ -322,7 +566,43 @@ public sealed class EntryTraceAnalyzerTests
Assert.Contains(result.Diagnostics, diagnostic => diagnostic.Reason == EntryTraceUnknownReason.InferredEntrypointFromSupervisor);
var terminal = Assert.Single(result.Terminals);
Assert.Equal("/usr/bin/gunicorn", terminal.Path);
Assert.Contains("gunicorn", terminal.Arguments[0]);
Assert.Contains("app:app", terminal.Arguments);
}
[Fact]
public async Task ResolveAsync_DiscoversEntrypointScriptCandidate()
{
var fs = new TestRootFileSystem();
fs.AddFile("/usr/local/bin/image-entrypoint", """
#!/bin/sh
exec python /srv/service.py
""", executable: true);
fs.AddBinaryFile("/usr/bin/python", CreateGoBinary(), executable: true);
fs.AddFile("/srv/service.py", "print('run')\n", executable: false);
var config = new OciImageConfig();
var options = new EntryTraceAnalyzerOptions();
var imageContext = EntryTraceImageContextFactory.Create(
config,
fs,
options,
"sha256:image-entrypoint",
"scan-entrypoint",
NullLogger.Instance);
var candidates = imageContext.Context.Candidates;
var scriptCandidate = Assert.Single(candidates);
Assert.Equal("entrypoint-script", scriptCandidate.Source);
Assert.True(scriptCandidate.Command.SequenceEqual(new[] { "/usr/local/bin/image-entrypoint" }));
var analyzer = CreateAnalyzer();
var result = await analyzer.ResolveAsync(imageContext.Entrypoint, imageContext.Context);
Assert.Equal(EntryTraceOutcome.Resolved, result.Outcome);
Assert.Contains(result.Diagnostics, diagnostic => diagnostic.Reason == EntryTraceUnknownReason.InferredEntrypointFromEntrypointScript);
var terminal = Assert.Single(result.Terminals);
Assert.Equal("/usr/bin/python", terminal.Path);
Assert.Contains("/srv/service.py", terminal.Arguments);
}
[Fact]

View File

@@ -1,64 +1,64 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using Mongo2Go;
namespace StellaOps.Signals.Tests.TestInfrastructure;
internal sealed class SignalsTestFactory : WebApplicationFactory<Program>, IAsyncLifetime
{
private readonly MongoDbRunner mongoRunner;
private readonly string storagePath;
public SignalsTestFactory()
{
mongoRunner = MongoDbRunner.Start(singleNodeReplSet: true);
storagePath = Path.Combine(Path.GetTempPath(), "signals-tests", Guid.NewGuid().ToString());
Directory.CreateDirectory(storagePath);
}
public string StoragePath => storagePath;
public MongoDbRunner MongoRunner => mongoRunner;
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration((context, configuration) =>
{
var settings = new Dictionary<string, string?>
{
["Signals:Authority:Enabled"] = "false",
["Signals:Authority:AllowAnonymousFallback"] = "true",
["Signals:Mongo:ConnectionString"] = mongoRunner.ConnectionString,
["Signals:Mongo:Database"] = "signals-tests",
["Signals:Mongo:CallgraphsCollection"] = "callgraphs",
["Signals:Storage:RootPath"] = storagePath
};
configuration.AddInMemoryCollection(settings);
});
}
public Task InitializeAsync() => Task.CompletedTask;
public async Task DisposeAsync()
{
await Task.Run(() => mongoRunner.Dispose());
try
{
if (Directory.Exists(storagePath))
{
Directory.Delete(storagePath, recursive: true);
}
}
catch
{
// best effort cleanup.
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using Mongo2Go;
namespace StellaOps.Signals.Tests.TestInfrastructure;
internal sealed class SignalsTestFactory : WebApplicationFactory<Program>, IAsyncLifetime
{
private readonly MongoDbRunner mongoRunner;
private readonly string storagePath;
public SignalsTestFactory()
{
mongoRunner = MongoDbRunner.Start(singleNodeReplSet: true);
storagePath = Path.Combine(Path.GetTempPath(), "signals-tests", Guid.NewGuid().ToString());
Directory.CreateDirectory(storagePath);
}
public string StoragePath => storagePath;
public MongoDbRunner MongoRunner => mongoRunner;
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration((context, configuration) =>
{
var settings = new Dictionary<string, string?>
{
["Signals:Authority:Enabled"] = "false",
["Signals:Authority:AllowAnonymousFallback"] = "true",
["Signals:Mongo:ConnectionString"] = mongoRunner.ConnectionString,
["Signals:Mongo:Database"] = "signals-tests",
["Signals:Mongo:CallgraphsCollection"] = "callgraphs",
["Signals:Storage:RootPath"] = storagePath
};
configuration.AddInMemoryCollection(settings);
});
}
public Task InitializeAsync() => Task.CompletedTask;
public async Task DisposeAsync()
{
await Task.Run(() => mongoRunner.Dispose());
try
{
if (Directory.Exists(storagePath))
{
Directory.Delete(storagePath, recursive: true);
}
}
catch
{
// best effort cleanup.
}
}
}

View File

@@ -0,0 +1,7 @@
# OpenSSL 1.1 Test Shim
These binaries (libcrypto.so.1.1 and libssl.so.1.1) are bundled for Mongo2Go-based integration tests so they can run on hosts that only provide OpenSSL 3.
Source package: https://launchpad.net/ubuntu/+archive/primary/+files/libssl1.1_1.1.1f-1ubuntu2_amd64.deb
Licensing follows the OpenSSL and SSLeay licenses that accompany the upstream package.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,43 @@
using System;
using System.IO;
using System.Linq;
namespace StellaOps.Testing;
/// <summary>
/// Ensures OpenSSL 1.1 native libraries are visible to Mongo2Go on platforms that no longer ship them.
/// </summary>
public static class OpenSslLegacyShim
{
private const string LinuxLibraryVariable = "LD_LIBRARY_PATH";
public static void EnsureOpenSsl11()
{
if (!OperatingSystem.IsLinux())
{
return;
}
var nativeDirectory = Path.Combine(AppContext.BaseDirectory, "native", "linux-x64");
if (!Directory.Exists(nativeDirectory))
{
return;
}
var current = Environment.GetEnvironmentVariable(LinuxLibraryVariable);
if (string.IsNullOrEmpty(current))
{
Environment.SetEnvironmentVariable(LinuxLibraryVariable, nativeDirectory);
return;
}
const char separator = ':';
var segments = current.Split(separator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (segments.Contains(nativeDirectory, StringComparer.Ordinal))
{
return;
}
Environment.SetEnvironmentVariable(LinuxLibraryVariable, string.Concat(nativeDirectory, separator, current));
}
}