Skip to main content

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.

AgeLevel
Less than 90 daysLow
90 to 180 daysMedium
More than 180 daysHigh
Creation date unknownLow

Last Used​

Measured from the Secret's most recent observed use. Unused credentials are strong candidates for retirement.

Last usedLevel
Less than 90 days agoLow
90 to 180 days agoMedium
More than 180 days agoHigh
Never usedHigh
Last-use time unknownLow

Reuse​

Based on the number of Applications that reference the Secret.

ApplicationsLevel
1Low
2Medium
More than 2High

Exposure​

Based on where the Secret was found. Locations that offer less protection score higher.

LocationLevel
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.

TierLevelExamples
Scoped read-onlyLowRead-only actions, every resource scoped to a specific ARN.
Broad read or scoped writeMediumRead-only access using Resource=*, or non-sensitive writes scoped to specific resources.
Write or sensitive data accessHighAny write on Resource=*, any access to a sensitive service, or bulk data-read access.
Admin or privilege escalation possibilityCriticalWildcard administrative access, privilege escalation, cross-account sts:AssumeRole, or inverted (NotAction / NotResource) Allow statements.
UnanalyzedMediumThe Identity has policies attached, but their documents could not be inspected. Surfaced for manual review rather than assumed safe.
InvocableMediumBedrock 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, or critical.
  • A factor you include must specify all of its keys. For example, reuse needs oneApp, twoApps, and manyApps.
  • ranges in age and lastUsed must have at least one entry. To keep the defaults, omit the factor instead of passing an empty list.
  • lessThanDays must be a whole number of days, at least 1.
  • lessThanDays must 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.