SkillAgentSearch skills...

audit-ppmplugin

Statically audit a built `.ppmplugin` before wrap testing. Checks archive layout, manifest compatibility, bundle consistency, Android DEX integrity and SDK leakage, iOS framework structure, native source-to-receiver alignment, and the PCF composite-key/sendAsync transport contract.

Install / Use

npx skills add microsoft/power-platform-skills --skill audit-ppmplugin

Installs into whichever agent you are using.

About this skill
📄

SKILL.md

Installable skill definition

Quality Score

85/100

Category

Legal

Supported Platforms

Universal

Our assessment of audit-ppmplugin

audit-ppmplugin scores 85/100 on our quality scale, 116th of 207 Legal skills we index.

Its SKILL.md is 22 KB long, well organised into 10 sections with 1 code example: a thorough specification that gives an agent plenty to work with.

It has 919 GitHub stars, a meaningful sign that others use it.

Substance
30/30
Structure
17/20
Description
15/15
Adoption
13/20
Freshness
15/15

Maintenance, license and trust

  • The repository was last updated 12 days ago, so audit-ppmplugin is actively maintained.
  • It is released under the MIT license, a permissive license that allows use, modification and commercial use with attribution.
  • Its trust signals score 100/100, with no cautions. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.

audit-ppmplugin compared with similar skills

All 4 of these similar skills score higher than audit-ppmplugin; compare them before choosing.

SkillScoreStarsUpdatedFormat
audit-ppmplugin (this skill)by microsoft8591912d agoSKILL.md
algorithmic-artby anthropics100177.9k14d agoSKILL.md
pptxby anthropics100177.9k14d agoSKILL.md
designby nextlevelbuilder100130.2k15d agoSKILL.md
ui-ux-pro-maxby nextlevelbuilder100130.2k15d agoSKILL.md

Frequently asked questions

How do I install audit-ppmplugin?
Run npx skills add microsoft/power-platform-skills --skill audit-ppmplugin. The install tabs above show the steps for each supported agent.
Which AI agents does audit-ppmplugin work with?
It is written for Universal, as a SKILL.md file. Other agents that read the same format can often use it too.
Is audit-ppmplugin safe to use?
It is MIT-licensed and scores 100/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
Is audit-ppmplugin still maintained?
The repository was last updated 12 days ago, so audit-ppmplugin is actively maintained.

name: audit-ppmplugin description: "Statically audit a built .ppmplugin before wrap testing. Checks archive layout, manifest compatibility, bundle consistency, Android DEX integrity and SDK leakage, iOS framework structure, native source-to-receiver alignment, and the PCF composite-key/sendAsync transport contract. Reports CRITICAL, WARNING, and INFO findings with fixes routed to the owning stage; never modifies the archive. Requires jar; dexdump or strings improves DEX inspection. Run after /assemble-ppmplugin or standalone on an existing bundle." allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion, Skill model: sonnet

/audit-ppmplugin

The verification gate. /assemble-ppmplugin proves the bundle is well-formed; this skill proves it will actually load and dispatch on the wrap runtime. Most .ppmplugin failures are silent — the bundle uploads fine, then a method returns native module 'X' not loaded, the runtime cannot instantiate the package class, or the upload is rejected with 0x80040265 (canonical-prefix violation). Those cost a full wrap-build round-trip to discover. This skill surfaces them in seconds, on disk.

It is read-only on the bundle — it unzips to a temp dir for inspection and never mutates the .ppmplugin. Fixes route back upstream (/generate-ppmplugin-manifest for manifest issues, the build skills for binary issues), then re-assemble + re-audit.

Read shared/ppmplugin-format.md §1 (layout), §3 (canonical-prefix), §4 (validator rules), §5 (Android DEX requirements) — this skill enforces all four against the built artifact.

What this skill does NOT do

  • Does not build, zip, or author anything — it inspects a finished .ppmplugin. Fixes are made upstream and re-assembled.
  • Does not patch the zip in place — a bundle is an immutable deliverable; mutating it would desync it from the staged sources. It points at the upstream skill instead.
  • Does not upload to Dataverse / wire into a canvas app (Stage 3 — deferred). "READY TO UPLOAD" means passes local verification, not uploaded.

Step 1 — Read shared docs + resolve the artifact + prereqs

  1. Read shared/shared-instructions.md and shared/ppmplugin-format.md.
  2. Resolve the bundle to audit:
    • If the user passed a path, use it. If it's a directory, look for <dir>/*.ppmplugin (prompt if multiple).
    • Else default to the assemble output: the single ppmplugin/<name>.ppmplugin at the repo root (the most-recently-built if several).
    • If none found, STOP with NEEDS_CONTEXT: no .ppmplugin to audit — run /assemble-ppmplugin first, or pass a path.
  3. Prereq block (shared-instructions §9.2). Policy: resolve, don't punt (§1.5) — locate a tool by path before failing:

| Check | Verify | If missing | |---|---|---| | jar (JDK) | jar --version — used to list + extract the bundle | STOP BLOCKED: jar not found — install a JDK (JDK 17) | | DEX scanner | dexdump (preferred — locate in Android SDK build-tools/*/dexdump like /build-android-binary resolves $D8); else strings (mac/linux ships it) | If neither: the DEX string checks degrade to WARNING ("DEX scan skipped — no dexdump/strings"); structural checks (magic, size) still run. On Windows without either, note the gap. |

  1. Extract the bundle to a fresh temp dir for inspection (read-only on the original):
    work=$(mktemp -d); ( cd "$work" && jar xf "<abs path to .ppmplugin>" )
    
    $work = New-Item -ItemType Directory -Force (Join-Path $env:TEMP "ppm-audit"); Push-Location $work; jar xf "<abs path>"; Pop-Location
    

Each subsequent step appends findings to a running list as [SEVERITY] <check-id>: <result>. Don't stop at the first CRITICAL — collect everything so the user fixes in one pass. Severities: CRITICAL (will fail at upload or on device — blocks), WARNING (likely-wrong, may still work), INFO (style / advisory).


Step 2 — Category A: Zip structure

Listing comes from jar tf "<.ppmplugin>". The bundle is native-only (format §1): manifest.json at root + only the declared android/ / ios/ slices.

| Check | Asserts | Severity | |---|---|---| | zip-manifest-at-root | manifest.json is at the archive root | CRITICAL | | zip-only-native-slices | top-level entries ⊆ { manifest.json, android/, ios/ } | CRITICAL | | zip-no-ts-js-layer | NO src/, *.ts, *.tsx, *.js, extension.js, extension.hbc anywhere — the bundle ships no TS/JS layer | CRITICAL | | zip-no-build-dirs | NO android-build/, ios-build/, node_modules/, dist/, build/ leaked in | CRITICAL | | zip-no-stray-files | NO META-INF/, .DS_Store, dotfiles, nested *.zip/*.ppmplugin | WARNING | | zip-reasonable-size | bundle < 50 MB (a larger one usually means React or node_modules got swept in) | WARNING |

A src/ tree, an extension.js/.hbc, or any *.ts is SDK-era / JS-layer leakage — flag CRITICAL and point at format §6 (the bundle is native binaries only).


Step 3 — Category B: Manifest schema, validator rules + field leakage

Read the extracted manifest.json. Re-run every ppmplugin-format §4 rule against the built artifact (defense in depth — the manifest may have been hand-edited after /generate-ppmplugin-manifest):

| Check | Asserts | Severity | |---|---|---| | mf-name-shape | name matches ^[a-z0-9][a-z0-9-]{0,63}$ | CRITICAL | | mf-canonical-prefix | each receivers[].nativeModule starts with the canonical prefix of name (split on -/_, PascalCase each segment, join — §3) (Ordinal, case-sensitive) | CRITICAL | | mf-reserved-prefix | no nativeModule starts with a reserved prefix (case-insensitive list in §4: Microsoft, MS, Intune, Wrap, Pcf, PowerApps, …) | CRITICAL | | mf-reserved-exact-known | no nativeModule matches the locally checked incompatible-name subset (§4: DeviceInfo, AuthenticationHelper, NetworkClient, DataverseOfflineProvider, IntuneMAM). Non-exhaustive; fix = rename getName() to a non-reserved form (add Module suffix or a vendor prefix) and re-derive | CRITICAL | | mf-nativeModule-generic-noun | nativeModule does NOT match the generic-platform-noun heuristic ^(Device\|Network\|File\|Audio\|Camera\|Sensor\|Location\|Storage\|Notification\|Bluetooth\|Wifi\|Media\|Photo\|Contact\|Calendar\|Battery) — these bare names are both denylist-prone and collision-prone in the shared NativeModules namespace. (Heuristic, not the real list — DeviceInfo returned READY locally yet was server-rejected; this would have warned.) | WARNING | | mf-methods | every receivers[].methods is present, non-empty, ≤32, each matches ^[a-zA-Z_$][a-zA-Z0-9_$]{0,127}$ | CRITICAL | | mf-receiver-name | each receivers[].name matches the JS-identifier regex | CRITICAL | | mf-version-present | version is a non-empty string | WARNING | | mf-no-sdk-fields | NO entrypoints.js, entrypoints.ts, extension.js, extension.hbc, extensionClassName, jsLayer fields in the manifest. Our native-only policy, NOT a server gate — the wrap injector treats entrypoints.js as optional and accepts it; we flag it so the native-only bundle stays unambiguous. WARNING, not a block. | WARNING |

Note in the report that only a known subset of incompatible exact names is checked locally (mf-reserved-exact-known), so a clean local result does not guarantee the upload service will accept the name.


Step 4 — Category C: Manifest ↔ bundle consistency

The declared entrypoints must match the binaries actually in the zip — exactly. (/assemble-ppmplugin reconciles this at build time; auditing the finished bundle catches a hand-edited manifest or a hand-zipped bundle.)

| Check | Asserts | Severity | |---|---|---| | consistency-has-native | at least one of entrypoints.android / entrypoints.ios is declared (a manifest-only bundle has nothing to load) | CRITICAL | | consistency-android-dex | if entrypoints.android declared → android/<entrypoints.android.dex> exists in the zip | CRITICAL | | consistency-ios-framework | if entrypoints.ios declared → ios/<entrypoints.ios.framework>.framework/ exists in the zip (flat framework — see ios-flat-framework-not-xcframework in Category E) | CRITICAL | | consistency-no-orphan | every android/ or ios/ slice in the zip is backed by a matching declared entrypoint (no undeclared binary the runtime won't route) | WARNING | | consistency-packageClass-shape | entrypoints.android.packageClass is a fully-qualified Java class name (^([a-z][a-z0-9_]*\.)+[A-Z][A-Za-z0-9_]*$) | CRITICAL | | dispatch-composite-key | the composite routing key <name>/<receivers[].name> is well-formed (both segments present, JS-identifier-safe suffix) — this is what a dispatcher PCF binds as ReceiverKey to reach NativeModules.<nativeModule>.<method> (format §2 — Runtime dispatch contract) | WARNING |


Step 5 — Category D: DEX integrity + SDK-leakage byte-scan (Android)

Only if entrypoints.android is declared. The DEX is loaded via DexClassLoader (format §5); these confirm it's loadable and free of SDK-era leakage. Use dexdump -l plain <dex> if available, else strings <dex> — both expose the string/type pool the checks scan.

| Check | Asserts | Severity | |---|---|---| | dex-magic | first bytes are dex\n03[5-9] (valid DEX) | CRITICAL | | dex-has-packageClass | the entrypoints.android.packageClass type descriptor (Lcom/.../<Pascal>Package;) is in the DEX — otherwise runtime class loading fails | CRITICAL | | dex-has-module-class | the module class descriptor is in the DEX | CRITICAL | | dex-no-INativeExtension | NO INativeExtension, INativeOperation, INativeExtensionContext strings — SDK-era symbols in a native-only bundle. Cleanliness policy, not a server gate: the player ignores them (an old-pattern plugin still dispatches if its receivers[] map to real native modules), so surface it but don't block. | WARNING | | dex-no-jslayer-loader | NO HermesBytecodeLoader, WrapPluginJsLayerLoader strings — a JS-layer loader has no place in a native-only DEX (same: flagged, not blocking). | WARNING | | dex-no-sendAsync | NO sendAsync symbol — possible SDK transport leak | WARNING |

These SDK-leakage checks are the heart of the native-only contract: a clean compile can still pull these symbols in transitively. Scanning the built DEX is the only place they're catchable.


Step 6 — Category E: iOS framework structure (the wrap-CI gates)

Only if entrypoints.ios is declared. The CRITICAL set is exactly what the runtime's dlopen loading and wrap signing pipeline require. The umbrella header + module map are build hygiene only (the loader dlopens the binary; it never imports the module), so they're INFO — do not block a valid bundle on their absence.

| Check | Asserts | Severity | |---|---|---| | ios-flat-framework-not-xcframework | the bundle ships ios/<framework>.framework/ — NOT ios/<framework>.xcframework/. If an .xcframework is present, FAIL with the wrap-CI error verbatim ("Framework '<Name>.framework' not found in plugin") and the fix: rebuild with /build-ios-binary (device slice, flat framework — drop -create-xcframework). | CRITICAL | | ios-binary-present | ios/<framework>.framework/<framework> (the Mach-O binary, named exactly <framework>) exists — the loader dlopens Frameworks/<framework>.framework/<framework>, so a missing or differently-named binary won't load | CRITICAL | | ios-framework-has-infoplist | `ios/<framework>.framew

Truncated for display — read the full file on GitHub.

Related Skills

View on GitHub
GitHub Stars919
CategoryLegal
Updated12d ago
Forks186

Languages

JavaScript

Trust signals

100/100

From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.

No cautions