Activates and provisions a Power Pages website in a Power Platform environment via the Power Platform REST API. Use when the user wants to activate, provision, turn on, or enable a Power Pages website or portal.
日本語の概要は準備中です。原文の説明を表示しています。
Audits existing table permissions on a Power Pages site by analyzing them against site code and Dataverse metadata, then GRADES the configuration with deterministic 1–5 scores across 3 security/usability/correctness categories (9 dimensions). Detection is anchored by a recurring-issue catalog so findings and severities stay consistent run-to-run. Generates an HTML report with an at-a-glance verdict and per-category scores in the Overview plus a single list of **major/minor** issues (each major issue labelled permissions-only or upstream-caused with a short justification), and suggested fixes. Use when the user wants to review, verify, check, score, or grade table permissions for security issues.
インストールする前に、エージェントに与えられる指示の中身を確認できます。
Plugin check: Run
node "${PLUGIN_ROOT}/scripts/check-version.js"— if it outputs a message, show it to the user before proceeding.
Audit existing table permissions on a Power Pages code site. Analyze permissions against the site code and Dataverse metadata, then generate a visual HTML report with deterministic 1–5 category scores, an advisory verdict, findings, reasoning, and suggested fixes.
The skill has two parts:
$expand gaps, broken parent chains, over-provisioned grants) and emits each as a major/minor issue with concrete evidence and a fix.Golden rule of scoring: you only ever find and label issues (
major/minor). You do NOT invent the numeric score. The 1–5 numbers come from a fixed formula applied to the issue counts (see Appendix A). Same issue set ⇒ same score.
.powerpages-site folder and table permissions existImportant: Do NOT ask the user questions during analysis. Autonomously gather all data, then present findings.
At the very start of Step 1, before site verification, initialize local run state:
node "${PLUGIN_ROOT}/scripts/emit-audit-permissions-telemetry.js" --action start
Parse the JSON stdout and retain its auditRunId as <AUDIT_RUN_ID> for this invocation. The command does not emit a telemetry event; the existing generic skill invocation event is the start signal. The local run state provides the opaque random ID and start time needed for completion telemetry. Never derive the ID from, or replace it with, a site, environment, tenant, user, path, or report value.
Telemetry is best-effort and must never block the audit. If the initialization command fails or returns no auditRunId, continue the audit without completion telemetry. Do not invent an id and do not search local state for the latest run.
On every controlled terminal failure after a run id was returned, close the run before stopping:
node "${PLUGIN_ROOT}/scripts/emit-audit-permissions-telemetry.js" \
--action complete \
--runId "<AUDIT_RUN_ID>" \
--outcome failure \
--failureStage "<FAILURE_STAGE>"
Use exactly one of these fixed failure stages:
site_verificationconfiguration_gatheringschema_validationrelationship_discoveryaudit_checksscoringroot_cause_attributionreport_renderingreport_validationmetrics_validationunknown_controlled_failureDo not pass error messages, paths, table names, finding content, or other user/site data to the telemetry script. A telemetry completion failure does not change the audit outcome and must not trigger a second completion attempt.
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, site code; run validator; build preflight |
| 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 |
| Score configuration | Scoring configuration | Detect/label by the 3 categories via the recurring-issue catalog, consolidate to root causes, compute 1–5 scores |
| Root-cause attribution | Attributing root causes | Label every major issue permissions-only or upstream-caused in a single pass |
| Generate report | Generating report | Create and validate the HTML report and display it 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.
Use Glob to find:
**/powerpages.config.json — identifies the project root**/.powerpages-site/table-permissions/*.tablepermission.yml — existing permissionsIf 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.
Before stopping because .powerpages-site is absent, close completion telemetry with failureStage: site_verification as described above.
Gather all local evidence in one bounded pass before making judgments:
.datamodel-manifest.json in grouped operations.planClaims; do not reopen or re-extract the plan later.audit-evidence.json. Steps 4–6 consume only this ledger plus any gated Dataverse results.Do not rescan source code or the plan to gain confidence. A second read is allowed only when a specific ledger field is missing and the exact file/region needed is known. Record the added evidence once, then continue. Never perform multiple searches for differently worded versions of the same plan claim.
Read all files matching **/.powerpages-site/web-roles/*.yml. Keys may carry a publisher prefix — try mspp_<key>, then adx_<key>, then the bare <key> (first found wins). Extract id, name, anonymoususersrole, authenticatedusersrole from each. If YAML fails to parse or its root isn't an object, record a parse error (feeds preflight IS).
Read all files matching **/.powerpages-site/table-permissions/*.tablepermission.yml. Using the same prefix rule, for each permission extract:
entityname (permission name)entitylogicalname (table)scope (numeric code) → map to a label: 756150000 = Global, 756150001 = Contact, 756150002 = Account, 756150003 = Parent, 756150004 = Self; anything else → Unknown(<code>); missing → scope -1, label Unknownread, create, write, delete, append, appendto (boolean flags)adx_entitypermission_webrole (array of web role UUIDs) → resolve each GUID to a role name via §2.1; unresolved → mark <unresolved:…>contactrelationship, accountrelationship (if Contact/Account scope)parententitypermission, parentrelationship (if parent scope)Record a parse error on failure (feeds preflight IS).
In the single source pass, search the site source code for:
/_api/)@odata.bind)uploadFileColumn, uploadFile, upload*Photo, upload*Image)$expand usage ($expand, buildExpandClause, ExpandOption)Also check for .datamodel-manifest.json in the project root for the authoritative table list. It has { environmentUrl?, tables: [{ logicalName, displayName, status?, columns: [{ logicalName, type }] }] }. If missing or tables isn't an array, treat the data model as absent.
Additionally scan Web API artifacts (used by the root-cause attribution in Step 6 — best-effort):
src/shared/powerPagesApi.ts(x) or src/lib/powerPagesApi.ts exists.src/shared/services/** (.ts/.tsx/.js/.jsx): record which data-model table logical names and which HTTP methods (GET/POST/PATCH/PUT/DELETE) appear in each.$select=* (or a $select whose value is *) in any scanned source file, together with its file path and the table being queried.src/types/**: extract property names of the first interface/type literal; infer the table from filename/content when possible..powerpages-site/site-settings/ (files named Webapi-* / Webapi/*): parse name + value. Split name on /: Webapi/<entitySet>/enabled → kind enabled; .../fields → kind fields; Webapi/error → error; else other. webapiEnabledEntitySets = entity sets whose enabled value is exactly "true". For every fields setting also record isWildcard — true when the trimmed value is *, or when any comma-separated entry is *. A list that names column logical names is NOT a wildcard, however long it is.Also scan authentication/registration site settings in .powerpages-site/site-settings/ regardless of filename. Parse name + value for settings under Authentication/Registration/, including Enabled, OpenRegistrationEnabled, ExternalLoginEnabled, and InvitationEnabled. Also parse each external provider's Authentication/<Type>/<Provider>/RegistrationEnabled; a configured provider (any settings under Authentication/<Type>/<Provider>/) without that setting counts as true, the server default. Compare names case-insensitively and treat a value as enabled only when its trimmed value is case-insensitively "true". Record missing settings as unknown, not false.
Build an audience reachability profile before judging permissions:
authenticatedusersrole: true is automatically assigned to every signed-in site user; it is a broad baseline role, not proof of staff, employee, teacher, parent, or other trusted-persona membership.OpenRegistrationEnabled is true and either local registration is on (Authentication/Registration/Enabled is true) or ExternalLoginEnabled is not false (missing counts as true, the server default) and at least one configured external provider's RegistrationEnabled is true. External-only sites often omit Authentication/Registration/Enabled. With open registration, any visitor can create an account and enter that broad Authenticated Users audience.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).
Also build a capability matrix before judging missing access. One row per authorization-relevant capability:
persona | route/component | reachability | table | operation | current grant | plan claim | evidence
Classify reachability mechanically:
reachable — the page/component is registered in the deployed router or invoked by another reachable component, and no explicit feature flag or authorization gate disables it for that persona.disabled — an explicit false feature flag, server-side authorization gate, build exclusion, or unreachable/unregistered component prevents the capability from running.ambiguous — code exists but neither reachability nor exclusion can be established.A UI condition based on the signed-in user's own data (for example an isAccountAdmin field or a role flag on their profile record) never makes a capability disabled — it identifies the persona that uses it. Plan or comment wording such as "deferred", "future phase" or "not yet supported" never makes a routed capability disabled or ambiguous.
Use this evidence precedence everywhere in the audit: reachable implementation > explicit runtime gate/build exclusion > implementation plan > comments/names. A plan cannot make a routed, callable feature hypothetical merely by calling its persona "back-office." Conversely, dead, unregistered, explicitly disabled, or server-gated code does not establish a required portal permission. If reachability is ambiguous and conflicts with the plan, emit at most one minor internal-consistency issue for the conflict; do not presume a broken flow.
After the source pass, do not reopen routes, pages, services, or the permission plan during scoring. Reachability and plan claims are frozen ledger facts, not questions to reconsider in Step 5.
Run the shared validator against the existing site:
node "${PLUGIN_ROOT}/scripts/validate-permissions-schema.js" --projectRoot "<PROJECT_ROOT>"
Parse the JSON output and carry the findings into the audit as major/minor issues. Map:
error findings → major issuewarning findings → major issue (unless purely forward-looking/cosmetic → minor)info findings → minor issueThese findings should be included in the final report even if the later code/Dataverse analysis also finds additional issues. Exception: drop the validator's "no associated web roles" warning for a Parent-scope child permission whose parent chain is valid (see check B).
If the validator reports wildcard field access in a Webapi/<table>/fields setting, add this finding as a major security-posture issue (one per affected setting):
Unsupported wildcard Web API fields for <table>${PLUGIN_ROOT}/references/webapi-field-allowlist.md: LogicalNames for ordinary columns, _<LogicalName>_value for lookup reads, and exact Navigation Properties used by @odata.bindAfter Step 3.1 determines the environment URL, rerun the shared validator with live relationship verification only when the Step 3 discovery gate is open. Merge any additional findings:
node "${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 that need relationship evidence. Do not require it in CI or other offline contexts, and do not run it for Global-only/read-only designs with no relationship-dependent reachable flow.
Purely mechanical cross-checks over the parsed YAML + data model + Web API artifacts. Emit ALL that apply. Number IDs per prefix in encounter order (IS1, IS2, DM1, …). These are ground truth: carry them into the right dimension's issue list in Step 5 and never drop or downgrade them.
| Condition | id prefix | dimension | severity |
|---|---|---|---|
| Web-role YAML parse error | IS | internal-consistency | major |
| Table-permission YAML parse error | IS | internal-consistency | major |
| Permission references a web-role GUID not defined in any role | IS | internal-consistency | major |
| Web role has a blank id | IS | internal-consistency | major |
Permission has a blank entitylogicalname | DM | data-model-alignment | major |
| Permission grants no privileges (all 6 CRUD bits false) | TC | table-coverage | major |
Permission has no scope (scope < 0) | SC | scope-correctness | major |
Permission scope code unrecognized (Unknown(code)) | SC | scope-correctness | minor |
| Permission grants write/create/delete to an Anonymous role | AH | anonymous-access-hygiene | major |
| Permission targets a table not in the data model (only when a valid data-model manifest exists) | DM | data-model-alignment | major |
| Web API site-setting YAML parse error | IS | internal-consistency | major |
-enabled entity set with no matching -fields whitelist | IS | internal-consistency | major |
-fields whitelist with no matching -enabled setting | IS | internal-consistency | major |
| Type file declaring 1–6 properties absent from the data model (only when a valid data-model manifest exists) | DM | data-model-alignment | minor |
<OUTPUT_DIR> is <PROJECT_ROOT>/docs/ when working in a website (a project root with powerpages.config.json); otherwise it is the system temp directory. The temporary files and the final report all go there.
Before running judgment-based checks, write a temporary <OUTPUT_DIR>/audit-evidence.json containing the normalized facts gathered so far: roles, permissions, data-model tables/columns, service operations, route/component reachability, capability matrix, lookup/expand usage, client-side row filters, implementation-plan claims, authentication reachability, and preflight issues. Sort tables, permissions, routes, and capabilities by stable identifiers.
Steps 4–6 MUST reason from this ledger instead of rescanning and selectively rediscovering evidence. If live Dataverse discovery adds facts in Step 3, append those facts and rewrite the ledger once before Step 4. Do not change a reachability classification during scoring unless new evidence is added to the ledger. Delete audit-evidence.json after the final HTML report passes validation.
Use deterministic Node.js scripts for all Dataverse API calls. These scripts handle auth token acquisition, HTTP requests, and JSON parsing consistently.
Before calling pac env who or any Dataverse query script, compute needsLiveDiscovery. It is true only when at least one reachable capability has one of these unresolved dependencies:
$expand or related-table access whose target cannot be resolved from local metadataIt is false when none of those dependencies apply — for example, Global-only/read-only flows, explicit Administrator-only CRUD unused by reachable site code, and cases fully resolved by the manifest and local source. When false, skip all of Step 3, record liveDiscovery: "not-needed" in the evidence ledger, and continue to Step 4. When true, first confirm the active Dataverse environment URL matches the manifest environment URL; if it does not, skip live queries and record the mismatch instead of querying the wrong environment.
pac env who
Extract the Environment URL (e.g., https://org12345.crm.dynamics.com) and use it as <envUrl> in subsequent script calls.
For each table that has permissions with create or write enabled, use the lookup query script:
node "${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:
appendto on the source table.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"] }]
order_item → [{ column: "cr4fc_orderid", targets: ["cr4fc_order"] }]cr4fc_order → [{ sourceTable: "order_item", column: "cr4fc_orderid" }]Both maps are used in Sections H and H2:
appendto: true — it links TO other records (checked via the source map)targets needs append: true — other records link TO it (checked via the reverse target map)For tables with parent-scope permissions, verify the relationship names using the relationship query script:
node "${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.
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.
Use per-table task tracking to systematically run every audit check. These code- and Dataverse-grounded checks are the primary issue detectors — they catch concrete problems (missing CRUD, append/appendto, $expand gaps, broken parent chains, over-provisioned grants) that rubric judgment alone would miss, which is what makes the issue set richer and the score more faithful. Each failed check emits one provisional candidate (title, responsible table, evidence, and a suggested fix); Step 5 assigns its final major/minor severity and dimension. A check that passes produces no candidate. Where a check says to search the service code, look the fact up in the evidence ledger (Step 2.6); search the source only when the ledger lacks it.
First, build a combined list of all tables to audit from two sources:
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?"
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.
For each table, mark its task in_progress and run through the following checks in order. For every issue, note the specific evidence (file path, permission name, code pattern) that supports it. Skip checks that don't apply to this table.
Do not finalize severity or dimension in Step 4. Each check below carries a provisional audit tier (critical, warning, or info) and emits a factual candidate as { sourceId, auditTier, title, table, reasoning, fix, evidence, reachability? }. A check that passes yields no candidate. Step 5 is the only place that assigns the final major|minor severity and canonical dimension, using fixed catalog rules before generic policy. This prevents whichever audit path runs first from locking in a different score.
A. Permission Existence
Does this table have a table permission?
criticalMissing permission for <table>infoUnused permission for <table>pass, proceed to remaining checksB. Web Role Association
Does the permission have web role(s) assigned?
adx_entitypermission_webrole — if empty or missing → finding:
warningPermission <name> has no web role association756150003) child permission may leave its roles empty — it inherits role association through the parent chain. Raise no finding when its parententitypermission resolves to an existing permission; a broken chain is reported by check I.passC. Scope Appropriateness
Is the scope the least-privileged option that fits?
getCurrentContactId, _contactid_value, contactid) and account-scoped filters (_accountid_value, parentcustomerid)authenticatedusersrole: true, so every registered account receives it; (3) that role is attached to this table's permission with read: true and Global scope; and (4) the table carries per-person, roster, grade, contact, or internal operational data, or the site intent limits it to staff/teachers/another purpose-specific persona → finding:
criticalSelf-registered users can read all <table> records756150000) with write or delete enabled → finding:
warningGlobal scope with write/delete on <table>read, pass ONLY when the table and exposed columns are explicitly public/non-sensitive and every assigned role is an intended audience. Otherwise evaluate it under the mandatory trigger above and recurring issues C3/C4; read-only access can still be a major confidentiality exposure.warningScope could be narrower for <table>passD. Read Permission
Is read correctly set?
/_api/<entity_set>, list/get functions (list<TableName>, get<TableName>)read: false → finding:
criticalMissing read permission for <table>read: trueread: true and code reads → passE. Create Permission
Is create correctly set?
method: 'POST'), create functions (create<TableName>)create: false → finding:
criticalMissing create permission for <table>create: truecreate: true but no create patterns in code → finding:
infoCreate enabled but not used for <table>create if not neededpassF. Write Permission
Is write correctly set?
method: 'PATCH'), update functions (update<TableName>), file upload patterns (uploadFileColumn, uploadFile, upload*Photo, upload*Image, upload*File)write: false → finding:
criticalMissing write permission for <table>write: truewrite: false → finding:
warningFile upload detected but write is disabled on <table>write: truewrite: true but read: false → finding:
warningWrite enabled without read on <table>read: truewrite: true but no write patterns in code → finding:
infoWrite enabled but not used for <table>write if not neededpassG. Delete Permission
Is delete correctly set?
method: 'DELETE'), delete functions (delete<TableName>)delete: false → finding:
criticalMissing delete permission for <table>delete: truedelete: true but no delete patterns in code → finding:
infoDelete enabled but not used for <table>delete if not neededpassH. 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.
create or write permissions?@odata.bind references to this table's entity set (e.g., /<entity_set>()append: false → finding:
criticalMissing append on <table><source_table> has lookup column <column> targeting this table and sets it during create/write. The target table needs append permission so records can be linked to it. Users will see "You don't have permission to associate or disassociate"append: trueappend: true and justified → passappend: true but this table does NOT appear in the reverse target map and no code references it as a lookup target → finding:
infoAppend enabled but not needed on <table>append if not neededH2. 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.
@odata.bind patterns in create/update calls for this tablecreate or write enabled, but appendto: false → finding:
criticalMissing appendto on <table><column> targeting <target_table> during create/write, which requires appendto permission. Users will see "You don't have permission to associate or disassociate"appendto: trueappendto: true and justified → passI. Parent Chain Integrity
If the permission has Parent scope (756150003):
parententitypermission references a valid permission ID that existsparentrelationship is a valid Dataverse relationship (if API available, using Step 3.3 results)criticalBroken parent chain for <permission>passJ. $expand Related Table Coverage
Is this table fetched via $expand on another table's query?
$expand analysis from Step 2.3 (search site source code for $expand, buildExpandClause, ExpandOption)read: true for the same web role → finding:
criticalMissing read permission for expanded table <table>$expand on <parent_table> in <service_file>, but has no read permission. Power Pages enforces table permissions on every entity in the query.read: true for the same web role. For collection-valued expansions (one-to-many), use Parent scope with the relationship name. For single-valued expansions (lookups to reference data), use Global scope with read-only access.passK. Record Findings & Complete
After all checks, mark the table's task as completed via TaskUpdate.
After all per-table audits are complete, run these cross-table checks:
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$expand usage, verify the expanded table has read: trueUse TaskList to review all completed audits, then mark the "Compile audit findings" task as in_progress and proceed to Step 5.
This is the scoring overlay. You already have the evidence: the audit issues (Step 4) and the deterministic preflight issues (Step 2.5). Now turn them into a category-organized issue set and a reproducible 1–5 score. The audit checks supply most of the detected issues; the recurring-issue catalog below plus the grader severity policy (Appendix C) finalize major/minor; the formula (Appendix A) decides the number. Detection and scoring are organized by CATEGORY (each category owns 3 dimensions — Appendix A / Appendix B).
Assess the 3 categories; within each, reason across its 3 dimensions together. Populate each issue from three sources:
RECURRING ISSUE CATALOG — DETECTION + SEVERITY (the primary lever for run-to-run consistency). Test EACH pattern against the artifacts; whenever a pattern's TRIGGER holds, raise exactly ONE issue for it in the stated dimension with the FIXED severity — on every run, no matter how obvious or minor it seems. Match on the described SHAPE, not on specific table/role names (names in parentheses are illustrations). Patterns are evaluated PER affected table-group and are NOT mutually exclusive: if one table hits a MAJOR pattern and a different table hits a similar MINOR pattern (e.g. C2 on a PII table AND C14 minor on a draft-content table), raise BOTH as separate issues — the ordering only resolves severity WITHIN one table-group, it never suppresses a finding on a different table.
AH row (Step 2.5); do not raise it again here.anonymous-access-hygiene · major.privilege-calibration; read-only → security-posture · major. This explicitly includes the baseline Authenticated Users role: when open registration lets any visitor acquire that role, treat it as a broad external audience, not as trusted staff. Evaluate and emit this pattern separately for each affected table; do not merge Student and Teacher (or other disjoint table exposures) into one issue.privilege-calibration · major only when the extra access reaches another audience's sensitive records (PII, confidential, financial); otherwise minor. C4 never applies to a case covered by C3, C9, C16, C17 or C18.intent-coverage · major (if the authoring is expected to be performed by the intentionally-omitted Administrators role, do NOT raise it — see the ADMINISTRATORS EXCEPTION below).table-coverage · major (if the only missing grant would be for the Administrators role, do NOT raise it — see the ADMINISTRATORS EXCEPTION).role-completeness · major (a missing Administrators role/persona is NOT a gap — see the ADMINISTRATORS EXCEPTION).role-completeness · minor.scope-correctness · minor.permissions-plan.html present, or only a generic/boilerplate plan ("Global is the broadest scope") → dim internal-consistency · minor.table-coverage · minor. Clearly backend/system tables with no permission are correct, so raise no issue.* column exposure — a Web API query with $select=*, or any other column list set to * → dim security-posture · major, one issue per affected table. A Webapi/<table>/fields wildcard is reported through the Step 2.4 validator finding instead. An explicitly enumerated column list is never this issue, however long.$filter/query predicate. Client filtering is not an authorization boundary. If bypass exposes sensitive or confidential content (PII, credentials/secrets, financial/health data, internal staff or agent notes, private case details, security data) → dim security-posture · major. If bypass exposes only non-sensitive content that is merely unpublished, draft, or workflow-incomplete → dim security-posture · minor. If Dataverse permissions, column security, a server-side endpoint, or a separate protected table enforces the separation, C14 does not trigger.reachable portal capability but lacks the persona web role and the permissions needed across that capability's tables/operations → emit exactly ONE consolidated dim role-completeness · major issue. Absorb the resulting missing-table and missing-operation symptoms into it. If the persona role exists and only one table is uncovered, use C6 table-coverage; if the table permission exists and only an operation bit is missing, use intent-coverage. disabled code does not trigger C15. ambiguous code conflicting with the plan produces only the minor internal-consistency issue described in Step 2.3.privilege-calibration · major. Any mutation by Anonymous or a customer/baseline role stays C1/C3 major.privilege-calibration · minor, listing every unused grant. Do not split it per table. A C16 unused delete stays a separate major issue.ADMINISTRATORS EXCEPTION (respect strongly). By design, a site should not create table permissions for the Administrators web role — admin authoring normally happens outside the portal (the model-driven app or Dataverse directly), so explicit Administrators CRUD permissions are unnecessary and add security noise. The Administrators web role does not bypass table permissions for portal data access. Therefore:
reachable site code performs the admin work — NEVER raise a missing-permission, table-coverage, role-completeness, or entirely-read-only issue solely because the Administrators role lacks table permissions. This includes an under-exposure gap (C5 read-only site, C6 missing grant, C7 missing persona) that exists only because the authoring/CRUD belongs to the intentionally-omitted Administrators role.reachable site code performs admin-only CRUD through the portal (for example a routed admin page calling the Web API) with no grant, the exception does not apply: raise C6 or C15 as for any other role.Administrators role and ONLY to under-exposure/usability gaps. It NEVER downgrades an over-exposure (security) finding — if Administrators (or any role) actually holds an over-broad grant, score it by the security rules as usual.NOVEL ISSUES (not covered by C1–C18): still detect them — do NOT restrict yourself to the catalog. Assign severity by the Appendix C security-first policy. A novel issue is NOT automatically minor: if it exposes data to the wrong audience, over-scopes access, or breaks a required flow, it is major.
Number LLM-derived issue ids per canonical dimension prefix (IC, PC, SC, RC, TC, AH, DM, IS, SP). Within each dimension, continue after the highest preflight id already used for that prefix (or start at 1 when there is none), so generated ids never collide with fixed preflight ids. The issue id prefix MUST match the issue's final dimension after consolidation. Record each issue as {id, dimension, severity, title, reasoning, fix} plus the evidence that supports it — the shape of a report finding (Step 7.2). Do not assign a per-dimension 1–5 score — scoring happens at the category level after consolidation.
Consolidate the full pooled issue list into distinct root causes, then classify each into exactly ONE category by dominant impact. The number of issues after this step equals the number of root causes.
dimension within the chosen category.role-completeness; existing persona role but missing table permission → table-coverage; existing role and table permission but missing CRUD/append bit → intent-coverage. Never choose among these dimensions by prose emphasis.reachable capabilities, implementation wins and the applicable C6/C7/C15 issue remains major; for disabled capabilities, the plan wins and no under-exposure issue is raised; for ambiguous capabilities, raise only the minor internal-consistency conflict.The consolidated issues are what feed scoring and all later phases. Re-bucket each under its (possibly reassigned) dimension. This single consolidated major/minor list is the one issue set for everything downstream — it becomes the report's FINDINGS_DATA (Step 7.2), the Overview's Major/Minor counts, and the input to the score. One issue set, one tally, so more issues ⇒ lower score.
Apply Appendix A exactly: one 1–5 score per category from its pooled issues (no overall score), the verdict from the major count, and issue counts { major, minor, total } over all consolidated issues.
Write a 2–3 sentence grader verdict: the top thing the config gets right, the top thing it gets wrong, and the verdict. State the verdict verbatim — "Safe to go" when there are zero major issues, "Needs revision" when there is at least one major issue. Minor issues never change it. Do NOT tell the user whether to deploy, release, or ship — the verdict summarises the findings; the decision is theirs. Prose only. Use it as the report's SUMMARY (Step 7.2).
If the data-model manifest is missing (SCORECARD_DATA is null), skip the verdict: write a 2–3 sentence summary that says this is a partial audit, and use neither verdict phrase.
Analyze all MAJOR issues in a SINGLE batched pass (after consolidation; skip minors). The site evidence is shared, so classify every major issue together in one reasoning step. For each, decide whether it is purely a permissions-design fault or is caused, wholly or partly, by an upstream artifact. Be conservative — default to permissions unless upstream evidence is clear (false upstream attribution is worse than missing one).
Root-cause values:
permissions — genuine permission-design issue; nothing upstream forces it. Fix it in the permission or web-role YAML.data-model — the manifest is missing a table/column or uses a wrong publisher prefix.webapi-code — site code (src/shared/services/*, src/types/*, pages) references tables/columns in a way that forces the permission shape or relies on a browser-side rule.webapi-settings — Webapi-*-enabled / -fields YAML forces the grant (or gap).mixed — an upstream cause AND a permissions-side mistake both contribute.For each major issue decide rootCause and write explanation; they become the finding's rootCause and rootCauseReason in Step 7.2.
explanation is shown to the user next to the major issue, so write one plain-language sentence that justifies the label: for permissions, say why the permission configuration alone is at fault; otherwise, name the specific upstream file or setting and what it forces.
powerpages.config.json exists): write to <PROJECT_ROOT>/docs/permissions-audit.htmlThis folder is the <OUTPUT_DIR> defined in Step 2.6.
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 */],
"SCORECARD_DATA": {/* grader overlay — see below; use null if scoring was skipped */}
}
SITE_NAME, AUDIT_DESC, SUMMARY, FINDINGS_DATA, INVENTORY_DATA are the required keys the render script validates. SCORECARD_DATA is an extra key the enhanced template consumes — always include it (object or null) so no placeholder is left unreplaced. In SCORECARD_DATA.reportPaths, list the absolute path to the HTML report — the Overview tab renders it so the user can open the report.
FINDINGS_DATA format — this is the single consolidated major/minor issue list from Step 5.2 (one entry per issue; it is exactly what the score is computed from). Use the canonical issue id from Step 5 (e.g. AH1). For major issues, copy rootCause and explanation from that issue's Step 6 entry; omit both fields for minor issues:
{
"id": "AH1",
"severity": "major",
"title": "Staff email/phone readable by Anonymous",
"table": "contoso_staff",
"dimension": "anonymous-access-hygiene",
"scope": "Global",
"permission": "Staff - Public Read",
"reasoning": "Staff email/phone are in the $select and the Webapi field whitelist, and Staff-Public-Read grants the Anonymous role Global read — public internet can read staff PII.",
"fix": "Restrict staff read to a purpose-specific role assigned only to verified users, or drop contoso_email/contoso_phone from the whitelist and $select.",
"details": "Staff-Public-Read.tablepermission.yml; Webapi-contoso_staff-fields.sitesetting.yml; src/shared/services/staffService.ts:85",
"rootCause": "mixed",
"rootCauseReason": "The anonymous Global read grant is a permission mistake, and the Web API fields setting also whitelists contoso_email and contoso_phone."
}
id: The canonical issue id from Step 5 — the same id used in Step 6.severity: major or minor — the only two tiers (never critical/warning/info/pass).table: The table logical name this issue relates to (or null for general issues)dimension: The grader dimension the issue rolls up to (via the Appendix C bridge)scope: The current scope if applicable (numeric code or friendly name), or nullpermission: The permission name if this issue is about an existing permission, or nullreasoning: Detailed explanation of why this is an issue — reference specific code files, line patterns, or Dataverse metadatafix: Actionable suggestion for how to resolve the issuedetails: Additional context like file references, column names, or relationship detailsrootCause: Major issues only — the Step 6 root cause (permissions, mixed, data-model, webapi-code, or webapi-settings)rootCauseReason: Major issues only — the Step 6 explanation, one sentence justifying the root causeINVENTORY_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
}
SCORECARD_DATA format (from Step 5; the report reads only these fields):
{
"categories": [
{ "name": "Over-Exposure (Security)", "score": 3.10 },
{ "name": "Under-Exposure (Usability & Coverage)", "score": 4.21 },
{ "name": "Correctness (Validity & Alignment)", "score": 3.48 }
],
"reportPaths": [
{ "label": "HTML report", "path": "<PROJECT_ROOT>/docs/permissions-audit.html" }
]
}
The report surfaces this data automatically. The Overview tab leads with the Audit Summary, then an Issues row (major and minor counts — the single problem tally, which is exactly what drives the score) and a Verdict section (Safe to go / Needs revision, computed from the major count in FINDINGS_DATA, plus the per-category 1–5 score bars), plus the report paths. Every issue is major or minor — there is no critical/warning/info/pass tier anywhere in the report. The Issues tab lists each major/minor issue with its evidence and fix; each major issue also shows a root-cause badge (Permissions only, Permissions + upstream, or Upstream: data model / site code / Web API settings) and the one-sentence justification.
Run the render script (it creates the output directory if needed):
node "${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, and use the same path in reportPaths.
Run the semantic validator on the rendered report:
node "${PLUGIN_ROOT}/skills/audit-permissions/scripts/validate-audit.js" \
--report "<OUTPUT_PATH>" \
--data "<DATA_JSON_PATH>" \
--auditRunId "<AUDIT_RUN_ID>"
When local run initialization returned no run id, omit --data and --auditRunId; report validation must still run.
Do not present or copy a report until validation succeeds. The validator reads FINDINGS_DATA, INVENTORY_DATA, and SCORECARD_DATA from the report and checks unique issue ids whose prefix matches the dimension, major/minor severities, non-empty title/reasoning/fix, a valid rootCause and rootCauseReason on every major finding (and none on minor findings), that the inventory is an array, the three category scores recomputed from the findings, that SUMMARY states the verdict matching the major count (and neither verdict phrase on a partial audit), and leftover placeholders. When the run id and data path are supplied, the same deterministic command revalidates the source data, derives the closed aggregate metrics, and records successful completion and elapsed duration.
If validation fails, delete the report you just rendered, fix the named problem in the data file, render again to the same path, and rerun the validator. Do not repeat evidence gathering to fix a validation error.
The validator reports telemetryStatus as recorded_success, recorded_failure (the report is valid but its source data could not be reconciled, so the run was recorded with failureStage: metrics_validation), disabled (telemetry is hard-disabled for this plugin, so nothing was recorded), failed, or not_requested. Telemetry is best-effort: none of recorded_failure, disabled, or failed invalidates the report. Do not retry with a reduced payload and do not expose report data in diagnostic output.
Delete the temporary audit-data.json and audit-evidence.json files only after report validation succeeds. The telemetry status does not affect cleanup.
Open the actual HTML output path in the user's default browser.
Reference:
${PLUGIN_ROOT}/references/skill-tracking-reference.md
Follow the skill tracking instructions in the reference to record this skill's usage. Use --skillName "AuditPermissions".
🚦 Gate (plan · audit-permissions:6.fix-offer): Offer to apply auto-fixes for major/minor issues. The report has already been written; declining here just leaves the HTML report in place — no Dataverse / filesystem mutation.
Trigger: Phase 8 has tallied findings and the report is saved. Why we ask: Tooling could silently invoke the table-permissions-architect agent — accept-by-default would write or mutate permission YAML against the user's intent. Cancel leaves: Nothing — the report stays at its saved path. No web-role / table-permission files written.
Present a summary to the user:
SCORECARD_DATA is non-null, show Safe to go or Needs revision plus the three category scores; otherwise state that this is a partial audit and omit both verdict phrases and scores.AskUserQuestion: "Would you like me to fix any of these issues? I can create or update table permissions to resolve the major issues."If the user wants fixes applied:
$expand read coverage — any finding that would result in a 403 Forbidden response from the Power Pages Web API): Spawn the table-permissions-architect agent using the Agent tool. Pass it a prompt that includes the specific tables, the required CRUD flags, scope recommendations, and relationship details from the findings. The agent will analyze the site, propose a permissions plan, and create the correct table permission YAML files after user approval. Example prompt: "Create table permissions for the following tables based on audit findings: <table1> needs Global scope with read:true; <table2> needs Parent scope under <parent_table> with read:true, create:true, append:true; <table3> needs appendto:true for lookups from <source_table>. The site project root is <PROJECT_ROOT>."/integrate-webapi so the Web API settings architect can address site-setting-level issues.Scoring is a pure function of the consolidated issues — reproduce it exactly.
| Category | Dimensions |
|---|---|
| Over-Exposure (Security) | privilege-calibration, anonymous-access-hygiene, security-posture |
| Under-Exposure (Usability & Coverage) | intent-coverage, table-coverage, role-completeness |
| Correctness (Validity & Alignment) | scope-correctness, data-model-alignment, internal-consistency |
For a set of issues, let nMajor and nMinor be the counts. Then:
load = 1.0 * nMajor + 0.25 * nMinor
penalty = tanh(0.2 * load) # tanh maps [0,∞) → [0,1); penalty(0)=0
score = 5 - 4 * penalty # 0 issues → 5.00 ; grows → 1.00
score = round(score * 100) / 100 # 2 decimals
Constants: MAJOR_LOAD = 1.0, MINOR_LOAD = 0.25, PENALTY_STEEPNESS = 0.2.
scoreFromIssues(pooled deduplicated issues of that category's dimensions). The category is the scoring unit — do NOT average per-dimension scores.Reference points (0.2 steepness): 0 issues → 5.00; 1 major → 4.21; 2 major → 3.48; 3 major → 2.85; 1 minor → 4.80; the first major always hurts most (diminishing returns).
The verdict is driven by severity, not by any score:
| Condition | Verdict | Meaning |
|---|---|---|
| 0 major issues | Safe to go | No major issues were identified. |
| ≥ 1 major issue | Needs revision | One or more major issues were identified; review and address them. |
Minor issues never change the verdict — they are still recorded and still lower their category score, but they are left to the user's judgement. State the verdict phrase verbatim in Step 5.4 and in the report.
The verdict is advisory. It summarises what the audit found; it is not an approval or a release gate, and the decision to act on it rests with the human reviewer. Never instruct the user to deploy or not deploy.
Use each dimension's definition to decide where an issue belongs. Order is canonical for output.
1. Intent Coverage — IC · Under-Exposure — Does every authorization-relevant capability in the request map to at least one (role × table × op) grant? Usability rule: if the request implies managing/authoring records but NO role has ANY Create/Write/Delete on the relevant tables (read-only everywhere), that's a coverage FAILURE → a major issue.
2. Privilege Calibration — PC · Over-Exposure — Per (role, table, op): is each CRUD bit right-sized — not over- or under-granted?
3. Scope Correctness — SC · Correctness — Does scope (Global/Contact/Account/Parent/Self) match the data-ownership model? Per-user → Contact; public bulletin → Global; child records reached through a parent record → Parent; the user's own contact record → Self.
4. Role Completeness — RC · Under-Exposure — Are all personas implied by the request present as web roles? Spurious roles? Anonymous + Authenticated correct?
5. Table Coverage (Permissions) — TC · Under-Exposure — Every user-facing table has ≥1 permission; system/non-user-facing tables correctly excluded.
6. Anonymous Access Hygiene — AH · Over-Exposure — Anonymous gets minimal read only where the request implies public content; no anonymous write/create/delete unless justified.
7. Data Model Alignment — DM · Correctness — Every table referenced in a permission resolves to a real data-model table; no phantom/misnamed tables.
8. Internal Consistency — IS · Correctness — YAML valid; every web-role GUID in a permission resolves; permissions-plan.html aligns with the YAML.
9. Security Posture — SP · Over-Exposure — Overall least-privilege. No surprising broad grants on sensitive tables (Contact, system tables); no avoidable delete/write on shared records. If Web API field whitelists exist, check they don't broadly expose sensitive PII columns.
Only two severities exist: major (a real problem — breaks the site or leaks data, or a genuinely sub-optimal config that should be fixed) and minor (cosmetic / forward-looking / nice-to-have). There is no "info" tier in the grader (audit info findings map to minor).
Default stance — SECURITY FIRST / LEAST PRIVILEGE. Access should be granted only where the role's real-world use case requires it, scoped as narrowly as possible. Reaching or mutating records beyond that need is over-granted → major, even for an otherwise-"authorized" role.
Anchor examples. The recurring-issue catalog (C1–C18, Step 5.1) is the primary anchor set — label any case it covers exactly as the catalog says. Two further recurring cases:
Map each Step-4 finding into a dimension, then apply the severity policy above to set major/minor. (Audit critical almost always → major; audit info → minor; audit warning → apply the policy.)
| Audit check / finding | Grader dimension | Typical grader severity |
|---|---|---|
| A. Missing permission for referenced table | intent-coverage (+ table-coverage) | major |
| A. Unused permission (not referenced in code) | privilege-calibration (C18, one issue per role) | minor |
| B. Permission has no web role | role-completeness | major; none for a Parent-scope child with a valid parent chain |
| C. Global scope with write/delete | privilege-calibration / security-posture | major, except staff shared work queues (C16) and child-table parity (C17) |
| C. Scope could be narrower | scope-correctness | major if per-owner/PII, else minor |
| D. Missing read | intent-coverage | major |
| E. Missing create · F. Missing write · G. Missing delete | intent-coverage | major |
| E/F/G. Enabled-but-not-used | privilege-calibration (C18, one issue per role) | minor |
| F. File upload but write disabled | intent-coverage | major |
| F. Write without read | privilege-calibration | major |
| H. Missing append · H2. Missing appendto | privilege-calibration / intent-coverage | major |
| H. Append enabled but not needed | privilege-calibration (C18, one issue per role) | minor |
| I. Broken parent chain | internal-consistency | major |
J. Missing read on $expand table | intent-coverage | major |
| PII readable by anonymous | anonymous-access-hygiene / security-posture | major |
Schema-validator error / warning / info | internal-consistency (or the specific dimension) | error→major, warning→policy, info→minor |
query-table-lookups.js, query-table-relationships.js) for Dataverse API queries — never use inline PowerShell Invoke-RestMethod calls.getAuthToken().SCORECARD_DATA to null rather than fabricating a score — the report then shows a partial-audit notice instead of a verdict and scores, and SUMMARY states no verdict (Step 5.4).まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Activates and provisions a Power Pages website in a Power Platform environment via the Power Platform REST API. Use when the user wants to activate, provision, turn on, or enable a Power Pages website or portal.
日本語の概要は準備中です。原文の説明を表示しています。
Integrates Power Pages generative-AI summarization APIs (PREVIEW) into a Single Page Application (SPA) site — the Search Summary API and the Data Summarization API — on any record-detail or list page. Generates per-target service code (CSRF-handled) and AI site settings; delegates Web API settings, table permissions, and web roles to `/integrate-webapi` and `/create-webroles`. Use whenever a user wants AI/Copilot output that condenses Dataverse content on a Power Pages site — an AI summary, AI-generated overview or "key insights" across a record or list, a search-results summary, a case/incident summary, or recommendation-chip refinement — even when phrased as "AI-generated paragraph", "insights", or "overview". Do NOT use for: generative pages in model-driven apps (use the model-apps `genpage` skill), Copilot Studio agents/chatbots, summarizing documents or PDFs, Power BI dashboards, plain keyword search with no AI summary, or plain Dataverse CRUD (use `/integrate-webapi`).
日本語の概要は準備中です。原文の説明を表示しています。
Adds Azure DevOps connector to a Power Apps code app. Use when querying work items, creating bugs, managing pipelines, or making ADO API calls.
日本語の概要は準備中です。原文の説明を表示しています。
Internal implementation skill invoked by /add-native for camera, image picker, barcode scanner, QR scanner, and camera/gallery Dataverse artifact workflows.
日本語の概要は準備中です。原文の説明を表示しています。
Integrates Power Automate cloud flows into a Power Pages site. Lists available flows, suggests relevant ones based on intent, identifies scenarios and web roles, creates metadata files, and generates client-side code to call flows. Handles both new flow registration and adding already-registered flows to additional pages without re-creating metadata. Use when the user wants to add, connect, register, or link a Power Automate cloud flow to their site.
日本語の概要は準備中です。原文の説明を表示しています。
Adds any Power Platform connector to a Power Apps code app. Generic fallback for connectors not covered by a specific skill.
日本語の概要は準備中です。原文の説明を表示しています。