How to Tune Risk Signal Weights via /settings/risk-weights

Advanced 20 minutes Security Engineers / Risk Officers Advanced Configuration

Override the default weights of every supply-chain signal that feeds the trust score. When the defaults don't match your org's risk model, tune them in the settings UI without writing custom policy.

Why You’d Override the Defaults

The default trust-score weights (tutorial 08) are tuned for the median Chainsaw deployment. They’re correct for most organizations most of the time. But:

  • A regulated org might want to weight license compliance much higher than the default 10 points.
  • A research-heavy org might want to weight package age < 30 days lower because they pull cutting-edge pre-releases regularly.
  • An ML-focused org might want to crank up aiml.* signal weights because their installed packages are unusually weighted toward HuggingFace artifacts.
  • An air-gapped org might want to weight attestation availability down because Sigstore lookups are unreliable in their environment.

/settings/risk-weights lets you override these without writing custom policy code. The change is per-org, persisted, and applied to every newly-computed trust score.

Prerequisites

  • Owner role. The Risk Weights leaf is gated minRole: "owner" in nav-config.ts; managers and admins will not see it.
  • Familiarity with the trust score components from tutorial 08.

Step 1: Open the Risk Weights Page

Navigate to Settings → Risk Weights (/settings/risk-weights). The page shows a table with one row per signal:

SignalDefault weightCurrent weightDirectionDescription
vulnerability.cvss>=9-20-20penaltyA reachable critical vulnerability
vulnerability.kev_pinned-30-30penaltyKEV-pinned CVE in the closure
vulnerability.epss>=0.5-15-15penaltyHigh exploit prediction score
provenance.verified+25+25rewardVerified SLSA attestation
license.compliant+10+10rewardSPDX-licensed
package.age>=30d+10+10rewardMature package
attack.publisherChanged-25-25penaltyMaintainer set diff
attack.has_hidden_unicode-20-20penaltySource contains zero-width / bidi marks
aiml.pickle_unsafe-30-30penaltyPickle artifact references unsafe symbols
…etc…

The full list reflects every signal that contributes to the score, including all Wave-4 attack detectors and Wave-6 AI/ML signals.

Risk Weights page with the full signal table
Each signal has a default weight, an editable current weight, and a description. Edits apply to newly-computed scores.

Step 2: Edit a Weight

Click any cell in the Current weight column and type a new number. The new weight is applied immediately to new score computations — packages that are scored after the change reflect the new weight. Packages that were scored before remain at their old score until they’re re-computed.

Trigger an immediate recompute via the API:

curl -X POST -H "Authorization: Bearer $CHAINSAW_TOKEN" \
     https://chain305.com/chainproxy/api/admin/risk-tuning/recompute \
     -d '{"scope": "all"}'

This walks the inventory and recomputes every package’s trust score with the new weights. Expect this to take a few minutes for large inventories.

Step 3: The Math

Trust score is a bounded sum:

score = clamp(
  base_score
  + sum(reward_signals_fired * their_weights)
  + sum(penalty_signals_fired * their_weights),
  -100, 100
)

base_score is 0 by default. The kill-switch case (confirmed malware) bypasses this and clamps to -100 directly.

A signal’s weight is the amount it adjusts the score by. Doubling a weight doubles its impact on the final score. Setting a weight to 0 removes the signal from the score entirely (it still appears as a finding but doesn’t move the number).

Step 4: Set a Custom Risk Profile

The page lets you save a named profile. For example: a regulated-finance profile that weights license compliance and provenance heavily, and a research-eng profile that’s more permissive about new packages.

  1. Set the weights you want.
  2. Click Save profile → name it.
  3. Apply via the dropdown at the top of the page.

You can apply different profiles via API per scope (per repository, per client) for environments where one org runs multiple risk regimes:

curl -X POST -H "Authorization: Bearer $CHAINSAW_TOKEN" \
     https://chain305.com/chainproxy/api/admin/risk-tuning/scope \
     -d '{
       "client_id": "ci-prod",
       "profile": "regulated-finance"
     }'

The proxy picks the profile based on the client making the request.

Step 5: Common Tuning Patterns

“We can’t tolerate KEV”

Crank vulnerability.kev_pinned from -30 to -100 (i.e., a single KEV match drops the score to 0). Pair with a Block trust_score < 1 policy and you have effectively a hard KEV gate without writing a custom policy.

“Pre-release packages are part of our workflow”

Drop package.age>=30d from +10 to +0 — i.e., remove the maturity reward. New packages now don’t get docked relative to old ones. (You probably still want a freshness guard for unrelated reasons — see the freshness-guards tutorial.)

“ML team operates with different signals”

Save a profile that boosts every aiml.* weight by 50% and apply it to the ML team’s client credentials. Their packages get scored more strictly on AI/ML signals; the rest of the org isn’t affected.

“Air-gapped, no Sigstore”

Drop provenance.verified from +25 to +5. In an air-gapped deployment without Sigstore, attestation availability is unreliable and giving it 25 points distorts the score. The change reflects what’s actually verifiable in your environment.

Step 6: Auditing Tuning Changes

Every weight change is recorded in the audit log:

curl -H "Authorization: Bearer $CHAINSAW_TOKEN" \
     "https://chain305.com/chainproxy/api/audit?event=risk-tuning.weight_changed&since=30d" \
     | jq '.events[] | {time, actor, signal, old, new}'

For compliance audits, this is the trail that proves “we didn’t quietly disable a signal to hide a violation”. Walk the log; the auditor sees every tuning change in chronological order.

Step 7: Resetting

If a tuning experiment goes sideways, Reset to defaults at the top of the page restores every weight to its shipped value. The reset is itself an audit event.

For a single signal, just delete the value in the Current weight column to fall back to the default.

Step 8: When NOT to Tune

Risk-tuning is the right surface when:

  • You’re fitting the score to a consistent risk philosophy (regulated vs. research, etc.).
  • The signal already exists and you want to change its weight.

It’s the wrong surface when:

  • You want to disable a signal globally — use a policy with Action: Allow and the inverse condition, scoped narrowly.
  • You want a one-off exception for a single package — use the exception system (tutorial 19), not a weight change.
  • You think the score is wrong for a single package because of a bug in the signal — file a bug, don’t paper over it with weight tuning.

Verification

  1. Edit a weight on the risk-tuning page.
  2. Trigger a recompute via the API.
  3. Confirm the trust score on a previously-scored package changes by the expected delta.
  4. The audit log shows the weight-change event with the old and new values.
  5. Reset to defaults restores the original score.