Repository navigation
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).
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).
| 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.
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 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 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.
-
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). - 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.
- 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.
- Check the site. Review content, media, comments, terms and users changed during the exposure window, and restore from revisions or backups where needed.
- Reissue narrowly. Create a new key with the lowest tier and toolsets that work, then update the client's secret store.
-
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.
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.
| 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.
xNetVN Agent Hub WP · GPL-2.0-or-later · Copyright (c) 2026 xNetVN Inc.
This wiki summarizes the repository documentation. The source of truth is the repository; when they disagree, the repository wins.
Start here
Using the plugin
Security
Engineering
Project