# Security rules

> Policies every new gateway key must meet, and the response you choose for a signal: notify, lower the limit, keep last week's countries, or turn the key off.

Source: https://infro.io/docs/security/rules

> **Warning:** **On the roadmap — not available yet.** Key policies and automatic responses are built and in testing, and not deployed. This page describes what they will ship with.

**Security → Rules** holds two things: the organization's **key policy**, which every new gateway key must meet, and **automatic responses**, which act on a key when a [signal](https://infro.io/docs/security/signals) you name is raised. INFRO's checks never refuse a request on their own; a rule is you choosing that they should. Both are on every plan.

## Key policies

| Setting | What it does to a new key |
| --- | --- |
| Maximum key age (1 to 3,650 days) | A key created with no expiry expires this many days after it is made; one created with a later expiry is refused. |
| Default spend limit | A key created with no spend limit gets this one (hourly, daily, weekly or monthly). The create response shows it in `credit_limit`. |
| Production keys need a spend limit | A key in the `production` environment is refused without one, unless the default spend limit gives it one. |
| Production keys need a network rule | A key in the `production` environment is refused without allowed networks. |

- The policy applies wherever a key is created: the console, the [management API](https://infro.io/docs/manage/resources#gateway-keys) and Terraform. A refusal is `400` `invalid_request` with code `key_policy`, naming what to set.
- It never changes a key that exists. The [posture checks](https://infro.io/docs/security#posture-checks) list the keys that fall short, and the age check reads the policy's maximum age.
- Everyone who opens the Security screen reads it; owners, admins and Security members change it. Security members only tighten it: a change that loosens anything is refused with `403` `containment_widens`, and an owner or admin makes it. Changes are refused while the organization is held or locked for a lapsed plan.
- Each change is in the audit log as `security.key_policy_updated` with the fields it loosened, and a change that loosens it is listed as an access change.

Through the console session: `GET /console/security/key-policy` returns `max_key_age_days`, `production_keys_need_ceiling`, `production_keys_need_network_rule`, `default_ceiling` (`{amount, period}` or `null`), `updated_at`, `updated_by` and `version`. `PATCH /console/security/key-policy` takes any of them and changes only those.

## Automatic responses

A rule says: when a signal of these kinds, at or above this severity, is raised for a key in this scope, do this. The default action is to notify; turning a key off is something you choose.

| Action | What it does |
| --- | --- |
| Notify (`notify`) | Records the match and, when you pick a [notification channel](https://infro.io/docs/observability/alerts), posts the signal to it. Every member is emailed about the signal anyway. |
| Lower the limit (`lower_limit`) | Sets the key's spend limit to the one the rule names (hourly, daily, weekly or monthly). |
| Keep last week's countries (`allow_last_week_countries`) | Gives the key a country rule that allows only the countries it was used from in the seven UTC days before today. (Country rules are not available yet.) A key with no use in those days is left as it is. |
| Turn off (`turn_off`) | Turns the key off, as **Contain → Turn off** does. Release or Undo turns it on again. |

- Every key action goes through [containment](https://infro.io/docs/security/signals#containing-a-key), so a rule only ever narrows a key. A key already tighter than the action is left alone, and the run says *skipped*.
- The three key actions apply only to signals about one gateway key: many countries, a volume or spend jump, a turned-off key called, a refusal burst, a new model or provider, and distillation-shaped traffic. Any other kind takes notify only, or the rule is refused with `400` `rule_action_unfit`.
- A scope narrows which keys a rule watches: the organization, an environment, a project or one key. Severity: `info`, `warning` (the default) or `critical`.
- Rules are checked every minute against the signals raised or upgraded in the two hours before, so a rule acts within about a minute of its signal. Each rule runs once for a signal. A rule that waits for `critical` runs when a warning signal is upgraded to critical.
- An action a rule takes is in the audit log as `api_key.contained`, with the rule as the actor and the signal it answered.
- At most 50 rules per organization (`400` `rule_limit`). Owners, admins and Security members create and change them; deleting a rule or turning it off is listed as an access change. Security members only tighten: an enabled rule whose action is not notify they may rename or leave as it is, and turning it off, deleting it or changing anything else answers `403` `containment_widens` (an owner or admin makes that change).

## Runs and Undo

Each time a rule fires is a run: *applied*, *skipped* (with why) or *failed*. **Undo** on an applied key action restores what the key had before, and only while the key still holds what the rule set: if a person changed it since, Undo answers `409` `conflict` with code `rule_run_changed` and leaves their change in place. A run that is not a key action, or was already undone, answers `400` `rule_run_not_undoable`. Undo needs the role that contains keys (owners, admins, Security members, developers for keys in their reach) and is in the audit log as `security_rule.run_undone`.

## API

- `name` (string, required) — What the rule is for.
- `enabled` (boolean) — Default `true`.
- `event_types` (string[], required) — 1 to 20 `security.*` event types, as [Alerts](https://infro.io/docs/observability/alerts) names them.
- `min_severity` (string) — `info`, `warning` (default) or `critical`.
- `scope` (string) — `organization` (default), `environment`, `project` or `key`.
- `scope_id` (string) — The environment, project or key the scope names.
- `action` (string) — `notify` (default), `lower_limit`, `allow_last_week_countries` or `turn_off`.
- `limit` (object) — `lower_limit` only: `{amount, period}`.
- `channel_id` (string) — `notify` only: the notification channel to post to.

Through the console session: `GET /console/security/rules`, `POST /console/security/rules` (returns `201` and the rule, with `fired_count`, `last_fired_at` and `version`), `PATCH` and `DELETE /console/security/rules/{id}` (audited `security_rule.created`, `.updated`, `.deleted`). `GET /console/security/rule-runs`, optionally with `key_id` and `limit`, lists runs with `outcome`, `detail`, `finding_id`, `key_id` and `undoable`; `POST /console/security/rule-runs/{id}/undo` undoes one.

**On the roadmap.** Security through the management API and MCP is built and not deployed. Rules and the key policy are also [management API resources](https://infro.io/docs/manage/resources#security), `security_rules` and `security_key_policy`, with versions, `If-Match` and `/export`, and Terraform resources (`infro_security_rule`, `infro_security_key_policy`).
