Risk Scoring
Overview
Ledger assigns a risk level to every Secret it discovers, so you can prioritize which credentials to remediate first. Scores are derived from the metadata Ledger collects and are recalculated after every Scan, so each Secret's level always reflects the current state of your environment.
Factor Scores
Ledger evaluates each Secret across several independent factors. Each factor produces its own risk level and a short description of why it applies. A factor is only evaluated when the relevant data is available; if data is unavailable for a factor, it does not contribute to the overall score.
Age
Measured from the Secret's creation date.
| Age | Level |
|---|---|
| Less than 90 days | Low |
| 90 to 180 days | Medium |
| More than 180 days | High |
| Creation date unknown | Low |
Last Used
Measured from the Secret's most recent observed use. Unused credentials are strong candidates for retirement.
| Last used | Level |
|---|---|
| Less than 90 days ago | Low |
| 90 to 180 days ago | Medium |
| More than 180 days ago | High |
| Never used | High |
| Last-use time unknown | Low |
Reuse
Based on the number of Applications that reference the Secret.
| Applications | Level |
|---|---|
| 1 | Low |
| 2 | Medium |
| More than 2 | High |
Exposure
Based on where the Secret was found. Locations that offer less protection score higher.
| Location | Level |
|---|---|
| Kubernetes Secret (env var or volume mount) | Low |
| Kubernetes ConfigMap (env var or volume mount) | Medium |
| Hardcoded in the workload (env var literal, image, or command arguments) | Critical |
Authorization Scope
Ledger analyzes permissions attached to the Secret's owning Identity and classifies the breadth of access into a single tier. This factor is only evaluated when permission metadata is available for the owning Identity.
When permissions exist but Ledger can't fully analyze them, the factor still applies: The Unanalyzed and Invocable tiers score Medium to flag the Identity for manual review, not because Ledger observed broad access.
| Tier | Level | Examples |
|---|---|---|
| Scoped read-only | Low | Read-only actions, every resource scoped to a specific ARN. |
| Broad read or scoped write | Medium | Read-only access using Resource=*, or non-sensitive writes scoped to specific resources. |
| Write or sensitive data access | High | Any write on Resource=*, any access to a sensitive service, or bulk data-read access. |
| Admin or privilege escalation possibility | Critical | Wildcard administrative access, privilege escalation, cross-account sts:AssumeRole, or inverted (NotAction / NotResource) Allow statements. |
| Unanalyzed | Medium | The Identity has policies attached, but their documents could not be inspected. Surfaced for manual review rather than assumed safe. |
| Invocable | Medium | Bedrock AgentCore only. The Identity can invoke Bedrock, but its write or admin reach could not be determined. |
Overall Risk Score
A Secret's overall risk level is derived from its individual factor scores. The overall level starts as the highest level among all factors, then escalation rules are applied so that several moderate concerns can combine into a more serious one.
The escalation rules are:
- If two or more factors are High or Critical, the overall level is raised to Critical.
- If three or more factors are Medium or above, the overall level is raised to at least High.
These rules are fixed and apply to every organization.
Example
A Secret stored in a Kubernetes Secret (exposure: Low), created 200 days ago (age: High), last used 210 days ago (last used: High), and used by a single Application (reuse: Low):
- Highest individual factor: High.
- Two factors are High (age and last used), so the overall level is escalated to Critical.
The Secret is reported as Critical.
Customizing Scores
You can override the default factor scores for your organization. This is supported via Defakto's Managed Configuration in the LedgerRiskScoring section. Modification of risk scores applies to every integration in the organization.
Overrides are per factor:
- A factor you omit keeps its default scores.
- A factor you include replaces that factor's defaults in full, so it must specify every key of that factor.
Applying the section with spirlctl or Terraform replaces the whole stored section. A factor you overrode earlier but leave out of the new section goes back to its defaults, so include every factor you want to override. Run spirlctl config get org to see the current section before you change it.
The section has five factors: age, lastUsed, reuse, exposure, and authz. These factors correspond to the Age, Last Used, Reuse, Exposure, and Authorization Scope tables above. Severities are low, medium, high, or critical. The score for a Secret whose creation date is unknown is fixed at Low and can't be overridden.
Customization options
There are three ways to customize risk scores in Ledger. All three modify your Managed Configuration.
Console
You can customize risk scores directly in the Defakto Console under Organization Settings.
Apply with spirlctl
spirlctl config set org ledger-risk-scoring.yaml
spirlctl config set org replaces only the sections in the files you pass. Don't add --prune unless you intend to remove every other organization-level section.
Apply with Terraform
Use the spirl_org_config resource:
resource "spirl_org_config" "this" {
sections = {
LedgerRiskScoring = <<-YAML
section: LedgerRiskScoring
schema: v1
spec:
reuse:
oneApp: low
twoApps: high
manyApps: critical
YAML
}
}
The spirl_org_config resource manages every organization-level section. Each terraform apply makes the stored sections match its sections map: It reverts changes made in the Console or with spirlctl, and removes any organization-level section not in the map. Manage organization-level sections in one place.
To read back what is stored, run spirlctl config get org.
Full Example
Below is an example of a customized LedgerRiskScoring section in the configuration yaml. This sets every risk factor to its default value. Use it as a starting point and change the severities or thresholds you want to override.
section: LedgerRiskScoring
schema: v1
spec:
age:
ranges:
- lessThanDays: 90
severity: low
- lessThanDays: 180
severity: medium
- severity: high
lastUsed:
ranges:
- lessThanDays: 90
severity: low
- lessThanDays: 180
severity: medium
- severity: high
neverUsed: high
unknown: low
reuse:
oneApp: low
twoApps: medium
manyApps: high
exposure:
kubernetesSecret: low
kubernetesConfigMap: medium
kubernetesHardcoded: critical
authz:
scopedReadOnly: low
broadRead: medium
writeOrSensitive: high
adminLevel: critical
unanalyzed: medium
invocable: medium
Overriding a Single Factor
This example raises the score for Secrets shared between Applications. The other four factors keep their defaults.
section: LedgerRiskScoring
schema: v1
spec:
reuse:
oneApp: low
twoApps: high
manyApps: critical
Because omitted factors aren't stored, they follow any future change to the defaults.
Resetting to the Defaults
To return every factor to its default, apply the section with an empty spec:
section: LedgerRiskScoring
schema: v1
spec: {}
Validation Rules
Defakto rejects a section that breaks any of these rules. If you are modifying your configuration file directly, most errors name the path of the offending key (for example, /reuse/twoApps). If you are customizing risk scoring via Console, it applies these validation rules upon clicking Save.
- Every severity must be one of
low,medium,high, orcritical. - A factor you include must specify all of its keys. For example,
reuseneedsoneApp,twoApps, andmanyApps. rangesinageandlastUsedmust have at least one entry. To keep the defaults, omit the factor instead of passing an empty list.lessThanDaysmust be a whole number of days, at least 1.lessThanDaysmust increase strictly from one range to the next.- Exactly one range omits
lessThanDays, and it must be the last range. This range covers everything at or above the previous threshold. - Severity must not decrease from one range to the next. Repeating a severity is allowed.
- Keys not listed in the full example are rejected. The error for an unknown key gives its line number and field name instead of a path.
When Changes Apply
A change takes effect from the next Scan of each integration. Existing Secrets keep their current scores until the integration that discovered them runs again.