Skip to main content
Version: 3.0.0-preview

Pack security

A pack runs PowerShell code in your Maester session, with every connection in it, your environment and your files. Maester is not a sandbox. What it does is make sure that the code that runs is the code you approved, that you saw what it reaches before approving it, and that your organisation can set limits. Read-only Graph scopes limit what a pack can change in your tenant; they do not stop it reading local files or sending data out.

What is checked, and whenโ€‹

WhenCheck
InstallThe pack is read without running any of it: manifest, every file, and an AST scan of every script. Errors stop the install.
InstallThe release is older than the cooldown, the pack policy allows it, and it is not on the blocklist.
InstallIts modules are saved at their exact versions into the pack store, and the digest of each module folder is recorded.
InstallYou approve the prompt. A High risk pack, or one newer than the cooldown, needs its code typed.
Every loadThe stored files match the digest in the lock file, the policy still allows the pack, it is not blocked, and every module still matches its digest. Only then is any file loaded.

Every path that loads pack code goes through that last check: Invoke-Maester, Invoke-MtTest, Connect-Maester, Disconnect-Maester, Test-MtConnection and Get-MtTenantContext.

The lock fileโ€‹

maester-packs.lock.json pins each pack to a commit and a digest: SHA-256 over the sorted list of every file's path and SHA-256, with Authenticode signature blocks left out so that signing a pack does not change it. The pack store keeps the files read-only under that digest. A changed file stops the pack with PackUnavailable until you run Restore-MtPack (to get the approved files back) or Install-MtPack again (to review the change).

Only a complete signature block of PowerShell comments at the end of a file is left out. Marker text inside strings, and a trailer that is incomplete or followed by code, stay part of the digest. Pack archives refuse paths that contain : on every platform, so a pack from GitHub can't carry Windows alternate data streams.

Windows alternate data streams on files already on your computer, such as the Zone.Identifier mark that downloads get or the stream Microsoft Defender Endpoint DLP adds, are not part of the digest and don't stop a pack. PowerShell never runs a stream's content, a pack's code can read one only with a call you can see when you review it, and only someone who can already change the stored files could add one.

A module that the pack runs in a separate process from your own install ("Install": "User") cannot be pinned: it is shown as High risk, and AllowExternalEnvironments: false refuses the pack.

Each module a pack uses is pinned the same way, by the digest of its folder. A module you installed yourself (used when the pack policy's AllowExternalEnvironments is on) is pinned by its digest at approval, raises the pack to High risk, and needs approving again when it changes.

Cooldownโ€‹

A new release is not installed until it is 72 hours old, so a compromised release has time to be noticed and blocked. The age comes from the content, never from a date a publisher can set:

  • an immutable release counts from when it was published, because its tag cannot be moved;
  • anything else counts from when this computer first saw that commit of that repository, so a tag moved to new code starts a new cooldown.

-IncludeRecent installs a newer release; at a console you also type the pack's code. PackPolicy.CooldownHours changes the length.

Pack policyโ€‹

The PackPolicy section of maester-config.json, the MAESTER_PACK_POLICY environment variable (a path to a JSON file) and a machine file (%ProgramData%\Maester\pack-policy.json or /etc/maester/pack-policy.json) are combined, and the strictest value of each setting wins:

SettingEffect
MaximumRiskLow, Medium or High: the highest risk a pack may have.
AllowedPublishersOnly packs from these GitHub owners. An empty list denies all non-built-in packs; an omitted or null value adds no restriction.
TrustedSourcesOnly packs from these sources (owner/repo).
AllowUnlistedfalse: only packs listed in the Maester Packs index.
RequireBadgeOnly packs with this index badge.
AllowExternalEnvironmentsfalse: a pack may use only modules that Maester saved.
AllowedRuntimesThe pack runtimes allowed.
CooldownHoursThe cooldown, in hours.
RequireFreshBlocklisttrue: no pack runs when the blocklist cannot be fetched.

Packs that ship with Maester are always allowed.

Blocklistโ€‹

Maester fetches a list of blocked packs, signed with an ECDSA P-256 key that ships with the module, and caches it for an hour. Only a list with a valid signature, a serial at least as high as the last one seen, and an unexpired date is used. A blocked pack does not install and, when already installed, does not run (PackBlocked). When the list cannot be fetched, the cached list is used with a warning, unless RequireFreshBlocklist is set. Strict freshness also rejects missing trust keys and cached failures. A verified, unexpired list fetched within the cache interval can be reused, but reading it from disk or memory does not restart that interval. Both its original fetch time and signed expiry are checked on every call. After an hour, a refresh is attempted; if that fails, only permissive mode can use an unexpired cached list, with a warning. When several entries match a pack, any blocking entry takes precedence over a warning regardless of their order in the list.

Repairing stored packsโ€‹

Restore-MtPack and reinstalling replace a stored pack whose files changed. The new files and the copy made of them must both match the approved digest before the old copy is removed, so a failed download or a copy that doesn't match leaves the stored pack as it was, and Restore-MtPack reports that pack as failed.

Reporting a packโ€‹

Report a malicious or vulnerable pack to the Maester maintainers through GitHub security advisories.