@@ -43,6 +43,10 @@ func run(name string) error {
4343 return err
4444 }
4545
46+ if err := isCommitPinnedIfNecessary (name ); err != nil {
47+ return err
48+ }
49+
4650 if err := areSecretsValid (name ); err != nil {
4751 return err
4852 }
@@ -72,10 +76,21 @@ func run(name string) error {
7276 return nil
7377}
7478
79+ // legacyNameExceptions enumerates catalog entries added before current naming rules.
80+ var legacyNameExceptions = map [string ]bool {
81+ "SQLite" : true ,
82+ "osp_marketing_tools" : true ,
83+ "youtube_transcript" : true ,
84+ }
85+
7586// check if the name is a valid
7687func isNameValid (name string ) error {
7788 // check if name has only letters, numbers, and hyphens
7889 if ! regexp .MustCompile (`^[a-z0-9-]+$` ).MatchString (name ) {
90+ if legacyNameExceptions [name ] {
91+ fmt .Printf ("⚠️ Name %s is grandfathered and bypasses naming rules.\n " , name )
92+ return nil
93+ }
7994 return fmt .Errorf ("name is not valid. It must be a lowercase string with only letters, numbers, and hyphens" )
8095 }
8196
@@ -104,21 +119,63 @@ func isDirectoryValid(name string) error {
104119 return nil
105120}
106121
122+ var commitSHA1Pattern = regexp .MustCompile (`^[a-f0-9]{40}$` )
123+
124+ // isCommitPinnedIfNecessary ensures that every local server is pinned to a specific commit.
125+ func isCommitPinnedIfNecessary (name string ) error {
126+ server , err := readServerYaml (name )
127+ if err != nil {
128+ return err
129+ }
130+
131+ if server .Type != "server" {
132+ fmt .Println ("✅ Commit pin not required (non-local server)" )
133+ return nil
134+ }
135+
136+ if server .Source .Commit == "" {
137+ return fmt .Errorf ("local server must specify source.commit to pin the audited revision" )
138+ }
139+
140+ if ! commitSHA1Pattern .MatchString (strings .ToLower (server .Source .Commit )) {
141+ return fmt .Errorf ("source.commit must be a 40-character lowercase SHA1 (got %q)" , server .Source .Commit )
142+ }
143+
144+ fmt .Println ("✅ Commit is pinned" )
145+ return nil
146+ }
147+
148+ // secretNamePattern validates that secret names match the expected prefix.name
149+ // format requirement.
150+ var secretNamePattern = regexp .MustCompile (`^[A-Za-z0-9_-]+\.[A-Za-z0-9._-]+$` )
151+
152+ // legacySecretNameExceptions enumerates secrets defined before the current
153+ // naming rules were introduced.
154+ var legacySecretNameExceptions = map [string ]map [string ]bool {
155+ "nasdaq-data-link" : {
156+ "nasdaq_data_link_api_key" : true ,
157+ },
158+ "sec-edgar" : {
159+ "sec_edgar_user_agent" : true ,
160+ },
161+ }
162+
107163// check if the secrets are valid
108- // secrets must be prefixed with the name of the server
109164func areSecretsValid (name string ) error {
110- // read the server.yaml file
111165 server , err := readServerYaml (name )
112166 if err != nil {
113167 return err
114168 }
115169
116- // check if the server.yaml file has a valid secrets
117- if len (server .Config .Secrets ) > 0 {
118- for _ , secret := range server .Config .Secrets {
119- if ! strings .HasPrefix (secret .Name , name + "." ) {
120- return fmt .Errorf ("secret %s is not valid. It must be prefixed with the name of the server" , secret .Name )
170+ // Ensure that all secrets match the expected format. We no longer require
171+ // that the prefix matches the server name.
172+ for _ , secret := range server .Config .Secrets {
173+ if ! secretNamePattern .MatchString (secret .Name ) {
174+ if legacySecretNameExceptions [name ][secret.Name ] {
175+ fmt .Printf ("⚠️ Secret %s for %s is grandfathered and bypasses naming rules.\n " , secret .Name , name )
176+ continue
121177 }
178+ return fmt .Errorf ("secret %s is not valid. It must use prefix.name format with alphanumeric characters, hyphen, period, or underscore" , secret .Name )
122179 }
123180 }
124181
@@ -182,44 +239,51 @@ func isIconValid(name string) error {
182239 }
183240
184241 if server .About .Icon == "" {
185- fmt .Println ("🛑 No icon found" )
242+ fmt .Println ("⚠️ No icon found" )
186243 return nil
187244 }
188245 // fetch the image and check the size
189246 resp , err := http .Get (server .About .Icon )
190247 if err != nil {
191- fmt .Println ("🛑 Icon could not be fetched" )
248+ fmt .Println ("⚠️ Icon could not be fetched" )
192249 return nil
193250 }
194251 defer resp .Body .Close ()
195252
196253 if resp .StatusCode != 200 {
197- fmt .Printf ("🛑 Icon could not be fetched, status code: %d, url: %s\n " , resp .StatusCode , server .About .Icon )
254+ fmt .Printf ("⚠️ Icon could not be fetched, status code: %d, url: %s\n " , resp .StatusCode , server .About .Icon )
198255 return nil
199256 }
200257 if resp .ContentLength > 2 * 1024 * 1024 {
201- fmt .Println ("🛑 Icon is too large. It must be less than 2MB" )
258+ fmt .Println ("⚠️ Icon is too large. It must be less than 2MB" )
202259 return nil
203260 }
204261
205- // Check content type for SVG support
262+ // Check content type for SVG, favicon, and WebP support
206263 contentType := resp .Header .Get ("Content-Type" )
207- if contentType == "image/svg+xml" {
264+ switch contentType {
265+ case "image/svg+xml" :
208266 fmt .Println ("✅ Icon is valid (SVG)" )
209267 return nil
268+ case "image/x-icon" :
269+ fmt .Println ("✅ Icon is valid (favicon)" )
270+ return nil
271+ case "image/webp" :
272+ fmt .Println ("✅ Icon is valid (WebP)" )
273+ return nil
210274 }
211275
212276 img , format , err := image .DecodeConfig (resp .Body )
213277 if err != nil {
214278 return err
215279 }
216- if format != "png" {
217- fmt .Println ("🛑 Icon is not a png or svg. It must be a png or svg" )
280+ if format != "png" && format != "jpeg" {
281+ fmt .Println ("⚠️ Icon is not a png or svg. It must be a png or svg" )
218282 return nil
219283 }
220284
221285 if img .Width > 512 || img .Height > 512 {
222- fmt .Println ("🛑 Icon is too large. It must be less than 512x512" )
286+ fmt .Println ("⚠️ Icon is too large. It must be less than 512x512" )
223287 return nil
224288 }
225289
@@ -304,6 +368,11 @@ func hasValidTools(server servers.Server) error {
304368 return nil
305369}
306370
371+ // Some special entries bypass the dynamic tools requirement.
372+ var oauthDynamicToolExceptions = map [string ]bool {
373+ "github-official" : true ,
374+ }
375+
307376// check if servers with OAuth have dynamic tools enabled
308377func isOAuthDynamicValid (name string ) error {
309378 server , err := readServerYaml (name )
@@ -314,7 +383,11 @@ func isOAuthDynamicValid(name string) error {
314383 // If server has OAuth configuration, it must have dynamic tools enabled
315384 if len (server .OAuth ) > 0 {
316385 if server .Dynamic == nil || ! server .Dynamic .Tools {
317- return fmt .Errorf ("server with OAuth must have 'dynamic: tools: true' configuration" )
386+ if oauthDynamicToolExceptions [name ] {
387+ fmt .Printf ("⚠️ OAuth dynamic rule bypassed for %s (special configuration).\n " , name )
388+ } else {
389+ return fmt .Errorf ("server with OAuth must have 'dynamic: tools: true' configuration" )
390+ }
318391 }
319392 }
320393
0 commit comments