Skip to main content

Secret Encryption Key Ring

Values of Secret attributes are encrypted with a key ring that every engine-hosting service receives through its configuration. This guide is for operators: what the key is, how it is delivered, how to back it up and rotate it, how existing plaintext credentials are migrated, and how to monitor the result.

Availability

The key ring is used from System CK model 2.5.0 and the engine release that ships the Secret value type, together with the bot services secret sweep, the octo-cli commands SecretStatus / ReprotectSecrets / DeleteSecretSweepDump and the MCP tools get_secret_status / start_secret_sweep.

Never put real keys into tickets, chats or documents

All examples on this page use <base64-32-bytes> as a stand-in. A key is 32 random bytes, base64-encoded (openssl rand -base64 32).

The Key​

  • Algorithm: AES-256-GCM with a random nonce per value.
  • Stored form: enc:v2:<kid>:<base64url(nonce ‖ tag ‖ ciphertext)>. The header enc:v2:<kid>: names the key id (kid) and is authenticated as additional data. The tenant id and the entity id are not part of it, so restores into a renamed database, tenant copies and child-tenant restores keep working.
  • First key k1: the existing per-cluster instance secret key (Vault instance_secret_key). It already encrypts secret-flagged value overrides and Helm repository passwords in the older format enc:v1: (no key id). No new key has to be generated to start.
  • Legacy key: values in the old enc:v1: format are decrypted with LegacyV1Key, which is the same instance secret key.
  • Helm value overrides: the communication controller's encrypt-value endpoint keeps returning enc:v1 and refuses enc:v2 input. Secret Helm overrides belong in the Secret record member ValueOverride.SecretValue (System.Communication 3.41, record key Path, unique among overrides that carry a SecretValue; clients write plaintext there and no longer call encrypt-value). Legacy entries that keep an enc:v1 value in Value still deploy but are deprecated.

Because k1 reuses the instance secret key, a leak of that key exposes both the old enc:v1 data and the new secrets. The key id in the new format makes it possible to move to a new key with one re-protect sweep (see Rotation).

Configuration​

The key ring is bound from the configuration section SecretEncryption by every engine host (asset repository, communication controller, mesh adapter, bot, identity, platform, report, AI and MCP services).

Configuration keyEnvironment variableValue
SecretEncryption:Keys:<kid>OCTO_SECRETENCRYPTION__KEYS__<kid> (e.g. OCTO_SECRETENCRYPTION__KEYS__k1)Base64, 32 bytes. One entry per key id
SecretEncryption:ActiveKeyIdOCTO_SECRETENCRYPTION__ACTIVEKEYIDKey id used to encrypt new values, e.g. k1. Must name a key of the ring
SecretEncryption:LegacyV1KeyOCTO_SECRETENCRYPTION__LEGACYV1KEYBase64, 32 bytes. Decrypts enc:v1: values. Remove once no enc:v1 value remains
SecretEncryption:StrictModeOCTO_SECRETENCRYPTION__STRICTMODEtrue / false (default false). Rejects reads of legacy clear text, see Strict mode

Notes:

  • Key ids are 1–32 lowercase letters or digits (enforced by the Helm charts). The key id keeps its case in the variable name because it is the id written into the envelope header.
  • Without a key ring a service still starts and answers "is set" queries (stored protected values then report keyMissing). Writing a secret fails with SecretEncryptionNotConfiguredException. Clients read keyRingConfigured from the environment status to disable secret inputs.
  • All services of one installation must use the same ring. A service that does not know a key id cannot read values written with it: such values stay stored, read as not set with keyMissing: true and become readable once the key id is added (see Unreadable secrets).

Delivery​

Kubernetes: core services (octo-mesh chart)​

No new value is required. The chart derives the ring from the existing secrets.communicationInstanceSecretKey (from Vault instance_secret_key): k1 = that key, active key k1, legacy key = that key. The variables are rendered into the shared octo-mesh.system-env block, so identity, asset repository, bot, communication controller, platform services and AI services all receive them. The key values are stored in the backend Secret (secretEncryptionKey-<kid>).

For a rotation the chart accepts an override that replaces the derived ring:

secrets:
communicationInstanceSecretKey: <base64-32-bytes> # unchanged, stays k1 and the legacy key
secretEncryptionKeys:
k1: <base64-32-bytes> # list k1 as long as it is still needed
k2: <base64-32-bytes>
secretEncryptionActiveKeyId: k2 # empty = k1

Kubernetes: operator-deployed workloads​

Workloads that the communication operator deploys (mesh adapter and other adapters) receive the ring only when their adapter has ReceivesClusterSecrets=true. Set the operator chart value to the same instance secret key:

operator:
clusterSecrets:
instanceSecretKey: <base64-32-bytes> # same value as the core chart; becomes k1 and the legacy key
# optional rotation override, replaces the derived ring:
secretEncryptionKeys: {}
secretEncryptionActiveKeyId: ""

The operator binds these as OPERATOR__CLUSTERSECRETS__SECRETENCRYPTIONKEYS__<kid>, OPERATOR__CLUSTERSECRETS__SECRETENCRYPTIONACTIVEKEYID and OPERATOR__CLUSTERSECRETS__SECRETENCRYPTIONLEGACYV1KEY (ClusterSecretsOptions) and injects them into the workload as secrets.secretEncryption.keys.<kid>, secrets.secretEncryption.activeKeyId and secrets.secretEncryption.legacyV1Key; the mesh adapter chart renders the OCTO_SECRETENCRYPTION__* variables from them. If instanceSecretKey is empty, no ring is injected: adapters start, but reading or writing a Secret attribute fails.

Kubernetes: reporting, MCP and AI charts​

The separately packaged reporting (octo-mesh-reporting ≥ 0.3.0), MCP (octo-mesh-mcp ≥ 0.2.0) and AI (octo-mesh-ai ≥ 0.24.0) charts host the runtime engine too and render the same variables. Set the same value as the core chart:

secrets:
communicationInstanceSecretKey: <base64-32-bytes> # same value as the octo-mesh chart; becomes k1 and the legacy key
secretEncryptionKeys: {} # optional rotation override, replaces the derived ring
secretEncryptionActiveKeyId: "" # empty = k1

The values are stored in the chart's backend Secret. If the key is empty nothing is rendered: the service starts, and only reading or writing a Secret attribute fails. The AI chart falls back to aiInstanceSecretKey (documented as the same value).

Coverage

Edge operator pipelines and engine hosts deployed by other charts need the same variables. Check every engine host of an installation before Secret attributes are used there.

Local development​

Start-Octo (octo-tools) sets the ring for host-run services from the shared development key (Get-OctoDevInstanceSecretKey): OCTO_SECRETENCRYPTION__KEYS__k1, OCTO_SECRETENCRYPTION__ACTIVEKEYID=k1 and OCTO_SECRETENCRYPTION__LEGACYV1KEY. It removes any other OCTO_SECRETENCRYPTION__KEYS__* variable left over from an earlier session. Deploy-OctoOperator passes the same key as operator.clusterSecrets.instanceSecretKey, so adapters in the local kind cluster read what the host services wrote. Test-OctoEncryption checks that the ring is present and consistent. The development key is for local use only.

Backup and the Provisioning Guard​

Losing the key means every Secret value on the cluster must be re-entered. Only external credentials are affected — no business data is lost — but every connection that uses them stops working until then. The communication controller's encrypted values (enc:v1) depend on the same key.

Disaster recovery = database dump + key ring backup. A dump alone restores the secrets only as unreadable (KEY_MISSING) values; together with the key ring from Vault or Keeper they are readable again.

  • Keep a copy of every cluster's instance secret key, and of every additional key id, in Vault and in Keeper. Create the Keeper record when the key is generated and re-check it whenever the Vault entry changes. A Vault restore alone is not a sufficient backup.
  • The Vault provisioning playbook refuses to overwrite an existing instance_secret_key with a different value. Keep that guard. Never rotate by overwriting the key in place — that makes every enc:v1 and enc:v2:k1 value unreadable at once.

Rotation​

Rotation never replaces the instance secret key; it adds a new key next to it.

  1. Add k2. Generate a new key, store it in Vault and Keeper, and deploy it to every engine host with k1 still active (core chart and operator chart override maps list k1 and k2, secretEncryptionActiveKeyId: k1). Verify all services restarted with both keys.
  2. Switch the active key. Set secretEncryptionActiveKeyId: k2 on both charts and deploy. New values are written as enc:v2:k2:; k1 values keep decrypting.
  3. Re-protect. Run the Reprotect sweep over all tenants (octo-cli -c ReprotectSecrets -a -y -w), then check octo-cli -c SecretStatus -a until no tenant reports a value with kid=k1 (octo.secrets.values{kid="k1"} = 0).
  4. Remove k1. Drop k1 from both override maps and deploy. Restoring a dump taken before the rotation brings back k1 values — keep k1 until no such restore is expected.
  5. Remove LegacyV1Key only when a Verify sweep shows that no enc:v1 value remains, and no other component (such as the communication controller's own enc:v1 data) still depends on it.
warning

Never deploy step 2 before step 1 has reached every engine host, including operator-deployed workloads. A service that does not know k2 cannot read values written with it.

Sweep​

The sweep walks all Secret attributes of a tenant and reports counts per stored form (notSet, placeholder (legacy clear-text placeholders), plaintext, encV1, encV2 per key id, unknownKeyId per key id, failed) — in total and per CK type and attribute path — plus the list of unreadable values (key id not in the ring) as re-entry tasks. It runs per tenant, including the system tenant and child tenants, and is executed by the bot services.

ModeEffectConfirmation
VerifyRead-only. Reports counts per form and key id and the unreadable values—
EncryptEncrypts plaintext and enc:v1 values with the active key (enc:v2:<active kid>). Legacy clear-text values that are exactly a placeholder (TODO_SET_<UPPER_SNAKE> or a single <…>) are converted once to "not set" (placeholdersNormalized). Unreadable values are reported, never clearedRequired
ReprotectRe-encrypts every value whose key id is not the active key (after a rotation or after adding a source key for a restore). CLI / ops only, not offered in StudioRequired
CleanupUnreadableRemoves values whose key id the ring does not know and lists them in cleared[]. Irreversible except via the pre-sweep dump. enc:v1 values that are unreadable only because LegacyV1Key is not configured are kept (configure the legacy key instead) and stay in unreadable[]. Needs the SecretManagement roleRequired

There is no decrypt or plaintext export mode in any API (bot, octo-cli, MCP); a Decrypt request is refused with 400. See Rollback.

Every sweep that writes (Encrypt, Reprotect, CleanupUnreadable) first takes a fresh dump of the tenant (except the Encrypt step after a restore, whose pre-state is the restored backup itself). If that dump cannot be taken, the tenant is skipped with the reason in its report. These pre-sweep dumps hold the secrets as they were before the sweep (possibly clear text): they are kept in their own directory, are never downloadable (no endpoint), appear with their state in the list of sweep runs, and are deleted after BackupRetentionDays (7 days) or earlier by a user with SecretManagement.

Bot configuration​

Section Bot:SecretSweep of the bot services (environment OCTO_BOT__SECRETSWEEP__*). The key ring itself is the engine section SecretEncryption above.

KeyDefaultMeaning
VerifyCron0 3 * * *Cron (UTC) of the recurring Verify sweep over all tenants. Empty disables it
RequirePreSweepBackuptrueTake a tenant dump before every writing sweep; skip the tenant if that fails. Set false only deliberately (e.g. local environments without the MongoDB database tools)
BackupStoragePath<temp>/octo-bot/secret-backupsDirectory of the pre-sweep dumps. Must not lie inside the tus or dump directories; use a persistent volume in Kubernetes
BackupRetentionDays7Days a pre-sweep dump is kept before the hourly cleanup deletes it
BatchSize500Entities read per repository call
RunAfterRestoretrueRun the secret steps after a repository restore (see Restore across environments)
StrictModeSinceemptyStart of strict mode for this environment (UTC, e.g. 2026-11-15T00:00:00Z). From then on every sweep that still finds legacy values logs an error and reports the tenant in octo.secrets.strict_mode.violations

Bot endpoints​

EndpointRoleResponseSDK (IBotServicesClient)
GET {tenantId}/v1/secrets/statusAny user with tenant accessSecretEnvironmentStatusDtoGetSecretEnvironmentStatusAsync
POST {tenantId}/v1/jobs/secret-sweep?mode=Verify|Encrypt|Reprotect|CleanupUnreadable&confirm=trueSecretManagementJobResponseDto { id }; 400 ConfirmationRequired for a writing mode without confirm=true; 400 for DecryptStartSecretSweepAsync(tenantId, mode, confirm)
GET {tenantId}/v1/jobs/secret-sweep/reportAdminPanelManagementLast report of the tenant; 404 if noneGetSecretSweepReportAsync
GET {tenantId}/v1/secrets/sweep-runs?limit=20AdminPanelManagementSecretSweepRunDto[], newest first (last 50 kept per tenant)GetSecretSweepRunsAsync
DELETE {tenantId}/v1/secrets/sweep-runs/{runId}/dumpSecretManagement204; 404 unknown run or no dump; 409 dump already deletedDeleteSecretSweepDumpAsync
POST system/v1/secrets/sweep?mode=…System tenant adminsJobResponseDto (all tenants)StartSecretSweepAllTenantsAsync
GET system/v1/secrets/reportsSystem tenant adminsLast report of every tenantGetSecretSweepReportsAsync

A missing role is answered with 403. Job status: GET system/v1/jobs?id=…. Enums are serialized as names, JSON in camelCase.

Environment status (SecretEnvironmentStatusDto, identical in every tenant except lastVerifyAt):

{
"keyRingConfigured": true, // false: secret writes fail with SecretEncryptionNotConfigured
"activeKeyId": "k1", // null when not configured
"knownKeyIds": ["k1"],
"legacyV1KeyConfigured": true,
"strictMode": false,
"strictModeSince": null, // ISO-8601 when strict mode is scheduled or active
"recurringVerifyCron": "0 3 * * *", // null when disabled
"lastVerifyAt": "2026-10-06T03:00:12Z", // this tenant's last Verify run, null if none
"warnings": [] // "NoKeyRing", "NoLegacyV1Key" (see below)
}

warnings lists codes: NoKeyRing when no key ring is configured (keyRingConfigured: false; Studio shows a prominent banner, secret inputs are disabled, Encrypt is skipped) and NoLegacyV1Key when the tenant's last completed sweep found enc:v1 values but LegacyV1Key is not configured.

Sweep run (SecretSweepRunDto):

{
"runId": "<job id>",
"mode": "Encrypt", // Verify | Encrypt | Reprotect | CleanupUnreadable
"trigger": "Manual", // Manual | Recurring | Restore
"outcome": "Succeeded", // Succeeded | CompletedWithFailures | Skipped | Failed | Running
"startedAt": "…", "completedAt": "…",
"triggeredBy": "user name or null",
"totals": { /* form counts as in the report */ },
"placeholdersNormalized": 0,
"unreadableCount": 0,
"dump": { // null for Verify (no dump)
"fileName": "…presweep.tar.gz",
"exists": true,
"sizeBytes": 123456,
"createdAt": "…",
"expiresAt": "…", // createdAt + 7 days
"deletedAt": null, // set when deleted early or expired
"deletedBy": null
}
}

Report (SecretSweepReport): tenantId, mode, trigger, outcome, reason, startedAt, completedAt, backupFileName, activeKeyId, strictModeActive, strictModeViolation, remainingLegacyValues, placeholdersNormalized, steps[] (per step: totals, slots[], cleared[], failures[]), unreadable[] (ckTypeId, rtId, attributePath, keyId — the re-entry list) and cleared[] (filled only by CleanupUnreadable). It never contains values.

octo-cli commands​

The generated command reference has the full help.

SecretStatus — prints the environment status (key ring configured, active key id, known key ids, legacy v1 key, strict mode and since when, recurring Verify cron, last Verify), the last 10 sweep runs with their dump state (exists, size, expiry, deleted), and the last sweep report: counts per form and key id, a table per CK type and attribute path, strict-mode flags, placeholdersNormalized and the unreadable values to re-enter. Never prints values.

ArgumentRequiredDescription
-tid, --tenantIdNoTenant to report on (default: tenant of the context)
-a, --allNoOne line per tenant (system API, run against the system tenant). Not together with -tid
-j, --jsonNoJSON output: { environment, recentRuns, report } for one tenant, the raw reports with -a
octo-cli -c SecretStatus -tid "mytenant"
octo-cli -c SecretStatus -a

ReprotectSecrets — starts a sweep job and prints the job id.

ArgumentRequiredDescription
-tid, --tenantIdNoTenant to sweep (default: tenant of the context)
-a, --allNoAll tenants (system endpoint system/v1/secrets/sweep). Not together with -tid
-m, --modeNoReprotect (default), Encrypt, CleanupUnreadable or Verify. Decrypt is refused
-y, --yesNoConfirm a writing mode. Verify needs no confirmation; Reprotect and Encrypt ask interactively unless -y is given; CleanupUnreadable is refused without -y (no prompt). After confirmation the CLI sends confirm=true to the bot
-w, --waitNoWait for the job and print the resulting report
# after switching the active key
octo-cli -c ReprotectSecrets -tid "mytenant" -w

# fresh status of one tenant (read-only, no confirmation)
octo-cli -c ReprotectSecrets -tid "mytenant" -m Verify -w

# encrypt remaining legacy values in all tenants (CI/CD)
octo-cli -c ReprotectSecrets -a -m Encrypt -y

# remove values whose key id is unknown (irreversible except via the pre-sweep dump)
octo-cli -c ReprotectSecrets -tid "mytenant" -m CleanupUnreadable -y -w

DeleteSecretSweepDump — deletes the pre-sweep dump of a sweep run before it expires (role SecretManagement).

ArgumentRequiredDescription
-tid, --tenantIdNoTenant of the run (default: tenant of the context)
-r, --runIdYesRun id as listed by SecretStatus
-y, --yesNoSkip the confirmation prompt

An unknown run or a run without a dump is an error; a dump that is already deleted only produces a warning.

octo-cli -c DeleteSecretSweepDump -tid "mytenant" -r "1234" -y

MCP tools​

  • get_secret_status (low risk) — allTenants (default false), tenantId. Returns the environment status, the 10 most recent sweep runs with their dump state, the last report(s) with the unreadable values, and a compact summary per tenant.
  • start_secret_sweep (high risk) — mode (Verify default, Encrypt, Reprotect, CleanupUnreadable), allTenants, confirm (required for Encrypt, Reprotect and CleanupUnreadable; passed to the server), waitForCompletion, waitTimeoutMinutes (default 30), tenantId. Decrypt is refused.
  • The secrets overview (inventory) is available through the asset repository GraphQL query secrets { inventory … }; an MCP tool for it is planned.

Strict mode​

Strict mode is phase 5 of the migration, enabled per environment 14 days after the sweep reported zero plaintext:

  • Engine hosts: SecretEncryption:StrictMode=true. A read of a Secret value that is still clear text fails (LegacyPlaintextSecretRejectedException) and increments octo.secrets.strict_mode.rejected_reads. enc:v1 stays readable as long as LegacyV1Key is configured. The encrypt / reprotect sweep and the write path still convert remaining clear text.
  • Bot services: Bot:SecretSweep:StrictModeSince=<date>. Sweeps that still find legacy values (clear text or enc:v1) report the tenant in the gauge octo.secrets.strict_mode.violations{tenant} and set strictModeViolation in the report.

Migration of Existing Credentials​

Credential attributes that are still String are converted to Secret in phases. Each environment goes through the phases in the order local → test → staging → production, and proceeds only when the sweep reports zero plaintext.

PhaseContent
0Rotate credentials that were committed to source control, replace them with empty values
1Engine release with the Secret value type, key ring (k1 = instance secret key), legacy read, write rules, sweep, System 2.5 gate. Deploy the key ring to every engine host
2Consumers handle ciphertext before any model changes: the communication controller decrypts secrets for adapter configuration, the mesh adapter handles Secret attributes and offers RevealSecret@1, Studio and other clients stop selecting secret values. All clients run the new engine
3Model changes (Minor): credential attributes become valueType: Secret. From here on nothing projects the value, even if it is still stored as plaintext
4Sweep: Encrypt once over all tenants (ReprotectSecrets -a -m Encrypt), then the recurring Verify (VerifyCron). Each writing run starts with a fresh tenant dump (kept 7 days). Adapter service accounts switch to impersonation with an installation-level adapter credential (follow-up AB#5551); their stored secrets are cleared
5Strict mode, 14 days after the sweep reported zero plaintext: legacy plaintext is no longer readable
6Rotation of credentials that were exposed before the migration

Gates:

  • Phase 2 before phase 3. Clients that still select a secret as a string break when the model changes. The phase 2 code must have shipped one release before phase 4.
  • Strict mode is enabled per environment 14 days after the sweep reported zero plaintext.
  • Pre-sweep dumps are kept 7 days, treated as secret material, then deleted.
  • Pipelines that read credentials with GetRtEntities* receive only an is-set marker after phase 3; switch them to RevealSecret@1 in phase 2.
  • Adapter configuration is cached until the next deployment: after changing a secret, redeploy the data flow (except where RevealSecret@1 reads the value on demand).

Rollback​

Phases 1–3 are code-only and can be rolled back with the binaries. After phase 4, older binaries cannot read the encrypted values. The emergency path is then an engine-internal decrypt with the key, not a binary rollback. No API (bot endpoints, octo-cli, MCP) offers decryption or a plaintext export: it is an engine-level operation (ISecretMaintenanceService with explicit decrypt confirmation) that has to be run as a planned emergency change.

Restore Across Environments​

Dumps keep their format and contain the encrypted values after phase 4. A restore keeps the ciphertext: nothing is decrypted or cleared. With Bot:SecretSweep:RunAfterRestore=true (default) every repository restore runs these steps on the restored tenant and stores the result as a report with trigger Restore:

  1. Verify — counts the forms as restored.
  2. Encrypt — converts plaintext and enc:v1 values from older dumps to the active key. No extra dump is taken: the uploaded backup that was restored is the pre-sweep state.
  3. Verify — the final counts and the list of unreadable values.

Values whose key id is not in the target ring stay stored encrypted with the form KEY_MISSING: they read as isSet: false, keyMissing: true, appear in the report's unreadable[] and in the secrets overview as re-entry tasks, and become readable automatically when the key id is added to the ring.

  • Same environment: nothing to do; the steps find no unknown key id.
  • Bot without key ring: only a key-free Verify runs. It classifies without decrypting: every enc:v2 value (its key id is not in the empty ring) and every enc:v1 value while no LegacyV1Key is configured (key id enc:v1) counts as key missing and is listed in unreadable[] for re-entry; clear text stays clear text. Nothing is written. The run ends Succeeded with the reason "No key ring configured: secrets were classified only; set the key ring and run Encrypt" — configure the key ring, then run Encrypt.
  • Other environment, tenant copy, child-tenant restore (default): the secrets arrive unreadable and are re-entered (Studio, API, set_entity_secrets). Check octo-cli -c SecretStatus -tid <tenant> or secrets { inventory(needsReEntry: true) }. Once everything is re-entered, the remaining unreadable values can be removed with the admin sweep CleanupUnreadable (octo-cli -c ReprotectSecrets -tid <tenant> -m CleanupUnreadable -y; a pre-sweep dump is taken first).
  • Optional ops step instead of re-entry: temporarily add the source environment's key to the target ring (deployed to every engine host, not active), restore, run Reprotect (octo-cli -c ReprotectSecrets -tid <tenant> -y -w) so the values move to the active key, check SecretStatus until no value with the source key id remains, then remove the source key from the ring again.

See also Repository Backup & Restore.

Monitoring​

Metrics are emitted on the meter Meshmakers.Octo.Secrets. None of them carries a value.

MetricTypeLabelsMeaning
octo.secrets.decryptCountertenant, ckType, attribute, service, formEvery server-side decryption (adapter configuration, RevealSecret@1)
octo.secrets.plaintext_readsCountertenant, ckType, attribute, service, formA legacy plaintext value was read from a Secret attribute. Must stay at 0 after the sweep
octo.secrets.envelope_not_allowedCountertenant, ckType, attribute, serviceAn enc:v2 envelope stored as a plain string in a Secret slot was refused instead of decrypted. Nothing legitimate produces one — investigate any count
octo.secrets.strict_mode.rejected_readsCountertenant, ckType, attribute, serviceReads of legacy clear text rejected by strict mode
octo.secrets.unreadableCounterreason (unknown_key_id, corrupt, decrypt_failed), tenant, ckType, attribute, serviceA stored value could not be read and was treated as not set (e.g. after a restore from another environment)
octo.secrets.sweep.rewrittenCountertenant, mode (verify, encrypt, reprotect, cleanup_unreadable)Values changed by the sweep
octo.secrets.sweep.failedCountertenant, modeValues the sweep could not process
octo.secrets.valuesGaugetenant, model, form (not_set, placeholder, plaintext, enc_v1, enc_v2, unknown_kid), kidForms found by the last sweep of each tenant (reported by the bot services; env comes from the OTel resource)
octo.secrets.strict_mode.violationsGaugetenantLegacy values found by the last sweep while StrictModeSince is in force (0 = compliant)

Alerts: octo.secrets.strict_mode.violations > 0 (equivalent: octo.secrets.values{form="plaintext"} > 0 after strict mode), and any increase of octo.secrets.strict_mode.rejected_reads or octo.secrets.sweep.failed. During a rotation, octo.secrets.values{kid="k1"} shows the progress of the re-protect sweep.

See Also​