Skip to content

Key Management

xNetVN Development edited this page Nov 12, 2026 · 1 revision

Key Management

An API key is a delegated credential owned by one WordPress user. It never creates an identity of its own: the owner's current capabilities, the key scope, the site ceiling and the selected toolsets are intersected on every request. A key can therefore reduce what its owner may do, but it can never add authority (docs/en/adr/0004-key-model.md, docs/en/adr/0005-capability-intersection.md).

Creating a key

Keys are created from Profile (self-service) or Agent Hub > Keys (managers). Creation always targets the current user: the controller passes get_current_user_id() to the service, so an administrator cannot mint a key for someone else (src/Rest/KeysController.php). The owner must hold use_xnetvn_agent_hub_wp.

The creation form asks for:

  • a display name up to 100 characters;
  • an expiry in days;
  • a maximum risk tier;
  • the toolsets the client needs (system is always included);
  • optionally, allowed IP networks and blocked abilities.

The service validates the requested tier against the site ceiling, the owner's capability for that tier, and the admin-tier switch. It also verifies that every requested toolset is reachable at that tier and every ability name exists in the catalogue (src/Auth/KeyService.php). Destructive and admin keys require the owner's current password and must have an expiry. The default expiry is 90 days or the site's maximum lifetime, whichever is shorter; the default maximum lifetime is 365 days, and the default active-key limit is 10 per user (src/Persistence/WpSiteSettings.php).

The full credential is returned exactly once, in the creation response. Copy it directly into the client's secret store; it cannot be retrieved later (src/Rest/KeysController.php, docs/user-guide.md).

What is stored

Stored Not stored
Public key identifier Plaintext credential
HMAC-SHA256 digest of the secret Reversible ciphertext
Pepper version identifier Pepper in the key row
Name, owner, tier, toolsets, allow/deny lists, IP allowlist, expiry Bearer header or request body
Created/last-used/revoked timestamps and last-used peer metadata, masked when configured Password used for reauthentication

The digest is keyed by a separate random 32-byte per-site pepper stored in its own non-autoloaded option; the key table stores the digest and the pepper version, never the plaintext (src/Auth/KeyHasher.php, src/Auth/PepperStore.php, src/Persistence/Schema.php). Losing or replacing the pepper makes every existing key fail verification. It does not expose any credential.

Changing a scope

Only narrowing is allowed. A key update may lower the maximum tier, remove a toolset, add a denied ability, remove an allowed ability, shorten the expiry or narrow the IP allowlist. The reverse operations return a conflict (scope_expansion) and leave the key unchanged (src/Domain/Scope.php, src/Auth/KeyService.php). To grant more, issue a new key with the intended scope and retire the old one.

Renaming is allowed without changing authority. A revoked key cannot be patched. Managers may inspect, narrow or revoke another owner's key, but creation and rotation remain owner-only.

Rotation

Rotation issues a replacement with the same scope and a newly generated secret. The new credential is shown once. The old key is not revoked immediately: its expiry is shortened to the earlier of its existing expiry and now + grace_hours. The grace period accepts 0–24 hours and defaults to 24 (src/Auth/KeyService.php, src/Rest/KeysController.php).

Use rotation for planned replacement, so the client can switch credentials without an outage. A key above the write tier requires the owner's current password to rotate. The active-key limit still applies. The old key remains usable during the grace window, so rotation is not an incident response for a leaked credential.

Revocation

Revocation is immediate. The service records the revocation time, actor and a fixed reason (owner_request or administrator_request), and every later authentication fails (src/Auth/KeyService.php, src/Persistence/WpdbKeyRepository.php). No password is required, which keeps emergency revocation fast. The owner can revoke their own key; a site manager can revoke any key they may administer. The key row is retained for audit and privacy handling rather than deleted.

A revoked key cannot be un-revoked or edited. Create a replacement with the intended scope.

If a key leaks

  1. Revoke first. Do not rotate: rotation leaves the old credential valid for up to 24 hours. Revocation stops the next request (docs/user-guide.md, docs/en/operations/incident-response.md).
  2. Preserve and review evidence. Filter Agent Hub > Activity by that key, outcome and date range. Record the request IDs, abilities, durations and peer metadata before retention removes them.
  3. Contain the owner. If the WordPress account may also be compromised, reset its password and review its sessions and role. A role downgrade or ban applies to the key on the next request.
  4. Check the site. Review content, media, comments, terms and users changed during the exposure window, and restore from revisions or backups where needed.
  5. Reissue narrowly. Create a new key with the lowest tier and toolsets that work, then update the client's secret store.
  6. Escalate the pepper only if it leaked. A pepper compromise invalidates all keys on the site; the incident procedure is a separate, site-wide action (docs/en/operations/incident-response.md).

Never paste a key, digest or pepper into a ticket, chat, log or screenshot. Support reports need the public key identifier only.

Last-used metadata

A successful authentication updates the key's last-used timestamp, peer address and a truncated user agent. The write is throttled: it is skipped when the recorded timestamp is newer than five minutes, so a busy key does not write on every request. Revoked keys are not touched (src/Persistence/WpdbKeyRepository.php).

The Keys screen shows last_used_at; the stored peer and user agent are used for the unusual-network notification and are included in the owner's privacy export, not in the normal key view (src/Rest/KeyView.php, src/Auth/Notifications.php). With Mask IP addresses in audit and key metadata enabled, only the IPv4 /24 or IPv6 /48 network is retained (docs/user-guide.md).

Last-used data is a signal, not proof of legitimacy. A key used only from an expected network can still be stolen; a changed network can be legitimate mobile use. Correlate it with the activity rows and the owner's expectations.

Lifecycle states

State Meaning What still works
Active Not revoked and not expired Requests within scope and limits
Expired Past expires_at Nothing; create a replacement
Revoked Explicitly stopped Nothing; cannot be edited or restored

Owners can opt out of key email on their profile. Site settings independently control creation, administrator-revocation, expiry and unusual-network notices (src/Auth/Notifications.php).

See Security-Model for how the credential is verified and Audit-and-Logging for investigating its use.

Clone this wiki locally