@@ -463,3 +463,255 @@ func ListOrgRepositorySecurityAdvisories(t translations.TranslationHelperFunc) i
463463 },
464464 )
465465}
466+
467+ func ReportSecurityVulnerability (t translations.TranslationHelperFunc ) inventory.ServerTool {
468+ return NewTool (
469+ ToolsetMetadataSecurityAdvisories ,
470+ mcp.Tool {
471+ Name : "report_security_vulnerability" ,
472+ Description : t ("TOOL_REPORT_SECURITY_VULNERABILITY_DESCRIPTION" , "Report a security vulnerability to the maintainers of a repository. Creates a private security advisory in 'triage' state." ),
473+ Annotations : & mcp.ToolAnnotations {
474+ Title : t ("TOOL_REPORT_SECURITY_VULNERABILITY_USER_TITLE" , "Report security vulnerability" ),
475+ ReadOnlyHint : false ,
476+ },
477+ InputSchema : & jsonschema.Schema {
478+ Type : "object" ,
479+ Properties : map [string ]* jsonschema.Schema {
480+ "owner" : {
481+ Type : "string" ,
482+ Description : "The owner of the repository." ,
483+ },
484+ "repo" : {
485+ Type : "string" ,
486+ Description : "The name of the repository." ,
487+ },
488+ "summary" : {
489+ Type : "string" ,
490+ Description : "A short summary of the security vulnerability." ,
491+ },
492+ "description" : {
493+ Type : "string" ,
494+ Description : "A detailed description of what the vulnerability entails." ,
495+ },
496+ "severity" : {
497+ Type : "string" ,
498+ Description : "The severity of the advisory. You must choose between setting this field or cvss_vector_string." ,
499+ Enum : []any {"critical" , "high" , "medium" , "low" },
500+ },
501+ "cvss_vector_string" : {
502+ Type : "string" ,
503+ Description : "The CVSS vector that calculates the severity of the advisory. You must choose between setting this field or severity." ,
504+ },
505+ "cwe_ids" : {
506+ Type : "array" ,
507+ Description : "A list of Common Weakness Enumeration (CWE) IDs (e.g. [\" CWE-79\" , \" CWE-89\" ])." ,
508+ Items : & jsonschema.Schema {
509+ Type : "string" ,
510+ },
511+ },
512+ "vulnerabilities" : {
513+ Type : "array" ,
514+ Description : "An array of products affected by the vulnerability." ,
515+ Items : & jsonschema.Schema {
516+ Type : "object" ,
517+ Properties : map [string ]* jsonschema.Schema {
518+ "package" : {
519+ Type : "object" ,
520+ Description : "The package affected by the vulnerability." ,
521+ Properties : map [string ]* jsonschema.Schema {
522+ "ecosystem" : {
523+ Type : "string" ,
524+ Description : "The package ecosystem (e.g., npm, pip, maven, rubygems)." ,
525+ },
526+ "name" : {
527+ Type : "string" ,
528+ Description : "The package name." ,
529+ },
530+ },
531+ Required : []string {"ecosystem" , "name" },
532+ },
533+ "vulnerable_version_range" : {
534+ Type : "string" ,
535+ Description : "The range of versions that are vulnerable (e.g., '>= 1.0.0, < 1.0.1')." ,
536+ },
537+ "patched_versions" : {
538+ Type : "string" ,
539+ Description : "The versions that patch the vulnerability (e.g., '1.0.1')." ,
540+ },
541+ "vulnerable_functions" : {
542+ Type : "array" ,
543+ Description : "The names of vulnerable functions in the package." ,
544+ Items : & jsonschema.Schema {
545+ Type : "string" ,
546+ },
547+ },
548+ },
549+ Required : []string {"package" },
550+ },
551+ },
552+ "start_private_fork" : {
553+ Type : "boolean" ,
554+ Description : "Whether to create a temporary private fork of the repository to collaborate on a fix. Default: false" ,
555+ Default : json .RawMessage (`false` ),
556+ },
557+ },
558+ Required : []string {"owner" , "repo" , "summary" , "description" },
559+ },
560+ },
561+ []scopes.Scope {scopes .SecurityEvents },
562+ func (ctx context.Context , deps ToolDependencies , _ * mcp.CallToolRequest , args map [string ]any ) (* mcp.CallToolResult , any , error ) {
563+ owner , err := RequiredParam [string ](args , "owner" )
564+ if err != nil {
565+ return utils .NewToolResultError (err .Error ()), nil , nil
566+ }
567+ repo , err := RequiredParam [string ](args , "repo" )
568+ if err != nil {
569+ return utils .NewToolResultError (err .Error ()), nil , nil
570+ }
571+ summary , err := RequiredParam [string ](args , "summary" )
572+ if err != nil {
573+ return utils .NewToolResultError (err .Error ()), nil , nil
574+ }
575+ description , err := RequiredParam [string ](args , "description" )
576+ if err != nil {
577+ return utils .NewToolResultError (err .Error ()), nil , nil
578+ }
579+
580+ severity , err := OptionalParam [string ](args , "severity" )
581+ if err != nil {
582+ return utils .NewToolResultError (err .Error ()), nil , nil
583+ }
584+ cvssVectorString , err := OptionalParam [string ](args , "cvss_vector_string" )
585+ if err != nil {
586+ return utils .NewToolResultError (err .Error ()), nil , nil
587+ }
588+
589+ // Validate that only one of severity or cvss_vector_string is set
590+ if severity != "" && cvssVectorString != "" {
591+ return utils .NewToolResultError ("cannot specify both severity and cvss_vector_string" ), nil , nil
592+ }
593+
594+ cweIDs , err := OptionalStringArrayParam (args , "cwe_ids" )
595+ if err != nil {
596+ return utils .NewToolResultError (err .Error ()), nil , nil
597+ }
598+
599+ startPrivateFork , err := OptionalParam [bool ](args , "start_private_fork" )
600+ if err != nil {
601+ return utils .NewToolResultError (err .Error ()), nil , nil
602+ }
603+
604+ client , err := deps .GetClient (ctx )
605+ if err != nil {
606+ return nil , nil , fmt .Errorf ("failed to get GitHub client: %w" , err )
607+ }
608+
609+ // Build the request body
610+ type vulnerabilityReport struct {
611+ Summary string `json:"summary"`
612+ Description string `json:"description"`
613+ Severity * string `json:"severity,omitempty"`
614+ CVSSVectorString * string `json:"cvss_vector_string,omitempty"`
615+ CWEIDs * []string `json:"cwe_ids,omitempty"`
616+ Vulnerabilities * []* github.AdvisoryVulnerability `json:"vulnerabilities,omitempty"`
617+ StartPrivateFork * bool `json:"start_private_fork,omitempty"`
618+ }
619+
620+ report := & vulnerabilityReport {
621+ Summary : summary ,
622+ Description : description ,
623+ }
624+
625+ if severity != "" {
626+ report .Severity = & severity
627+ }
628+ if cvssVectorString != "" {
629+ report .CVSSVectorString = & cvssVectorString
630+ }
631+
632+ if len (cweIDs ) > 0 {
633+ report .CWEIDs = & cweIDs
634+ }
635+
636+ // Handle vulnerabilities array
637+ if vulnsData , ok := args ["vulnerabilities" ]; ok {
638+ if vulnsArray , ok := vulnsData .([]any ); ok {
639+ var vulnerabilities []* github.AdvisoryVulnerability
640+ for _ , v := range vulnsArray {
641+ if vulnMap , ok := v .(map [string ]any ); ok {
642+ vuln := & github.AdvisoryVulnerability {}
643+
644+ // Parse package
645+ if pkgData , ok := vulnMap ["package" ].(map [string ]any ); ok {
646+ pkg := & github.VulnerabilityPackage {}
647+ if ecosystem , ok := pkgData ["ecosystem" ].(string ); ok {
648+ pkg .Ecosystem = & ecosystem
649+ }
650+ if name , ok := pkgData ["name" ].(string ); ok {
651+ pkg .Name = & name
652+ }
653+ vuln .Package = pkg
654+ }
655+
656+ // Parse other fields
657+ if versionRange , ok := vulnMap ["vulnerable_version_range" ].(string ); ok {
658+ vuln .VulnerableVersionRange = & versionRange
659+ }
660+ if patchedVersions , ok := vulnMap ["patched_versions" ].(string ); ok {
661+ vuln .PatchedVersions = & patchedVersions
662+ }
663+ if vulnFuncs , ok := vulnMap ["vulnerable_functions" ].([]any ); ok {
664+ var functions []string
665+ for _ , f := range vulnFuncs {
666+ if funcStr , ok := f .(string ); ok {
667+ functions = append (functions , funcStr )
668+ }
669+ }
670+ if len (functions ) > 0 {
671+ vuln .VulnerableFunctions = functions
672+ }
673+ }
674+
675+ vulnerabilities = append (vulnerabilities , vuln )
676+ }
677+ }
678+ report .Vulnerabilities = & vulnerabilities
679+ }
680+ }
681+
682+ if startPrivateFork {
683+ report .StartPrivateFork = & startPrivateFork
684+ }
685+
686+ // Make HTTP POST request to the security-advisories/reports endpoint
687+ // The go-github library doesn't have this method yet, so we use NewRequest directly
688+ url := fmt .Sprintf ("repos/%s/%s/security-advisories/reports" , owner , repo )
689+ req , err := client .NewRequest ("POST" , url , report )
690+ if err != nil {
691+ return nil , nil , fmt .Errorf ("failed to create request: %w" , err )
692+ }
693+
694+ var advisory github.SecurityAdvisory
695+ resp , err := client .Do (ctx , req , & advisory )
696+ if err != nil {
697+ return nil , nil , fmt .Errorf ("failed to report security vulnerability: %w" , err )
698+ }
699+ defer func () { _ = resp .Body .Close () }()
700+
701+ if resp .StatusCode != http .StatusCreated {
702+ body , err := io .ReadAll (resp .Body )
703+ if err != nil {
704+ return nil , nil , fmt .Errorf ("failed to read response body: %w" , err )
705+ }
706+ return ghErrors .NewGitHubAPIStatusErrorResponse (ctx , "failed to report security vulnerability" , resp , body ), nil , nil
707+ }
708+
709+ r , err := json .Marshal (advisory )
710+ if err != nil {
711+ return nil , nil , fmt .Errorf ("failed to marshal advisory response: %w" , err )
712+ }
713+
714+ return utils .NewToolResultText (string (r )), nil , nil
715+ },
716+ )
717+ }
0 commit comments