MCP / Action Fabric Exposure Scope Too Broad
This page describes the
nowisor-ai-mcp-exposure-scopecheck from the nowisor Instance Scan Pack v1.2.0 (ai-agent-securitygroup, spec AIG-007). The check flags write-capable tool packages on the inbound agent surface, and OAuth clients with no bounded token lifetime.
What this finding means
Your inbound agent surface grants more than it needs. Two independent questions, evaluated and reported separately:
- (a) scope - are write-capable tool packages granted where read-only would serve?
- (b) credentials - do the OAuth clients used by inbound agents have a bounded token lifetime?
This complements rather than duplicates the ungoverned-inbound-agent check. That check asks who is connected and whether they are catalogued. This asks how much the connection can do. An agent can be perfectly registered, owned and reviewed and still hold a write-everything package on a non-expiring client - which is the more common real-world failure, because registration is a governance task somebody owns and scope minimisation is not.
The two questions are independent: one can be answerable while the other is not, and the check reports N/A per question rather than guessing at a schema that differs across releases.
Why it matters for your compliance
NIS2 Article 21(2)(i) access control and 21(2)(d) supply chain security both apply: an external agent with write tools is a supplier with write access.
ISO 27001 A.8.2 (privileged access rights) is the operative control - MCP tool packages are privileged grants in everything but name.
DORA Article 9(4)(c) limits access to approved functions; a blanket write grant approves every function at once.
OWASP LLM06:2025 (Excessive Agency) names the pattern exactly, and LLM03:2025 (Supply Chain) covers the external-party dimension.
The attack path
Over-granting is the default, not the exception. Tool packages are assembled to make an integration work, then never narrowed. Nobody revisits a working integration to remove capabilities it is not using - which is precisely why the permission-vs-usage correlation exists, and why this configuration check is the cheap first pass.
Non-expiring credentials outlive their justification. A client issued for a three-month pilot keeps authenticating for years. The pilot ends, the owner moves on, and the access remains - held by a party outside your instance, against tools that can write.
The blast radius is external. Unlike an over-privileged internal account, an inbound agent's credential lives in someone else's system, subject to someone else's security posture. Compromise there is compromise here, and you will not see it happen.
How to fix it
For broad tool grants:
- Reduce each package to the narrowest set that satisfies the use case. Prefer read-only tools wherever they serve - a summarisation agent does not need write tools.
- Where a write tool is genuinely required, scope it to specific tables rather than granting a blanket operation.
For OAuth clients without expiry:
- Set a bounded token lifetime and a rotation schedule.
- Revoke clients whose project has ended.
Run this alongside the ungoverned-inbound check: register first, then minimise. Narrowing the scope of an agent nobody owns solves half a problem.
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.