11import type { Endpoints } from "@octokit/types" ;
22import type {
3+ IssuesOpenedEvent ,
34 PullRequestOpenedEvent ,
45 PullRequestReadyForReviewEvent ,
56 WebhookEvent ,
@@ -26,6 +27,48 @@ async function getBotMessage(ai: Ai, prompt: string) {
2627 return message . response ;
2728}
2829
30+ async function analyzeIssueSecurity (
31+ ai : Ai ,
32+ issueTitle : string ,
33+ issueBody : string
34+ ) : Promise < boolean > {
35+ const prompt = `Analyze this GitHub issue to determine if it's likely reporting a security vulnerability or security concern.
36+
37+ Issue Title: ${ issueTitle }
38+
39+ Issue Body: ${ issueBody }
40+
41+ Look for keywords and patterns that suggest this is a security report, such as:
42+ - Vulnerability, exploit, security flaw, CVE
43+ - Authentication bypass, privilege escalation
44+ - XSS, SQL injection, CSRF, RCE
45+ - Unauthorized access, data exposure
46+ - Security disclosure, responsible disclosure
47+
48+ Respond with only "YES" if this appears to be a security-related issue, or "NO" if it appears to be a regular bug report or feature request.` ;
49+
50+ const chat = {
51+ messages : [
52+ {
53+ role : "system" ,
54+ content :
55+ "You are a security analyst assistant that helps identify potential security vulnerability reports in GitHub issues. Respond only with YES or NO." ,
56+ } ,
57+ {
58+ role : "user" ,
59+ content : prompt ,
60+ } ,
61+ ] as RoleScopedChatInput [ ] ,
62+ } ;
63+
64+ const message = await ai . run ( "@cf/meta/llama-2-7b-chat-int8" , chat ) ;
65+ if ( ! ( "response" in message ) || ! message . response ) {
66+ return false ;
67+ }
68+
69+ return message . response . trim ( ) . toUpperCase ( ) === "YES" ;
70+ }
71+
2972type PRList = Endpoints [ "GET /repos/{owner}/{repo}/pulls" ] [ "response" ] [ "data" ] ;
3073async function getPrs ( pat : string ) {
3174 const workersSdk = await fetch (
@@ -312,6 +355,12 @@ function isPullRequestReadyForReviewEvent(
312355 return "action" in message && message . action === "ready_for_review" ;
313356}
314357
358+ function isIssueOpenedEvent (
359+ message : WebhookEvent
360+ ) : message is IssuesOpenedEvent {
361+ return "issue" in message && message . action === "opened" ;
362+ }
363+
315364function sendReviewMessage ( webhookUrl : string , message : WebhookEvent ) {
316365 if (
317366 ( isPullRequestOpenedEvent ( message ) ||
@@ -364,6 +413,66 @@ function sendReviewMessage(webhookUrl: string, message: WebhookEvent) {
364413 }
365414}
366415
416+ async function sendSecurityAlert ( webhookUrl : string , issue : IssuesOpenedEvent ) {
417+ return sendMessage (
418+ webhookUrl ,
419+ {
420+ cardsV2 : [
421+ {
422+ cardId : "unique-card-id" ,
423+ card : {
424+ header : {
425+ title : "🚨 Potential Security Issue Detected" ,
426+ subtitle : `Issue #${ issue . issue . number } in ${ issue . repository . full_name } ` ,
427+ imageUrl : issue . sender . avatar_url ,
428+ imageType : "CIRCLE" ,
429+ imageAltText : "Reporter Avatar" ,
430+ } ,
431+ sections : [
432+ {
433+ collapsible : false ,
434+ widgets : [
435+ {
436+ textParagraph : {
437+ text : `<b>Title:</b> ${ issue . issue . title } \n\n<b>Reporter:</b> ${ issue . sender . login } ` ,
438+ } ,
439+ } ,
440+ {
441+ buttonList : {
442+ buttons : [
443+ {
444+ text : "View Issue" ,
445+ onClick : {
446+ openLink : {
447+ url : issue . issue . html_url ,
448+ } ,
449+ } ,
450+ } ,
451+ ] ,
452+ } ,
453+ } ,
454+ ] ,
455+ } ,
456+ {
457+ collapsible : true ,
458+ uncollapsibleWidgetsCount : 0 ,
459+ widgets : [
460+ {
461+ textParagraph : {
462+ text : issue . issue . body || "No description provided" ,
463+ } ,
464+ } ,
465+ ] ,
466+ } ,
467+ ] ,
468+ } ,
469+ } ,
470+ ] ,
471+ } ,
472+ "security-alerts"
473+ ) ;
474+ }
475+
367476async function sendUpcomingReleaseMessage ( pat : string , webhookUrl : string ) {
368477 const releasePr = await getVersionPackagesPR ( pat ) ;
369478
@@ -620,6 +729,17 @@ export default {
620729 if ( url . pathname === "/github" ) {
621730 const body = await request . json < WebhookEvent > ( ) ;
622731 await sendReviewMessage ( env . PROD_WEBHOOK , body ) ;
732+
733+ if ( isIssueOpenedEvent ( body ) ) {
734+ const isSecurityIssue = await analyzeIssueSecurity (
735+ env . AI ,
736+ body . issue . title ,
737+ body . issue . body || ""
738+ ) ;
739+ if ( isSecurityIssue ) {
740+ await sendSecurityAlert ( env . ALERTS_WEBHOOK , body ) ;
741+ }
742+ }
623743 }
624744
625745 if ( url . pathname . startsWith ( "/pr-project" ) && request . method === "POST" ) {
0 commit comments