Skip to content

Policy overrides

A policy override is a per-tenant setting that overrides one of Upwarden’s tier-driven defaults. Tightening a policy (making it stricter than the default) is always allowed. Loosening a policy past the tier default requires a typed acknowledgement — the operator is putting their name on the deviation, and the row carries that confirmation forward into the audit log.

This page covers the customer-facing surface. The operator runbook for policy-override governance lives in docs/admin-guide.md.

Owner role on a tenant. Most overrides require Org or Enterprise tier; the Rate-limit RPM cap is also available from Team tier. Free-tier tenants run on Upwarden’s defaults, and the upgrade path is “go to Team for the rate-limit cap, or Org to unlock the full override set”.

Owner-only is the same gating posture as IP allowlist (see Sign-in & access control) — a misset policy can lock the org out of its own dashboard, so the write capability stays on the smallest possible set of accounts.

The tighten-freely / loosen-with-confirm model

Section titled “The tighten-freely / loosen-with-confirm model”

Every overrideable policy has two states relative to the tier default:

  • Tighter than default. No acknowledgement needed. The owner clicks save, the row writes, the audit row records the change with event_string = policy_override_tightened. Example: dropping the MFA grace period from 14 days to 0.
  • Looser than default. The dashboard demands a typed acknowledgement before the save button enables. The text the owner types lands in the audit row as acknowledgement_text so an auditor can verify the step-up actually happened. Example: extending the MFA grace period from 14 days to 30.

The shape of “tighter vs looser” is policy-specific — Upwarden knows MFA tighter means more enforcement and audit-retention tighter means longer retention. The per-policy comparator lives in the engine and isn’t a knob.

Today:

PolicyTier eligibilityDefaultWhat it controlsTighten direction
MFA enforcementOrg+Tier-driven (Team = owners only; Org+ = everyone)The set of members required to register a TOTP factor at sign-in (see Sign-in & access control).More members required, shorter grace period
Session duration (minutes)Org+STYTCH_SESSION_DURATION_MINUTES (60 by default)How long an authenticated dashboard session stays valid before re-auth.Shorter window
Rate-limit RPM (enforcement_rate_limit_rpm)Team+Platform RATE_LIMIT_RPM env knobPer-org proxy request rate cap.Lower cap (or non-zero from 0)
Unknown-version disposition (enforcement_unknown_disposition)Org+DEFAULT_ENFORCEMENT.unknownDisposition (typically allow)What the proxy does with a version it has no verdict for yet — allow (let through), quarantine (hold for review), or block.allowquarantineblock is the tightening direction
Audit retention (days) (audit_retention_days)Org+Tier-dependent (Free 7 / Team 30 / Org 365 / Enterprise 3650)How long audit rows are kept before sweep. Inverse: shorter retention = looser (less defensible trail), so shortening prompts the acknowledgement.Longer window

The dashboard’s /admin/ui/orgs/<slug>/settings/policy-overrides page is the canonical list — anything not surfaced there is not overrideable yet. For a per-policy reference of the underlying wire shape (default source, comparator direction, edit-form layout) see Policies and overrides.

The audit_retention_days inversion is the gotcha — shortening retention is the loosening direction and triggers the typed-acknowledgement flow, the opposite of every other policy on the list. Upwarden knows this per-policy and routes accordingly; you don’t need to think about it.

/admin/ui/orgs/<slug>/settings/policies — every overrideable policy is listed with its tier default and the current effective value. Each row has an “Edit” button.

The edit form is policy-specific (MFA shows the three knobs — required owners, required members, grace period; future policies surface their own). When the new value would loosen the policy past the default, a typed-acknowledgement field appears below the save button. The save button enables only when the acknowledgement text is non-empty.

The dashboard also surfaces a banner at the top of the settings page when a policy is currently overridden in the looser direction — visual reminder that the org is running outside Upwarden’s tier defaults. Banner dismissal is per-policy per-tenant; dismissing audits a policy_override_banner_dismissed row.

Terminal window
curl -fsS -X PUT \
-H "x-upwarden-admin-token: $ADMIN_API_TOKEN" \
-H "content-type: application/json" \
-d '{
"value": { "required_owner": true, "required_members": true, "grace_period_days": 7 },
"acknowledgement_text": "Extending grace from 14d to 7d (still tighter than default) — approved by CISO"
}' \
"https://vg-admin.<your-domain>/api/v1/admin/orgs/<slug>/policy-overrides/mfa"

acknowledgement_text is required when the new value loosens the policy past the default, and only then. A tighten-only call without acknowledgement text succeeds; a loosen call without it 400s with the per-policy reason string.

The response carries the stored override record (including the acknowledged_at and acknowledged_by timestamps the server stamped). Subsequent reads of the policy surface return the override until it’s restored.

Restoring a policy to the tier default is a DELETE:

Terminal window
curl -fsS -X DELETE \
-H "x-upwarden-admin-token: $ADMIN_API_TOKEN" \
"https://vg-admin.<your-domain>/api/v1/admin/orgs/<slug>/policy-overrides/mfa"

(Or “Reset to default” on the dashboard surface.) The audit row carries event_string = policy_override_restored. From that point the tier default applies until the next override.

Every override mutation writes one row into audit_log. The decision_reason envelope carries the policy name and (for loosen calls) the typed acknowledgement:

EventCarries
policy_override_tightened{ policy_name, default_value, override_value, by_user_id }
policy_override_loosened{ policy_name, default_value, override_value, acknowledgement_text, by_user_id }
policy_override_restored{ policy_name, by_user_id }
policy_override_banner_dismissed{ policy_name, by_user_id }

Find these rows the same way as any other admin mutation — see Querying the audit log for the filter shape. Pivot by policy_name to get a per-policy history; pivot by by_user_id to attribute the change to a specific operator.