Back to skill

Security audit

Dynamics 365 CRM

Security checks for vulnerabilities and agentic risk

Overview

This skill appears purpose-aligned, but it needs review because it can change live Dynamics 365 CRM records and does not validate the API destination that receives bearer tokens.

Install only in a controlled tenant with least-privilege Dataverse permissions. Validate and lock DYNAMICS365_INSTANCE_URL to the exact expected HTTPS Dynamics 365 origin, protect the client secret and refresh tokens, and require users to confirm the exact CRM record and fields before any create or update action.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T09 · Insecure Skill Coding Practices

Error
Location
src/client.ts:35
Finding

Unvalidated API Destination Can Disclose Azure Bearer Tokens and CRM Data

Content
View full analysis
{ const filter = `name eq '${escapeSingleQuotes(name)}'`; const resp = await fetch( `${this.baseUrl}/opportunities?$select=opportunityid&$filter=${encodeURIComponent(filter)}&$top=1`, { headers: this.headers }, ); ``` The same bearer-token-bearing headers and caller-derived base URL are used by the remaining request methods at lines 84–105, 120–157, 173–208, 223–256, and 275–280. ### Technical Analysis `instanceUrl` is used to construct the request destination without parsing or validating it. The implementation does not: - Require HTTPS. - Restrict the hostname to an approved Dynamics 365 tenant. - Reject localhost, private, loopback, or link-local destinations. - Reject embedded URL credentials, unexpected ports, query strings, or fragments. - Verify that the destination corresponds to the audience and tenant of the supplied access token. At the same time, the client places th ...[truncated 2838 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (19)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared purpose is CRM record management in Microsoft Dynamics 365 through Dataverse and Azure AD OAuth, but the provided code does not implement any CRM operations, API calls, authentication, triggers, or data access. Instead, it only configures the Jest test runner for a TypeScript project. This is a materially different primary purpose, so the description does not accurately represent the supplied code chunk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The declared description centers on CRUD-style CRM record management in Dynamics 365/Dataverse. The supplied code does something materially different: it contains Jest tests for OAuth authentication helper functions only. While OAuth 2.0 is mentioned in the description as part of the integration, the primary behavior shown here is authentication-flow testing, not creating or updating Opportunities, Leads, Contacts, Accounts, or Tasks. No Dataverse entity operations, record writes, or Web API calls to CRM resources are present in this chunk, so the code does not accurately represent the declared purpose.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · SKILL.md (reported line 151)May include surrounding context.

md
- Lookup fields (e.g. linking a Contact to an Account) use OData binding syntax:
  `"parentcustomerid_account@odata.bind": "/accounts({accountId})"`
- Rate limit: **6,000 requests / 5 minutes** per user — well within typical usage
- Access tokens expire after **~1 hour** — implement refresh logic using `refreshAccessToken()`

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · src/oauth.ts (reported line 103)May include surrounding context.

ts
/**
 * Refresh an expired access token using a stored refresh token.
 * Dynamics 365 access tokens expire after ~1 hour.
 * Refresh tokens are long-lived but can be invalidated by policy changes.
 *
 * @throws Error if the refresh fails (user must re-authorize)

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · src/oauth.ts (reported line 102)May include surrounding context.

ts
export interface OAuthTokens {
  accessToken: string;
  refreshToken: string;
  /** Unix timestamp (ms) when the access token expires */
  expiresAt: number;
  /** Dynamics 365 instance URL associated with these tokens */
  instanceUrl: string;

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · src/oauth.ts (reported line 151)May include surrounding context.

ts
export interface OAuthTokens {
  accessToken: string;
  refreshToken: string;
  /** Unix timestamp (ms) when the access token expires */
  expiresAt: number;
  /** Dynamics 365 instance URL associated with these tokens */
  instanceUrl: string;

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · src/types.ts (reported line 30)May include surrounding context.

ts
export interface OAuthTokens {
  accessToken: string;
  refreshToken: string;
  /** Unix timestamp (ms) when the access token expires */
  expiresAt: number;
  /** Dynamics 365 instance URL associated with these tokens */
  instanceUrl: string;

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · tests/oauth.test.ts (reported line 209)May include surrounding context.

ts
export interface OAuthTokens {
  accessToken: string;
  refreshToken: string;
  /** Unix timestamp (ms) when the access token expires */
  expiresAt: number;
  /** Dynamics 365 instance URL associated with these tokens */
  instanceUrl: string;

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The markdown explicitly advertises voice-driven creation and updates of Opportunities, Leads, Contacts, Accounts, and Tasks, which can affect user data and business records. Under the markdown criteria for SQP-2, the description should warn that the skill will write to external CRM data and may change existing records.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
85% confidence
Finding

The environment variables include a client secret, and the skill sends CRM data through the Dataverse Web API, but the markdown does not include any privacy or credential-handling warning. For markdown files, SQP-2 applies when descriptions omit warnings about behaviors that could affect privacy or system integrity.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
83% confidence
Finding

The skill clearly describes network-capable behavior against the Dynamics 365/Dataverse API but does not declare an explicit tool scope such as allowed network access. In an agent environment, missing scope boundaries can permit broader-than-expected outbound requests or make review and enforcement of intended destinations impossible.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The skill performs write operations to a production CRM system but does not require an explicit confirmation or warning before creating or updating records. This raises the risk of unintended modification of customer data, especially when user input is ambiguous or when an agent acts too eagerly.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
86% confidence
Finding

The skill requires a client secret and transmits CRM data to external Microsoft services, but it does not warn about sensitive credential handling or external data transfer. In agent deployments, this omission can lead to unsafe secret exposure in prompts/logs and unintentional processing of sensitive customer information.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest description says the skill will 'Create and update CRM records' for specified Dynamics 365 entities. This client also performs explicit GET-based search/read operations to locate existing Opportunities, and similar read methods exist for Leads, Contacts, and Accounts, expanding behavior beyond the stated write-oriented scope.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Content

Scanner excerpt · package.json (reported line 45)May include surrounding context.

json
"node": ">=20.0.0"
  },
  "devDependencies": {
    "@types/jest": "^29.5.0",
    "@types/node": "^22.0.0",
    "jest": "^29.7.0",
    "ts-jest": "^29.2.0",

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Content

Scanner excerpt · package.json (reported line 46)May include surrounding context.

json
},
  "devDependencies": {
    "@types/jest": "^29.5.0",
    "@types/node": "^22.0.0",
    "jest": "^29.7.0",
    "ts-jest": "^29.2.0",
    "typescript": "^5.7.0"

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Content

Scanner excerpt · package.json (reported line 47)May include surrounding context.

json
"devDependencies": {
    "@types/jest": "^29.5.0",
    "@types/node": "^22.0.0",
    "jest": "^29.7.0",
    "ts-jest": "^29.2.0",
    "typescript": "^5.7.0"
  }

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Content

Scanner excerpt · package.json (reported line 48)May include surrounding context.

json
"@types/jest": "^29.5.0",
    "@types/node": "^22.0.0",
    "jest": "^29.7.0",
    "ts-jest": "^29.2.0",
    "typescript": "^5.7.0"
  }
}

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Content

Scanner excerpt · package.json (reported line 49)May include surrounding context.

json
"@types/node": "^22.0.0",
    "jest": "^29.7.0",
    "ts-jest": "^29.2.0",
    "typescript": "^5.7.0"
  }
}

Static analysis

No suspicious patterns detected.