Imposed rule: Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
Pack Signing & RBAC Controls
This document defines the signing, verification, and authorization requirements for Stella Ops Task Packs across the CLI, Packs Registry, Task Runner, and Offline Kit. It is written for operators who publish and approve packs and for engineers building pack tooling. It aligns with Authority sprint tasks (AUTH-PACKS-41-001, AUTH-PACKS-43-001) and security-guild expectations. For the canonical scope catalogue and role model, see authority-scopes.md and scopes-and-roles.md.
Implementation status (reconciled 2026-05-30). Several controls in this document are the target posture; the shipped implementation is narrower. Where they diverge, an explicit Status note is given. Ground truth:
StellaOpsScopes.cs(scope catalog),StellaOps.PacksRegistry.*(registry service),PasswordGrantHandlers.cs/StellaOpsScopeAuthorizationHandler.cs(Authority approval-token enforcement), andStellaOps.Cli(PackClient.cs,CommandFactory.cs).Key gaps to be aware of before relying on this page:
- The Packs Registry service signature verifier is
SimpleSignatureVerifier(SHA-256 digest match, accepts unsigned) orRsaSignatureVerifier(RSA-PKCS1 over the digest string). Cosign/Fulcio/KMS and DSSE verification are not implemented in the registry service — those rows are roadmap.- The Packs Registry WebService authorizes with a single static
X-API-Key(PacksRegistry:Auth:ApiKey); when no key is configured, auth is disabled. It does not enforce thepacks.*OAuth scopes — scope enforcement lives in the CLI token requests and the shared Authority resource-server policies (StellaOpsResourceServerPolicies), applied when the registry is fronted by the gateway/router.
1 · Threat Model Highlights
| Threat | Mitigation |
|---|---|
| Unsigned or tampered pack uploaded to registry | Digest is recomputed (SHA-256) and the supplied signature is verified on upload (PackService.UploadAsync). Status: today the verifier is SimpleSignatureVerifier (digest match; unsigned accepted unless Policy.RequireSignature=true) or RsaSignatureVerifier. Mandatory cosign/DSSE verification is roadmap (not implemented). |
| Unauthorized user publishing or promoting packs | Authority scope packs.write is requested by the CLI and enforced by the shared resource-server policy when the registry is gateway-fronted. Status: the standalone Packs Registry WebService gates on a static X-API-Key, not on the packs.* scopes. |
| Privilege escalation during approvals | Approval tokens require packs.approve and the pack_run_id / pack_gate_id / pack_plan_hash parameters at the token endpoint, plus fresh-auth (≤ 5 minutes); enforced in PasswordGrantHandlers (issuance) and StellaOpsScopeAuthorizationHandler (resource layer). All three are recorded as sensitive audit properties (pack.run_id, pack.gate_id, pack.plan_hash). |
| Secret exfiltration via pack steps | Secrets injection with redaction, sealed-mode network guardrails, evidence review. Status: the CLI exposes secret injection (PackClient.InjectSecretAsync); the broader sandbox/redaction guarantees are roadmap. |
| Replay of old approval tokens | Approval tokens bind pack_plan_hash (validated as a 32–128 char hex digest) and require fresh authentication; stale auth_time is rejected at the resource layer. |
| Malicious pack in Offline Kit | Mirror/offline-seed export carries provenance and digests. Status: signed-manifest + DSSE provenance verification on import is roadmap. |
2 · Signing Requirements
2.1 Implemented today
- On upload, the registry recomputes the bundle digest as
sha256:<hex>(PackService.ComputeSha256) and validates the supplied signature via the configuredIPackSignatureVerifier:SimpleSignatureVerifier(default when no public key is configured): accepts an empty signature (treated as “unsigned, digest-only”), a signature equal to the computedsha256:digest, or the base64 of that digest string. This is a placeholder, explicitly marked in source as “Replace with real signature verification when keys/attestations are available.”RsaSignatureVerifier(selected whenPacksRegistry:Verification:PublicKeyPemis set): verifies a base64 RSA-PKCS1 signature over the UTF-8 digest text using SHA-256.
- Signature, digest, and (optional) provenance digest are stored alongside the bundle in registry storage (
PackRecord). - A signature can be required by policy:
PacksRegistry:Policy:RequireSignature(default false). Whentrue, upload with an empty signature is rejected ("Signature is required by policy."). - Signatures can be rotated post-upload via
POST /api/v1/packs/{packId}/signature(re-verified against the stored content; emits asignature.rotatedaudit event). - CLI:
stella pack push <path> [--sign] [--key-id <id>] [--force]sends the bundle to the registry;--sign/--key-idset upload metadata flags. The CLI does not refuse to publish unsigned bundles client-side, and there is no--insecure-publishflag.
2.2 Roadmap (NOT IMPLEMENTED in the registry service)
The following describe the intended target posture and are not present in StellaOps.PacksRegistry:
- Cosign signatures (keyless/Fulcio OIDC, KMS/HSM-backed, offline-vault keys).
- DSSE attestations embedding manifest digest, build metadata (repo, commit, CI run), and CLI version (
stella/pack). - Trust-policy verification modes:
| Policy | Description | Status |
|---|---|---|
anyOf | Accepts any key in configured trust store. | Roadmap |
keyRef | Accepts specific key ID (kid). | Roadmap |
oidcIssuer | Accepts Fulcio certificates from allowed issuers (e.g., https://fulcio.sigstore.dev). | Roadmap |
threshold | Requires N-of-M signatures. | Roadmap |
3 · RBAC & Scopes
Authority exposes exactly four pack-related scopes (canonical source: StellaOpsScopes.cs):
| Scope | Const | Description |
|---|---|---|
packs.read | StellaOpsScopes.PacksRead | Read-only access to Task Packs catalogues and manifests. |
packs.write | StellaOpsScopes.PacksWrite | Publish or update Task Packs in the registry. |
packs.run | StellaOpsScopes.PacksRun | Execute Task Packs via CLI or Task Runner. |
packs.approve | StellaOpsScopes.PacksApprove | Fulfil Task Pack approval gates. |
All four are exposed as resource-server policies in StellaOpsResourceServerPolicies and seeded into the authority.permissions catalog by S002_seed_full_scope_permissions.sql. Tenant assignment is mandatory for any pack scope under the client-credentials grant (ClientCredentialsHandlers rejects with "Pack scopes require a tenant assignment.").
Note. The CLI
PackClientadditionally requests two scopes —packs.secrets(secret injection) andpacks.cache(cache management) — that are not registered inStellaOpsScopes.cs. They are therefore unknown scopes and will not be issued in an OpTok; treat the corresponding CLI surfaces (InjectSecretAsync,ManageCacheAsync) as not yet scope-backed.
3.1 Role Mapping (illustrative)
These role bundles are guidance only — they are not defined as named roles in the codebase. Operators assemble custom roles from the four scopes above via the Console Admin role editor.
| Role (suggested) | Scopes | Use Cases |
|---|---|---|
pack.viewer | packs.read | Inspect packs, plan runs. |
pack.publisher | packs.read, packs.write | Publish new versions, manage channels. |
pack.operator | packs.read, packs.run | Execute packs, monitor runs. |
pack.approver | packs.read, packs.approve | Fulfil approvals, authorize promotions. |
pack.admin | all four | Full lifecycle management (rare). |
Roles are tenant-scoped; cross-tenant access requires explicit addition.
3.2 CLI Enforcement
- The CLI requests a scope per operation when acquiring the access token (
PackClient.AuthorizeRequestAsync):stella pack plan→packs.read.stella pack run→packs.run.stella pack push→packs.write.stella pack pull/verify/runs logs→packs.read.stella pack runs cancel/pause/resume→packs.run.
- The shipped CLI command tree under
stella packis:plan,run,push,pull,verify, andruns(cancel,pause,resume,logs). There is nostella pack approvecommand — approval of a paused run is performed withstella pack runs resume <run-id> --approve [--reason ...] [--step ...], which uses thepacks.runscope. - Approval tokens (
packs.approve) are obtained directly from the Authority token endpoint, not via a dedicated CLI subcommand. Whenpacks.approveis requested, the/tokenendpoint requires thepack_run_id,pack_gate_id, andpack_plan_hashrequest parameters (PasswordGrantHandlers). Validation rules:pack_run_id,pack_gate_id: non-empty, ≤ 128 characters.pack_plan_hash: a 32–128 character lowercase hexadecimal digest.- Authority rejects grants that omit or truncate any field and records the values as sensitive audit properties
pack.run_id,pack.gate_id,pack.plan_hash.
4 · Approvals & Fresh Auth
- Approval commands require recent fresh-auth (< 5 minutes). When the
packs.approvescope is requested,/tokendemandspack_run_id,pack_gate_id, andpack_plan_hash(PasswordGrantHandlers), and the resource-layer scope handler (StellaOpsScopeAuthorizationHandler) re-verifies that the corresponding claims are present plus thatauthentication_timefalls within the five-minute window before authorizing the request. - Failure surfaces:
- At issuance, a missing/invalid metadata field rejects the grant and records
packs.approveunder the auditInvalidScopeproperty (AuditInvalidScopeProperty). - The
authority.pack_scope_violationactivity tag is emitted specifically by the client-credentials grant when pack scopes are requested without a tenant assignment (ClientCredentialsHandlers). - At the resource layer, missing metadata or stale auth fails authorization with reasons such as
"packs.approve tokens require pack_run_id claim."or"packs.approve tokens require fresh authentication.", and stampspack.fresh_auth_satisfied(and, when present,pack.auth_time) audit properties.
- At issuance, a missing/invalid metadata field rejects the grant and records
- Approval token metadata (as carried / audited):
pack_run_id/pack.run_idpack_gate_id/pack.gate_idpack_plan_hash/pack.plan_hash(validated 32–128 char hex)
- The plan hash binds the approval to a specific plan to prevent rerouting; the run pause/resume/approval flow is driven through the Packs Registry / Task Runner endpoints (
runs:pause,runs:resume).
5 · Secret Management
Status: largely roadmap / partially wired. The CLI exposes a secret-injection call (
PackClient.InjectSecretAsync→POST /api/v1/packs/runs/{runId}/secrets, requesting the unregisteredpacks.secretsscope), but the documented secret audit events below (secretRequested/secretDelivered) are not emitted by the Packs Registry service. Treat this section as the intended design.
- Secrets defined in a pack manifest are intended to map to Authority secret providers (e.g., HSM, Vault).
- Task Runner is intended to obtain secrets using a service account with scoped access; the CLI may prompt or read from profile.
- Intended secret audit trail (not yet implemented in the registry):
secretRequestedevent with reason, pack, step.secretDelivereddeliberately omitted (only aggregate metrics) to avoid leakage.- Evidence bundle includes hashed secret metadata (no values).
Sealed mode is intended to require secrets to originate from a sealed vault, with external endpoints blocked.
6 · Audit & Evidence
- The Packs Registry emits append-only audit records (
IAuditRepository.AppendAsync) plus structured audit events (IAuditEventEmitter). - Audit events actually emitted by the registry (source:
PackService,LifecycleService,ParityService,MirrorService,AttestationService,ExportService,Program.cs):pack.uploaded(on publish/upload)signature.rotatedlifecycle.updated(deprecation and other lifecycle transitions)parity.updatedattestation.uploadedmirror.upserted,mirror.syncoffline.seed.exported
- The richer event taxonomy previously listed here (
pack.version.published,pack.version.promoted,pack.run.started/completed,pack.approval.requested/granted,pack.secret.requested) is not implemented — it is a target design, not the current emitter set. - Dashboards can track the
authority.pack_scope_violationactivity tag (emitted byClientCredentialsHandlerson tenant-missing pack-scope grants) alongsideauthority.aoc_scope_violationto highlight mis-scoped automation clients. Break down counts byauthority.client_idto surface stale automation/registry configurations quickly. - Evidence Locker retention of attestations and run bundles for 90 days (configurable) is a target — verify against the Evidence Locker configuration before relying on the exact window.
- No
stella pack auditcommand exists. The shipped registry audit surface is the append-only audit log; run history is retrieved viastella pack runs .... A dedicatedstella pack audit --run <id>subcommand is not implemented.
7 · Offline / Air-Gap Policies
Status: partly roadmap. Offline-seed export is implemented (
POST /api/v1/export/offline-seed→offline.seed.exported); the manual offline-signing approval workflow (.approval-request.json,trust-bundle.pem) is a target design and is not wired in the registry/CLI today.
- Offline Kit is intended to include:
- Pack bundles + signatures.
- Trusted key store (
trust-bundle.pem). - Approval workflow instructions for manual signing.
- Air-gapped approvals (target design):
- CLI generates an approval request file (
.approval-request.json). - Approver uses offline CLI to sign with an offline key.
- Response imported to the Task Runner.
- CLI generates an approval request file (
- The CLI defines the relevant pack error codes (
StellaOps.Cli.Output.CliError):ERR_PACK_SIGNATURE_INVALID,ERR_PACK_VERIFY_FAILED,ERR_PACK_PULL_FAILED,ERR_PACK_APPROVAL_REQUIRED,ERR_PACK_OFFLINE_VIOLATION. A signature/verification failure during pull or verify surfacesERR_PACK_SIGNATURE_INVALID/ERR_PACK_VERIFY_FAILED; offline-mode violations surfaceERR_PACK_OFFLINE_VIOLATION.
8 · Incident Response
- Compromised pack signature:
- Revoke key via Authority trust store.
- Deprecate affected versions by transitioning lifecycle state to
deprecated(POST /api/v1/packs/{packId}/lifecycle, statedeprecated). There is noregistry deprecateCLI command; deprecation is via the lifecycle API. - Notify consumers via Notifier. Status: a dedicated
pack.security.alertnotification event is not defined in source today — use a general Notifier rule. - Forensically review run evidence for impacted tenants.
- Unauthorized approval:
- Review audit log for
packs.approveissuance failures (InvalidScope = packs.approve) and pack approval metadata (pack.run_id,pack.gate_id,pack.plan_hash). - Pause the affected run via
stella pack runs pause <run-id>(orPOST /api/v1/packs/runs/{runId}:pause). Status: there is no distinctpack.run.freezeaction; pause is the implemented mechanism. - Rotate approver credentials and require fresh-auth (the five-minute window forces re-authentication on the next approval token).
- Review audit log for
- Secret leak suspicion:
- Quarantine evidence bundles.
- Rotate secrets referenced by pack.
- Run sealed-mode audit script to confirm guardrails.
9 · Compliance Checklist
- [ ] Signing requirements (cosign/DSSE, trust policies) documented.
- [ ] Authority scope mapping and CLI enforcement captured.
- [ ] Approval workflow + fresh-auth expectations defined.
- [ ] Secret lifecycle (request, injection, audit) described.
- [ ] Audit/evidence integration noted (timeline, Evidence Locker).
- [ ] Offline/air-gap controls outlined.
- [ ] Incident response playbook provided.
- [ ] Imposed rule reminder retained at top.
Last updated: 2025-10-27 (Sprint 43). Reconciled against implementation 2026-05-30 — see the “Implementation status” note at the top; sections 2, 5, 6, 7, and 8 distinguish shipped behavior from roadmap.
