Skip to content

Policy and Enforcement

Policy is what makes the proxy more than a mirror. Every install that passes through ShieldedStack is checked against the policy of the workspace the API key belongs to, and the result is recorded with the reason it was reached.

This page explains what each control does, the order the checks run in, and how to combine them into a posture that fits an environment.

Policy is configured per workspace under Settings and Settings -> Package Filters. A workspace admin, or a member with the Manage configuration and Manage package policy permissions, can change it.

The checks run in a fixed order, and the first one that reaches a verdict wins. Order matters: an allow-list match is decided before the deny list and before any vulnerability check, so an explicit allow is the strongest statement you can make about a package.

Install request arrives, API key names the workspace1. Allow listA match ends the decision here and allows the install2. Deny listBlocks the package outright, whatever its severity3. Minimum block levelKnown severity, with active exceptions excluded4. Release grace periodBlocks versions younger than the configured age5. Running modeTrust then Verify allows what is leftVerify then Trust first requires a current assessment,and blocks if it cannot get oneAllowed on matchBlocked on matchBlocked at or above levelBlocked while too newBlocked if unverifiableAllowed, and the reason is recorded
Policy checks are applied in order. The first check that reaches a verdict decides the request.

Whatever the outcome, the decision and its reason are written to the request log, so a block is never silent and an allow is never unexplained. See Request Logs for how to read them back.

These live on the Settings page and govern how the allow and deny lists behave.

Setting Effect
Enable Allowlist Turns on allow-list matching. A package matching an allow rule is permitted even when it would otherwise be blocked.
Require Allowlist Match Available once the allow list is enabled. Blocks everything that is not on the allow list, regardless of vulnerability status. This turns the workspace into a closed catalogue.
Enable Denylist Turns on deny-list matching. A package matching a deny rule is blocked.

An allow list that is enabled without Require Allowlist Match is an exemption list: it lets specific packages through checks they would otherwise fail, and everything else follows normal policy. With Require Allowlist Match enabled it becomes the definition of what exists. Turn it on only after the allow list genuinely covers every dependency the workspace builds with, including transitive ones, or builds will start failing on packages nobody thought to list.

Setting Effect
Minimum Package Block Level The severity at which a known vulnerability blocks the install. A package whose highest known severity reaches this level is blocked. Active vulnerability exceptions are excluded from that calculation.
Running Mode How ShieldedStack treats a package it has no current assessment for. See below.
Package Release Grace Period (days) Blocks versions that have been published for fewer than this many days. Set it to 0 to disable the check.

ShieldedStack always applies your explicit filters and everything it already knows about a package. The running mode decides only what happens when current assessment facts are not available for the exact version being requested.

  • Trust then Verify allows the install and continues the assessment behind it. Findings show up as alerts shortly afterwards. Installs are never delayed by an assessment, and a first-seen version can reach a build before it has been judged.
  • Verify then Trust requires a current assessment before allowing the version. If one is not already available, ShieldedStack obtains it as part of the request. If it cannot, the request is blocked rather than allowed on trust.

Most rollouts start in Trust then Verify for long enough to build a clean inventory and tune filters against real traffic, then move higher-risk workspaces to Verify then Trust. Because that mode can block on a failure to verify, it depends on the deployment being able to reach its configured advisory sources.

A release grace period is the cheapest defence against a compromised or typosquatted release, because most malicious versions are found and pulled within days of publication. A value between 3 and 14 days is a common starting point.

It applies to the version, not the package: an established dependency publishing a new patch is subject to the same wait. Teams that need a specific new version before it ages out add a targeted allow-list rule for that version rather than lowering the setting for the whole workspace.

Settings -> Package Filters holds the rules themselves. Each rule is an allow or deny entry made of an ecosystem, a package pattern, an optional version range, an optional reason, and an optional expiry.

Settings -> Package Filters with allow and deny rules across multiple ecosystems.
Allow and deny rules for the workspace, grouped by list.

Patterns are simple glob-style matches on the package name:

Pattern Matches
lodash Exactly that package
@scope/* Every package under a scope
com.example.* Every package under a group or namespace prefix
* Every package in the ecosystem

A catch-all * has to be confirmed explicitly, because on a deny list it blocks the entire ecosystem and on an allow list it silently disables the protection the allow list exists to provide.

Leave the version range empty to apply a rule to every version. To scope it, use the range syntax you already know from npm and NuGet:

Range Meaning
^4.17.0 Compatible releases within the same major version
~1.2.3 Patch-level releases within the same minor version
>=2.0.0, <3.0.0 Comparison operators, which can be combined
1.2.x, * Wildcards
[1.0.0,2.0.0) NuGet bracket notation, inclusive and exclusive bounds

Ranges are stored in a normalized form, so a range typed as ^1.2.3 may read back in an equivalent notation. The set of versions it matches does not change.

A reason is optional and worth writing anyway. Filters outlive the incident that prompted them, and a rule nobody can explain is a rule nobody dares remove.

An expiry turns a rule into a temporary measure that cleans itself up. Use one for a deny rule covering an advisory you expect to be patched, or for an allow rule waiving a control while a migration is in flight. Expired filters stop applying and are left out of exports.

Export writes a workspace’s allow and deny lists to a JSON file; import applies such a file to any workspace you administer. Teams use this to keep policy under version control and review it in pull requests, to promote a reviewed policy from staging to production, to seed a new workspace without retyping every rule, and to keep a restorable copy of a curated list.

Import offers three modes:

  • Merge adds the filters that are missing and refreshes the reason and expiry of the ones already present. It never deletes anything.
  • Replace treats the file as the complete list, so filters not in it are deleted. Only the lists actually present in the file are touched, and a file containing just an allow list leaves the deny list alone.
  • Validate runs the import without changing anything and reports every problem it finds, each pointing at the entry that caused it.

Imports are all-or-nothing: if one entry is rejected, nothing is applied. Validating first is the reliable way to work through a hand-edited file.

A few things worth knowing about the files:

  • They carry only the policy itself: ecosystem, pattern, version range, reason, and expiry. Who created a filter and when is recorded fresh in the receiving workspace, so an imported file cannot rewrite your audit trail.
  • Catch-all patterns are refused unless you confirm them, for the reasons above.
  • Already-expired filters are left out of exports, since importing one has no effect.
  • Importing requires workspace admin rights. Exporting is available to any workspace member.

Vulnerability exceptions are never included in either direction. An exception records a decision that a named advisory is acceptable for a named version in one environment, and carrying that decision into another environment would move a risk acceptance somewhere nobody reviewed it.

An exception excludes one advisory on one exact package version from policy. While it is active, that advisory no longer counts toward the severity compared against Minimum Package Block Level, and it stops generating new alerts. Everything else about the package continues to be evaluated normally.

Exceptions are the release valve that keeps the block level honest. Without them, one unfixable advisory in a pinned dependency pushes teams toward lowering the block level for the whole workspace, which is a far larger concession than the one they actually wanted to make.

Add one from an alert, from a package’s Security tab, or from the CVEs page. Review and revoke them under Settings -> Vulnerability Exceptions, which keeps a record of who added each and when. See Alerts and Triage for the workflow.

Environment A reasonable starting point
Development, first rollout Trust then Verify, deny list on, block level High, no grace period. Build an inventory and see real traffic before enforcing.
Shared build agents Trust then Verify, deny list on, block level High, grace period of 7 days.
Production, release pipelines Verify then Trust, deny list on, block level Medium or High, grace period of 7 to 14 days.
Regulated or air-gapped Verify then Trust with Require Allowlist Match, once the allow list is complete and maintained.

Use separate workspaces when environments need different answers here. Policy, filters, exceptions, alerts, and history are all scoped to the workspace, so one environment tightening its posture never disturbs another.