Agent Skill · Microsoft Power Apps

audit-permissions

Audits existing table permissions on a Power Pages site by analyzing them against site code and Dataverse metadata. Generates an HTML audit report with findings grouped by severity (critical, warning, info, pass) and suggests fixes for issues found. Use when the user wants to review, verify, or check table permissions for security issues.

Provider: Microsoft Power Apps Path in repo: plugins/power-pages/skills/audit-permissions/SKILL.md

Skill body

Plugin check: Run node "${CLAUDE_PLUGIN_ROOT}/scripts/check-version.js" — if it outputs a message, show it to the user before proceeding.

Audit Permissions

Audit existing table permissions on a Power Pages code site. Analyze permissions against the site code and Dataverse metadata, then generate a visual HTML audit report with findings, reasoning, and suggested fixes.

Workflow

  1. Verify Site Deployment — Check that .powerpages-site folder and table permissions exist
  2. Gather Configuration — Read all web roles, table permissions, and site code
  3. Run Local Schema Validation — Use the shared validator to detect invalid permission/site-setting YAML before deeper analysis
  4. Analyze & Discover — Query Dataverse for relationships and lookup columns using deterministic scripts
  5. Run Audit Checks — Compare permissions against code usage and best practices
  6. Generate Report — Create the HTML audit report and display in browser
  7. Present Findings & Track — Summarize findings, record skill usage, and ask user if they want to fix issues

Important: Do NOT ask the user questions during analysis. Autonomously gather all data, then present findings.

Task Tracking

At the start of Step 1, create all tasks upfront using TaskCreate. Mark each task in_progress when starting and completed when done.

Task subject activeForm Description
Verify site deployment Verifying site deployment Check .powerpages-site folder and table permissions exist
Gather configuration Gathering configuration Read web roles, table permissions, and site code
Run local schema validation Validating local permissions schema Run shared validator against existing table permission and site setting YAML
Discover relationships Discovering relationships Query Dataverse for lookup columns and relationships
Run audit checks Running audit checks Create per-table tasks and run checklist (A–K) for each table, then cross-validate
Generate audit report Generating audit report Create HTML report and display in browser
Present findings Presenting findings Summarize results, record usage, and offer to fix issues

Note: The “Run audit checks” phase creates additional per-table tasks dynamically in Step 4.2. These per-table tasks track the systematic A–K checklist for each table independently.


Step 1: Verify Site Deployment

Use Glob to find:

If no .powerpages-site folder exists, stop and tell the user to deploy first using /deploy-site. If no table permissions exist, note this as a critical finding (the site may have no data access configured) and continue the audit — there may still be code references that need permissions.


Step 2: Gather Configuration

2.1 Read Web Roles

Read all files matching **/.powerpages-site/web-roles/*.yml. Extract id, name, anonymoususersrole, authenticatedusersrole from each.

2.2 Read Table Permissions

Read all files matching **/.powerpages-site/table-permissions/*.tablepermission.yml. For each permission, extract:

2.3 Analyze Site Code

Search the site source code for:

Also check for .datamodel-manifest.json in the project root for the authoritative table list.

Build a map of: which tables are referenced in code, which CRUD operations are performed on each, which lookup relationships are used, and which related tables are fetched via $expand (these need read permissions too).

2.4 Run Shared Schema Validator

Run the shared validator against the existing site:

node "${CLAUDE_PLUGIN_ROOT}/scripts/validate-permissions-schema.js" --projectRoot "<PROJECT_ROOT>"

Parse the JSON output and carry the findings into the audit. Treat:

These findings should be included in the final audit report even if the later code/Dataverse analysis also finds additional issues.

After Step 3.1 determines the environment URL, if this audit is running locally with Dataverse access available, rerun the shared validator with live relationship verification enabled and merge any additional findings:

node "${CLAUDE_PLUGIN_ROOT}/scripts/validate-permissions-schema.js" --projectRoot "<PROJECT_ROOT>" --validate-dataverse-relationships --envUrl "<envUrl>"

Use this Dataverse-backed relationship validation only for local runs. Do not require it in CI or other offline contexts.


Step 3: Analyze & Discover (Dataverse API)

Use deterministic Node.js scripts for all Dataverse API calls. These scripts handle auth token acquisition, HTTP requests, and JSON parsing consistently.

3.1 Get Environment URL

pac env who

Extract the Environment URL (e.g., https://org12345.crm.dynamics.com) and use it as <envUrl> in subsequent script calls.

3.2 Query Lookup Columns

For each table that has permissions with create or write enabled, use the lookup query script:

node "${CLAUDE_PLUGIN_ROOT}/skills/audit-permissions/scripts/query-table-lookups.js" --envUrl "<envUrl>" --table "<table_logical_name>"

The script returns a JSON array of { logicalName, targets } for each lookup column. Capture this output for the maps described below.

After querying all tables with create or write permissions, build two maps from the combined results:

  1. Source map (table → lookup columns): For each queried table, record which lookup columns it has and their targets. Used in Section H2 to check appendto on the source table.
  2. Reverse target map (target table → list of source tables): For each target table found in any lookup’s targets array, record which source table(s) reference it. Used in Section H to check append on the target table.

Example: querying order_item returns [{ logicalName: "cr4fc_orderid", targets: ["cr4fc_order"] }]

Both maps are used in Sections H and H2:

3.3 Query Relationships

For tables with parent-scope permissions, verify the relationship names using the relationship query script:

node "${CLAUDE_PLUGIN_ROOT}/skills/audit-permissions/scripts/query-table-relationships.js" --envUrl "<envUrl>" --table "<parent_table>"

The script returns a JSON array of { schemaName, referencedEntity, referencingEntity, referencingAttribute }. Use schemaName to validate the parentrelationship value in parent-scope permissions.

Error Handling

If any script exits with code 1, skip the API-dependent checks and note which checks were skipped in the report. Do NOT stop the entire audit for auth errors. Use the data model manifest and code analysis as fallback.


Step 4: Run Audit Checks

Use per-table task tracking to systematically run every audit check. Each check produces a finding with severity, title, reasoning, and a suggested fix. Findings can be critical, warning, info, or pass.

4.1 Build Audit Inventory

First, build a combined list of all tables to audit from two sources:

  1. Tables referenced in code (from Step 2.3) — these may or may not have permissions
  2. Tables with existing permissions (from Step 2.2) — these may or may not be referenced in code

The union of these two sets is the complete audit scope. Each table will be audited from both directions: “does the code need a permission that doesn’t exist?” and “does the permission match what the code actually does?”

4.2 Create Per-Table Audit Tasks

For each table in the audit inventory, create a task:

TaskCreate:
  subject: "Audit <table_logical_name>"
  activeForm: "Auditing <table_display_name> permissions"
  description: "Run all audit checks for <table_logical_name>"

Also create a summary task:

TaskCreate:
  subject: "Compile audit findings"
  activeForm: "Compiling audit findings"
  description: "Combine all per-table findings into the final report"

Use TaskList at any point to review progress and see which tables still need auditing.

4.3 Per-Table Audit Checklist

For each table, mark its task in_progress and run through the following checks in order. For every finding, note the specific evidence (file path, permission name, code pattern) that supports it. Skip checks that don’t apply to this table.

A. Permission Existence

Does this table have a table permission?

B. Web Role Association

Does the permission have web role(s) assigned?

C. Scope Appropriateness

Is the scope the least-privileged option that fits?

D. Read Permission

Is read correctly set?

E. Create Permission

Is create correctly set?

F. Write Permission

Is write correctly set?

G. Delete Permission

Is delete correctly set?

H. Append (target table check)

Does this table need append: true? Append is required on the target table — the table that other records link TO via lookup columns.

H2. AppendTo (source table check)

Does this table need appendto: true? AppendTo is required on the source table — the table that has lookup columns linking TO other records.

I. Parent Chain Integrity

If the permission has Parent scope (756150003):

J. $expand Related Table Coverage

Is this table fetched via $expand on another table’s query?

K. Record Findings & Complete

After all checks, mark the table’s task as completed via TaskUpdate.

4.4 Cross-Table Validation

After all per-table audits are complete, run these cross-table checks:

  1. Append/AppendTo consistency: Using the source map and reverse target map from Step 3.2, verify: (a) every source table (with lookups and create/write) has appendto: true, (b) every target table in the reverse map has append: true, (c) no table has appendto: true without lookup columns in the source map, (d) no table has append: true without being in the reverse target map
  2. $expand coverage: For every $expand usage, verify the expanded table has read: true
  3. Parent chain completeness: For every Parent scope permission, verify the parent permission exists and is valid
  4. Web role consistency: If two related tables (e.g., parent and child) are accessed by the same feature, verify they share the same web role assignment

Use TaskList to review all completed audits, then mark the “Compile audit findings” task as in_progress and proceed to Step 5.


Step 5: Generate Report

5.1 Determine Output Location

5.2 Prepare Data

Do NOT generate HTML manually or read/modify the template yourself. Use the render-audit-report.js script which mechanically reads the template and replaces placeholder tokens with your data.

Write a temporary JSON data file (e.g., <OUTPUT_DIR>/audit-data.json) with these keys:

{
  "SITE_NAME": "The site name (from powerpages.config.json or folder name)",
  "AUDIT_DESC": "Security audit of table permissions for Contoso Portal",
  "SUMMARY": "2-3 sentence summary of the audit results",
  "FINDINGS_DATA": [/* array of finding objects */],
  "INVENTORY_DATA": [/* array of current permission objects */]
}

FINDINGS_DATA format:

{
  "id": "f1",
  "severity": "critical",
  "title": "Missing permission for cra5b_product",
  "table": "cra5b_product",
  "scope": null,
  "permission": null,
  "reasoning": "The table cra5b_product is referenced in src/services/productService.ts with GET requests to /_api/cra5b_products, but no table permission exists for this table.",
  "fix": "Create a table permission with Global scope and read-only access for the Anonymous Users role.",
  "details": "Referenced in: src/services/productService.ts (line 23), src/components/ProductList.tsx (line 45)"
}

INVENTORY_DATA format:

{
  "name": "Product - Anonymous Read",
  "table": "cra5b_product",
  "scope": "Global",
  "roles": ["Anonymous Users"],
  "read": true,
  "create": false,
  "write": false,
  "delete": false,
  "append": true,
  "appendto": false
}

5.3 Render the HTML File

Run the render script (it creates the output directory if needed):

node "${CLAUDE_PLUGIN_ROOT}/scripts/render-audit-report.js" --output "<OUTPUT_PATH>" --data "<DATA_JSON_PATH>"

The render script refuses to overwrite existing files. Before calling it, check if the default output path (<PROJECT_ROOT>/docs/permissions-audit.html) already exists. If it does, choose a new descriptive filename based on context — e.g., permissions-audit-apr-2026.html, permissions-audit-post-migration.html. Pass the chosen name via --output.

Delete the temporary data JSON file after the script succeeds.

5.4 Open in Browser

Open the actual output path in the user’s default browser.


Step 6: Present Findings & Track

6.1 Record Skill Usage

Reference: ${CLAUDE_PLUGIN_ROOT}/references/skill-tracking-reference.md

Follow the skill tracking instructions in the reference to record this skill’s usage. Use --skillName "AuditPermissions".

6.2 Present Summary

Present a summary to the user:

  1. Critical findings count — these need immediate attention
  2. Warning findings count — should be addressed
  3. Report location — where the HTML file was saved
  4. Ask the user using AskUserQuestion: “Would you like me to fix any of these issues? I can create or update table permissions to resolve the critical and warning findings.”

If the user wants fixes applied:


Critical Constraints

Skill frontmatter

user-invocable: true argument-hint: [optional: specific table or concern] allowed-tools: Read, Write, Bash, Glob, Grep, AskUserQuestion, TaskCreate, TaskUpdate, TaskList, Agent model: opus