Skip to content
Draft
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/publish-container-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ jobs:
ghcr.io/githubsecuritylab/seclab-shell-network-analysis
ghcr.io/githubsecuritylab/seclab-shell-source-access
ghcr.io/githubsecuritylab/seclab-shell-sast
ghcr.io/githubsecuritylab/seclab-shell-reproduction
)

for image in "${images[@]}"; do
Expand Down
158 changes: 158 additions & 0 deletions scripts/audit_v2/run_audit_v2.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
#!/bin/bash
# SPDX-FileCopyrightText: GitHub, Inc.
# SPDX-License-Identifier: MIT

# Run the audit v2 pipeline against a repository.
#
# The five stages are separate taskflows rather than one file on purpose. Each
# one is expensive, and each ends at a durable checkpoint in the finding
# ledger, so a stage can be rerun on its own without redoing the ones before
# it. That is also why stage state lives in the ledger rather than in taskflow
# outputs: multi-model tasks do not feed a shared result channel, so the ledger
# is the only place the stages can meet.
#
# Usage: ./scripts/audit_v2/run_audit_v2.sh [options] <owner/repo>
#
# Options:
# -m <model_config> Override the model config each taskflow declares.
# Use seclab_taskflows.configs.model_config_audit_v2_lowercost
# for cheaper exploratory runs.
# -s <stage> Run a single stage: survey|hunt|contest|reproduce|report.
# Repeatable. Default: all five, in order.
# --from <stage> Run from this stage to the end, after fixing a stage
# that failed part way through.
# --no-reproduce Skip reproduction. Findings then top out at `confirmed`
# and the report says so.
# -h, --help Show this message.

set -euo pipefail

ALL_STAGES=(survey hunt contest reproduce report)
STAGES=()
FROM_STAGE=""
SKIP_REPRODUCE=false
MODEL_CONFIG_FLAG=()

usage() {
sed -n '6,27p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
}

while [[ $# -gt 0 && "$1" == -* ]]; do
case "$1" in
-m)
MODEL_CONFIG_FLAG=(-m "$2")
shift 2
;;
-s)
STAGES+=("$2")
shift 2
;;
--from)
FROM_STAGE="$2"
shift 2
;;
--no-reproduce)
SKIP_REPRODUCE=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1" >&2
usage >&2
exit 1
;;
esac
done

REPO="${1:-}"
if [ -z "$REPO" ]; then
usage >&2
exit 1
fi

if [ -n "$FROM_STAGE" ] && [ ${#STAGES[@]} -gt 0 ]; then
echo "Use either --from or -s, not both." >&2
exit 1
fi

if [ -n "$FROM_STAGE" ]; then
seen=false
for stage in "${ALL_STAGES[@]}"; do
[ "$stage" = "$FROM_STAGE" ] && seen=true
[ "$seen" = true ] && STAGES+=("$stage")
done
if [ "$seen" != true ]; then
echo "Unknown stage: ${FROM_STAGE}" >&2
exit 1
fi
fi

if [ ${#STAGES[@]} -eq 0 ]; then
STAGES=("${ALL_STAGES[@]}")
fi

for stage in "${STAGES[@]}"; do
valid=false
for known in "${ALL_STAGES[@]}"; do
[ "$stage" = "$known" ] && valid=true
done
if [ "$valid" != true ]; then
echo "Unknown stage: ${stage}" >&2
exit 1
fi
done

if [ "$SKIP_REPRODUCE" = true ]; then
filtered=()
for stage in "${STAGES[@]}"; do
[ "$stage" = "reproduce" ] || filtered+=("$stage")
done
STAGES=(${filtered[@]+"${filtered[@]}"})
fi

if [ ${#STAGES[@]} -eq 0 ]; then
echo "No stages left to run." >&2
exit 1
fi

# Reproduction is the one stage that executes attacker-controlled input, so a
# missing image is worth catching now rather than halfway through a finding.
for stage in "${STAGES[@]}"; do
if [ "$stage" = "reproduce" ] &&
! docker image inspect ghcr.io/githubsecuritylab/seclab-shell-reproduction:latest >/dev/null 2>&1; then
echo "The reproduction image is missing. Build it with:" >&2
echo " ./scripts/build_container_images.sh reproduction" >&2
exit 1
fi
done

# Source access containers run with CONTAINER_PERSIST, and their name is a hash
# of image, workspace and network rather than of the workspace's contents. A
# container left over from a run whose workspace has since been recreated keeps
# a bind mount on the old directory, so /workspace comes up empty and every
# stage reads nothing. Drop them and let this run make its own.
stale=$(docker ps -aq --filter "name=^seclab-persist-" 2>/dev/null || true)
if [ -n "$stale" ]; then
echo "Removing stale persistent containers"
# shellcheck disable=SC2086
docker rm -f $stale >/dev/null
fi

echo "audit v2: ${REPO}"
echo "stages: ${STAGES[*]}"
echo

for stage in "${STAGES[@]}"; do
echo "=== ${stage} ==="
python -m seclab_taskflow_agent \
${MODEL_CONFIG_FLAG[@]+"${MODEL_CONFIG_FLAG[@]}"} \
-t "seclab_taskflows.taskflows.audit_v2.${stage}" \
-g repo="${REPO}"
echo
done

echo "The findings are in the ledger. Re-read the report at any time with:"
echo " python -m seclab_taskflow_agent -t seclab_taskflows.taskflows.audit_v2.report -g repo=${REPO}"
14 changes: 12 additions & 2 deletions scripts/build_container_images.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# Must be run from the root of the seclab-taskflows repository.
# Images must be rebuilt whenever a Dockerfile changes.
#
# Usage: ./scripts/build_container_images.sh [base|malware|network|source-access|sast|all]
# Usage: ./scripts/build_container_images.sh [base|malware|network|source-access|sast|reproduction|all]
# default: all

set -euo pipefail
Expand Down Expand Up @@ -41,6 +41,11 @@ build_sast() {
docker build -t "${IMAGE_PREFIX}/seclab-shell-sast:latest" "${CONTAINERS_DIR}/sast/"
}

build_reproduction() {
echo "Building ${IMAGE_PREFIX}/seclab-shell-reproduction..."
docker build -t "${IMAGE_PREFIX}/seclab-shell-reproduction:latest" "${CONTAINERS_DIR}/reproduction/"
}

target="${1:-all}"

case "$target" in
Expand All @@ -62,16 +67,21 @@ case "$target" in
build_base
build_sast
;;
reproduction)
build_base
build_reproduction
;;
all)
build_base
build_malware
build_network
build_source_access
build_sast
build_reproduction
;;
*)
echo "Unknown target: $target" >&2
echo "Usage: $0 [base|malware|network|source-access|sast|all]" >&2
echo "Usage: $0 [base|malware|network|source-access|sast|reproduction|all]" >&2
exit 1
;;
esac
Expand Down
119 changes: 119 additions & 0 deletions src/seclab_taskflows/configs/model_config_audit_v2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# SPDX-FileCopyrightText: GitHub, Inc.
# SPDX-License-Identifier: MIT

# Model assignment for the audit v2 pipeline.
#
# The stage names below are chosen so that models can be swapped per stage
# without editing any taskflow. Two properties are deliberate:
#
# 1. The three hunt slots come from three different model families. Models in
# the same family tend to miss the same things, so mixing families buys
# real coverage rather than three correlated opinions.
#
# 2. The adjudicator belongs to a different family than either advocate, so it
# is never grading an argument written by a sibling model.
#
# Swap any entry for a different model if your entitlements differ; nothing in
# the taskflows depends on a specific provider.
#
# Two things to check when swapping.
#
# Backend. Each family is driven through its own SDK: Claude over the native
# Anthropic Messages API (`anthropic_sdk`), everything else over the OpenAI
# surface (`openai_agents`). All of it still goes to CAPI; the backend only
# decides which wire protocol is spoken. `backend` is set explicitly on every
# slot rather than left to the default, so a slot's provider and its SDK
# cannot drift apart unnoticed.
#
# Endpoint. CAPI serves most models from exactly one endpoint, and asking for
# the wrong one fails the whole stage with "model X is not accessible via the
# /chat/completions endpoint". Probed at the time of writing: gemini-3.6-flash
# is chat completions only; gpt-5.6-sol is responses only; gpt-5.4 and
# gpt-5-mini serve both; the Claude models answer on the native
# `/v1/messages` surface, which is `api_type: messages`. `responses` is the
# newer API and is preferred wherever a model offers it. Check a new model on
# both endpoints before relying on it.
#
# Reasoning effort is set on every slot that supports it, including the
# Anthropic ones, where the backend turns it into adaptive thinking.

seclab-taskflow-agent:
version: "1.0"
filetype: model_config
models:
# Cheap, high-volume bookkeeping: fetching, clearing, summarising ledger
# state and applying attribution. Not a reasoning slot, but it drives tool
# calls in a loop, so it is not the smallest model either.
general_tasks: gpt-5.4
# Attack-surface mapping and component inventory. Everything downstream hunts
# only what this stage found, so it is not a place to economise.
survey: gpt-5.6-sol
# Three independent hunters, one per family, each the strongest code-analysis
# model that family currently serves.
#
# xAI is deliberately absent everywhere in this file. CAPI rejects grok-4.5
# for security analysis at the platform level, not the model level: any
# request whose content is vulnerability analysis comes back
# `403 permission-denied ... Failed check: SAFETY_CHECK_TYPE_CYBER`, down to
# a five-line snippet. It is unusable for every role in this pipeline.
hunt_gpt: gpt-5.6-sol
hunt_claude: claude-opus-5
hunt_gemini: gemini-3.6-flash
# Adversarial contest. Advocates are strong; the judge is from the one family
# that writes neither argument, so it is never grading a sibling's case.
prosecution: gpt-5.6-sol
defense: claude-opus-5
adjudication: gemini-3.6-flash
# Dynamic reachability validation is long-horizon tool use inside a
# container: the stage has to actually drive the target and observe the flow
# reach the sink, not describe it. claude-opus-5 soft-refuses this work under
# content filtering (it returns a single text turn and calls no tools), so
# this slot runs claude-opus-4.8, which does the same job without tripping it.
reproduction: claude-opus-4.8
# Final write-up.
reporting: gpt-5.6-sol
model_settings:
general_tasks:
backend: openai_agents
api_type: responses
survey:
backend: openai_agents
api_type: responses
reasoning:
effort: medium
hunt_gpt:
backend: openai_agents
api_type: responses
reasoning:
effort: high
hunt_claude:
backend: anthropic_sdk
api_type: messages
reasoning:
effort: high
hunt_gemini:
backend: openai_agents
api_type: chat_completions
prosecution:
backend: openai_agents
api_type: responses
reasoning:
effort: high
defense:
backend: anthropic_sdk
api_type: messages
reasoning:
effort: high
adjudication:
backend: openai_agents
api_type: chat_completions
reproduction:
backend: anthropic_sdk
api_type: messages
reasoning:
effort: high
reporting:
backend: openai_agents
api_type: responses
reasoning:
effort: medium
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# SPDX-FileCopyrightText: GitHub, Inc.
# SPDX-License-Identifier: MIT

# Lower-cost model assignment for the audit v2 pipeline.
#
# Same stage names as model_config_audit_v2, so it is a drop-in replacement via
# `-c seclab_taskflows.configs.model_config_audit_v2_lowercost`. Cross-family
# diversity is preserved where it matters most (hunt breadth and an
# independent adjudicator), but every slot uses a cheaper model.

seclab-taskflow-agent:
version: "1.0"
filetype: model_config
models:
general_tasks: gpt-5.4-nano
survey: gpt-5-mini
hunt_gpt: gpt-5.4
hunt_claude: claude-haiku-4.5
hunt_gemini: gemini-3.6-flash
prosecution: gpt-5.4
defense: claude-haiku-4.5
adjudication: gemini-3.6-flash
reproduction: claude-sonnet-4.6
reporting: gpt-5-mini
model_settings:
general_tasks:
backend: openai_agents
api_type: responses
survey:
backend: openai_agents
api_type: responses
hunt_gpt:
backend: openai_agents
api_type: responses
reasoning:
effort: high
hunt_claude:
backend: anthropic_sdk
api_type: messages
reasoning:
effort: high
hunt_gemini:
backend: openai_agents
api_type: chat_completions
prosecution:
backend: openai_agents
api_type: responses
reasoning:
effort: high
defense:
backend: anthropic_sdk
api_type: messages
reasoning:
effort: high
adjudication:
backend: openai_agents
api_type: chat_completions
reproduction:
backend: anthropic_sdk
api_type: messages
reasoning:
effort: high
reporting:
backend: openai_agents
api_type: responses
Loading
Loading