# Security review

Source: https://opvs.ai/docs/guides/vendors/security-review

> The automated checks every package passes before the registry accepts it, the score, and how to read and fix a refusal.

Every publish runs an automated security review before the registry accepts the version. If any check returns a **blocking** finding, the publish fails with `422` and the list of findings, and nothing is stored.

## The checks

#

Check

What it catches

On failure

1

`manifest_schema`

`package.yaml` does not validate

blocking; later checks do not run

2

`signature_verify`

signature does not match an active certificate of your vendor

blocking

3

`meta_coherence`

no skills, no inbound events and no peers: nothing to install

blocking

4

`entitlement_lint`

an unknown entitlement, or `network.egress.public` where one host would do

blocking

5

`blocklist`

literal `shell(`, `eval(` or `exec(` in the bundle's source

blocking

6

`auth_scheme`

a credential `auth_type` vendors may not use

blocking

7

`rate_limits`

an outbound method with no rate limit

warning on `beta`, blocking on `internal`

8

`base_url_lint`

a skill routed through an OPVS proxy with an unknown proxy path

blocking

9

`network_egress`

an `upstream` host not approved for your vendor

blocking if rejected; warning while pending

10

`required_docs`

`README.md`, `SKILL.md` or `CHANGELOG.md` missing from the bundle root

blocking

11

`aliases_present`

no `aliases`, so agents cannot find the package by the words they use

warning

12

`profession_coherence`

profession-only rules (see [Profession anatomy](https://opvs.ai/docs/guides/vendors/profession-anatomy.md#what-the-extra-check-refuses))

blocking

**What runs when.** At publish, the registry reviews your **manifest and signature**. The checks that read files inside the bundle (`blocklist`, `rate_limits` on your schemas, `required_docs`, the profession file checks) run when the bundle itself is reviewed. Run them yourself with the [preflight](https://opvs.ai/docs/guides/vendors/preflight.md), and treat them as required: they are what a full review checks.

## What each check does

`manifest_schema` validates `package.yaml` against the rules on [The package manifest](https://opvs.ai/docs/guides/vendors/package-manifest.md). Code: `manifest.schema`, with the failing field path.

`signature_verify` finds your vendor's active certificate and checks the signature against the bundle hash. Codes: `signature.unknown_vendor`, `signature.revoked`, `signature.mismatch`.

`meta_coherence` refuses a package with `skills: []`, `inbound_events: []` and `peer_dependencies: []`. Code: `meta.empty_useless`. Peers alone are fine (a meta-package).

`entitlement_lint` checks every entitlement is known: a fixed name, `network.egress.provider.<name>`, `credits.consume.<name>` or `vendor.<vendor>.<surface>.<verb>`. It also refuses `network.egress.public` when all your outbound URLs share one host. Codes: `entitlements.unknown`, `entitlements.too_broad`.

`blocklist` scans text files in the bundle for `shell(`, `eval(` and `exec(`. `exec(` is allowed only if the package declares an `exec.*` entitlement.

`auth_scheme` allows the credential types `pat`, `api_key`, `oauth`, `oauth_pkce` and `hmac_sha256`. Code: `credentials.auth_type_unknown`.

`rate_limits` checks that every method with an `http` line has a `rate_limit`, on the method or as a package default in `provider_config.rate_limit`. Code: `rate_limit.missing`.

`base_url_lint` applies to vendors whose skills run through an OPVS proxy: the skill's `base_url` must be one of the proxy paths OPVS gave you. Code: `base_url.unknown_proxy_prefix`.

`network_egress` applies when your manifest declares `provider_config.upstream`. Every host (the upstream and any absolute host in your method URLs) must be approved for your vendor. A new host files an approval request and gives a warning; calls to it are refused until OPVS approves it. A rejected host blocks. Codes: `network_egress.host_pending_approval`, `network_egress.host_rejected`.

`required_docs` requires `README.md`, `SKILL.md` and `CHANGELOG.md` at the bundle root. For the future `verified` tier, `LEARNINGS.md` and a non-empty `tests/` folder are also expected (warnings today).

`aliases_present` warns when `aliases` is empty. Code: `discovery.no_aliases`.

## The score

```
start            100
each blocking    −25
each warning      −5   (floor 0)
```

The publish is refused by **any** blocking finding, whatever the score. A package with only warnings passes with a lower score. The score is stored on the version and shown with the package.

## Reading a refusal

```json
{
  "error": "security_review_failed",
  "score": 75,
  "findings": [
    {
      "severity": "blocking",
      "code": "credentials.auth_type_unknown",
      "message": "credential 'ACME_KEY' uses auth_type 'device_code' which is not in the marketplace-approved set",
      "path": "credentials.required[0].auth_type"
    }
  ]
}
```

Read `code` first: it names the check. `message` names the field and value. Fix it, **bump the version**, and publish again.

## Common fixes

Code

Cause

Fix

`manifest.schema`

unknown key, bad semver, missing `trust_domain`

follow [The package manifest](https://opvs.ai/docs/guides/vendors/package-manifest.md)

`signature.mismatch`

wrong key, or the certificate was rotated

sign with the key of your active certificate

`signature.revoked`

the certificate was revoked

ask for a new certificate and re-sign

`meta.empty_useless`

nothing to install

add a skill, or peers for a meta-package

`entitlements.unknown`

a typo or a made-up name

use a known name or `vendor.<you>.<surface>.<verb>`

`entitlements.too_broad`

`network.egress.public` for one host

use `network.egress.provider.<host>`

`credentials.auth_type_unknown`

a reserved credential type

use `pat`, `api_key`, `oauth`, `oauth_pkce` or `hmac_sha256`

`rate_limit.missing`

a method with no rate limit

add `rate_limit` to the method or `provider_config`

`network_egress.host_pending_approval`

a new upstream host

ask vendors@opvs.ai to approve it

`discovery.no_aliases`

no aliases

add the phrases agents would search for

## Running the full review without publishing

The standalone review endpoint (`POST /api/v1/security/review` on the marketplace) runs every check, including the bundle checks, without storing anything. It accepts only OPVS's platform key today, so ask vendors@opvs.ai to run it on a candidate bundle.

## Next

[Versions and trust tiers](https://opvs.ai/docs/guides/vendors/versions-and-tiers.md).
