Skip to content

feat(labeler): reclassify undeclared-access as a warning, not a block#2097

Merged
ascorbic merged 2 commits into
feat/plugin-registry-labelling-servicefrom
feat/labeler-undeclared-access-warn
Jul 17, 2026
Merged

feat(labeler): reclassify undeclared-access as a warning, not a block#2097
ascorbic merged 2 commits into
feat/plugin-registry-labelling-servicefrom
feat/labeler-undeclared-access-warn

Conversation

@ascorbic

@ascorbic ascorbic commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Reclassifies the undeclared-access label from an automated block to a warning, from a finding in the severity-amended calibration sweep. Targets the feat/plugin-registry-labelling-service integration branch (RFC #694, umbrella #1909).

The sweep's only flagged code-lane false positive was glm-5.2 blocking a "clean" fixture on a real undeclared-access finding — the plugin declared storage: {} but its code called ctx.storage.gallery.put(...). glm was correct, but the finding shouldn't block: the runtime already enforces declared access. The sandbox throws Storage collection not declared (packages/cloudflare/src/sandbox/bridge.ts:300), and the allowedHosts / capability gates reject undeclared network/content/media/users access — so an undeclared capability cannot execute. It's a manifest/behavior mismatch (the plugin's undeclared feature silently fails), not an executable threat. Genuine over-reach is already caught by the specific malicious categories (data-exfiltration, credential-harvesting, …) which block on their own merits. Hard-blocking (hiding from install surfaces) a merely-inaccurate manifest is over-enforcement; a warning is the honest signal.

  • Policy (moderation-policy.json): undeclared-access category automated-block → warning, officialEffect block → warn; policyVersion/effectiveAt bumped to 2026-07-17.experimental.1. The label value is unchanged, so queryLabels/subscription consumers are unaffected — only the effect softens (additive-safe). The resolver, code-AI prompt, and console are all policy-data-driven — no code hardcodes the classification, confirmed by grep.
  • Fixture fix (calibration/fixtures/clean-with-images): declares its gallery storage collection so it's a genuine clean baseline (matching its name and expected: passed), rather than an accidental undeclared-access case.
  • Tests: undeclared-access now resolves to warned at any source/severity (never blocked); the "deterministic blocks at any severity" test moved to credential-harvesting to keep its stated behavior true; policy-version-string pins updated; docs/moderation-model.md moved the row into the warning group.

Adversary check (in the plan + verified): is there a real threat that manifests only as undeclared-access with no other blocking finding, now let through with a warning? No. Runtime capability enforcement is label-independent — it gates execution, not the label — so undeclared access can never run regardless of the label; genuine malice either trips a specific blocking category (which still blocks, with undeclared-access riding along as a warning) or requires the attacker to declare the access to make it run, at which point it's judged as declared behavior. Residual executable harm: none.

Follow-up: because the code-AI system prompt is data-driven from the policy's block∪warn category sets, it now presents undeclared-access as a warning category — a confirming calibration re-sweep is warranted before enforcement launch (tracked; the label + fixture changes here are the code side).

Part of #1909. Related to #694.

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes (all three labeler projects)
  • pnpm lint passes (no new diagnostics)
  • pnpm test passes (policy-resolver, policy, service, xrpc-router suites; full labeler suite 1005)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable) — n/a: server/policy-fixture change; no admin UI strings.
  • I have added a changeset (if this PR changes a published package) — n/a: moderation-policy.json is a labeler-internal fixture; @emdash-cms/labeler is private: true.
  • New features link to an approved Discussion — the umbrella Plugin registry labelling service #1909 tracks RFC RFC: Decentralized Plugin Registry #694.

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Opus 4.8 (implementation + threat-model check), orchestrated by Claude Fable 5

Screenshots / test output

policy-resolver + policy + service + xrpc-router: 76 passed
full labeler suite: 1005 passed
typecheck (3 projects): clean

Try this PR

Open a fresh playground →

A full working EmDash site, deployed from this branch. Each visit gets its own session-scoped sandbox: no login needed and no shared state. Try the admin, edit content, hit the public site.

Tracks feat/labeler-undeclared-access-warn. Updated automatically when the playground redeploys.

The severity-amended calibration sweep flagged glm-5.2 blocking a plugin that
used undeclared storage — a correct finding, but the runtime already enforces
declared access (the sandbox throws 'Storage collection not declared', and the
allowedHosts/capability gates reject undeclared network/content/media/users),
so undeclared access can never execute. It is a manifest/behavior mismatch, not
an executable threat; genuine over-reach is caught by the specific malicious
categories (data-exfiltration, credential-harvesting, ...) which block on their
own. Hard-blocking a merely-inaccurate manifest is over-enforcement.

- moderation-policy: undeclared-access category automated-block->warning,
  officialEffect block->warn; policyVersion + effectiveAt bumped to 2026-07-17.
  The label value is unchanged, so queryLabels consumers are unaffected; only
  the effect softens. Resolver/adapter/console are all policy-driven — no code
  hardcodes the classification.
- fixture: clean-with-images declares its gallery storage so it is a genuine
  clean baseline (its code used ctx.storage.gallery without declaring it).
- tests: undeclared-access now resolves to warned at any source/severity; the
  deterministic-blocks-at-any-severity test moved to credential-harvesting;
  version-string pins updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jul 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: bf350b0

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 20 packages
Name Type
@emdash-cms/registry-moderation Minor
@emdash-cms/registry-client Patch
@emdash-cms/labeler Patch
@emdash-cms/admin Patch
emdash Patch
@emdash-cms/plugin-cli Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added size/M review/needs-review No maintainer or bot review yet cla: signed labels Jul 17, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 17, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-playground bf350b0 Jul 17 2026, 05:12 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 17, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-do bf350b0 Jul 17 2026, 05:13 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 17, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-cache bf350b0 Jul 17 2026, 05:12 PM

@emdashbot emdashbot Bot 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.

The reclassification itself is sound: the threat-model reasoning in the PR description is correct, the labeler policy fixture is updated consistently, the clean-with-images fixture now honestly declares the storage it uses, and the labeler-side tests cover the new warning behavior. I checked the policy fixture, policy-resolver.ts, policy.ts, the code-AI adapter, the console label menus, registry-client, and the consumer-side @emdash-cms/registry-moderation evaluator.

The headline issue is that the PR does not complete the reclassification for downstream consumers. packages/registry-moderation/src/index.ts hardcodes undeclared-access in AUTOMATED_BLOCKS and omits it from WARNINGS. Because RELEASE_BLOCK_VALUES is built from AUTOMATED_BLOCKS, the admin UI and any other registry-client/registry-moderation consumer will still treat the label as a hard block — directly contradicting the PR's stated goal and its claim that no code hardcodes the classification.

To resolve this, undeclared-access needs to move from the hardcoded AUTOMATED_BLOCKS set into WARNINGS, the registry-moderation tests need to stop asserting that it blocks (and start asserting that it warns), and because @emdash-cms/registry-moderation is a published package, that consumer-side fix requires a changeset.

Everything inside apps/labeler looks correct; the remaining work is the matching consumer-side policy update.


Findings

  • [needs fixing] packages/registry-moderation/src/index.ts:167-176

    The AUTOMATED_BLOCKS set still includes "undeclared-access". Because RELEASE_BLOCK_VALUES is derived from this set, every consumer of @emdash-cms/registry-moderation (and @emdash-cms/registry-client) will continue to hard-block releases carrying this label, defeating the PR's reclassification.

    This also contradicts the PR description's claim that no code hardcodes the classification. Since @emdash-cms/registry-moderation is a published package, moving this value out of the block set will also require a changeset.

    const AUTOMATED_BLOCKS = new Set<string>([
    	"malware",
    	"data-exfiltration",
    	"credential-harvesting",
    	"supply-chain-compromise",
    	"critical-vulnerability",
    	"artifact-integrity-failure",
    	"invalid-bundle",
    	"impersonation",
    ]);
    
  • [needs fixing] packages/registry-moderation/src/index.ts:179-187

    WARNINGS does not include "undeclared-access". Even if it is removed from AUTOMATED_BLOCKS, the label would be treated as neither block nor warning and would silently disappear from consumer-facing moderation output instead of surfacing as the intended warning.

    const WARNINGS = new Set<string>([
    	"suspicious-code",
    	"obfuscated-code",
    	"privacy-risk",
    	"misleading-metadata",
    	"low-quality",
    	"broken-release",
    	"package-disputed",
    	"undeclared-access",
    ]);
    
  • [needs fixing] packages/registry-moderation/tests/moderation.test.ts:381-386

    This test loops over every RELEASE_BLOCK_VALUES entry and asserts it blocks the release. Once undeclared-access is removed from that set, the loop no longer covers it and the existing test suite provides no coverage for the new warning behavior. Add a dedicated test proving undeclared-access now keeps a release eligible with a warning label.

    	it("blocks every RELEASE_BLOCK_VALUES value on the exact release", () => {
    		for (const value of RELEASE_BLOCK_VALUES) {
    			const result = evaluate([label({ val: "assessment-passed" }), label({ val: value })]);
    			expect(result.eligibility, `${value} should block the release`).toBe("blocked");
    		}
    	});
    
    	it("treats undeclared-access as a warning, not a block", () => {
    		const result = evaluate([
    			label({ val: "assessment-passed" }),
    			label({ val: "undeclared-access" }),
    		]);
    		expect(result).toMatchObject({
    			eligibility: "eligible",
    			reasonCodes: ["eligible-assessment-pass", "warning-labels"],
    			warningLabels: ["undeclared-access"],
    			blockingLabels: [],
    		});
    	});
    
    	it("blocks every PACKAGE_SCOPE_BLOCK_VALUES value on the publisher DID", () => {
    

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-review No maintainer or bot review yet labels Jul 17, 2026
…onsumer-side

The labeler-side policy change softened undeclared-access to a warning, but the
consumer-side evaluator hardcodes block-vs-warn in registry-moderation's
AUTOMATED_BLOCKS/WARNINGS sets — so the aggregator, registry-client, and admin
would still hard-block releases carrying the label. Move undeclared-access from
AUTOMATED_BLOCKS to WARNINGS so RELEASE_BLOCK_VALUES drops it and the evaluator
surfaces it as a warning (release stays eligible). Membership in RELEASE_VALUES
is preserved (moved within the union), so it is not silently dropped.

Addresses the emdashbot review on #2097.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/awaiting-author Reviewed; waiting on the author to respond labels Jul 17, 2026
@ascorbic

Copy link
Copy Markdown
Collaborator Author

Good catch — the reclassification was incomplete on the consumer side (commit bf350b0).

There are two sources of block-vs-warn: the labeler's moderation-policy.json (issue-side, what the labeler issues) and registry-moderation's hardcoded AUTOMATED_BLOCKS/WARNINGS (enforce-side, what the aggregator/registry-client/admin enforce). The first PR touched only the issue-side.

Fix: moved undeclared-access from AUTOMATED_BLOCKS into WARNINGS. Verified the downstream derivations follow automatically — RELEASE_BLOCK_VALUES (spreads AUTOMATED_BLOCKS) drops it so it no longer hard-blocks (incl. the aggregator's label-enforcement.ts SQL IN (...) clause), the evaluator's AUTOMATED_BLOCKS.has no longer classes it as a block, and WARNINGS.has now surfaces it into warningLabels with the warning-labels reason. Membership in RELEASE_VALUES is preserved (moved within the union), so it's still an accepted value, not silently dropped. Added a positive test asserting an undeclared-access release stays eligible with warningLabels: ["undeclared-access"], and a minor changeset for @emdash-cms/registry-moderation.

Confirmed registry-moderation is the only consumer-side classifier — admin/src/lib/api/registry.ts is a display-only name/description map, registry-client derives from registry-moderation, and core doesn't consume the block set.

registry-moderation 154, registry-client 85, aggregator 313, labeler 830 all green; typecheck/lint/format clean.

Noted for the maintainer, not fixed here: the two independent classification sources (labeler policy fixture vs registry-moderation constants) have no compile-time link and must be kept in sync by hand — this drift is what the miss came from. If it's worth a single-source-of-truth, that's a Discussion rather than this PR.

~ 🤖 Claude Fable 5

@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Jul 17, 2026

@emdashbot emdashbot Bot 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.

Re-review: all prior findings resolved

This is a clean follow-up. In my previous review I flagged three blockers: @emdash-cms/registry-moderation still hardcoded undeclared-access as an automated block, omitted it from the warning set, and had no test coverage for the new warning behavior. Those are all fixed in this revision.

What changed since the last review

  • packages/registry-moderation/src/index.ts is now consistent with the labeler policy: undeclared-access is removed from AUTOMATED_BLOCKS and added to WARNINGS. Because RELEASE_BLOCK_VALUES derives from AUTOMATED_BLOCKS, consumer-side evaluation correctly keeps releases with this label eligible.
  • packages/registry-moderation/tests/moderation.test.ts adds a dedicated test proving that RELEASE_BLOCK_VALUES no longer contains the label and that an exact-CID undeclared-access label surfaces as a warning on an otherwise eligible release.
  • .changeset/undeclared-access-warning.md adds the required minor changeset for the published @emdash-cms/registry-moderation package.

Cross-cutting consistency check

I searched the repo for undeclared-access references. The only remaining non-test/ non-doc references are now harmless:

  • apps/labeler/fixtures/moderation-policy.json — data-driven policy source, now lists the label as warning/warn.
  • apps/labeler/src/policy-resolver.ts and apps/labeler/src/policy.ts — derive block/warn sets from the policy fixture, no hardcoded classification.
  • apps/labeler/console/src/labels.ts — derives AUTOMATED_BLOCK_LABELS from automatedBlockCategories(MODERATION_POLICY).
  • apps/labeler/src/code-ai-adapter.ts — prompt mentions the label name, but the allowed category set is passed in from the policy.
  • packages/admin/src/lib/api/registry.ts — only has human-readable display text (name/description); it does not hardcode the category/effect.
  • packages/registry-client/src/moderation.ts — re-exports the block/warn sets from @emdash-cms/registry-moderation.

So the PR's claim that "no code hardcodes the classification" is now accurate.

Approach evaluation

The reclassification remains sound. The runtime sandbox already rejects undeclared capability use (Storage collection not declared, allowedHosts / capability gates on network/content/media/users), so the label describes a manifest/behavior mismatch rather than an executable threat. Genuine malice is still caught by the dedicated blocking categories. The label value itself is unchanged, so queryLabels consumers are unaffected; only the effect softens.

Conventions

  • Changeset added for the published package change.
  • Tests updated on both the labeler side (policy-resolver.test.ts, policy.test.ts, service.test.ts, xrpc-router.test.ts) and the consumer side (registry-moderation/tests/moderation.test.ts).
  • Admin UI label strings were already wrapped in msg descriptors; no new untranslated strings were introduced.

No remaining blocking issues. LGTM.

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Jul 17, 2026
@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-rereview Author pushed changes since the last review labels Jul 17, 2026
@ascorbic
ascorbic merged commit 748af2d into feat/plugin-registry-labelling-service Jul 17, 2026
11 checks passed
@ascorbic
ascorbic deleted the feat/labeler-undeclared-access-warn branch July 17, 2026 19:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla: signed review/approved Approved; no new commits since size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant