{"record":{"id":"bd8171a5d7a9013b","repo":"router-for-me/CLIProxyAPI","slug":"artifacts-d-pinned-artifact-url-must-not-contai-bd8171","errorCode":null,"errorMessage":"artifacts[%d]: pinned artifact url must not contain query or fragment","messagePattern":"artifacts\\[(.+?)\\]: pinned artifact url must not contain query or fragment","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginstore/manifest.go","lineNumber":160,"sourceCode":"\t\t\treturn fmt.Errorf(\"release-tag %q resolves version %q, want %q\", releaseTag, releaseVersion, normalizeVersion(version))\n\t\t}\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported install type %q\", m.Install.Type)\n\t}\n}\n\nfunc validatePinnedArtifactURLs(artifacts []Artifact) error {\n\tfor index, artifact := range artifacts {\n\t\tparsed, errParse := url.Parse(strings.TrimSpace(artifact.URL))\n\t\tif errParse != nil {\n\t\t\treturn fmt.Errorf(\"artifacts[%d]: invalid artifact url\", index)\n\t\t}\n\t\tif parsed.User != nil {\n\t\t\treturn fmt.Errorf(\"artifacts[%d]: pinned artifact url must not contain credentials\", index)\n\t\t}\n\t\tif parsed.RawQuery != \"\" || parsed.Fragment != \"\" {\n\t\t\treturn fmt.Errorf(\"artifacts[%d]: pinned artifact url must not contain query or fragment\", index)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc validateManifestPluginID(id string) error {\n\tid = strings.TrimSpace(id)\n\tif id == \"\" {\n\t\treturn fmt.Errorf(\"missing required field id\")\n\t}\n\tif !validPluginID(id) {\n\t\treturn fmt.Errorf(\"invalid plugin id %q\", id)\n\t}\n\treturn nil\n}\n\nfunc validateManifestSourceURL(sourceURL string) error {\n\tsourceURL = strings.TrimSpace(sourceURL)","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/manifest.go#L142-L178","documentation":"validatePinnedArtifactURLs() requires artifact URLs to be bare: any query string (?...) or fragment (#...) makes the pin ambiguous or a channel for hidden tokens, so artifacts[index] is rejected when parsed.RawQuery or parsed.Fragment is non-empty.","triggerScenarios":"Artifact URL with tracking params (\"...plug.so?src=registry\"), a pre-signed AWS/GCS URL (\"...?X-Goog-Signature=...\"), or an anchor (\"...plug.so#sha256-abc\").","commonSituations":"Copying CDN or object-store links that append signature/token query params; attaching checksum intents as fragments; analytics parameters added by whatever tool generated the link.","solutions":["Strip the query string and fragment from the artifact URL; if a checksum is needed, put it in the manifest's dedicated checksum field if the plan schema provides one","Use plain, stable, public artifact paths (path tokens are fine, query strings are not)","If you relied on signed URLs for access control, switch to public artifacts or path-based short tokens"],"exampleFix":"# before\nartifacts:\n  - url: https://dl.acme.io/plug.so?token=abc123\n# artifacts[0]: pinned artifact url must not contain query or fragment\n\n# after\nartifacts:\n  - url: https://dl.acme.io/plug.so","handlingStrategy":"validation","validationCode":"for i, a := range plan.Artifacts {\n    if u, err := url.Parse(strings.TrimSpace(a.URL)); err == nil && (u.RawQuery != \"\" || u.Fragment != \"\") {\n        return fmt.Errorf(\"artifacts[%d] carries query/fragment\", i)\n    }\n}\n_ = m.Validate()","typeGuard":"func barePinnedURL(raw string) bool { u, err := url.Parse(strings.TrimSpace(raw)); return err == nil && u.User == nil && u.RawQuery == \"\" && u.Fragment == \"\" }","tryCatchPattern":"if err := m.Validate(); err != nil && strings.Contains(err.Error(), \"query or fragment\") { /* strip ?... and #..., re-validate */ }","preventionTips":["Use plain artifact paths, not signed/tracking URLs","Put checksums in the manifest's checksum fields, not URL fragments"],"tags":["pluginstore","security","artifact-url","query-string"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}