@@ -63,13 +63,27 @@ export const listCustomBlocksQuerySchema = z.object({
6363 workspaceId : workspaceIdSchema ,
6464} )
6565
66+ /**
67+ * Icon URLs are rendered as org-wide `<img>` sources, so only https URLs and
68+ * internal file-serve paths (what the icon upload UI stores) are accepted —
69+ * never data:/blob:/other schemes an admin could smuggle into shared metadata.
70+ * Shared with the copilot deploy_custom_block handler's pass-through branch.
71+ */
72+ export function isAllowedCustomBlockIconUrl ( value : string ) : boolean {
73+ return value . startsWith ( 'https://' ) || value . startsWith ( '/api/files/serve/' )
74+ }
75+
76+ const iconUrlSchema = z . string ( ) . min ( 1 ) . max ( 2048 ) . refine ( isAllowedCustomBlockIconUrl , {
77+ message : 'iconUrl must be an https URL or an internal /api/files/serve/ path' ,
78+ } )
79+
6680export const publishCustomBlockBodySchema = z . object ( {
6781 workspaceId : workspaceIdSchema ,
6882 workflowId : workflowIdSchema ,
6983 name : z . string ( ) . min ( 1 , 'Name is required' ) . max ( 60 , 'Name must be 60 characters or fewer' ) ,
7084 description : z . string ( ) . max ( 280 , 'Description must be 280 characters or fewer' ) . default ( '' ) ,
71- /** Uploaded icon image URL; omit for the default icon. */
72- iconUrl : z . string ( ) . min ( 1 ) . max ( 2048 ) . optional ( ) ,
85+ /** Uploaded icon image URL (https or internal serve path) ; omit for the default icon. */
86+ iconUrl : iconUrlSchema . optional ( ) ,
7387 /** Per-input placeholder hints keyed by Start field id; the field set itself is always derived from the deployment. */
7488 inputs : z . array ( inputPlaceholderSchema ) . max ( 50 ) . optional ( ) ,
7589 /** Curated outputs; omit/empty to expose the child's whole result. */
@@ -87,8 +101,8 @@ export const updateCustomBlockBodySchema = z
87101 name : z . string ( ) . min ( 1 ) . max ( 60 ) . optional ( ) ,
88102 description : z . string ( ) . max ( 280 ) . optional ( ) ,
89103 enabled : z . boolean ( ) . optional ( ) ,
90- /** A URL sets/replaces the icon; `null` clears it (default icon). */
91- iconUrl : z . string ( ) . min ( 1 ) . max ( 2048 ) . nullable ( ) . optional ( ) ,
104+ /** A URL (https or internal serve path) sets/replaces the icon; `null` clears it (default icon). */
105+ iconUrl : iconUrlSchema . nullable ( ) . optional ( ) ,
92106 inputs : z . array ( inputPlaceholderSchema ) . max ( 50 ) . optional ( ) ,
93107 exposedOutputs : z . array ( exposedOutputSchema ) . max ( 50 ) . optional ( ) ,
94108 } )
0 commit comments