Skip to content

Commit 3df883a

Browse files
Merge branch 'main' into smoketest2
2 parents a4ca478 + 211f98e commit 3df883a

37 files changed

Lines changed: 1302 additions & 1046 deletions

.devcontainer/post-attach.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ if [ -v CODESPACES ]; then
77
if [ ! -v AI_API_TOKEN ]; then
88
echo "⚠️ Running in Codespaces - please add AI_API_TOKEN to your Codespaces secrets"
99
fi
10-
if [ ! -v GITHUB_PERSONAL_ACCESS_TOKEN ]; then
11-
echo "⚠️ Running in Codespaces - please add GITHUB_PERSONAL_ACCESS_TOKEN to your Codespaces secrets"
10+
if [ ! -v GH_TOKEN ]; then
11+
echo "⚠️ Running in Codespaces - please add GH_TOKEN to your Codespaces secrets"
1212
fi
1313
fi
1414

.github/workflows/ci.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ jobs:
3535

3636
- name: Run static analysis
3737
run: |
38-
# hatch fmt --check
39-
echo linter errors will be fixed in a separate PR
38+
hatch fmt --linter --check
4039
4140
- name: Run tests
4241
run: hatch test --python ${{ matrix.python-version }} --cover --randomize --parallel --retries 2 --retry-delay 1

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2025 GitHub
3+
Copyright GitHub, Inc.
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
# Seclab Taskflow Agent
1+
# GitHub Security Lab Taskflow Agent
22

33
The Security Lab Taskflow Agent is an MCP enabled multi-Agent framework.
44

55
The Taskflow Agent is built on top of the [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/).
66

7-
While the Taskflow Agent does not integrate into the GitHub Dotcom Copilot UX, it does operate using the Copilot API (CAPI) as its backend, similar to Copilot IDE extensions.
8-
97
## Core Concepts
108

119
The Taskflow Agent leverages a GitHub Workflow-esque YAML based grammar to perform a series of tasks using a set of Agents.
@@ -16,38 +14,48 @@ Agents are defined through [personalities](examples/personalities/), that receiv
1614

1715
Agents can cooperate to complete sequences of tasks through so-called [taskflows](doc/GRAMMAR.md).
1816

19-
You can find a detailed overview of the taskflow grammar [here](taskflows/GRAMMAR.md) and example taskflows [here](examples/taskflows/).
17+
You can find a detailed overview of the taskflow grammar [here](doc/GRAMMAR.md) and example taskflows [here](examples/taskflows/).
2018

2119
## Use Cases and Examples
2220

2321
The Seclab Taskflow Agent framework was primarily designed to fit the iterative feedback loop driven work involved in Agentic security research workflows and vulnerability triage tasks.
2422

2523
Its design philosophy is centered around the belief that a prompt level focus of capturing vulnerability patterns will greatly improve and scale security research results as frontier model capabilities evolve over time.
2624

27-
While the maintainer himself primarily uses this framework as a code auditing tool it also serves as a more generic swiss army knife for exploring Agentic workflows. For example, the GitHub Security Lab also uses this framework for automated code scanning alert triage.
25+
At GitHub Security Lab, we primarily use this framework as a code auditing tool, but it can also serve as a more generic swiss army knife for exploring Agentic workflows. For example, we also use this framework for automated code scanning alert triage.
2826

2927
The framework includes a [CodeQL](https://codeql.github.com/) MCP server that can be used for Agentic code review, see the [CVE-2023-2283](examples/taskflows/CVE-2023-2283.yaml) taskflow for an example of how to have an Agent review C code using a CodeQL database ([demo video](https://www.youtube.com/watch?v=eRSPSVW8RMo)).
3028

3129
Instead of generating CodeQL queries itself, the CodeQL MCP Server is used to provide CodeQL-query based MCP tools that allow an Agent to navigate and explore code. It leverages templated CodeQL queries to provide targeted context for model driven code analysis.
3230

3331
## Requirements
3432

35-
Python >= 3.9 or Docker
33+
Python >= 3.10 or Docker
3634

3735
## Configuration
3836

39-
Provide a GitHub token for an account that is entitled to use [GitHub Models](https://models.github.ai) via the `AI_API_TOKEN` environment variable. Further configuration is use case dependent, i.e. pending which MCP servers you'd like to use in your taskflows.
37+
Provide a GitHub token for an account that is entitled to use [GitHub Models](https://models.github.ai) via the `AI_API_TOKEN` environment variable. Further configuration is use case dependent, i.e. pending which MCP servers you'd like to use in your taskflows. In a terminal, you can add `AI_API_TOKEN` to the environment like this:
38+
39+
```sh
40+
export AI_API_TOKEN=<your_github_token>
41+
```
42+
43+
Or, if you are using GitHub Codespaces, then you can [add a Codespace secret](https://github.com/settings/codespaces/secrets/new) so that `AI_API_TOKEN` is automatically available when working in a Codespace.
44+
45+
Many of the MCP servers in the [seclab-taskflow](https://github.com/GitHubSecurityLab/seclab-taskflows) repo also need an environment variable named `GH_TOKEN` for accessing the GitHub API. You can use two separate PATs if you want, or you can use one PAT for both purposes, like this:
46+
47+
```sh
48+
export GH_TOKEN=$AI_API_TOKEN
49+
```
4050

41-
You can set persisting environment variables via an `.env` file in the project root.
51+
We do not recommend storing secrets on disk, but you can persist non-sensitive environment variables by adding a `.env` file in the project root.
4252

4353
Example:
4454

4555
```sh
46-
# Tokens
47-
AI_API_TOKEN=<your_github_token>
4856
# MCP configs
49-
GITHUB_PERSONAL_ACCESS_TOKEN=<your_github_token>
5057
CODEQL_DBS_BASE_PATH="/app/my_data/codeql_databases"
58+
AI_API_ENDPOINT="https://models.github.ai/inference"
5159
```
5260

5361
## Deploying from Source
@@ -318,7 +326,7 @@ seclab-taskflow-agent:
318326
319327
taskflow:
320328
- task:
321-
# taskflows can optionally choose any of the support CAPI models for a task
329+
# taskflows can optionally choose any of the models supported by your API for a task
322330
model: gpt-4.1
323331
# taskflows can optionally limit the max allowed number of Agent task loop
324332
# iterations to complete a task, this defaults to 50 when not provided
@@ -384,7 +392,7 @@ taskflow:
384392
385393
Taskflows support [Agent handoffs](https://openai.github.io/openai-agents-python/handoffs/). Handoffs are useful for implementing triage patterns where the primary Agent can decide to handoff a task to any subsequent Agents in the `Agents` list.
386394

387-
See the [taskflow examples](taskflows/examples) for other useful Taskflow patterns such as repeatable and asynchronous templated prompts.
395+
See the [taskflow examples](examples/taskflows) for other useful Taskflow patterns such as repeatable and asynchronous templated prompts.
388396

389397

390398
You can run a taskflow from the command line like this:

docker/run.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@ touch -a .env
1919

2020
docker run -i \
2121
--mount type=bind,src="$PWD",dst=/app \
22-
-e GITHUB_PERSONAL_ACCESS_TOKEN="$GITHUB_PERSONAL_ACCESS_TOKEN" \
22+
-e GH_TOKEN="$GH_TOKEN" \
2323
-e AI_API_TOKEN="$AI_API_TOKEN" \
2424
"ghcr.io/githubsecuritylab/seclab-taskflow-agent" "$@"

pyproject.toml

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ name = "seclab-taskflow-agent"
77
dynamic = ["version"]
88
description = "A taskflow agent for the SecLab project, enabling secure and automated workflow execution."
99
readme = "README.md"
10-
requires-python = ">=3.9"
10+
requires-python = ">=3.10"
1111
license = "MIT"
1212
keywords = []
1313
authors = [
@@ -16,8 +16,6 @@ authors = [
1616
classifiers = [
1717
"Development Status :: 4 - Beta",
1818
"Programming Language :: Python",
19-
"Programming Language :: Python :: 3.8",
20-
"Programming Language :: Python :: 3.9",
2119
"Programming Language :: Python :: 3.10",
2220
"Programming Language :: Python :: 3.11",
2321
"Programming Language :: Python :: 3.12",
@@ -29,7 +27,7 @@ dependencies = [
2927
"annotated-types==0.7.0",
3028
"anyio==4.9.0",
3129
"attrs==25.3.0",
32-
"Authlib==1.6.5",
30+
"Authlib==1.6.6",
3331
"certifi==2025.6.15",
3432
"cffi==2.0.0",
3533
"charset-normalizer==3.4.2",
@@ -83,7 +81,7 @@ dependencies = [
8381
"python-dotenv==1.1.1",
8482
"python-lsp-jsonrpc==1.1.2",
8583
"python-lsp-server==1.12.2",
86-
"python-multipart==0.0.20",
84+
"python-multipart==0.0.22",
8785
"PyYAML==6.0.2",
8886
"referencing==0.36.2",
8987
"requests==2.32.4",
@@ -104,7 +102,7 @@ dependencies = [
104102
"typing-inspection==0.4.1",
105103
"typing_extensions==4.14.1",
106104
"ujson==5.10.0",
107-
"urllib3==2.5.0",
105+
"urllib3==2.6.3",
108106
"uvicorn==0.35.0",
109107
"zipp==3.23.0",
110108
]
@@ -145,3 +143,91 @@ exclude_lines = [
145143
"if __name__ == .__main__.:",
146144
"if TYPE_CHECKING:",
147145
]
146+
147+
[tool.ruff]
148+
# Set target version to 3.10 to support match statements
149+
target-version = "py310"
150+
151+
[tool.ruff.lint]
152+
# Suppress all current linter errors to establish a baseline
153+
ignore = [
154+
"A001", # Variable shadows built-in
155+
"A002", # Argument shadows built-in
156+
"A004", # Import shadows built-in
157+
"ARG001", # Unused function argument
158+
"B006", # Mutable default argument
159+
"B007", # Unused loop control variable
160+
"B008", # Function call in argument defaults
161+
"B023", # Function uses loop variable
162+
"B904", # raise-without-from-inside-except
163+
"BLE001", # Blind except
164+
"C405", # Unnecessary literal set
165+
"C416", # Unnecessary comprehension
166+
"E721", # Type comparison using ==
167+
"E722", # Bare except
168+
"E741", # Ambiguous variable name
169+
"EM101", # Exception string literal
170+
"EM102", # Exception f-string
171+
"F541", # f-string without placeholders
172+
"F811", # Redefinition of unused name
173+
"F821", # Undefined name
174+
"F841", # Unused variable
175+
"FA100", # Missing from __future__ import annotations
176+
"FA102", # Missing from __future__ import annotations in stub
177+
"FBT001", # Boolean positional arg in function definition
178+
"FBT002", # Boolean default value in function definition
179+
"FURB188", # Prefer removeprefix over conditional slice
180+
"G004", # Logging with f-string
181+
"I001", # Import block unsorted or unformatted
182+
"INP001", # Implicit namespace package
183+
"LOG015", # root logger usage
184+
"N801", # Class name should use CapWords convention
185+
"N802", # Function name should be lowercase
186+
"N806", # Variable in function should be lowercase
187+
"N818", # Exception name should end with Error
188+
"PERF102", # Use keys() or values() instead of items()
189+
"PERF401", # Use list comprehension
190+
"PIE790", # Unnecessary pass statement
191+
"PLC0415", # Import should be at top of file
192+
"PLC1802", # Use of len(x) == 0
193+
"PLR2004", # Magic value used in comparison
194+
"PLW0602", # Global variable not assigned
195+
"PLW0603", # Using global statement
196+
"PLW1508", # Invalid envvar default
197+
"PLW2901", # Outer loop variable overwritten
198+
"PT011", # pytest.raises too broad
199+
"PYI041", # Use float instead of int | float
200+
"RET503", # Missing explicit return
201+
"RET504", # Unnecessary assignment before return
202+
"RET505", # Unnecessary else after return
203+
"RET506", # Unnecessary else after raise
204+
"RUF005", # Unpack instead of concatenation
205+
"RUF010", # Use explicit conversion flag
206+
"RUF015", # Prefer next() over single element slice
207+
"RUF059", # Use of private function/attribute
208+
"RUF100", # Unused noqa directive
209+
"S108", # Hardcoded temp file/directory
210+
"S607", # Starting process with partial path
211+
"SIM102", # Use single if statement
212+
"SIM115", # Use context handler for file
213+
"SIM210", # Use ternary operator
214+
"SLF001", # Private member access
215+
"T201", # print found
216+
"TID252", # Relative imports from parent modules
217+
"TRY003", # Raise vanilla args
218+
"TRY004", # Prefer TypeError for wrong type
219+
"TRY300", # Consider moving statement to else
220+
"TRY301", # Abstract raise to inner function
221+
"TRY400", # Use logging.exception instead of logging.error
222+
"UP004", # Use X | Y for union types
223+
"UP006", # Use X | Y for union types in isinstance
224+
"UP009", # UTF-8 encoding declaration
225+
"UP015", # Unnecessary mode argument
226+
"UP020", # Use builtin open
227+
"UP024", # Replace aliased errors with OSError
228+
"UP032", # Use f-string
229+
"UP035", # Import from collections.abc
230+
"UP045", # Use X | None for type annotations
231+
"W291", # Trailing whitespace
232+
"W293", # Blank line contains whitespace
233+
]

release_tools/copy_files.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,20 @@
33

44
import os
55
import shutil
6-
import sys
76
import subprocess
7+
import sys
8+
89

910
def read_file_list(list_path):
1011
"""
1112
Reads a file containing file paths, ignoring empty lines and lines starting with '#'.
1213
Returns a list of relative file paths.
1314
"""
14-
with open(list_path, "r") as f:
15+
with open(list_path) as f:
1516
lines = [line.strip() for line in f]
1617
return [line for line in lines if line and not line.startswith("#")]
1718

19+
1820
def copy_files(file_list, dest_dir):
1921
"""
2022
Copy files listed in file_list to dest_dir, preserving their relative paths.
@@ -29,6 +31,7 @@ def copy_files(file_list, dest_dir):
2931
shutil.copy2(abs_src, abs_dest)
3032
print(f"Copied {abs_src} -> {abs_dest}")
3133

34+
3235
def ensure_git_repo(dest_dir):
3336
"""
3437
Initializes a git repository in dest_dir if it's not already a git repo.
@@ -56,6 +59,7 @@ def ensure_git_repo(dest_dir):
5659
print(f"Failed to ensure 'main' branch in {dest_dir}: {e}")
5760
sys.exit(1)
5861

62+
5963
def git_add_files(file_list, dest_dir):
6064
"""
6165
Runs 'git add' on each file in file_list within dest_dir.
@@ -72,6 +76,7 @@ def git_add_files(file_list, dest_dir):
7276
finally:
7377
os.chdir(cwd)
7478

79+
7580
if __name__ == "__main__":
7681
if len(sys.argv) != 3:
7782
print("Usage: python copy_files.py <file_list.txt> <dest_dir>")

release_tools/publish_docker.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,37 @@
11
# SPDX-FileCopyrightText: 2025 GitHub
22
# SPDX-License-Identifier: MIT
33

4-
import os
5-
import shutil
64
import subprocess
75
import sys
86

7+
98
def get_image_digest(image_name, tag):
109
result = subprocess.run(
1110
["docker", "buildx", "imagetools", "inspect", f"{image_name}:{tag}"],
12-
stdout=subprocess.PIPE, check=True, text=True
11+
stdout=subprocess.PIPE,
12+
check=True,
13+
text=True,
1314
)
1415
for line in result.stdout.splitlines():
1516
if line.strip().startswith("Digest:"):
1617
return line.strip().split(":", 1)[1].strip()
1718
return None
1819

20+
1921
def build_and_push_image(dest_dir, image_name, tag):
2022
# Build
21-
subprocess.run([
22-
"docker", "buildx", "build", "--platform", "linux/amd64", "-t", f"{image_name}:{tag}", dest_dir
23-
], check=True)
23+
subprocess.run(
24+
["docker", "buildx", "build", "--platform", "linux/amd64", "-t", f"{image_name}:{tag}", dest_dir], check=True
25+
)
2426
# Push
25-
subprocess.run([
26-
"docker", "push", f"{image_name}:{tag}"
27-
], check=True)
27+
subprocess.run(["docker", "push", f"{image_name}:{tag}"], check=True)
2828
print(f"Pushed {image_name}:{tag}")
2929
digest = get_image_digest(image_name, tag)
3030
print(f"Image digest: {digest}")
3131
with open("/tmp/digest.txt", "w") as f:
3232
f.write(digest)
3333

34+
3435
if __name__ == "__main__":
3536
if len(sys.argv) != 3:
3637
print("Usage: python build_and_publish_docker.py <ghcr_username/repo> <tag>")
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# SPDX-FileCopyrightText: 2025 GitHub
22
# SPDX-License-Identifier: MIT
3-
__version__ = "0.0.7"
3+
__version__ = "0.0.10"

0 commit comments

Comments
 (0)