Inactive AI Agent Retains Capability Grants
This page describes the
nowisor-ai-dormant-skill-grantscheck from the nowisor Instance Scan Pack v1.2.0 (ai-agent-securitygroup, spec AIA-005). The check flags agents switched off whose execution identity still holds platform roles. Deactivation is not revocation.
What this finding means
One or more inactive agents still have an execution identity holding platform roles.
Turning an agent off stops it being invoked; it removes nothing from its identity. The roles survive, the credentials keep working, and the OAuth tokens keep authenticating. A decommissioned agent therefore leaves behind a fully privileged identity that nobody monitors - precisely because the agent it belonged to is "not in use".
The evidence flags which of those identities are still active user records (identities_still_active), because that is what determines whether the credentials remain usable independently of the agent's state.
Scope discipline. This check reports held-but-inactive grants from configuration alone. It says nothing about whether the grants were ever exercised - that is the permission-vs-usage correlation in the Agent Least-Privilege Report, which requires log coverage this check does not have. Severity is deliberately MEDIUM: a cleanup obligation, not an active exploit.
Why it matters for your compliance
NIS2 Article 21(2)(i) asset management and access control both require grants to track the lifecycle of the thing they were granted for.
ISO 27001 A.5.16 (identity management) requires the full lifecycle to be managed - decommissioning is part of the lifecycle, and a retired agent whose identity keeps its roles has been half-decommissioned.
ISO 27001 A.8.2 applies where the retained roles are privileged. DORA Article 9(4)(c) limits access to approved functions; a retired agent has no approved function at all.
The attack path
The standing-credential pattern. This is the classic dormant non-human-identity problem. A service account with live roles and no active consumer is attractive precisely because nobody watches it: alerting is usually tuned to the systems people believe are in use.
Two routes back in. Either the credentials are used directly (they still authenticate), or the agent is re-enabled - a single field flip that restores a fully privileged automation without passing through any review, because the agent already existed and its grants were never removed.
Why it accumulates. Decommissioning an agent is a product decision made by whoever owns the process; revoking its roles is a security task owned by somebody else. The handoff is where the grants survive. This is the same structural gap as inactive users retaining roles, applied to non-human identities.
How to fix it
Step 1 - confirm the agent is genuinely decommissioned rather than temporarily paused. Paused agents need their grants; retired ones do not.
Step 2 - revoke the roles listed in dormant_grants[].retained_roles from the execution identity.
Step 3 - deactivate the service account where no other agent or integration uses it. The evidence tells you which identities are still active records.
Step 4 - document grants for agents that may return. If the agent might be re-enabled, record what it needed so the grants can be restored deliberately instead of left standing indefinitely.
Make revocation part of the decommissioning runbook, not a periodic cleanup - otherwise this finding returns with every retired agent.
How this check stays honest
Every check in the ai-agent-security group is enumeration-driven: table and
field names are resolved from sys_db_object and sys_dictionary on your
instance, never asserted from a hardcoded list. The sn_aia namespace is
paid-SKU gated and cannot be verified against a ServiceNow developer instance, so
asserting its identifiers from documentation alone would risk exactly the
fabricated-identifier problem this codebase audits for.
Two consequences you will see in the output:
- An absent plugin produces
not_applicable, never a pass. The absence of Now Assist is not a security win; it means the control is out of scope. A check that silently passed would let a customer believe they were assessed on something that was never examined. - An unresolvable field produces
not_applicable, not a finding. Where the release does not expose the column a check needs, the check says so and names it. Firing anyway would flag healthy instances on releases we simply cannot read.
All detection is read-only. No record contents are inspected or transmitted.