Repository navigation
Conversation
Theme app extensions can now list the origins they contact in
shopify.extension.toml:
[extensions.capabilities.network_access]
allowed_sources = ["https://api.example.com", "https://*.example-cdn.com"]
Each entry must be an HTTPS origin with no path, query or fragment.
Entries are normalized and deduplicated, and a list can have up to 20
origins. The theme deploy config includes capabilities only when the
table is declared, so existing payloads don't change. UI extensions keep
the boolean network_access.
Use a wildcard example under example.com in the error hint, and test empty labels, trailing dots, newlines and single-label wildcards.
Knip flags it as unused outside the module.
pt2pham
left a comment
There was a problem hiding this comment.
I didn't give this a tophat but the proposed changes make sense to me to just include this as a field for theme extensions
|
/snapit |
|
🫰✨ Thanks @jplhomer! Your snapshot has been published to npm. Built from Test the snapshot by installing your package globally: pnpm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20261025175303Caution After installing, validate the version by running |
| {invalid_type_error: 'network_access must be a table with an allowed_sources list for theme app extensions'}, | ||
| ) | ||
|
|
||
| const ThemeCapabilitiesSchema = CapabilitiesSchema.extend({ |
There was a problem hiding this comment.
why extend CapabilitiesSchema if you are going to only need network_access and not even with the same type?.
Isn't it easier to just define ThemeCapabilitiesSchema without extending anything? it should simplify everything.
There was a problem hiding this comment.
(i went ahead and pushed a commit with this)
7192199 to
df8579f
Compare
df8579f to
b3d5736
Compare
isaacroldan
left a comment
There was a problem hiding this comment.
The IP-validation fix is now separate from the schema changes.
🤖 Generated by the review-pr skill
| const labels = host.split('.') | ||
| if (labels.length < 2 || !labels.every((label) => HOST_LABEL_PATTERN.test(label))) return undefined | ||
| const parsedOrigin = safeParseURL(`https://${host}`) | ||
| if (!parsedOrigin || isIP(parsedOrigin.hostname) !== 0) return undefined |
There was a problem hiding this comment.
Fixed in 652c7903c7. The previous decimal-only last-label check accepted https://127.0.0x1 and https://0x7f.0x1. URL parsing converts both to https://127.0.0.1, so checking only the input spelling missed these IP addresses.
The isolated commit parses the hostname with safeParseURL and checks the normalized hostname with isIP. It also checks wildcard base hosts and rejects invalid URL hosts. Regression tests cover these cases.
🤖 Generated by the review-pr skill
Important
Needs server support before release. The platform rejects unknown keys in a theme app extension's deploy config today, so deploying an extension that declares
[extensions.capabilities.network_access]fails until the server acceptscapabilities.network_access.allowed_sources. Extensions that don't declare it send the same payload as before and aren't affected.WHY are these changes introduced?
Theme app extensions load scripts and make requests on storefronts, but there's no way for them to say which origins they contact. Declaring them lets the platform build a storefront Content Security Policy for each extension, starting in report-only mode. Undeclared origins may be reported now and blocked later.
UI extensions (checkout, customer accounts) already use a boolean
capabilities.network_access. They run sandboxed, so they keep the boolean. Theme app extensions use the same key as a table.WHAT is this pull request doing?
Theme app extensions can declare the origins they contact in
shopify.extension.toml:network_accessis a table withallowed_sources. The sharedCapabilitiesSchemaand UI dev payload remain unchanged. Extension model and factory generics use a common configuration constraint withoutcapabilities, so each extension schema defines its own capability types without castingThemeSchema. Their default configuration type remainsBaseConfigType.deployConfigaddscapabilities: {network_access: {allowed_sources: [...]}}next totheme_extensiononly when the table is declared. Otherwise the payload stays{theme_extension: {files: {}}}, byte for byte.Validation rules (each entry in
allowed_sources):https://only, with a host and an optional port. No path, query, fragment or credentials, sohttp://, scheme-less values, CSP keywords like'self', and*are rejected.localhostis rejected). Labels can't start or end with a hyphen, and the host is at most 253 characters. URL parsing rejects invalid hosts, and an IP check rejects IP addresses after URL normalization, including hexadecimal forms. The same checks apply to wildcard base hosts.*.wildcard is allowed, and it must cover at least two labels (https://*.cdn.example.comis fine, buthttps://*.comandhttps://api.*.example.comaren't). CSP host sources support this form, and extensions often load from CDN subdomains.:443is dropped, and other ports are kept./is dropped, and duplicates are removed after normalization. The normalized list is what gets deployed.Omitted vs. empty:
[extensions.capabilities.network_access]tableallowed_sources = []allowed_sources: []allowed_sourcesallowed_sources is required. Use an empty list if your extension contacts no other origins.network_access = true/false(the UI extension boolean)allowed_sourceslist. UI extensions still accept booleans.app devneeds no separate change: dev sessions build the app manifest from the samedeployConfig, so declared sources reach the server there too, and are subject to the same server-support caveat.There's no generated JSON schema or docs for extension TOML in this repo. The field should be documented in the theme app extension configuration docs once the server supports it.
Tests (
packages/app):pnpm vitest run src/cli/models/extensions/specifications/theme.test.ts: 46 tests pass. They cover valid origins and normalization, empty and omitted lists, rejected theme booleans, missingallowed_sources, wrong types, HTTP, paths, queries, fragments, credentials, wildcard placement, invalid hosts, decimal and hexadecimal IPs, ports, CSP keywords and injection, the 20-origin limit, deploy payloads, and UI extensions retaining boolean support.loader.test.ts: new test that loads a theme extension TOML with the table and checks the deploy config.src/cli/models/extensions/,dev/extension/payload,app/loader,app/appandutilities/extensions/theme: 36 files, 664 tests pass.pnpm tsc --noEmit,pnpm eslinton changed files andprettier --check: clean.How to manually test your changes?
shopify app buildorshopify app deploy. The config loads, and the deploy payload includes the normalizedallowed_sources. Deploy succeeds only once the server supports the field.http://api.example.comorhttps://api.example.com/path. Loading fails with the invalid-entry error.{"theme_extension":{"files":{}}}as before.Post-release steps
Document
[extensions.capabilities.network_access] allowed_sourcesfor theme app extensions once the server supports it.Checklist
patchfor bug fixes ·minorfor new features ·majorfor breaking changes) and added a changeset withpnpm changeset add