Security Hardening Guide — Stella Ops Suite v1.0.0-RC1

Applies to: the published stellaops-bundle-v1.0.0-RC1.tar.gz evaluation bundle, not an arbitrary checkout of main.

Reconciled: 2026-08-04 against the retained public artifact with SHA-256 e04dc3242a97d95266fee98c5b2f8d0ad8113f88e9be1ddd4b2a8a005a4864ec. Its detached manifest signature verified with the published release key; tools/verify-bundle.py --require-signature --require-digests verified 249/249 files and 47/47 product-image digests. The artifact’s internal release-manifest.yaml is authoritative for its version and image digests.

Audience: operators and security engineers installing Stella Ops on a Docker Engine/Compose host. RC1 does not ship a Kubernetes deployment. This guide therefore contains no Kubernetes procedure.

See also the Security Policy, release-bundle procedure, secrets and keys guide, and backup/restore drill.

1. Security boundary and threat model

The RC1 bundle is a multi-service Compose application. It is not a single backend container. Its main security boundaries are:

BoundaryRC1 realityPrimary control
Public ingressRouter Gateway publishes HTTP 8080 and HTTPS 8443Bind to loopback by default; put a production TLS reverse proxy/firewall in front before remote access
Evaluation registryThe bundled registry publishes 5000 and has no authentication in RC1Keep it on loopback; do not expose it to an untrusted network
Service trafficTwo Docker bridge networks, stellaops and frontdoor; no service port other than the three above is host-publishedHost firewall, Docker network isolation, Router authorization
PersistencePostgreSQL, Valkey, SeaweedFS (rustfs service), the registry, and product state use named volumesHost/storage access control, encryption at the storage layer, tested backup and restore
Control credentialsInstallation-specific secrets are generated into .env; private signing material is generated or supplied under installation-local pathsRestrictive file ACLs, external custody for production keys, controlled rotation
Supply chain47 product images plus four digest-pinned infrastructure imagesVerify the signed manifest and deploy the digest-pinned Compose file
Host controlOpt-in agent-core mounts /var/run/docker.sockEnable only on a dedicated promotion host; Docker-socket access is root-equivalent

The bundle defaults are suitable for a loopback-bound evaluation. They are not a claim that an unchanged workstation install is production hardened.

2. Verify before installation

Work from a newly extracted bundle. Obtain the release public key over an independent channel and compare it with the copy in the archive before trusting the manifest.

sha256sum stellaops-bundle-v1.0.0-RC1.tar.gz
# expected for this reconciliation:
# e04dc3242a97d95266fee98c5b2f8d0ad8113f88e9be1ddd4b2a8a005a4864ec

tar xzf stellaops-bundle-v1.0.0-RC1.tar.gz
cd stellaops-bundle-v1.0.0-RC1

curl -fsSL https://stella-ops.org/keys/cosign.pub -o public-release-signing.pub
cmp public-release-signing.pub release-signing.pub
cosign verify-blob --insecure-ignore-tlog --key public-release-signing.pub \
  --signature release-manifest.yaml.sig release-manifest.yaml
python tools/verify-bundle.py --require-signature --require-digests

RC1 uses a pinned offline release key and has no Rekor entry. The --insecure-ignore-tlog option skips only the online transparency-log lookup; it does not skip detached-signature verification.

Run ./install.sh --no-start (or ./install.ps1 -NoStart) before the first start. Then review the generated .env, key paths, certificate paths, and secrets/docker-compose.installation.yml. Start with the generated digest-pinned file when reproducibility is required:

docker compose --env-file .env \
  -f docker-compose.pinned.yml \
  -f secrets/docker-compose.installation.yml config --quiet

Do not use latest, substitute an unverified image, or copy a manifest from a different release.

3. Host and container runtime baseline

Compose-level controls in RC1

The published RC1 Compose file does not apply a uniform read_only, cap_drop: [ALL], security_opt: no-new-privileges, seccomp, or explicit non-root-user policy to all services. Do not claim those controls are present. Four one-shot initialization helpers explicitly run as root: advisory-ai-data-init, doctor-evidence-init, release-orchestrator-agent-ca-init, and scanner-cache-init (concelier-jobs-init left the estate compose with SPRINT_20260722_008 VULN-G3, 2026-09-08).

Apply additional Compose restrictions only through a reviewed override and validate every affected service’s health and write paths. A blanket read_only: true can break migrations, queues, caches, and key provisioning. At minimum:

  1. add no-new-privileges and capability drops service by service;
  2. make writable paths explicit volumes or bounded tmpfs mounts;
  3. pin the runtime seccomp/AppArmor/SELinux policy;
  4. verify the container’s effective UID and mounted-file ownership; and
  5. retain the override beside the deployment change record, not by editing the generated docker-compose.yml.

The agents profile is exceptional: agent-core mounts the Docker socket and must be treated as host-root code. Leave that profile disabled unless the host is intentionally a deployment target.

4. Network hardening

The RC1 artifact resolves to 56 Compose services when both opt-in profiles are included. It declares two bridge networks:

Only these host bindings exist in the published Compose file:

ServiceDefault host bindingContainer portPurpose
router-gateway127.0.0.1:80808080/tcpHTTP entry point/redirect
router-gateway127.0.0.1:8443443/tcpConsole and API over HTTPS
registry127.0.0.1:50005000/tcpLocal OCI registry for evaluation scans

postgres, valkey, and rustfs have no host-published ports. Do not add any.

Production checklist:

5. TLS and identity

The installer creates a self-signed evaluation certificate and sets GATEWAY_ALLOW_INVALID_INTERNAL_CERTIFICATES=true. Before exposing the system:

  1. replace the gateway and internal service certificates with certificates issued by the operator’s CA;
  2. distribute only the CA/public chain to trust stores;
  3. set GATEWAY_ALLOW_INVALID_INTERNAL_CERTIFICATES=false;
  4. recreate the affected services; and
  5. verify sign-in, token issuance, Router forwarding, and a protected API call.

Do not publish the Authority, service certificates, or internal host aliases directly. Rotate Authority signing keys with the Authority signing-key playbook; preserve retired public keys for the required token and evidence verification window.

6. Secret ownership and rotation

The installer writes generated values to .env, generates installation-local private keys under config/etc/**/keys and secrets/**, and creates an installation-only Attestor trust overlay. No private-key header is present in the downloaded RC1 archive before installation. Public verification keys are not secrets and must remain distributable.

Environment-backed secrets

Secret(s)OwnerRotation pointer
STELLAOPS_ADMIN_PASSInstallation owner / Authority administratorChange immediately through the Authority/Console account flow; treat the .env value as bootstrap material, not a password vault
POSTGRES_PASSWORD and the password copy in every STELLAOPS_POSTGRES_<SERVICE>_CONNECTIONDatabase operatorRotate the database role and all consumer strings in one maintenance change; validate startup migrations and service health before retiring the old credential. Enumerate the variables from the deployed compose rather than from memory — ADR-039 gave each service its own database, role and connection string, so there is one password copy per service (sixteen and counting), not the two this row named until 2026-09-14. Rotating a subset leaves the rest authenticating with a credential you believe is retired. The generic STELLAOPS_POSTGRES_CONNECTION is retired and must not be reintroduced.
STELLAOPS_S3_ACCESS_KEY_ID, STELLAOPS_S3_SECRET_ACCESS_KEYObject-storage operatorRotate the SeaweedFS identity and all scanner/object-store consumers atomically; verify upload and read-back before revocation
STELLAOPS_IDENTITY_ENVELOPE_SIGNING_KEYRouter/platform security ownerCoordinated full-stack rotation; all producers and verifiers must receive the same new key before the old key is removed
STELLAOPS_INTEGRATIONS_CREDENTIAL_KEK, STELLAOPS_DEPLOYMENT_BUNDLE_KEK, STELLAOPS_SECRETS_ENCRYPTION_KEYPlatform cryptography ownerFollow the master KEK rotation runbook; re-seal stored ciphertext before old-key retirement
KESTREL_CERT_PASSWORDPKI/operator ownerRotate with the PFX files and recreate every consumer; never change only the password or only the PFX
CONCELIER_AUTHORITY_CLIENT_SECRET, SCHEDULER_CONCELIER_AUTHORITY_CLIENT_SECRET, SCANNER_WORKER_AUTHORITY_CLIENT_SECRET, SCANNER_POE_AUTHORITY_CLIENT_SECRET, FINDINGS_SECURITY_AUTHORITY_CLIENT_SECRET, NOTIFY__AUTHORITY__CLIENT_SECRET, ADVISORY_AI__AUTHORITY__CLIENT_SECRET, STELLAOPS_EXPORTCENTER_AUTHORITY_CLIENT_SECRET, STELLAOPS_CLI_AUTOMATION_CLIENT_SECRET, STELLAOPS_RELEASE_DISPATCH_CLIENT_SECRET, CONSOLE_WORKSPACE_CLIENT_SECRETNamed service owner plus Authority administratorUse the Authority client-secret rotation procedure; overlap credentials where supported and prove the client flow before revoking the old secret
RELEASE_ORCHESTRATOR_AGENT_CA_ROOT_PFX_PASSWORD_FILE (preferred) / RELEASE_ORCHESTRATOR_AGENT_CA_ROOT_PFX_PASSWORDRelease operations PKI ownerPrefer the mounted file: it wins over the inline value and an unresolvable path fails the host closed rather than falling back, so a stale mount cannot silently downgrade custody. Supplying neither refuses startup — no default ships. Rotate with the agent CA root and re-enrol affected agents; retain required public chain/history
STELLAOPS_CONCELIER_FEDERATION_HMAC_KEY_B64 and non-secret STELLAOPS_CONCELIER_FEDERATION_HMAC_KEYIDConcelier federation ownerCoordinate every federation peer; overlap trust where supported, verify a signed exchange, then revoke the old key
STELLAOPS_EVIDENCELOCKER_CAPSULE_ED25519_SEED_B64Evidence/Attestor signing ownerFollow production signing and verification-key rotation; preserve old public verification material
INTEGRATIONS_VAULT_TOKENIntegrations/Vault operatorRC1 generates a placeholder only. For a real Vault integration, issue a least-privilege token, replace it, restart integrations-web, prove a read, then revoke the old token
STELLAOPS_EXPORTCENTER_NIS2_SOA_SIGNING_KEY_PEM_FILECompliance-export signing ownerOperator-supplied private key; the installer deliberately does not generate it. Follow production signing and publish/retain the matching public trust root

Installer-generated file-backed private material

The installer creates the following installation-local material. Back it up in encrypted custody; never add it to source control or a rendered Compose artifact.

Path/classOwnerRotation
config/etc/authority/keys/signing-dev.pem, ack-token-dev.pemAuthority security ownerAuthority signing-key playbook; keep retired public keys until expiry
config/etc/scanner/keys/scan-attestation-dev.pemScanner attestation ownerIntroduce the new public trust root, switch the signer, verify a scan attestation, then retire the old key
config/etc/release-orchestrator/keys/ro-gate-decision-dev.pemRelease decision-signing ownerRotate with downstream decision-verification trust; verify a signed gate decision
secrets/certs/signing-dev.pem and public halfAttestor transparency/verdict ownerVerification-key rotation; retain public history needed by existing evidence
config/etc/authority/keys/kestrel-dev.pfx, copied to secrets/certs/kestrel-dev.pfxPKI/operator ownerReplace certificate, key, and password together; then set strict internal-certificate validation
Operator-supplied config/etc/exportcenter/keys/nis2-soa-signing.pemCompliance-export signing ownerProduction-signing runbook; publish matching public trust before switching

For every rotation: inventory all consumers, take an encrypted backup, rotate one secret class at a time, update all consumers atomically, recreate only the affected services, run a real authentication/sign/verify forcing function, and record the non-secret key id/fingerprint in the audit change. Do not delete old public verification material merely because the private key is retired.

7. Image, SBOM, and plugin supply-chain controls

8. Logging, monitoring, and audit

9. Patch, incident, and recovery workflow

Patch by building or obtaining a new signed release, verifying it in a clean directory, and promoting the same digest-pinned artifact. Do not edit the RC1 manifest or retag an image in place.

For a suspected compromise:

  1. contain at the host firewall/Router and disable the affected integration or service path;
  2. preserve logs, the signed manifest, container/image ids, audit records, and volume snapshots;
  3. identify the exact credential, signing key, image digest, or plugin trust root involved;
  4. rotate/revoke with the relevant runbook, preserving historical public verification material unless the key must be distrusted retroactively;
  5. redeploy a verified digest-pinned artifact and run a real protected-path or sign/verify forcing function; and
  6. complete the backup/restore and evidence-verification checks before closing the incident.

Report product vulnerabilities through the Coordinated Vulnerability Disclosure policy.

Appendix A. RC1 Compose service inventory

This release-scoped inventory was produced from the published artifact with:

docker compose --env-file .env.example -f docker-compose.yml \
  --profile '*' config --services

It returned 56 services.

STALE as of 2026-09-14 — this inventory describes an older bundle. The service set below has not been re-derived since it was taken, and the published bundle (devops/release/bundle/docker-compose.yml) has since consolidated well past the three hosts this note’s own sprint touched. Measured at HEAD by parsing that file’s services: block: 32 top-level keys (infrastructure and one-shot init helpers included), with offlinekit-web and offlinekit-worker present and airgap-controller, export-web and export-worker absent. Other names below — concelier, doctor-web, packsregistry-*, riskengine-*, issuer-directory, notifier-worker — are likewise gone from the bundle through folds this sprint does not own. The list is left unedited on purpose: a static parse is not the docker compose --profile '*' config --services run that produced it, and replacing a measured inventory with an inferred one would weaken the claim rather than fix it. Re-deriving it is recorded against SPRINT_20260722_025 for the bundle’s owner.

Default product services (45): advisory-ai-web, advisory-ai-worker, airgap-controller, attestor, attestor-tileproxy, authority, binaryindex-web, concelier, doctor-web, evidence-locker-web, evidence-locker-worker, excititor-web, excititor-worker, export-web, export-worker, findings-ledger-web, graph-api, integrations-web, issuer-directory, notifier-worker, notify-web, opsmemory-web, packsregistry-web, packsregistry-worker, platform, policy-engine, reachgraph-web, registry-token, release-orchestrator, replay-web, riskengine-web, riskengine-worker, router-gateway, sbomservice, scanner-web, scanner-worker, scheduler-web, signals, signer, symbols, timeline-web, tsa, unknowns-web, vexhub-web, vexlens-web.

One-shot initialization helpers (4): advisory-ai-data-init, doctor-evidence-init, release-orchestrator-agent-ca-init, scanner-cache-init.

Infrastructure services (4): postgres, valkey, rustfs, registry.

Opt-in profiles (2): agent-core (agents) and airgap-time (airgap).

Any change to this list, the manifest image list, a published port, network, secret input, or generated key path requires this guide to be re-reconciled.

Change log and review cadence

Guide revisionDateRelease scopeChange
3.02026-08-04v1.0.0-RC1Rebuilt from the retained signed public artifact: exact service/image inventory, Compose networks and ports, RC1 registry exposure, secret ownership/rotation, and removal of unsupported Kubernetes and single-backend guidance
2.02025-07-12Historical draftEarlier generic host/runtime guidance; superseded because it did not describe the shipped RC1 architecture

The release owner must re-run this reconciliation at every RC and GA cut. The guide review is a release gate: compare the signed manifest, resolved Compose services/images/networks/ports, .env.example secret inputs, installer-generated key paths, and applicable rotation runbooks. Do not carry this RC1 service list forward into another release without repeating those checks.