Stella Ops Configuration Guide

Audience: Stella Ops platform operators and deployment engineers. Purpose: Describe the consolidated etc/ configuration structure for a Stella Ops deployment — where each service’s templates live, how configuration is layered and resolved, and how to scaffold and customize active configs.

Layout note (reconcile against repo). The grouped directory tree below (and the matching one in etc/README.md) is the intended layout that devops/scripts/init-config.sh scaffolds. The committed repository today is a hybrid: many service templates live as flat files at the etc/ root (e.g. etc/router.yaml.sample, etc/policy-engine.yaml.sample, etc/signals.yaml.sample, etc/concelier.yaml.sample, etc/vexlens.yaml.sample), and several of the per-service subdirectories that init-config.sh creates (etc/scanner/, etc/router/, etc/scheduler/, etc/signals/, etc/vex/, etc/policy/packs/, etc/policy/schemas/, etc/crypto/profiles/) are created empty because no sample files are placed inside them. The actual on-disk locations are called out per section below. Verified against etc/ on 2026-05-30.

Directory Structure Overview

Configuration is rooted at etc/ at the repository root. This is intended as a single source of truth for service configurations, trust anchors, crypto profiles, and plugin manifests.

The grouped layout that init-config.sh provisions (mirrored in etc/README.md):

etc/
├── authority/            # Authority keys + auth provider plugin configs
├── certificates/         # Trust anchors / router-plugin trust roots
├── crypto/               # (created by init-config.sh; profiles live in etc/rootpack/)
├── env/                  # Environment-specific profiles (dev/stage/prod/airgap)
├── issuer-directory/     # Issuer directory config
├── keys/                 # Sample scanner signing key material
├── llm-providers/        # AI/LLM provider configurations
├── notify/               # (created by init-config.sh; templates live in etc/notify-templates/)
├── notify-templates/     # Notification templates (YAML)
├── plugins/              # Plugin manifests (NOT binaries)
├── rootpack/             # Regional crypto profiles (cn/eu/kr/ru/us-fips)
├── scm-connectors/       # Source control integration configs
├── secrets/              # Development secrets only (NEVER production)
├── trust-anchors/        # Trust anchor material
├── trust-profiles/       # Region/scheme trust profiles (JSON)
├── weights/              # Scoring weight bundles
└── README.md             # etc/ overview (same aspirational tree as above)

Many single-service templates are committed as flat files directly under etc/, not under a per-service subdirectory — for example:

etc/authority.yaml.sample      etc/router.yaml.sample
etc/concelier.yaml.sample      etc/scanner.poe.yaml.sample
etc/policy-engine.yaml.sample  etc/scanner.vexgate.yaml.sample
etc/policy-gates.yaml.sample   etc/signals.yaml.sample
etc/policy-gateway.yaml.sample etc/scm-connectors.yaml.sample
etc/excititor.worker.yaml.sample  etc/vexlens.yaml.sample
etc/trust-lattice.yaml.sample  etc/packs-registry.yaml.sample

Configuration Precedence

Configuration values are resolved in the following order (highest priority first):

  1. Command-line flags - service-specific (most services do not expose a generic --config-key=value)
  2. Environment variables - service-specific prefix (see below)
  3. Active config file - etc/<service>.yaml (flat) or etc/<service>/<service>.yaml
  4. Default values - Built into the application

Environment Variable Naming

There is no single STELLAOPS_<SERVICE>__<KEY> convention across all services. Each .NET service registers its own environment-variable prefix in its Program.cs via AddEnvironmentVariables(prefix: ...), and the double underscore (__) is the .NET standard separator for nested configuration. Known prefixes (verified in src/**/Program.cs):

ServiceEnv prefixExample
Advisory AI (WebService/Worker)ADVISORYAI__ADVISORYAI__SOMEKEY__NESTED=value
Notifier (WebService/Worker)NOTIFIER_NOTIFIER_...
SBOM ServiceSBOM_SBOM_...
CartographerCARTOGRAPHER_CARTOGRAPHER_...
TimelineTIMELINE_TIMELINE_...
Attestor Tile ProxyTILE_PROXY__TILE_PROXY__...
Runtime Instrumentation WorkerRUNTIME_INSTRUMENTATION_RUNTIME_INSTRUMENTATION_...
Router (StellaOps.Router.Config)configurable (RouterConfigOptions.EnvironmentVariablePrefix)
# .NET nested-config example (Advisory AI prefix):
# Translates to { "Some": { "Nested": "value" } } under the ADVISORYAI__ prefix
ADVISORYAI__SOME__NESTED=value

Top-level deployment knobs in the etc/env/*.env.sample profiles do use the STELLAOPS_ prefix without __ nesting — e.g. STELLAOPS_PROFILE, STELLAOPS_LOG_LEVEL, STELLAOPS_CRYPTO_PROFILE. These are flat process environment variables consumed by Compose and bootstrap scripts, not the per-service nested-config binding shown above.

Service Configuration

File Naming Convention

File PatternPurpose
<service>.yamlActive configuration (git-ignored in production)
<service>.yaml.sampleDocumented template with all options
<service>.<profile>.yamlProfile-specific configuration

Creating Active Configuration

# Copy sample to create active config (router is a flat root-level template)
cp etc/router.yaml.sample etc/router.yaml

# Edit for your environment
vi etc/router.yaml

The repo does not ship etc/scanner/scanner.yaml.sample. Scanner-related templates are etc/scanner.poe.yaml.sample and etc/scanner.vexgate.yaml.sample at the etc/ root. The etc/scanner/ directory is created empty by init-config.sh. Use init-config.sh (below) to copy every committed *.sample to its active counterpart in one pass.

Directory Reference

etc/authority/ - Authentication & Authorization

The main Authority service template is flat at the etc/ root (etc/authority.yaml.sample), not inside etc/authority/. The etc/authority/ subdirectory holds keys, the standard plugin config, and the revocation bundle schema. All Compose-loaded Authority provider YAML lives under the single etc/authority/plugins/ root. The flat etc/authority.plugins/ directory is a legacy non-Compose fallback.

etc/authority.yaml.sample            # Main authority service config (flat, ~24 KB)
etc/authority/
├── keys/
├── plugins/
│   ├── harness.yaml                 # Deterministic signed-plugin harness config
│   ├── ldap.yaml                    # LDAP/Active Directory provider config
│   ├── oidc.yaml                    # OpenID Connect provider config
│   ├── registry.yaml                # Provider registry manifest
│   ├── saml.yaml                    # SAML provider config
│   └── standard.yaml                # Standard (built-in) auth plugin config
└── revocation_bundle.schema.json
etc/authority.plugins/
├── ldap.yaml                        # Legacy non-Compose fallback
└── standard.yaml                    # Legacy non-Compose fallback

Authority provider manifests use active .yaml names rather than .sample. Base, recommended, harness, and IdP-testing Compose surfaces all read /app/etc/authority/plugins; overlays change only the executable bundle root.

Key settings (see etc/authority.yaml.sample and StellaOpsScopes.cs):

etc/certificates/ - Trust Anchors & Signing

etc/certificates/
└── trust-roots/
    └── router-plugins/
        └── stellaops-local-plugin-root.pem   # Local plugin signing root

The previously documented globalsign.pem, russian-trusted.pem, and an etc/certificates/signing/ directory with authority-signing-2025-dev.pem are not present. Sample scanner signing key material is under etc/keys/ (scanner-signing-2025.key.json.sample, scanner-signing-2025.pub.json.sample). Additional trust material lives in etc/trust-anchors/ and etc/trust-profiles/ (global.trustprofile.json, eu-eidas.trustprofile.json, us-fips.trustprofile.json, bg-gov.trustprofile.json).

Usage:

etc/concelier.yaml.sample - Advisory Ingestion

The main Concelier template is a flat root-level file (etc/concelier.yaml.sample). There is no etc/concelier/ directory; per-source connector manifests live under etc/plugins/concelier/connectors/.

etc/concelier.yaml.sample                  # Main concelier config (flat)
etc/excititor-calibration.yaml.sample      # Excititor calibration (flat)
etc/plugins/concelier/connectors/
├── cve.yaml          ├── kev.yaml         ├── osv.yaml
├── distro-debian.yaml├── nvd.yaml         ├── ru-bdu.yaml
├── distro-redhat.yaml├── epss.yaml        ├── ghsa.yaml
├── stellaops-mirror.yaml  ├── vndr-msrc.yaml  └── registry.yaml

The previously documented etc/concelier/sources/nist-nvd.yaml.sample, github-advisory.yaml.sample, oval-debian.yaml.sample, oval-rhel.yaml.sample do not exist. NVD is nvd.yaml, GHSA is ghsa.yaml, and Debian/RHEL are the distro-debian.yaml/distro-redhat.yaml connector manifests above.

Key settings:

etc/rootpack/ - Regional Cryptographic Profiles

The committed regional crypto profiles live in etc/rootpack/, not etc/crypto/profiles/. init-config.sh creates an empty etc/crypto/profiles/ skeleton, but the actual crypto.profile.yaml files are in etc/rootpack/<region>/. Global crypto settings are flat root-level files (etc/appsettings.crypto.<region>.yaml, etc/crypto-plugins-manifest.json), and crypto provider manifests are under etc/plugins/crypto/providers/.

etc/rootpack/
├── cn/crypto.profile.yaml      # China - GM/T (SM2/SM3/SM4)
├── eu/crypto.profile.yaml      # EU - eIDAS qualified signatures
├── kr/crypto.profile.yaml      # Korea - KCMVP
├── ru/crypto.profile.yaml      # Russia - GOST R 34.10/34.11/34.12
├── us-fips/crypto.profile.yaml # USA - FIPS 140-3
└── pq-vectors.txt              # Post-quantum test vectors (single shared file)

etc/appsettings.crypto.china.yaml          # Per-region global crypto settings (flat)
etc/appsettings.crypto.eu.yaml
etc/appsettings.crypto.russia.yaml
etc/appsettings.crypto.international.yaml
etc/crypto-plugins-manifest.json           # Crypto plugin manifest (flat)
etc/plugins/crypto/providers/
├── bouncycastle.yaml  ├── cryptopro.yaml  ├── eidas.yaml
├── pq-soft.yaml       └── smsoft.yaml

There is no per-region env.sampleunder each profile and no etc/crypto/crypto.yaml.sample. pq-vectors.txt is a single file at etc/rootpack/pq-vectors.txt, not per-region.

Crypto profile structure (read the real file, e.g. etc/rootpack/us-fips/crypto.profile.yaml, for the authoritative schema — the keys below are illustrative and may not match field-for-field).

Activation (verified in devops/compose/docker-compose.compliance-*.yml):

# Via environment variable (real, consumed by compliance overlays)
export STELLAOPS_CRYPTO_PROFILE=us-fips

# Via Docker Compose compliance overlay (note: paths are under devops/compose/)
docker compose \
  -f devops/compose/docker-compose.stella-ops.yml \
  -f devops/compose/docker-compose.compliance-eu.yml up

Compliance overlays additionally set STELLAOPS_CRYPTO_CONFIG_PATH (e.g. /app/etc/appsettings.crypto.yaml) and STELLAOPS_CRYPTO_MANIFEST_PATH and mount etc/appsettings.crypto.<region>.yaml + etc/crypto-plugins-manifest.json into the container. The overlay file names are docker-compose.compliance-{china,eu,russia}.yml — there are no docker-compose.fips.yml/eidas.yml/russia.yml files at the repo root.

etc/env/ - Environment Profiles

etc/env/
├── dev.env.sample              # Development defaults
├── stage.env.sample            # Staging environment
├── prod.env.sample             # Production hardened
└── airgap.env.sample           # Air-gapped deployment

Environment profile contents (excerpt from the real etc/env/dev.env.sample):

# dev.env.sample
STELLAOPS_PROFILE=dev
STELLAOPS_LOG_LEVEL=Debug
POSTGRES_HOST=localhost
POSTGRES_DB=stellaops_platform     # not "stellaops_dev"
STELLAOPS_TELEMETRY_ENABLED=true
STELLAOPS_TELEMETRY_ENDPOINT=http://localhost:4317

Usage with Docker Compose:

cp etc/env/dev.env.sample .env
docker compose -f devops/compose/docker-compose.stella-ops.yml up

etc/llm-providers/ - AI/LLM Configuration

etc/llm-providers/
├── claude.yaml.sample          # Anthropic Claude
├── gemini.yaml.sample          # Google Gemini
├── ollama.yaml.sample          # Local Ollama server
├── openai.yaml.sample          # OpenAI API
├── llama-server.yaml.sample    # llama.cpp server
├── dummy.yaml                  # Dummy/offline provider
└── registry.yaml               # Provider registry manifest

Provider configuration (illustrative — read etc/llm-providers/claude.yaml.sample for the authoritative keys; the API-key environment-variable name is provider/service specific and not a global STELLAOPS_LLM_APIKEY):

# claude.yaml.sample
provider: claude
endpoint: https://api.anthropic.com
model: claude-sonnet-4-20250514
options:
  maxTokens: 4096
  temperature: 0.1

Offline/air-gapped deployments should use ollama.yaml.sample or llama-server.yaml.sample with local model bundles.

Notification Service & Templates

The main Notify config is a flat root-level template (etc/notify.yaml.sample) with per-environment variants (etc/notify.dev.yaml, etc/notify.stage.yaml, etc/notify.prod.yaml, etc/notify.airgap.yaml). At runtime, the shipped containers read the mounted file at /app/etc/notify/notify.yaml, so operators copy the selected template into etc/notify/notify.yaml before compose mounts etc/notify/ read-only. Notification templates are YAML, not HTML, and live in etc/notify-templates/. The etc/notify/ directory is created empty by init-config.sh. Channel and provider plugin manifests live under etc/plugins/notify/.

etc/notify.yaml.sample           # Main notify config (flat)
etc/notify.{dev,stage,prod,airgap}.yaml
etc/notify/notify.yaml           # Runtime-mounted copy consumed by containers
etc/notify-templates/
└── vex-decision.yaml.sample     # VEX decision templates (YAML, multi-channel)
etc/plugins/notify/
├── channels/                    # discord, email, inapp, inappinbox, slack, telegram, webhook
├── discord/  email/  opsgenie/  pagerduty/  slack/  teams/  telegram/  webhook/
│                                # per-provider config.yaml + plugin.json
└── registry.yaml

Template structure (real format — channel-typed, Liquid/{{ ... }} placeholders, render_mode: markdown; there are no standalone *.html templates):

# etc/notify-templates/vex-decision.yaml.sample
templates:
  - key: vex.decision.changed
    channel_type: email
    locale: en-US
    render_mode: markdown
    description: "Notification when VEX decision status changes"
    body: |
      ## VEX Decision Changed: {{ vulnerability_id }}

      **Product:** {{ product.name }} ({{ product.version }})
      **PURL:** `{{ product.purl }}`

Templates are imported via the CLI: stella notify template import vex-decision.yaml. The previously documented scan-complete.html, policy-violation.html, and alert.html do not exist.

etc/plugins/ - Plugin Manifests

Plugin manifests define available plugins. Compiled binaries live in plugins/ at root.

The actual etc/plugins/ tree is much broader than language/OS scanner analyzers and notify channels — it also covers authority, concelier connectors, crypto providers, excititor (VEX) connectors, messaging transports, router transports, and per-module registry.yaml manifests:

etc/plugins/
├── README.md
├── authority/     # ldap.yaml, oidc.yaml, standard.yaml, registry.yaml
├── binaryindex/   # registry.yaml
├── cli/           # registry.yaml
├── concelier/connectors/   # cve, nvd, ghsa, kev, epss, osv, ru-bdu, distro-*, ...
├── crypto/        # registry.yaml + providers/{bouncycastle,cryptopro,eidas,pq-soft,smsoft}.yaml
├── excititor/     # *-csaf, oci-openvex, suse-rancher (config.yaml + plugin.json each) + registry.yaml
├── export/        # registry.yaml
├── messaging/     # inmemory, postgres, valkey (config.yaml + plugin.json) + registry.yaml
├── notify/        # channels/, per-provider dirs, registry.yaml
├── policy/        # registry.yaml
├── router/        # registry.yaml + transports/{inmemory,tcp,tls,udp,rabbitmq}/
├── scanner/       # analyzers/lang, analyzers/os, buildx, entrytrace, node, + legacy lang/ os/ shims
├── scheduler/     # registry.yaml
└── vex/           # registry.yaml

The scanner analyzers exist in two forms under etc/plugins/scanner/:

Manifests are committed primarily as manifest.json / plugin.json (+ config.yaml), not the single <lang>.yaml schema shown in earlier drafts. Read an actual etc/plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Java/manifest.json for the authoritative manifest shape rather than relying on the illustrative id/assembly/capabilities/filePatterns example previously documented here.

Policy Engine & Gates

Policy templates are flat root-level files, not under an etc/policy/ directory. init-config.sh creates empty etc/policy/packs/ and etc/policy/schemas/ directories, but no pack bundles or JSON Schema are committed there.

etc/policy-engine.yaml.sample    # Engine configuration (flat)
etc/policy-gates.yaml.sample     # Gate definitions (flat)
etc/policy-gateway.yaml.sample   # Policy gateway config (flat)
etc/policy.poe.yaml.sample       # Proof-of-exploit policy config (flat)
etc/score-policy.yaml.sample     # Scoring policy (flat)
etc/weights/v2026-01-22.weights.json  # Scoring weight bundle

There is no etc/policy/packs/starter-day1.yaml, no enterprise.yaml.sample, and no etc/policy/schemas/policy-pack.schema.jsoncommitted. Treat those as roadmap items, not shipped artifacts.

Policy gate example (illustrative — read etc/policy-gates.yaml.sample for the authoritative gate schema):

# etc/policy-gates.yaml.sample (flat)
gates:
  - id: no-critical-unfixed
    name: "No Critical Unfixed Vulnerabilities"
    action: block

Policy authoring/promotion is governed by the policy:* scope family (policy:write, policy:author, policy:review, policy:submit, policy:approve, policy:publish, policy:promote, policy:activate, policy:simulate, policy:run, policy:audit, policy:read, policy:edit, policy:operate) — see the Scope Reference appendix.

Container Scanning

The etc/scanner/ directory is created empty by init-config.sh. The committed scanner-related templates are flat root-level files:

etc/scanner.poe.yaml.sample      # Proof-of-exploit configuration (flat)
etc/scanner.vexgate.yaml.sample  # Scanner VEX gate configuration (flat)
etc/keys/scanner-signing-2025.{key,pub}.json.sample  # Sample signing key material

There is no etc/scanner/scanner.yaml.sampleand no etc/scanner/poe.yaml.sample. The illustrative scanner: YAML block previously shown here (concurrency / analyzers / sbom.formats / evidence) does not correspond to a committed sample file — do not rely on those exact keys. Scanner analyzer enablement is driven by the plugin manifests under etc/plugins/scanner/analyzers/.

Scanner access is governed by scanner:read, scanner:scan, scanner:export, scanner:write, and scanner.signed-sbom-material.write (see StellaOpsScopes.cs).

VEX Processing

The etc/vex/ directory is created empty by init-config.sh. VEX templates are flat root-level files; per-source VEX (excititor) connectors are under etc/plugins/excititor/:

etc/excititor.worker.yaml.sample       # Excititor (VEX ingestion) worker config (flat)
etc/excititor-calibration.yaml.sample  # Excititor calibration (flat)
etc/vexlens.yaml.sample                # VEX Lens consensus computation (flat)
etc/trust-lattice.yaml.sample          # Issuer/source trust configuration (flat)
etc/plugins/excititor/
├── cisco-csaf/  msrc-csaf/  oci-openvex/  oracle-csaf/
├── redhat-csaf/ suse-rancher/ ubuntu-csaf/   (config.yaml + plugin.json each)
└── registry.yaml

Trust lattice example (real schema — weighted P/C/R trust vectors, not trustLevels/precedenceRules):

# etc/trust-lattice.yaml.sample
# BaseTrust = (wP × Provenance) + (wC × Coverage) + (wR × Replayability)
defaultWeights:
  provenance: 0.45
  coverage: 0.35
  replayability: 0.20
defaultVectors:
  vendor:
    provenance: 0.90
    coverage: 0.85
    replayability: 0.70
  distro:
    provenance: 0.85
    coverage: 0.90
    replayability: 0.70   # see file for the authoritative values

VEX access is governed by vex:read, vex:ingest, vex.admin, plus vexhub:read/vexhub:admin and aoc:verify (see StellaOpsScopes.cs).

Directories Outside etc/

plugins/ - Compiled Plugin Binaries

Runtime artifacts, not configuration. Built during CI/CD.

plugins/
├── concelier/
├── scanner/
│   ├── analyzers/
│   │   ├── lang/               # Language analyzers (.dll, .pdb)
│   │   ├── os/                 # OS analyzers
│   │   └── ... (bun, ccpp, dart, deno, dotnet, elixir, golang, java, node,
│   │            php, python, ruby, rust, secrets, swift)
│   └── buildx/                 # BuildX SBOM plugin
└── notify/                     # discord, email, slack, teams, telegram, webhook, csaf,
                                # dora, enisa, ncs, opsgenie, pagerduty, inapp, inappinbox, ...

The entrytrace plugin manifest lives under etc/plugins/scanner/entrytrace/; it is not a top-level plugins/scanner/entrytrace/ binary directory in the repo.

opt/ - Optional Vendor Packages

Customer-provided packages for specific crypto providers:

opt/
└── cryptopro/
    └── downloads/              # CryptoPro CSP packages (.gitkeep + README.md placeholder)

offline/ - Air-Gap Operational Data

Runtime/offline-kit state for air-gapped deployments. The committed contents are the notifier offline kit and rule material (not the previously documented feeds/, packages/, advisory-ai/ directories):

offline/
├── notifier/                   # Notify offline kit (manifest.json, DSSE manifest, templates, verify script)
└── rules/                      # Offline rule material (incl. rules/secrets)

Docker Compose Integration

The base compose file is devops/compose/docker-compose.stella-ops.yml(there is no docker-compose.yml at the repo root). The examples below are illustrative of the mount pattern; consult the real compose files for exact service definitions and paths (note compose files reference etc/ as ../../etc relative to devops/compose/).

Volume Mounts (illustrative)

# devops/compose/docker-compose.stella-ops.yml
services:
  scanner:
    volumes:
      # Configuration (read-only)
      - ../../etc/certificates/trust-roots:/etc/ssl/certs/stellaops:ro
      # Plugin manifests (read-only)
      - ../../etc/plugins/scanner:/app/etc/plugins/scanner:ro
      # Runtime data (read-write)
      - scanner-data:/var/lib/stellaops/scanner

Environment File

services:
  scanner:
    env_file:
      - ../../.env             # Copied from etc/env/dev.env.sample by init-config.sh

Crypto Profile / Compliance Overlays (verified file names)

# EU / eIDAS deployment
docker compose \
  -f devops/compose/docker-compose.stella-ops.yml \
  -f devops/compose/docker-compose.compliance-eu.yml up

# Russian crypto deployment
docker compose \
  -f devops/compose/docker-compose.stella-ops.yml \
  -f devops/compose/docker-compose.compliance-russia.yml up

# China (GM/T) deployment
docker compose \
  -f devops/compose/docker-compose.stella-ops.yml \
  -f devops/compose/docker-compose.compliance-china.yml up

Crypto provider overlays (orthogonal to the compliance profiles) are docker-compose.crypto-provider.cryptopro.yml, ...smremote.yml, and ...crypto-sim.yml. Air-gap overlays are docker-compose.airgap-e2e.override.yml and docker-compose.airgap-seed.override.yml. There are no docker-compose.fips.yml, docker-compose.eidas.yml, docker-compose.russia.yml, or docker-compose.airgap.yml files.

Quick Start

1. Initialize Configuration

# Clone sample configs
./devops/scripts/init-config.sh dev

# This copies all .sample files to active configs for development

2. Customize for Environment

# Edit main service configs (flat root-level templates)
vi etc/authority.yaml      # from etc/authority.yaml.sample
vi etc/router.yaml         # from etc/router.yaml.sample

# Set environment-specific values (init-config.sh writes .env at repo root)
vi .env                    # from etc/env/dev.env.sample

3. Select Crypto Profile (if needed)

# For US/FIPS compliance — the env var is consumed by the compliance overlays.
# Committed profile data lives in etc/rootpack/us-fips/ (there is no per-profile env.sample).
export STELLAOPS_CRYPTO_PROFILE=us-fips

4. Start Services

docker compose up -d

Configuration Validation

Not implemented. devops/scripts/validate-config.sh and devops/scripts/validate-policy-packs.sh do not exist in the repo. The only committed configuration helper script is devops/scripts/init-config.sh, which scaffolds the directory tree and copies every *.sample to its active counterpart. Treat the validation commands below as a roadmap target, not a shipped capability.

# Roadmap (not yet implemented):
#   ./devops/scripts/validate-config.sh
#   ./devops/scripts/validate-config.sh scanner
#   ./devops/scripts/validate-policy-packs.sh

Migration from Legacy Structure

Caution: the crypto rows below are aspirational, not done. init-config.sh creates an empty etc/crypto/profiles/ skeleton, but the committed crypto profiles still live in etc/rootpack/— the etc/rootpack/ → etc/crypto/profiles/ migration has not been carried out. Likewise, policy templates remain flat at the etc/ root (e.g. etc/policy-engine.yaml.sample), not under etc/policy/.

Legacy LocationDocumented TargetStatus
certificates/etc/certificates/Present (etc/certificates/trust-roots/)
config/env/.env.*etc/crypto/profiles/*/env.sampleNOT IMPLEMENTED (no per-profile env.sample)
config/crypto-profiles.sample.jsonetc/crypto/crypto.yaml.sampleNOT IMPLEMENTED (no etc/crypto/crypto.yaml.sample)
policies/etc/policy/Partial (flat etc/policy-*.yaml.sample; etc/policy/ empty)
etc/rootpack/etc/crypto/profiles/NOT DONE (etc/rootpack/ is still the live location)

See docs-archive/operations/configuration-migration.md (archived — migration complete) for detailed migration steps.

Security Considerations

  1. Never commit production active configs - the intended posture is that active *.yaml are git-ignored and only *.yaml.sample are committed. Note, however, that the etc/**/*.yaml ignore rules are added by init-config.sh (they are not present in the repo’s base .gitignore), and the repo does commit some active .yaml files (e.g. etc/authority.yaml, etc/llm-providers/claude.yaml, etc/notify.dev.yaml). Run init-config.sh before generating real deployment configs so your active files are ignored.
  2. Secrets via environment / secret managers - prefer per-service env vars or external secret managers; do not hardcode production secrets in etc/.
  3. Development secrets are clearly marked - etc/secrets/ contains only dev/sample keys (*.secret, *.secret.example, dsse-dev.signing.json) plus a README.
  4. Provision the master key at setup, and keep production key material off plain files. Secret values (connector credentials, deployment bundles) default to the built-in floor: envelope-encrypted in each service’s PostgreSQL under an installation-wide master key (KEK), provisioned at the setup Master Key step (ADR-031 D1/D3). Operators may upgrade to an external, operator-run HashiCorp Vault (or operator-run OpenBao — the in-repo OpenBao compose profile is test-only dev/CI tooling, never shipped; ADR-031 §D5 as amended) for secret storage, and may source the master key itself from an env var (default), a file, Vault, or an HSM (Crypto:Kek:Source). There is deliberately no cloud-managed KMS (AWS/Azure/GCP KMS) default, stub, or plugin — Stella Ops is on-prem/air-gap first. Production signing and KEK material should be HSM- or Vault-backed, never left in plain files. Install-path and lifecycle detail: the Secrets & Keys guide.

Authority Scope Reference

The canonical JWT authorization surface is StellaOps.Auth.Abstractions.StellaOpsScopes (src/Authority/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOpsScopes.cs). A scope can appear in an issued token only if it is (a) declared as a const there and (b) listed in the requesting client’s authority.clients.allowed_scopes. The authority.permissions table is the Console RBAC catalog and is not consulted at token-issuance time.

Scope families currently declared (read the source for the authoritative, complete list — it is enumerated reflectively by StellaOpsScopes.All):

FamilyExample scopes
Authority (legacy)authority.users.manage, authority.clients.manage, authority.audit.read, authority.revocation.read
Authority (resource)authority:tenants.read/write, authority:users.read/write, authority:roles.read/write, authority:clients.read/write, authority:tokens.read/revoke, authority:branding.read/write
Concelier / Advisoryconcelier.jobs.read, concelier.jobs.trigger, concelier.merge, concelier:credentials:dry-run, advisory:read/ingest, advisory-ai:view/operate/admin
VEXvex:read/ingest, vex.admin, vexhub:read/admin, aoc:verify
Policypolicy:read/write/edit/author/review/submit/approve/publish/promote/activate/run/operate/simulate/audit
Findings / Vulnfindings:read/write, effective:write, vuln:view/investigate/operate/audit (vuln:read is obsolete)
Scanner / SBOMscanner:read/scan/export/write, scanner.signed-sbom-material.write, sbom:read/write/attest
Signer / Attestor / Trustsigner:read/sign/rotate/admin, attest:read/create/admin, trust:read/write/admin
Graph / Reachabilitygraph:read/write/export/simulate/admin, signals:read/write/admin
Evidence / Timeline / Replayevidence:create/read/hold, timeline:read/write/admin, replay:read/write
Export / Notifyexport.viewer/operator/admin, notify.viewer/operator/admin/escalate
Orchestrator / Scheduler / Packsorch:read/operate/quota/backfill, scheduler:read/operate/admin, packs.read/write/run/approve
Releaserelease:read/write/publish/bypass
Air-gapairgap:seal/import, airgap:status:read
Platform / Cryptoplatform.context.read/write, platform.idp.read/admin, crypto:read/admin, crypto:profile:admin, crypto:kek:read/rotate, platform:federation:read/write, platform:sar:read/erase
Doctor / Ops / Routerdoctor:run, doctor:run:full, doctor:export, doctor:admin, doctor-scheduler:read/write, ops.health, ops-memory:read/write, router:routing:read/write
Integration / Connector / Registryintegration:read/write/operate, connector:credentials:read/write, registry.admin
UI / Miscui.read, ui.admin, ui.preferences.read/write, analytics.read, obs:read, obs:incident, issuer-directory:read/write/admin, script:read/write, symbols:read/write, unknowns:read/write/admin, risk-engine:read/operate, zastava:read/trigger/admin, exceptions:read/write/request/approve, operator.verify.read/admin, sm-remote:hash/encrypt/decrypt/sign/verify, stellaops.bypass

The taskrunner:read/operate/admin scopes are removed (the TaskRunner service was deleted; the consts are kept commented-out only for DB/migration backward compatibility). vuln:read is [Obsolete] — use vuln:view.