From a618b1457e1fc8cd5bebe31d136b8baab0b6aae6 Mon Sep 17 00:00:00 2001 From: 12 <70921683+memkizz@users.noreply.github.com> Date: Thu, 12 Feb 2026 08:57:11 +0000 Subject: [PATCH 1/8] Add inital adivsories --- scripts/audit/run_audit.sh | 23 ++- .../mcp_servers/security_advisories.py | 150 ++++++++++++++++++ .../web_application_security_expert.yaml | 1 + .../audit/audit_issue_local_iter.yaml | 12 +- .../audit/classify_application_local.yaml | 21 ++- .../audit/fetch_security_advisories.yaml | 31 ++++ .../toolboxes/security_advisories.yaml | 15 ++ 7 files changed, 242 insertions(+), 11 deletions(-) create mode 100644 src/seclab_taskflows/mcp_servers/security_advisories.py create mode 100644 src/seclab_taskflows/taskflows/audit/fetch_security_advisories.yaml create mode 100644 src/seclab_taskflows/toolboxes/security_advisories.yaml diff --git a/scripts/audit/run_audit.sh b/scripts/audit/run_audit.sh index ea6e90f..ae19b62 100755 --- a/scripts/audit/run_audit.sh +++ b/scripts/audit/run_audit.sh @@ -4,14 +4,35 @@ set -e +USE_ADVISORY=false + +# Parse flags +while [[ "$1" == --* ]]; do + case "$1" in + --advisory) + USE_ADVISORY=true + shift + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + if [ -z "$1" ]; then - echo "Usage: $0 "; + echo "Usage: $0 [--advisory] "; exit 1; fi python -m seclab_taskflow_agent -t seclab_taskflows.taskflows.audit.fetch_source_code -g repo="$1" python -m seclab_taskflow_agent -t seclab_taskflows.taskflows.audit.identify_applications -g repo="$1" python -m seclab_taskflow_agent -t seclab_taskflows.taskflows.audit.gather_web_entry_point_info -g repo="$1" + +if [ "$USE_ADVISORY" = true ]; then + python -m seclab_taskflow_agent -t seclab_taskflows.taskflows.audit.fetch_security_advisories -g repo="$1" +fi + python -m seclab_taskflow_agent -t seclab_taskflows.taskflows.audit.classify_application_local -g repo="$1" python -m seclab_taskflow_agent -t seclab_taskflows.taskflows.audit.audit_issue_local_iter -g repo="$1" diff --git a/src/seclab_taskflows/mcp_servers/security_advisories.py b/src/seclab_taskflows/mcp_servers/security_advisories.py new file mode 100644 index 0000000..86b4701 --- /dev/null +++ b/src/seclab_taskflows/mcp_servers/security_advisories.py @@ -0,0 +1,150 @@ +# SPDX-FileCopyrightText: 2025 GitHub +# SPDX-License-Identifier: MIT + +import logging +from fastmcp import FastMCP +from pydantic import Field +import httpx +import json +import os +from datetime import datetime, timedelta +from seclab_taskflow_agent.path_utils import log_file_name, mcp_data_dir +from seclab_taskflow_agent.mcp_servers.memcache.memcache_backend.sqlite import SqliteBackend + +logging.basicConfig( + level=logging.DEBUG, + format="%(asctime)s - %(levelname)s - %(message)s", + filename=log_file_name("mcp_security_advisories.log"), + filemode="a", +) + +mcp = FastMCP("SecurityAdvisories") + +GH_TOKEN = os.getenv("GH_TOKEN", default="") + +# Initialize memcache backend to store advisories directly +MEMCACHE_DIR = mcp_data_dir("seclab-taskflow-agent", "memcache", "MEMCACHE_STATE_DIR") +memcache_backend = SqliteBackend(str(MEMCACHE_DIR)) + + +async def call_api(url: str) -> str: + """Call the GitHub API to fetch security advisories.""" + headers = { + "Accept": "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + "Authorization": f"Bearer {GH_TOKEN}", + } + + try: + async with httpx.AsyncClient(headers=headers) as client: + r = await client.get(url, follow_redirects=True) + r.raise_for_status() + return r.text + except httpx.RequestError as e: + return f"Request error: {e}" + except httpx.HTTPStatusError as e: + return f"HTTP error {e.response.status_code}: {e.response.text}" + except Exception as e: + return f"Error: {e}" + + +def is_recent_advisory(advisory: dict, years: int = 3) -> bool: + """ + Check if an advisory was published within the last N years. + + Args: + advisory: Advisory object from the API + years: Number of years to look back (default: 3) + + Returns: + True if the advisory was published within the specified years, False otherwise + """ + published_at = advisory.get("published_at") + if not published_at: + return True # Include advisories without a published_at date + + try: + published_date = datetime.fromisoformat(published_at.replace('Z', '+00:00')) + cutoff_date = datetime.now(published_date.tzinfo) - timedelta(days=365 * years) + return published_date >= cutoff_date + except (ValueError, TypeError): + return True # Include advisories we can't parse the date for + +def filter_advisories(advisories: list, years: int = 3) -> list: + """ + Filter advisory objects to include only essential fields and recent advisories. + + Args: + advisories: List of advisory objects from the API + years: Only include advisories from the last N years (default: 3) + + Returns: + Filtered list with only ghsa_id, summary, description, and severity + """ + filtered = [] + for advisory in advisories: + if is_recent_advisory(advisory, years): + filtered.append({ + "ghsa_id": advisory.get("ghsa_id"), + "summary": advisory.get("summary"), + "description": advisory.get("description"), + "severity": advisory.get("severity"), + }) + return filtered + + +@mcp.tool() +async def get_security_advisories( + owner: str = Field(description="The owner of the repository"), + repo: str = Field(description="The name of the repository"), + years: int = Field(description="Only include advisories from the last N years (default: 3)", default=3), +) -> str: + """ + Fetch security advisories for a GitHub repository. + Returns a filtered list of security advisories with ghsa_id, summary, description, and severity for each advisory published in the last N years. + Also stores the result in memcache under the key 'security_advisories_{owner}/{repo}'. + """ + owner = owner.strip().lower() + repo = repo.strip().lower() + gh_token = os.getenv("GH_TOKEN", "") + advisories = [] + page = 1 + while True: + url = f"https://api.github.com/repos/{owner}/{repo}/security-advisories?per_page=100&page={page}" + headers = { + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {gh_token}", + "X-GitHub-Api-Version": "2022-11-28", + } + try: + async with httpx.AsyncClient() as client: + r = await client.get(url, headers=headers) + r.raise_for_status() + data = r.text + page_advisories = json.loads(data) + if not page_advisories: + break + advisories.extend(page_advisories) + if len(page_advisories) < 100: + break + page += 1 + except httpx.HTTPStatusError as e: + return f"HTTP error {e.response.status_code}: {e.response.text}" + except Exception as e: + return f"Error: {e}" + filtered = filter_advisories(advisories, years) + + # Store in memcache for later retrieval (store the Python object, not JSON string) + memcache_key = f"security_advisories_{owner}/{repo}" + try: + memcache_backend.set_state(memcache_key, filtered) + logging.info(f"Stored {len(filtered)} advisories in memcache under key '{memcache_key}'") + except Exception as e: + logging.error(f"Failed to store advisories in memcache: {e}") + + # Return JSON string to the caller + return json.dumps(filtered, indent=2) + + +if __name__ == "__main__": + mcp.run(show_banner=False) diff --git a/src/seclab_taskflows/personalities/web_application_security_expert.yaml b/src/seclab_taskflows/personalities/web_application_security_expert.yaml index b2ae6b2..b5973e9 100644 --- a/src/seclab_taskflows/personalities/web_application_security_expert.yaml +++ b/src/seclab_taskflows/personalities/web_application_security_expert.yaml @@ -20,3 +20,4 @@ toolboxes: - seclab_taskflow_agent.toolboxes.memcache - seclab_taskflows.toolboxes.gh_file_viewer - seclab_taskflow_agent.toolboxes.codeql + - seclab_taskflows.toolboxes.security_advisories diff --git a/src/seclab_taskflows/taskflows/audit/audit_issue_local_iter.yaml b/src/seclab_taskflows/taskflows/audit/audit_issue_local_iter.yaml index 22fcc21..cbffe5c 100644 --- a/src/seclab_taskflows/taskflows/audit/audit_issue_local_iter.yaml +++ b/src/seclab_taskflows/taskflows/audit/audit_issue_local_iter.yaml @@ -29,20 +29,26 @@ taskflow: - seclab_taskflows.personalities.web_application_security_expert model: code_analysis user_prompt: | - The issue is in repo {{ result.repo }} with id {{ result.issue_id }}. The component is under the directory + The issue is in repo {{ result.repo }} with id {{ result.issue_id }}. The component is under the directory {{ result.location }} with component_id {{ result.component_id }}. The notes of the component is: {{ result.component_notes }} - You should use this to understand the intended purpose of the component and take it into account when + You should use this to understand the intended purpose of the component and take it into account when you audit the issue. The type of the issue is {{ result.issue_type }} and here is the notes of the issue: {{ result.issue_notes }} + ## Known Security Advisories for this Repository + + Fetch the security advisories for {{ globals.repo }} from memcache (stored under the key 'security_advisories_{{ globals.repo }}'). If the value in the memcache is null, clearly state so. Otherwise, state how many advisories were found. + Review these advisories and consider them when identifying security risks. If you identify code that is an actual vulnerability with similar pattern to a known advisory, highlight that connection. + {% include 'seclab_taskflows.prompts.audit.audit_issue' %} toolboxes: - seclab_taskflows.toolboxes.repo_context - seclab_taskflows.toolboxes.local_file_viewer - \ No newline at end of file + - seclab_taskflow_agent.toolboxes.memcache + - seclab_taskflows.toolboxes.security_advisories diff --git a/src/seclab_taskflows/taskflows/audit/classify_application_local.yaml b/src/seclab_taskflows/taskflows/audit/classify_application_local.yaml index d1da08a..d85121d 100644 --- a/src/seclab_taskflows/taskflows/audit/classify_application_local.yaml +++ b/src/seclab_taskflows/taskflows/audit/classify_application_local.yaml @@ -36,8 +36,13 @@ taskflow: Fetch the entry points and web entry points of the component, then the user actions of this component. Based on the entry points, web entry points, components, user actions and README.md and if available, SECURITY.md in the {{ globals.repo }}, can you tell me what type of application this repo is and what kind of security boundary it has. - Based on this, determine whether the component is likely to have security problems. - + Based on this, determine whether the component is likely to have security problems. + + ## Known Security Advisories for this Repository + + Fetch the security advisories for {{ globals.repo }} from memcache (stored under the key 'security_advisories_{{ globals.repo }}'). If the value in the memcache is null, clearly state so and skip advisory analysis. Otherwise, state how many advisories were found. + Review these advisories and consider them when identifying security risks. If you identify code that is similar to a known advisory pattern, highlight that connection. + Identify the most likely security problems in the component. Your task is not to carry out a full audit, but to identify the main risk in the component so that further analysis can be carried out. Do not be too specific about an issue, but rather craft your report based on the general functionality and type of @@ -50,7 +55,7 @@ taskflow: - Is this component likely to take untrusted user input? For example, remote web requests or IPC, RPC calls? - What is the intended purpose of this component and its functionality? Does it allow high privileged actions? Is it intended to provide such functionalities for all users? Or is there complex access control logic involved? - - The component itself may also have its own `README.md` (or a subdirectory of it may have a `README.md`). Take + - The component itself may also have its own `README.md` (or a subdirectory of it may have a `README.md`). Take a look at those files to help understand the functionality of the component. For example, an Admin UI/dashboard may be susceptible to client side Javascript vulnerabilities such as XSS, CSRF. @@ -60,7 +65,7 @@ taskflow: a web frontend may allow users to access their own content and admins to access all content, but users should not be able to access another users' content in general. - We're looking for more concrete and serious security issues that affects system integrity or + We're looking for more concrete and serious security issues that affects system integrity or lead to information leak, so please do not include issues like brute force, Dos, log injection etc. Also do not include issues that require the system to be already compromised, such as issues that rely on malicious @@ -72,9 +77,9 @@ taskflow: Your task is to identify risk rather than properly audit and find security issues. Do not look too much into the implementation or scrutinize the security measures such as access control and sanitizers at this stage. Instead, report more general risks that are associated with the type of component - that you are looking at. + that you are looking at. - It is not your task to audit the security measures, but rather just to identify the risks and suggest some issues + It is not your task to audit the security measures, but rather just to identify the risks and suggest some issues that is worth auditing. Reflect on your notes and check that the attack scenario meets the above requirements. Exclude low severity issues or @@ -84,4 +89,6 @@ taskflow: If you think the issues satisfy the criteria, store a component issue entry for each type of issue identified. toolboxes: - seclab_taskflows.toolboxes.repo_context - - seclab_taskflows.toolboxes.local_file_viewer \ No newline at end of file + - seclab_taskflows.toolboxes.local_file_viewer + - seclab_taskflow_agent.toolboxes.memcache + - seclab_taskflows.toolboxes.security_advisories diff --git a/src/seclab_taskflows/taskflows/audit/fetch_security_advisories.yaml b/src/seclab_taskflows/taskflows/audit/fetch_security_advisories.yaml new file mode 100644 index 0000000..a99ef11 --- /dev/null +++ b/src/seclab_taskflows/taskflows/audit/fetch_security_advisories.yaml @@ -0,0 +1,31 @@ +# SPDX-FileCopyrightText: 2025 GitHub +# SPDX-License-Identifier: MIT + +seclab-taskflow-agent: + filetype: taskflow + version: "1.0" + +model_config: seclab_taskflows.configs.model_config + +globals: + repo: + +# Example taskflow to fetch and review security advisories for a repository +taskflow: + - task: + must_complete: true + exclude_from_context: false + agents: + - seclab_taskflow_agent.personalities.assistant + model: general_tasks + user_prompt: | + Fetch and review the security advisories for the repo {{ globals.repo }}. + + Provide a summary of: + 1. How many advisories were found + 2. The severity levels of the advisories + 3. Key recommendations for addressing them + toolboxes: + - seclab_taskflows.toolboxes.security_advisories + - seclab_taskflows.toolboxes.local_file_viewer + - seclab_taskflows.toolboxes.gh_file_viewer diff --git a/src/seclab_taskflows/toolboxes/security_advisories.yaml b/src/seclab_taskflows/toolboxes/security_advisories.yaml new file mode 100644 index 0000000..b8eaf5f --- /dev/null +++ b/src/seclab_taskflows/toolboxes/security_advisories.yaml @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2025 GitHub +# SPDX-License-Identifier: MIT + +seclab-taskflow-agent: + filetype: toolbox + version: 1 + +server_params: + kind: stdio + command: python + args: ["-m", "seclab_taskflows.mcp_servers.security_advisories"] + env: + GH_TOKEN: "{{ env GH_TOKEN }}" + LOG_DIR: "{{ env LOG_DIR }}" + MEMCACHE_STATE_DIR: "{{ env MEMCACHE_STATE_DIR }}" From 26f6d3da351ea2d6b3e28bc88a131d935a888bdb Mon Sep 17 00:00:00 2001 From: Kevin Stubbings Date: Fri, 13 Feb 2026 08:37:17 +0000 Subject: [PATCH 2/8] Pass linter --- .../mcp_servers/security_advisories.py | 36 +++++-------------- .../taskflows/audit/filter_severity.yaml | 2 +- .../toolboxes/security_advisories.yaml | 10 +++--- 3 files changed, 14 insertions(+), 34 deletions(-) diff --git a/src/seclab_taskflows/mcp_servers/security_advisories.py b/src/seclab_taskflows/mcp_servers/security_advisories.py index 86b4701..f8f4a21 100644 --- a/src/seclab_taskflows/mcp_servers/security_advisories.py +++ b/src/seclab_taskflows/mcp_servers/security_advisories.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2025 GitHub +# SPDX-FileCopyrightText: GitHub, Inc. # SPDX-License-Identifier: MIT import logging @@ -27,27 +27,6 @@ memcache_backend = SqliteBackend(str(MEMCACHE_DIR)) -async def call_api(url: str) -> str: - """Call the GitHub API to fetch security advisories.""" - headers = { - "Accept": "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - "Authorization": f"Bearer {GH_TOKEN}", - } - - try: - async with httpx.AsyncClient(headers=headers) as client: - r = await client.get(url, follow_redirects=True) - r.raise_for_status() - return r.text - except httpx.RequestError as e: - return f"Request error: {e}" - except httpx.HTTPStatusError as e: - return f"HTTP error {e.response.status_code}: {e.response.text}" - except Exception as e: - return f"Error: {e}" - - def is_recent_advisory(advisory: dict, years: int = 3) -> bool: """ Check if an advisory was published within the last N years. @@ -104,16 +83,15 @@ async def get_security_advisories( Returns a filtered list of security advisories with ghsa_id, summary, description, and severity for each advisory published in the last N years. Also stores the result in memcache under the key 'security_advisories_{owner}/{repo}'. """ - owner = owner.strip().lower() - repo = repo.strip().lower() - gh_token = os.getenv("GH_TOKEN", "") + owner = owner.strip() + repo = repo.strip() advisories = [] page = 1 while True: url = f"https://api.github.com/repos/{owner}/{repo}/security-advisories?per_page=100&page={page}" headers = { "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {gh_token}", + "Authorization": f"Bearer {GH_TOKEN}", "X-GitHub-Api-Version": "2022-11-28", } try: @@ -130,6 +108,8 @@ async def get_security_advisories( page += 1 except httpx.HTTPStatusError as e: return f"HTTP error {e.response.status_code}: {e.response.text}" + except json.JSONDecodeError as e: + return f"JSON parsing error: {e}" except Exception as e: return f"Error: {e}" filtered = filter_advisories(advisories, years) @@ -139,8 +119,8 @@ async def get_security_advisories( try: memcache_backend.set_state(memcache_key, filtered) logging.info(f"Stored {len(filtered)} advisories in memcache under key '{memcache_key}'") - except Exception as e: - logging.error(f"Failed to store advisories in memcache: {e}") + except Exception: + logging.exception("Failed to store advisories in memcache") # Return JSON string to the caller return json.dumps(filtered, indent=2) diff --git a/src/seclab_taskflows/taskflows/audit/filter_severity.yaml b/src/seclab_taskflows/taskflows/audit/filter_severity.yaml index ac91465..3e0a577 100644 --- a/src/seclab_taskflows/taskflows/audit/filter_severity.yaml +++ b/src/seclab_taskflows/taskflows/audit/filter_severity.yaml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2025 GitHub +# SPDX-FileCopyrightText: GitHub, Inc. # SPDX-License-Identifier: MIT seclab-taskflow-agent: diff --git a/src/seclab_taskflows/toolboxes/security_advisories.yaml b/src/seclab_taskflows/toolboxes/security_advisories.yaml index b8eaf5f..190f13d 100644 --- a/src/seclab_taskflows/toolboxes/security_advisories.yaml +++ b/src/seclab_taskflows/toolboxes/security_advisories.yaml @@ -1,15 +1,15 @@ -# SPDX-FileCopyrightText: 2025 GitHub +# SPDX-FileCopyrightText: GitHub, Inc. # SPDX-License-Identifier: MIT seclab-taskflow-agent: filetype: toolbox - version: 1 + version: "1.0" server_params: kind: stdio command: python args: ["-m", "seclab_taskflows.mcp_servers.security_advisories"] env: - GH_TOKEN: "{{ env GH_TOKEN }}" - LOG_DIR: "{{ env LOG_DIR }}" - MEMCACHE_STATE_DIR: "{{ env MEMCACHE_STATE_DIR }}" + GH_TOKEN: "{{ env('GH_TOKEN') }}" + LOG_DIR: "{{ env('LOG_DIR') }}" + MEMCACHE_STATE_DIR: "{{ env('MEMCACHE_STATE_DIR', required=False) }}" From ef5f38920d73fa312178158978b7c648538dca24 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Fri, 13 Feb 2026 14:18:54 +0000 Subject: [PATCH 3/8] Update src/seclab_taskflows/taskflows/audit/fetch_security_advisories.yaml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../taskflows/audit/fetch_security_advisories.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seclab_taskflows/taskflows/audit/fetch_security_advisories.yaml b/src/seclab_taskflows/taskflows/audit/fetch_security_advisories.yaml index a99ef11..1c35d12 100644 --- a/src/seclab_taskflows/taskflows/audit/fetch_security_advisories.yaml +++ b/src/seclab_taskflows/taskflows/audit/fetch_security_advisories.yaml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2025 GitHub +# SPDX-FileCopyrightText: GitHub, Inc. # SPDX-License-Identifier: MIT seclab-taskflow-agent: From 7276a2debd596aed58f6aebc77c9fec186abb202 Mon Sep 17 00:00:00 2001 From: Kevin Stubbings Date: Fri, 13 Feb 2026 16:59:01 +0000 Subject: [PATCH 4/8] Use ghsa.py --- .../mcp_servers/security_advisories.py | 130 ------------------ .../web_application_security_expert.yaml | 2 +- .../audit/audit_issue_local_iter.yaml | 2 +- .../audit/classify_application_local.yaml | 2 +- .../audit/fetch_security_advisories.yaml | 7 +- .../toolboxes/security_advisories.yaml | 15 -- 6 files changed, 8 insertions(+), 150 deletions(-) delete mode 100644 src/seclab_taskflows/mcp_servers/security_advisories.py delete mode 100644 src/seclab_taskflows/toolboxes/security_advisories.yaml diff --git a/src/seclab_taskflows/mcp_servers/security_advisories.py b/src/seclab_taskflows/mcp_servers/security_advisories.py deleted file mode 100644 index f8f4a21..0000000 --- a/src/seclab_taskflows/mcp_servers/security_advisories.py +++ /dev/null @@ -1,130 +0,0 @@ -# SPDX-FileCopyrightText: GitHub, Inc. -# SPDX-License-Identifier: MIT - -import logging -from fastmcp import FastMCP -from pydantic import Field -import httpx -import json -import os -from datetime import datetime, timedelta -from seclab_taskflow_agent.path_utils import log_file_name, mcp_data_dir -from seclab_taskflow_agent.mcp_servers.memcache.memcache_backend.sqlite import SqliteBackend - -logging.basicConfig( - level=logging.DEBUG, - format="%(asctime)s - %(levelname)s - %(message)s", - filename=log_file_name("mcp_security_advisories.log"), - filemode="a", -) - -mcp = FastMCP("SecurityAdvisories") - -GH_TOKEN = os.getenv("GH_TOKEN", default="") - -# Initialize memcache backend to store advisories directly -MEMCACHE_DIR = mcp_data_dir("seclab-taskflow-agent", "memcache", "MEMCACHE_STATE_DIR") -memcache_backend = SqliteBackend(str(MEMCACHE_DIR)) - - -def is_recent_advisory(advisory: dict, years: int = 3) -> bool: - """ - Check if an advisory was published within the last N years. - - Args: - advisory: Advisory object from the API - years: Number of years to look back (default: 3) - - Returns: - True if the advisory was published within the specified years, False otherwise - """ - published_at = advisory.get("published_at") - if not published_at: - return True # Include advisories without a published_at date - - try: - published_date = datetime.fromisoformat(published_at.replace('Z', '+00:00')) - cutoff_date = datetime.now(published_date.tzinfo) - timedelta(days=365 * years) - return published_date >= cutoff_date - except (ValueError, TypeError): - return True # Include advisories we can't parse the date for - -def filter_advisories(advisories: list, years: int = 3) -> list: - """ - Filter advisory objects to include only essential fields and recent advisories. - - Args: - advisories: List of advisory objects from the API - years: Only include advisories from the last N years (default: 3) - - Returns: - Filtered list with only ghsa_id, summary, description, and severity - """ - filtered = [] - for advisory in advisories: - if is_recent_advisory(advisory, years): - filtered.append({ - "ghsa_id": advisory.get("ghsa_id"), - "summary": advisory.get("summary"), - "description": advisory.get("description"), - "severity": advisory.get("severity"), - }) - return filtered - - -@mcp.tool() -async def get_security_advisories( - owner: str = Field(description="The owner of the repository"), - repo: str = Field(description="The name of the repository"), - years: int = Field(description="Only include advisories from the last N years (default: 3)", default=3), -) -> str: - """ - Fetch security advisories for a GitHub repository. - Returns a filtered list of security advisories with ghsa_id, summary, description, and severity for each advisory published in the last N years. - Also stores the result in memcache under the key 'security_advisories_{owner}/{repo}'. - """ - owner = owner.strip() - repo = repo.strip() - advisories = [] - page = 1 - while True: - url = f"https://api.github.com/repos/{owner}/{repo}/security-advisories?per_page=100&page={page}" - headers = { - "Accept": "application/vnd.github+json", - "Authorization": f"Bearer {GH_TOKEN}", - "X-GitHub-Api-Version": "2022-11-28", - } - try: - async with httpx.AsyncClient() as client: - r = await client.get(url, headers=headers) - r.raise_for_status() - data = r.text - page_advisories = json.loads(data) - if not page_advisories: - break - advisories.extend(page_advisories) - if len(page_advisories) < 100: - break - page += 1 - except httpx.HTTPStatusError as e: - return f"HTTP error {e.response.status_code}: {e.response.text}" - except json.JSONDecodeError as e: - return f"JSON parsing error: {e}" - except Exception as e: - return f"Error: {e}" - filtered = filter_advisories(advisories, years) - - # Store in memcache for later retrieval (store the Python object, not JSON string) - memcache_key = f"security_advisories_{owner}/{repo}" - try: - memcache_backend.set_state(memcache_key, filtered) - logging.info(f"Stored {len(filtered)} advisories in memcache under key '{memcache_key}'") - except Exception: - logging.exception("Failed to store advisories in memcache") - - # Return JSON string to the caller - return json.dumps(filtered, indent=2) - - -if __name__ == "__main__": - mcp.run(show_banner=False) diff --git a/src/seclab_taskflows/personalities/web_application_security_expert.yaml b/src/seclab_taskflows/personalities/web_application_security_expert.yaml index b5973e9..58033e0 100644 --- a/src/seclab_taskflows/personalities/web_application_security_expert.yaml +++ b/src/seclab_taskflows/personalities/web_application_security_expert.yaml @@ -20,4 +20,4 @@ toolboxes: - seclab_taskflow_agent.toolboxes.memcache - seclab_taskflows.toolboxes.gh_file_viewer - seclab_taskflow_agent.toolboxes.codeql - - seclab_taskflows.toolboxes.security_advisories + - seclab_taskflows.toolboxes.ghsa diff --git a/src/seclab_taskflows/taskflows/audit/audit_issue_local_iter.yaml b/src/seclab_taskflows/taskflows/audit/audit_issue_local_iter.yaml index cbffe5c..ea695a1 100644 --- a/src/seclab_taskflows/taskflows/audit/audit_issue_local_iter.yaml +++ b/src/seclab_taskflows/taskflows/audit/audit_issue_local_iter.yaml @@ -51,4 +51,4 @@ taskflow: - seclab_taskflows.toolboxes.repo_context - seclab_taskflows.toolboxes.local_file_viewer - seclab_taskflow_agent.toolboxes.memcache - - seclab_taskflows.toolboxes.security_advisories + - seclab_taskflows.toolboxes.ghsa diff --git a/src/seclab_taskflows/taskflows/audit/classify_application_local.yaml b/src/seclab_taskflows/taskflows/audit/classify_application_local.yaml index d85121d..6b9539b 100644 --- a/src/seclab_taskflows/taskflows/audit/classify_application_local.yaml +++ b/src/seclab_taskflows/taskflows/audit/classify_application_local.yaml @@ -91,4 +91,4 @@ taskflow: - seclab_taskflows.toolboxes.repo_context - seclab_taskflows.toolboxes.local_file_viewer - seclab_taskflow_agent.toolboxes.memcache - - seclab_taskflows.toolboxes.security_advisories + - seclab_taskflows.toolboxes.ghsa diff --git a/src/seclab_taskflows/taskflows/audit/fetch_security_advisories.yaml b/src/seclab_taskflows/taskflows/audit/fetch_security_advisories.yaml index 1c35d12..13837e8 100644 --- a/src/seclab_taskflows/taskflows/audit/fetch_security_advisories.yaml +++ b/src/seclab_taskflows/taskflows/audit/fetch_security_advisories.yaml @@ -19,13 +19,16 @@ taskflow: - seclab_taskflow_agent.personalities.assistant model: general_tasks user_prompt: | - Fetch and review the security advisories for the repo {{ globals.repo }}. + Fetch all GitHub Security Advisories (GHSAs) for the repo {{ globals.repo }}. + + After fetching, store the list of advisories in memcache under the key 'security_advisories_{{ globals.repo }}'. Provide a summary of: 1. How many advisories were found 2. The severity levels of the advisories 3. Key recommendations for addressing them toolboxes: - - seclab_taskflows.toolboxes.security_advisories + - seclab_taskflows.toolboxes.ghsa + - seclab_taskflow_agent.toolboxes.memcache - seclab_taskflows.toolboxes.local_file_viewer - seclab_taskflows.toolboxes.gh_file_viewer diff --git a/src/seclab_taskflows/toolboxes/security_advisories.yaml b/src/seclab_taskflows/toolboxes/security_advisories.yaml deleted file mode 100644 index 190f13d..0000000 --- a/src/seclab_taskflows/toolboxes/security_advisories.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# SPDX-FileCopyrightText: GitHub, Inc. -# SPDX-License-Identifier: MIT - -seclab-taskflow-agent: - filetype: toolbox - version: "1.0" - -server_params: - kind: stdio - command: python - args: ["-m", "seclab_taskflows.mcp_servers.security_advisories"] - env: - GH_TOKEN: "{{ env('GH_TOKEN') }}" - LOG_DIR: "{{ env('LOG_DIR') }}" - MEMCACHE_STATE_DIR: "{{ env('MEMCACHE_STATE_DIR', required=False) }}" From 250186fadbcd1feae9ed4792c3f7b38b43cb4618 Mon Sep 17 00:00:00 2001 From: Kevin Stubbings Date: Tue, 17 Feb 2026 08:00:08 +0000 Subject: [PATCH 5/8] Add advisory support as a reusable prompt --- .../prompts/audit/known_security_advisories.yaml | 11 +++++++++++ .../taskflows/audit/audit_issue_local_iter.yaml | 6 +----- .../taskflows/audit/classify_application_local.yaml | 6 +----- 3 files changed, 13 insertions(+), 10 deletions(-) create mode 100644 src/seclab_taskflows/prompts/audit/known_security_advisories.yaml diff --git a/src/seclab_taskflows/prompts/audit/known_security_advisories.yaml b/src/seclab_taskflows/prompts/audit/known_security_advisories.yaml new file mode 100644 index 0000000..72e3d39 --- /dev/null +++ b/src/seclab_taskflows/prompts/audit/known_security_advisories.yaml @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: GitHub, Inc. +# SPDX-License-Identifier: MIT + +seclab-taskflow-agent: + filetype: prompt + version: "1.0" +prompt: | + ## Known Security Advisories for this Repository + + Fetch the security advisories for {{ globals.repo }} from memcache (stored under the key 'security_advisories_{{ globals.repo }}'). If the value in the memcache is null, clearly state so and skip advisory analysis. Otherwise, state how many advisories were found. + Review these advisories and consider them when identifying security risks. If you identify code that is similar to a known advisory pattern, highlight that connection. diff --git a/src/seclab_taskflows/taskflows/audit/audit_issue_local_iter.yaml b/src/seclab_taskflows/taskflows/audit/audit_issue_local_iter.yaml index ea695a1..2f5b03a 100644 --- a/src/seclab_taskflows/taskflows/audit/audit_issue_local_iter.yaml +++ b/src/seclab_taskflows/taskflows/audit/audit_issue_local_iter.yaml @@ -41,14 +41,10 @@ taskflow: {{ result.issue_notes }} - ## Known Security Advisories for this Repository - - Fetch the security advisories for {{ globals.repo }} from memcache (stored under the key 'security_advisories_{{ globals.repo }}'). If the value in the memcache is null, clearly state so. Otherwise, state how many advisories were found. - Review these advisories and consider them when identifying security risks. If you identify code that is an actual vulnerability with similar pattern to a known advisory, highlight that connection. + {% include 'seclab_taskflows.prompts.audit.known_security_advisories' %} {% include 'seclab_taskflows.prompts.audit.audit_issue' %} toolboxes: - seclab_taskflows.toolboxes.repo_context - seclab_taskflows.toolboxes.local_file_viewer - seclab_taskflow_agent.toolboxes.memcache - - seclab_taskflows.toolboxes.ghsa diff --git a/src/seclab_taskflows/taskflows/audit/classify_application_local.yaml b/src/seclab_taskflows/taskflows/audit/classify_application_local.yaml index 6b9539b..8c769bb 100644 --- a/src/seclab_taskflows/taskflows/audit/classify_application_local.yaml +++ b/src/seclab_taskflows/taskflows/audit/classify_application_local.yaml @@ -38,10 +38,7 @@ taskflow: can you tell me what type of application this repo is and what kind of security boundary it has. Based on this, determine whether the component is likely to have security problems. - ## Known Security Advisories for this Repository - - Fetch the security advisories for {{ globals.repo }} from memcache (stored under the key 'security_advisories_{{ globals.repo }}'). If the value in the memcache is null, clearly state so and skip advisory analysis. Otherwise, state how many advisories were found. - Review these advisories and consider them when identifying security risks. If you identify code that is similar to a known advisory pattern, highlight that connection. + {% include 'seclab_taskflows.prompts.audit.known_security_advisories' %} Identify the most likely security problems in the component. Your task is not to carry out a full audit, but to identify the main risk in the component so that further analysis can be carried out. @@ -91,4 +88,3 @@ taskflow: - seclab_taskflows.toolboxes.repo_context - seclab_taskflows.toolboxes.local_file_viewer - seclab_taskflow_agent.toolboxes.memcache - - seclab_taskflows.toolboxes.ghsa From f1ba721c331da2e9fd78a4e74f1e2ecadd8a7d05 Mon Sep 17 00:00:00 2001 From: Kevin Stubbings Date: Tue, 17 Feb 2026 08:46:18 +0000 Subject: [PATCH 6/8] add advisory support for run_audit_in_docker.sh --- scripts/audit/run_audit_in_docker.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/audit/run_audit_in_docker.sh b/scripts/audit/run_audit_in_docker.sh index 03d4a2c..9e99ea0 100755 --- a/scripts/audit/run_audit_in_docker.sh +++ b/scripts/audit/run_audit_in_docker.sh @@ -5,4 +5,4 @@ # https://stackoverflow.com/a/53122736 __dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -${__dir}/../run_in_docker.sh ${__dir}/run_audit.sh "$1" +${__dir}/../run_in_docker.sh ${__dir}/run_audit.sh "$@" From 9ec009c5a0e248ef3ef4f310c2694f36cc8fb7e6 Mon Sep 17 00:00:00 2001 From: Bas Alberts Date: Thu, 19 Feb 2026 15:17:56 -0500 Subject: [PATCH 7/8] Make advisory prompt conditional via use_advisory global --- scripts/audit/run_audit.sh | 4 ++-- .../taskflows/audit/audit_issue_local_iter.yaml | 3 +++ .../taskflows/audit/classify_application_local.yaml | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/audit/run_audit.sh b/scripts/audit/run_audit.sh index ae19b62..23aaae2 100755 --- a/scripts/audit/run_audit.sh +++ b/scripts/audit/run_audit.sh @@ -33,8 +33,8 @@ if [ "$USE_ADVISORY" = true ]; then python -m seclab_taskflow_agent -t seclab_taskflows.taskflows.audit.fetch_security_advisories -g repo="$1" fi -python -m seclab_taskflow_agent -t seclab_taskflows.taskflows.audit.classify_application_local -g repo="$1" -python -m seclab_taskflow_agent -t seclab_taskflows.taskflows.audit.audit_issue_local_iter -g repo="$1" +python -m seclab_taskflow_agent -t seclab_taskflows.taskflows.audit.classify_application_local -g repo="$1" -g use_advisory="$USE_ADVISORY" +python -m seclab_taskflow_agent -t seclab_taskflows.taskflows.audit.audit_issue_local_iter -g repo="$1" -g use_advisory="$USE_ADVISORY" set +e diff --git a/src/seclab_taskflows/taskflows/audit/audit_issue_local_iter.yaml b/src/seclab_taskflows/taskflows/audit/audit_issue_local_iter.yaml index 2f5b03a..64a375f 100644 --- a/src/seclab_taskflows/taskflows/audit/audit_issue_local_iter.yaml +++ b/src/seclab_taskflows/taskflows/audit/audit_issue_local_iter.yaml @@ -8,6 +8,7 @@ model_config: seclab_taskflows.configs.model_config globals: repo: + use_advisory: # Taskflow to audit some potential issues. taskflow: - task: @@ -41,7 +42,9 @@ taskflow: {{ result.issue_notes }} + {% if globals.use_advisory == 'true' %} {% include 'seclab_taskflows.prompts.audit.known_security_advisories' %} + {% endif %} {% include 'seclab_taskflows.prompts.audit.audit_issue' %} toolboxes: diff --git a/src/seclab_taskflows/taskflows/audit/classify_application_local.yaml b/src/seclab_taskflows/taskflows/audit/classify_application_local.yaml index 8c769bb..9f44ccc 100644 --- a/src/seclab_taskflows/taskflows/audit/classify_application_local.yaml +++ b/src/seclab_taskflows/taskflows/audit/classify_application_local.yaml @@ -8,6 +8,7 @@ model_config: seclab_taskflows.configs.model_config globals: repo: + use_advisory: # Taskflow to analyze the general contextual information of a project and classify the different applications within it taskflow: - task: @@ -38,7 +39,9 @@ taskflow: can you tell me what type of application this repo is and what kind of security boundary it has. Based on this, determine whether the component is likely to have security problems. + {% if globals.use_advisory == 'true' %} {% include 'seclab_taskflows.prompts.audit.known_security_advisories' %} + {% endif %} Identify the most likely security problems in the component. Your task is not to carry out a full audit, but to identify the main risk in the component so that further analysis can be carried out. From 35e41eede2252508b8df5ff5c13dc6c6996d6f8b Mon Sep 17 00:00:00 2001 From: Kevin Stubbings Date: Fri, 20 Feb 2026 09:00:36 +0000 Subject: [PATCH 8/8] Increase verbosity with error message --- .../prompts/audit/known_security_advisories.yaml | 2 +- .../taskflows/audit/fetch_security_advisories.yaml | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/seclab_taskflows/prompts/audit/known_security_advisories.yaml b/src/seclab_taskflows/prompts/audit/known_security_advisories.yaml index 72e3d39..6c5cb11 100644 --- a/src/seclab_taskflows/prompts/audit/known_security_advisories.yaml +++ b/src/seclab_taskflows/prompts/audit/known_security_advisories.yaml @@ -7,5 +7,5 @@ seclab-taskflow-agent: prompt: | ## Known Security Advisories for this Repository - Fetch the security advisories for {{ globals.repo }} from memcache (stored under the key 'security_advisories_{{ globals.repo }}'). If the value in the memcache is null, clearly state so and skip advisory analysis. Otherwise, state how many advisories were found. + Fetch the security advisories for {{ globals.repo }} from memcache (stored under the key 'security_advisories_{{ globals.repo }}'). If the value in the memcache is null or an error message, clearly state that no advisories are available and skip advisory analysis. Otherwise, state how many advisories were found. Review these advisories and consider them when identifying security risks. If you identify code that is similar to a known advisory pattern, highlight that connection. diff --git a/src/seclab_taskflows/taskflows/audit/fetch_security_advisories.yaml b/src/seclab_taskflows/taskflows/audit/fetch_security_advisories.yaml index 13837e8..35b747b 100644 --- a/src/seclab_taskflows/taskflows/audit/fetch_security_advisories.yaml +++ b/src/seclab_taskflows/taskflows/audit/fetch_security_advisories.yaml @@ -21,12 +21,14 @@ taskflow: user_prompt: | Fetch all GitHub Security Advisories (GHSAs) for the repo {{ globals.repo }}. - After fetching, store the list of advisories in memcache under the key 'security_advisories_{{ globals.repo }}'. + If an error occurs during fetching, store the error message in memcache under the key 'security_advisories_{{ globals.repo }}'. + Ensure the error message starts with "Error:" followed by a description of the error. - Provide a summary of: + If fetching is successful, store the list of advisories in memcache under the key 'security_advisories_{{ globals.repo }}'. + + If one ore more advisories are found, provide a summary of the findings including: 1. How many advisories were found 2. The severity levels of the advisories - 3. Key recommendations for addressing them toolboxes: - seclab_taskflows.toolboxes.ghsa - seclab_taskflow_agent.toolboxes.memcache