Skip to content

Harden external plugin validation: semver, SPDX, email, and unknown-field checks#2445

Open
aaronpowell wants to merge 2 commits into
mainfrom
aaronpowell-external-plugin-spec-hardening
Open

Harden external plugin validation: semver, SPDX, email, and unknown-field checks#2445
aaronpowell wants to merge 2 commits into
mainfrom
aaronpowell-external-plugin-spec-hardening

Conversation

@aaronpowell

Copy link
Copy Markdown
Contributor

Summary

Extends the canonical external-plugin validator (eng/external-plugin-validation.mjs) with additional Open-Plugins-aligned validation rules. All checks reuse the shared validation functions rather than duplicating logic in scripts/workflows.

Origin: #2398. Follows #2444 (which landed the markdown/table-injection and stricter plugin.name hardening).

Validation enhancements

  1. Semver version — enforces Semantic Versioning (major.minor.patch with optional -prerelease / +build metadata); non-semver → error. Accepts the existing committed 1.0.1161-preview1.
  2. SPDX license — validates SPDX identifiers and expressions (AND/OR/WITH, parentheses, trailing +, LicenseRef-*). Malformed syntax → error; a well-formed but unrecognized identifier → warning.
  3. author.email — format-validated only when the field is present.
  4. Unknown-field detection — warns on typo'd/unknown top-level, author, and source keys (forward-compatible; warnings, not errors).
  5. Immutable locator (evaluation of item 5) — kept the marketplace policy non-fatal because ~12 committed entries have no source.ref/source.sha. Added a new warnMissingImmutableLocator policy flag that warns for marketplace entries lacking an immutable locator, while publicSubmission retains its existing hard error.

Non-breaking decisions

  • Unknown fields and the marketplace missing-immutable-locator case are warnings, not errors — this keeps npm run build / plugin:validate green and stays forward-compatible with future supported fields.
  • SPDX validation warns (rather than errors) on well-formed-but-unrecognized identifiers, specifically so the already-committed SSAL-1.0 entry continues to pass marketplace validation.

Test coverage

New eng/external-plugin-validation.test.mjs (node:test, 9 tests) covering:

  • semver accept/reject cases (incl. prerelease/build);
  • SPDX ids, expressions, LicenseRef-*, warning on unknown id, errors on malformed;
  • author.email present-only validation;
  • unknown-field warnings for top-level/author/source;
  • immutable-locator warning (marketplace) vs error (publicSubmission);
  • a regression asserting the committed plugins/external.json passes the marketplace policy with zero errors.

Verification

  • node --test eng/external-plugin-validation.test.mjs → 9 pass
  • node --test eng/external-plugin-quality-gates.test.mjs → existing 7 pass
  • npm run plugin:validate → valid (surfaces the intended warnings)
  • npm run build → marketplace regenerates, no new errors
  • bash eng/fix-line-endings.sh → line endings normalized

Docs

Updated CONTRIBUTING.md external-plugin section to document semver version, SPDX license, optional author.email, unknown-field warnings, and the marketplace immutable-locator warning.

Extend the canonical external-plugin validator with Open-Plugins-aligned
rules, reusing the shared validation functions rather than duplicating checks:

- version: enforce Semantic Versioning (allows prerelease/build metadata)
- license: validate SPDX identifiers/expressions; warn (not error) on
  well-formed-but-unrecognized ids so existing entries like SSAL-1.0 pass
- author.email: validate format when present
- unknown-field detection: warn on typo'd top-level/author/source keys
- immutable locator: marketplace warns when source lacks ref/sha;
  publicSubmission keeps the existing hard error

Add eng/external-plugin-validation.test.mjs (node:test) covering each rule
plus a regression that committed external.json passes marketplace policy
with zero errors. Update CONTRIBUTING.md accordingly.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: eaa5eed6-5b65-4b28-9904-24f380d26728
Copilot AI review requested due to automatic review settings July 27, 2026 05:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Hardens canonical external-plugin metadata validation and documents the expanded rules.

Changes:

  • Adds semver, SPDX, email, unknown-field, and immutable-locator validation.
  • Adds nine validator tests, including committed-data regression coverage.
  • Updates contributor documentation.
Show a summary per file
File Description
eng/external-plugin-validation.mjs Implements new validation rules and warnings.
eng/external-plugin-validation.test.mjs Tests validator behavior and marketplace compatibility.
CONTRIBUTING.md Documents external-plugin requirements and warnings.

Review details

  • Files reviewed: 3/3 changed files
  • Comments generated: 3
  • Review effort level: Medium

Comment on lines +271 to +273
if (token.startsWith("LicenseRef-") || token.startsWith("DocumentRef-")) {
return;
}
Comment thread eng/external-plugin-validation.mjs Outdated
Comment on lines +301 to +305
// Validate as an SPDX license expression: identifiers separated by AND/OR/WITH,
// optionally parenthesized. Unknown-but-well-formed identifiers warn; malformed
// syntax errors.
const tokens = license
.replace(/[()]/g, " ")

let expectOperator = false;
for (const token of tokens) {
if (SPDX_EXPRESSION_OPERATORS.has(token.toUpperCase())) {
…ugins

The agent-plugins-spec schema does not enforce SPDX, and plugins may use
proprietary/non-OSS licenses. Relax license validation so any non-empty
license string that isn't a recognized SPDX identifier/expression produces a
warning rather than an error.

Extract the license check into a reusable validateLicenseField() and apply it
to both external plugins and local plugin.json manifests via
eng/validate-plugins.mjs, so licenses are validated consistently in one place.

Update tests and CONTRIBUTING.md accordingly.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: eaa5eed6-5b65-4b28-9904-24f380d26728
Copilot AI review requested due to automatic review settings July 27, 2026 06:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Comments suppressed due to low confidence (1)

eng/external-plugin-validation.mjs:343

  • The license check does not actually distinguish malformed SPDX syntax from an unknown but well-formed identifier. Every parse failure becomes a warning (the test even includes MIT OR), while the recognizer also strips parentheses and treats WITH like AND/OR; this lets unbalanced expressions pass and warns on valid exception expressions such as GPL-2.0-only WITH Classpath-exception-2.0. This contradicts the stated contract that malformed syntax is an error. Please parse the SPDX grammar separately from identifier recognition, report syntax failures as errors, and reserve warnings for syntactically valid unknown IDs.
  if (!isRecognizedSpdxExpression(license)) {
    warnings.push(
      `${prefix}"license" value "${license}" is not a recognized SPDX identifier; prefer a standard SPDX id (https://spdx.org/licenses), though non-SPDX or proprietary licenses are allowed`
    );
  • Files reviewed: 4/4 changed files
  • Comments generated: 0 new
  • Review effort level: Medium

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants