Series · 3 partsBrowser-to-Native Trust BoundariesPart 3 · You are here
- 1The Extension Was Sandboxed. The Native Host Was Not.
- 2The Page Never Had Permission. The Extension Did.
- 3The Package Was Signed. The Update Was Still a Security Decision.You are here
The boundary in 60 seconds
A valid extension signature answers a narrow question: did this package come from the identity the browser expects for this extension? It does not prove that the release was produced from the reviewed commit, that the build runner was clean, that the publisher account intended this upload, that permissions did not expand, or that remote configuration cannot activate a new capability.
Chrome automatically checks for extension updates on startup and every few hours. An available Manifest V3 update is installed after the extension becomes idle. Managed environments may pin a version or override its update source. Firefox-listed add-ons update through AMO; self-distributed add-ons may use an HTTPS update manifest and still require Mozilla signing for release and beta Firefox.
Those controls establish distribution mechanics. The security decision remains:
Which reviewed source, build identity, artifact digest, publisher action, permission set, rollout cohort, and runtime configuration produced the code now exercising extension authority?
This final part of the series builds a method for answering that question without publishing a malicious extension or touching a real user fleet.
One release, seven principals
| Principal | Security decision | Evidence that must survive |
|---|---|---|
| Source reviewer | Is this change intended and bounded? | Commit, review, changed files, threat note |
| Build service | Did the approved source produce this package? | Workflow identity, clean runner, dependency lock, logs |
| Artifact store | Is this the exact reviewed output? | SHA-256 digest, manifest snapshot, SBOM, provenance statement |
| Publisher account | Was this upload authorized? | Strong authentication, actor, timestamp, artifact digest |
| Store or update host | Which version is offered to which client? | Version mapping, update URL, rollout record |
| Browser | Which package is actually installed? | Extension ID, version, permissions, update source |
| Runtime control plane | Which behavior is active now? | Effective config, cohort, expiry, audit event |
What the platform proves—and what it does not
| Platform control | Establishes | Does not establish |
|---|---|---|
| Package signature | Continuity with the expected extension identity | The release is benign or matches reviewed source |
| Store review | Submitted package passed the store’s current process | Publisher credentials, backend config, or future releases remain safe |
| Manifest V3 packaged code | Extension logic is bundled rather than fetched as ordinary remote code | Remote data cannot act as a hidden instruction language |
| Permission warning | A declared permission delta may be visible or block activation | Existing broad permissions are used only for the old purpose |
| HTTPS update manifest | Network delivery has server authentication and integrity in transit | The update origin and publisher pipeline are correctly authorized |
| Version pin | A cohort remains on one version | The pinned version is safe or receives a critical fix |
| Staged rollout | Exposure can be limited over time | The selected cohort is observed or rollback is complete |
The distinction matters because an extension update is not merely new code. It is a fleet-wide
change to a principal that may already hold tabs, cookies, storage, downloads, host access,
or a native messaging bridge.
The five failure families
| Failure family | Boundary lost | Typical observable result |
|---|---|---|
| Publisher takeover | Store account or upload token becomes sufficient release authority | Validly distributed unauthorized package |
| Build substitution | CI output is not bound to reviewed source | Signed artifact contains unreviewed code or dependencies |
| Permission drift | Review considers code but not effective capability delta | Update gains broader host or browser access |
| Configuration-as-code | Remote JSON selects operations rather than bounded data | Store-reviewed package activates unreviewed behavior |
| Rollout blindness | Release success is inferred from upload completion | Vulnerable or unauthorized versions remain active in cohorts |
Remote hosted code restrictions are an important control, but the research question is broader. A remote response does not need to contain JavaScript to become logic. A configuration grammar with arbitrary operation names, URLs, selectors, scripts, or rule expressions can transfer the security decision out of the reviewed package.
A bounded synthetic release lab
The lab needs no store upload and no real user. Use a disposable extension ID, a local mock update
service, two inert packages, and one canary capability: incrementing a counter in extension-local
storage. Version 1.0.0 may increment only release_a; version 1.0.1 is tested against permission,
artifact, configuration, and rollout gates before it may increment release_b.
The positive test proves only that the authorized package and cohort can change the intended canary. Negative controls must show that a different digest, unexpected permission, expired config, wrong rollout cohort, and replayed approval create no state change.
Build an artifact contract
Record release facts next to the package rather than reconstructing them after an incident.
{
"extension_id": "lab-extension",
"version": "1.0.1",
"source_commit": "8d6d...e21",
"workflow_identity": "release.yml@refs/tags/v1.0.1",
"artifact_sha256": "7f4a...91c",
"manifest_sha256": "c1e3...a42",
"permissions": ["storage", "activeTab"],
"host_permissions": ["https://lab.invalid/*"],
"sbom_sha256": "0bd2...723",
"built_at": "2026-08-31T07:00:00Z"
}
The contract is useful only if the publisher verifies it against the artifact it uploads. A digest created after an uncontrolled upload is documentation, not authorization.
Treat manifest changes as security changes
The manifest is the extension’s declared authority surface. Diff it separately from application code and fail closed on unexpected additions.
const authority = (manifest) => ({
permissions: [...(manifest.permissions ?? [])].sort(),
optional_permissions: [...(manifest.optional_permissions ?? [])].sort(),
host_permissions: [...(manifest.host_permissions ?? [])].sort(),
content_scripts: (manifest.content_scripts ?? []).map(
({ matches, all_frames, world }) => ({
matches: [...matches].sort(),
all_frames: Boolean(all_frames),
world: world ?? "ISOLATED",
}),
),
externally_connectable: manifest.externally_connectable ?? null,
update_url: manifest.update_url ?? null,
});
Compare the normalized result with the previous production release. New host patterns, all_frames,
external callers, native messaging, debugger access, or an update-source change require a named
reviewer and an explicit rationale. A version bump is not that rationale.
Keep remote configuration below the policy line
Safer remote configuration selects among packaged, bounded behaviors:
{
"schema": 2,
"issued_at": "2026-08-31T07:00:00Z",
"expires_at": "2026-08-31T08:00:00Z",
"cohort": "canary-05",
"flags": { "new_panel": false },
"limits": { "batch_size": 20 }
}
High-risk configuration accepts a dispatcher, URL, expression, selector, or script-like data:
{
"operation": "run",
"target": "https://runtime-selected.example/*",
"steps": [{ "action": "inject", "payload": "server-controlled" }]
}
Reject unknown keys, enforce a small schema, bind the document to a release version and cohort, verify expiry, and default to the last known safe packaged behavior. Configuration signatures can authenticate a control plane; they do not make an overpowered grammar safe.
Rollout is a state machine, not a percentage
Reproduction methodology
- Inventory release identities, update sources, store roles, CI credentials, and runtime config.
- Build the same tagged source twice in isolated runners and compare outputs or explain every nondeterministic byte.
- Capture the unpacked manifest and package digest before upload.
- Test an unexpected permission and host pattern; the release gate must reject both.
- Offer an authorized package only to a synthetic canary cohort.
- Replay the approval with a different digest; it must not publish or promote.
- Serve expired, oversized, unknown-key, wrong-cohort, and wrong-version configuration.
- Prove rollback removes the tested effect and identify clients that remain on the old version.
Test matrix
| Dimension | Variants | Secure result |
|---|---|---|
| Source | Reviewed tag, moved tag, unreviewed commit | Only immutable reviewed source builds |
| Build | Clean runner, altered dependency, reused workspace | Artifact or provenance delta blocks release |
| Artifact | Expected digest, repackaged ZIP, manifest drift | Only exact authorized digest proceeds |
| Publisher | Human MFA, scoped workload identity, stale token | Short-lived scoped identity; stale token denied |
| Permissions | Same, added optional, broad host, native bridge | Any authority expansion requires explicit review |
| Update source | Store, approved enterprise URL, unexpected override | Inventory matches policy and installed state |
| Configuration | Valid, expired, replayed, unknown key, wrong cohort | Invalid documents create no privileged effect |
| Rollout | Canary, staged, pinned, offline, rollback | Every installed cohort is observable and bounded |
Evidence matrix
| Claim | Positive evidence | Negative control | Strong conclusion |
|---|---|---|---|
| Reviewed source produced the package | Commit, workflow identity, reproducible output, matching digest | Moved tag or changed dependency produces a different or rejected artifact | Tested artifact is bound to reviewed source and build context |
| Upload was authorized | Publisher actor and exact digest recorded | Same approval with another digest is denied | Approval cannot authorize an arbitrary package |
| Authority did not drift | Normalized manifest equals approved capability set | Added host or permission blocks promotion | Tested release cannot silently expand declared authority |
| Remote config is data | Valid bounded schema changes only documented flags | Operation, URL, unknown key, expiry, and replay are rejected | Tested control plane cannot select arbitrary privileged actions |
| Canary limits exposure | Only synthetic cohort receives candidate and reports version/digest | Non-canary client remains on production | Tested rollout selector is enforced |
| Rollback is complete | Known-safe version and config replace candidate | Candidate effect and digest disappear from every observed cohort | Tested fleet returned to the recorded safe state |
Hardening order
- Require phishing-resistant MFA for store owners and remove unused publisher roles.
- Replace long-lived upload secrets with short-lived, repository-bound workload identity where the store supports it.
- Build from immutable tags in isolated runners; pin actions and dependencies.
- Generate package digest, manifest snapshot, SBOM, and provenance before upload.
- Make the publisher accept only an approved digest and version.
- Gate manifest authority changes separately from code review.
- Keep remote config typed, bounded, expiring, cohort-bound, and incapable of selecting code-like operations.
- Measure installed versions and effective configuration by cohort.
- Rehearse both store rollback and kill-switch behavior without depending on the compromised publisher path.
Detection and incident evidence
Preserve store audit events, role changes, failed MFA, CI workflow identity, dependency-lock changes, artifact digests, manifest authority diffs, update-source policy, installed versions, configuration hashes, and privileged operation summaries. Never log cookies, page contents, tokens, or full URLs when a stable origin or operation identifier is sufficient.
{
"event": "extension_release_observed",
"extension_id": "lab-extension",
"version": "1.0.1",
"artifact_sha256": "7f4a...91c",
"manifest_policy": "approved",
"cohort": "canary-05",
"config_sha256": "2f91...bb0",
"decision": "promote-denied",
"reason": "unexpected-host-permission"
}
Final model
The package signature is necessary evidence, not the final verdict.
TRUSTED RELEASE
= expected publisher identity
+ reviewed immutable source
+ controlled build identity
+ authorized artifact digest
+ reviewed authority delta
+ bounded runtime configuration
+ observable rollout and rollback
Part 1 followed a message into a native process. Part 2 followed a page into extension authority. Part 3 closes the loop by proving how the privileged extension code reached the browser in the first place. The complete series has one rule: reachability, identity, signing, and delivery are inputs to authorization; none is authorization by itself.
How current is this note?
The latest source-review, content-update, or publication date is shown.
Primary public records were checked. Environment-specific behavior remains outside the claim unless separately reproduced.
