Files
aurora-wordpress-admin/bin/build-integrity-manifest.php
T
nixpanelandClaude Sonnet 5 cd66b70735 Add file-integrity check to protect the license gate from tampering
- New Integrity class checksums the files that implement/enforce the
  free/paid split (SubApps, Analytics, AnalyticsData, WhiteLabel, Shell,
  License itself, and the two bootstrap files) against a manifest
  generated at zip-build time. No manifest present (a dev checkout) is
  never treated as tampered.
- License::is_feature_available() checks Integrity::is_tampered() before
  checking the actual license, so a distributed copy with the check
  stripped/commented out re-locks every paid feature even with an
  otherwise-valid license, rather than the plugin trusting whatever code
  happens to still be there.
- bin/build-integrity-manifest.php generates the manifest against a
  staged zip-build copy (gitignored build artifact, same pattern as
  app/dist) — now a required step in the zip-build workflow.
- Verified live: untampered+unlicensed stays locked, untampered+licensed
  unlocks, one tampered file re-locks regardless of license status
  (tested via a temporary mu-plugin faking an active license, kept
  outside any protected file so it didn't itself trip the check).

This raises the bar against the realistic low-effort case (finding and
deleting/inverting the one license-check line) but can't stop someone who
rewrites a file's logic to never call the gate, or who edits this
integrity check itself — an inherent limit of protection living in plain,
editable PHP.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-07-03 20:44:18 -05:00

51 lines
1.8 KiB
PHP

<?php
/**
* Generates admin/src/Licensing/integrity-manifest.json — sha256 hashes of
* the files that implement the free/paid gate (see Integrity.php), used to
* detect tampering in distributed copies of the plugin.
*
* Run against the STAGING directory right before zipping, not against the
* git checkout — the manifest is a build artifact (same "regenerate, don't
* version" pattern as app/dist) and must never ship in a git checkout,
* since Integrity::is_tampered() treats "no manifest present" as "this is
* a dev checkout, don't lock anyone out."
*
* Usage: php bin/build-integrity-manifest.php /path/to/staging/aurora-admin
*/
$root = $argv[1] ?? null;
if (!$root || !is_dir($root)) {
fwrite(STDERR, "Usage: php build-integrity-manifest.php <path-to-staged-plugin-root>\n");
exit(1);
}
$root = rtrim($root, "/") . "/";
// Keep in sync with Integrity::is_tampered()'s callers — every file that
// implements or directly enforces the license gate.
$protected = [
"aurora-admin.php",
"admin/src/App/AuroraAdmin.php",
"admin/src/Licensing/License.php",
"admin/src/Licensing/Integrity.php",
"admin/src/Pages/SubApps.php",
"admin/src/Analytics/Analytics.php",
"admin/src/Rest/AnalyticsData.php",
"admin/src/WhiteLabel/WhiteLabel.php",
"admin/src/Shell/Shell.php",
];
$manifest = [];
foreach ($protected as $relative) {
$full = $root . $relative;
if (!file_exists($full)) {
fwrite(STDERR, "Missing protected file, aborting: {$relative}\n");
exit(1);
}
$manifest[$relative] = hash_file("sha256", $full);
}
$out_path = $root . "admin/src/Licensing/integrity-manifest.json";
file_put_contents($out_path, json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
echo "Wrote " . count($manifest) . " hashes to {$out_path}\n";