Update apis (#526)

* Update apis

Closes #436 

Updates APIs to 1.2-rc1 ahead of unscored round 2

* Update values.yaml

* Bump competition/crs API to v1.2

* Breaking changes in competition api config

* Check for SARIFs in CI (#527)

* Check for SARIFs in CI

* Remove trailing bracket

---------

Co-authored-by: Henrik Brodin <90325907+hbrodin@users.noreply.github.com>
This commit is contained in:
Michael D Brown
2025-05-02 08:44:33 -04:00
committed by GitHub
parent eb2b3e0415
commit 48cbaca8a0
35 changed files with 6676 additions and 152 deletions
+26
View File
@@ -34,12 +34,17 @@ on:
description: "Timeout for bundle submission in minutes"
required: false
default: ""
sarif_timeout:
description: "Timeout for SARIF submission in minutes"
required: false
default: ""
env:
FUZZER_BUILD_TIMEOUT: ${{ inputs.build_timeout || 20 }}
VULN_TIMEOUT: ${{ inputs.vuln_timeout || 25 }}
PATCH_TIMEOUT: ${{ inputs.patch_timeout || 15 }}
BUNDLE_TIMEOUT: ${{ inputs.bundle_timeout || 5 }}
SARIF_TIMEOUT: ${{ inputs.sarif_timeout || 5 }}
FUZZ_TOOLING_CONTAINER_ORG: ${{ inputs.fuzz_tooling_container_org || 'aixcc-afc' }}
permissions:
@@ -175,6 +180,27 @@ jobs:
while ! kubectl logs -n $BUTTERCUP_NAMESPACE -l app=scheduler | grep "SUBMIT_BUNDLE -> SUBMIT_MATCHING_SARIF"; do
sleep 10
done
- name: Send a SARIF broadcast
timeout-minutes: ${{ fromJSON(env.SARIF_TIMEOUT) }}
run: |
TASK_ID=$(kubectl logs -n $BUTTERCUP_NAMESPACE -l app=scheduler | grep "SUBMIT_BUNDLE -> SUBMIT_MATCHING_SARIF" | grep -o "\[[^]]*\]" | grep -o "[^[]*$" | cut -d: -f2 | tr -d ']')
echo "Task ID: $TASK_ID"
kubectl port-forward -n $BUTTERCUP_NAMESPACE service/buttercup-competition-api 31323:1323 &
sleep 5
./orchestrator/scripts/send_sarif.sh $TASK_ID
sleep 5
- name: Wait for SARIF to be submitted as correct
timeout-minutes: ${{ fromJSON(env.SARIF_TIMEOUT) }}
run: |
while ! kubectl logs -n $BUTTERCUP_NAMESPACE -l app=scheduler | grep "SUBMIT_MATCHING_SARIF -> SUBMIT_BUNDLE_PATCH"; do
sleep 10
done
- name: Wait for Bundle to be patched to include the SARIF
timeout-minutes: ${{ fromJSON(env.SARIF_TIMEOUT) }}
run: |
while ! kubectl logs -n $BUTTERCUP_NAMESPACE -l app=scheduler | grep "SUBMIT_BUNDLE_PATCH -> STOP"; do
sleep 10
done
# TODO: re-enable seed-gen once it's fixed
# - name: Wait for seed-gen to find PoV
# timeout-minutes: 10 # low timeout because already waiting for prior steps
+1 -1
View File
@@ -35,7 +35,7 @@ services:
# - shared-tmp:/tmp
scantron:
image: ghcr.io/aixcc-finals/example-crs-architecture/competition-test-api:v1.1-rc7
image: ghcr.io/aixcc-finals/example-crs-architecture/competition-test-api:v1.2-rc1
platform: linux/amd64
privileged: true
volumes:
+9
View File
@@ -39,3 +39,12 @@ api_host_and_port: "scantron:31323"
# # github Personal Access token. This is used by the server to download the
# # appropriate fuzz tooling if not using public repositories
# pat: ghp_XXXXXXXXXXXXXXXXXXXXX
generate:
enabled: true
delta:
repo_url: "https://github.com/aixcc-finals/integration-test.git"
base_ref: "5ac0917575e20464b5aa86434dff2a7626b558b4"
head_ref: "challenges/integration-test-delta-01"
fuzz_tooling_url: "https://github.com/aixcc-finals/oss-fuzz-aixcc.git"
fuzz_tooling_ref: "v1.1.0"
fuzz_tooling_project_name: "integration-test"
@@ -37,3 +37,12 @@ data:
listen_address: "[::]:1323"
api_host_and_port: "{{ .Release.Name }}-competition-api.{{ .Release.Namespace }}.svc.cluster.local:1323"
{{- end }}
generate:
enabled: true
delta:
repo_url: "https://github.com/aixcc-finals/integration-test.git"
base_ref: "5ac0917575e20464b5aa86434dff2a7626b558b4"
head_ref: "challenges/integration-test-delta-01"
fuzz_tooling_url: "https://github.com/aixcc-finals/oss-fuzz-aixcc.git"
fuzz_tooling_ref: "v1.1.0"
fuzz_tooling_project_name: "integration-test"
+1 -1
View File
@@ -30,7 +30,7 @@ global:
pullSecrets: ["ghcr-auth"]
competitionApiImage:
repository: ghcr.io/aixcc-finals/example-crs-architecture/competition-test-api
tag: v1.1-rc7
tag: v1.2-rc1
pullPolicy: Always
pullSecrets: ["ghcr-auth"]
+41
View File
@@ -0,0 +1,41 @@
# AIxCC Final Documentation
Welcome to the documentation page for the AIxCC Final Competition.
## Table of Contents
- [Changelog](api/CHANGELOG.md#changelog)
- [1.2.0](api/CHANGELOG.md#120)
- [v1.1.1](api/CHANGELOG.md#v111)
- [v1.1](api/CHANGELOG.md#v11)
- [v1.0](api/CHANGELOG.md#v10)
- [v0.4](api/CHANGELOG.md#v04)
- [v0.3](api/CHANGELOG.md#v03)
- [API](api/README.md#api)
- [Competition API Interaction](api/README.md#competition-api-interaction)
- [Submitting a vulnerability](api/README.md#submitting-a-vulnerability)
- [Submitting a SARIF assessment](api/README.md#submitting-a-sarif-assessment)
- [Submitting a patch](api/README.md#submitting-a-patch)
- [Submitting a bundle](api/README.md#submitting-a-bundle)
- [Request Integration Test Task](api/README.md#request-integration-test-task)
- [CRS API Task Statuses](api/README.md#crs-api-task-statuses)
- [Generating a Client or Server](api/README.md#generating-a-client-or-server)
- [OpenAPI Generator](api/README.md#openapi-generator)
- [Viewing the UI](api/README.md#viewing-the-ui)
- [CRS Telemetry Specification v1.0](telemetry/README.md#crs-telemetry-specification-v10)
- [GenAI (LLM requests and responses)](telemetry/README.md#genai-llm-requests-and-responses)
- [CRS Actions](telemetry/README.md#crs-actions)
- [Resource Attributes](telemetry/README.md#resource-attributes)
- [Mandatory Span Attributes](telemetry/README.md#mandatory-span-attributes)
- [Optional Attributes](telemetry/README.md#optional-attributes)
- [Specialized Attributes for LLM Requests](telemetry/README.md#specialized-attributes-for-llm-requests)
- [Specialized Attributes for Fuzzing](telemetry/README.md#specialized-attributes-for-fuzzing)
- [Example Python Implementation](telemetry/README.md#example-python-implementation)
- [Alternative Python Implementation](telemetry/README.md#alternative-python-implementation)
- [Example CRS Webservice](telemetry/README.md#example-crs-webservice)
- [Other Usage Examples](telemetry/README.md#other-usage-examples)
- [General telemetry guidance](telemetry/telemetry_best_practices.md#general-telemetry-guidance)
- [Improving the structure of OTEL data](telemetry/telemetry_best_practices.md#improving-the-structure-of-otel-data)
- [Verifying telemetry](telemetry/telemetry_best_practices.md#verifying-telemetry)
- [Tailnet-accessible hosted Competitor Test Server](tailscale-hosted-competitor-test-server/hosted_competitor_test_server.md#tailnet-accessible-hosted-competitor-test-server)
- [Tailscale Management Tools](tailscale-hosted-competitor-test-server/tailscale_management_tools.md#tailscale-management-tools)
+1 -1
View File
@@ -3,7 +3,7 @@
## Enum
* `X86_64` (value: `'x86_64'`)
* `ArchitectureX8664` (value: `'x86_64'`)
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+2 -2
View File
@@ -3,9 +3,9 @@
## Enum
* `CORRECT` (value: `'correct'`)
* `AssessmentCorrect` (value: `'correct'`)
* `INCORRECT` (value: `'incorrect'`)
* `AssessmentIncorrect` (value: `'incorrect'`)
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+5 -5
View File
@@ -3,15 +3,15 @@
## Enum
* `ACCEPTED` (value: `'accepted'`)
* `SubmissionStatusAccepted` (value: `'accepted'`)
* `PASSED` (value: `'passed'`)
* `SubmissionStatusPassed` (value: `'passed'`)
* `FAILED` (value: `'failed'`)
* `SubmissionStatusFailed` (value: `'failed'`)
* `DEADLINE_EXCEEDED` (value: `'deadline_exceeded'`)
* `SubmissionStatusDeadlineExceeded` (value: `'deadline_exceeded'`)
* `ERRORED` (value: `'errored'`)
* `SubmissionStatusErrored` (value: `'errored'`)
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+6 -2
View File
@@ -1,5 +1,10 @@
# Changelog
## 1.2.0
- revert to the default SARIF schema removing the required property on `ruleId`
- add endpoint for requesting an integration test challenge
## v1.1.1
- correct typo in swagger docs tags for freeform submission
@@ -40,8 +45,7 @@ For more information, see:
- Added optional `sarif_id` on patch submissions
- Added large max sizes to most string fields on the Competition API
- Added note that max sizes for base64 fields are before encoding
- Added metadata fields to Task and SARIF broadcasts containing a string to string map that should be attached
to outputs like log messages and OpenTelemetry trace attributes for traceability
- Added metadata fields to Task and SARIF broadcasts containing a string to string map that should be attached to outputs like log messages and OpenTelemetry trace attributes for traceability
- Added `focus` and `project_name` to Task broadcasts.
- Project name is the OSS Fuzz project name for the task.
- Focus is the name of the directory in the `repo` tarball that vulns, patches, and SARIF broadcasts should be submitted against.
+67 -47
View File
@@ -1,47 +1,12 @@
# API
See the [API Changelog](./docs/api/CHANGELOG.md) for information on recent API changes.
See the [API Changelog](./CHANGELOG.md) for information on recent API changes.
- Competitors will consume the API described by `competition-swagger-v1.1.1.yaml`
- Competitors will provide the API described by `crs-swagger-v1.1.yaml`
- Competitors will consume the API described by `competition-swagger.yaml`
- Competitors will provide the API described by `crs-swagger.yaml`
NOTE: The JSON and YAML documents contain the same information and are generated from the same source.
These files describe the CRS API and the Example Competition API. [Swagger](https://swagger.io/blog/api-strategy/difference-between-swagger-and-openapi/) is an ecosystem built around the [OpenAPI](https://www.openapis.org/what-is-openapi)
specification with which it is possible to render a UI documenting the API, to generate client code to interact with the API, and to generate
server code to be the API. In essence, the OpenAPI specification is a standard way of documenting HTTP APIs. This allows tools built to consume the description
to be portable across APIs.
The different components can be used as needed for different applications. The Example Competition API specification
is generated using inline code comments and served under the `/swagger/` path. A web framework specific third-party dependency that included the
swagger-ui was used to provide the endpoints.
## Viewing the UI
The provided Example Competition API will serve the swagger UI at `/swagger/index.html`. The spec is served at `/swagger/doc.json`
Competitors may find it useful to do something similar for their CRS.
Most webserver frameworks will have a package that can be used to help serve the UI. If this is not possible or you do not wish to use a third-party package,
the source can be downloaded from [https://github.com/swagger-api/swagger-ui/releases](https://github.com/swagger-api/swagger-ui/releases).
After unpacking the archive using tar or ZIP, the precompiled source can be found in the `dist` directory.
If you do not wish to integrate the UI directly into the CRS system it is still possible to use for development.
- Using the same method described above, download and unpack the release.
- Place any number of swagger specs inside the `swagger-ui/dist` folder.
- Start a basic webserver from the `swagger-ui/dist` folder. For example the built-in Python 3 webserver:
```bash
# PWD: swagger-ui/dist
python -m http.server
```
- Change the path to the swagger file at the top of your page to the desired file. For example, `/competition-swagger-v0.1.json` if you placed
a swagger spec called `competition-swagger-v0.1.json` at `swagger-ui/dist/competition-swagger-v0.1.json`.
- This will only allow viewing the documentation. Experimenting with the API endpoints requires a running API server serving the spec file.
If the spec is served from the API server and it supports CORS, it is possible to specify the full URL to the spec in the box.
This will allow you to experiment with the API using the `Try it Out` button.
## Competition API Interaction
During the competition, the Competition API sends tasks and SARIF broadcasts to CRSs and receives responses of several types (vulnerability, patch, or SARIF assessment). The Example Competition API checks interfaces
@@ -106,6 +71,29 @@ CRS->>API: Create Bundle "accepted"
CRS->>API: Modify Bundle
```
### Request Integration Test Task
After deploying your CRS, you now have the ability to task your CRS with a simple “[integration test](https://github.com/aixcc-finals/integration-test/tree/challenges/integration-test-delta-01)” challenge (delta scan) to
make sure your CRS and Telemetry are working as expected prior to the opening of the Round.
This endpoint is available from now until 5 minutes prior to the opening of a Round. It is accessible from the internet and from inside the tailnet. `duration_secs` is an optional parameter, and defaults to 4 hours.
Provide your Competition API credentials to kick off the task. This will send a task to the Round's expected CRS hostname (e.g. `team-moniker-exhibition2` for exhibition2) on the tailnet.
```mermaid
sequenceDiagram
accTitle: Smoke Test Workflow
User->>API: /v1/request/delta
API->>CRS: Hardcoded integration-test task
CRS->>API: POV/Patch/Bundle/Sarif Submission
```
Here is an example curl you may use to trigger the integration testing task.
```bash
curl -u 11111111-1111-1111-1111-111111111111:pY8rLk7FvQ2hZm9GwUx3Ej5BnTcV4So0 -X 'POST' 'https://api.aixcc.tech/v1/request/delta/' -H 'Content-Type: application/json' -d '{"duration_secs": 3600 }'
```
## CRS API Task Statuses
The CRS API has a status endpoint which provides a summary of tasks by status, among other things. The statuses which tasks go through are documented in the state diagram below.
@@ -127,12 +115,13 @@ stateDiagram-v2
## Generating a Client or Server
It is possible to generate client or server code from the swagger documents. The documents are a best effort to provide a strongly typed schema but are not perfect.
Use the generators at your own discretion as they are not maintained by AIxCC and may not produce working code.
It is possible to generate client or server code from the swagger documents. The documents are a best effort to provide a strongly typed schema but are not perfect. Use the generators at your own discretion as they are
not maintained by AIxCC and may not produce working code.
### OpenAPI Generator
OpenAPI generator is a fork and actively maintained continuation of the swagger-codegen project. The source code can be found here: [https://github.com/openapitools/openapi-generator](https://github.com/openapitools/openapi-generator).
OpenAPI generator is a fork and actively maintained continuation of the swagger-codegen project. The source code can be found here:
[https://github.com/openapitools/openapi-generator](https://github.com/openapitools/openapi-generator).
To run the generator using docker or podman:
@@ -143,18 +132,49 @@ docker run --rm -v $PWD:/local openapitools/openapi-generator-cli generate \
-o /local/out
```
- `-v $PWD:/local` mounts the current working directory into the `/local` directory in the container.
A different host path could be provided for `$PWD`. All paths used in the following steps would be relative to the new path instead of `$PWD`.
- `-v $PWD:/local` mounts the current working directory into the `/local` directory in the container. A different host path could be provided for `$PWD`. All paths used in the following steps would be relative to the new
path instead of `$PWD`.
- All arguments after `openapitools/openapi-generator-cli` are passed to the generator CLI inside the container
- `-i /local/competition-swagger-v0.1.json` is the path relative to the current working directory of the swagger file. On the host, `competition-swagger-v-0.1.json` is located in `$PWD`.
If you want to change the file or path it must be a descendant of `$PWD`.
- `-i /local/competition-swagger-v0.1.json` is the path relative to the current working directory of the swagger file. On the host, `competition-swagger-v-0.1.json` is located in `$PWD`. If you want to change the file or
path it must be a descendant of `$PWD`.
- `-g lang` is used to specify the generator to run. If you pass an invalid value it will list all of the options.
- `-o /local/out` is the path relative to the current working directory to output the generated code.
- A language specific config file can be optionally provided using `-c /local/path_to_config` [Project Docs](https://github.com/OpenAPITools/openapi-generator/blob/b218e238f4f6cac8c919a78b296d3062bdfec0be/docs/customization.md#customizing-the-generator).
The available config options can be found using the `config-help -g lang` subcommand instead of `generate`.
- A language specific config file can be optionally provided using `-c /local/path_to_config`
[Project Docs](https://github.com/OpenAPITools/openapi-generator/blob/b218e238f4f6cac8c919a78b296d3062bdfec0be/docs/customization.md#customizing-the-generator). The available config options can be found using the
`config-help -g lang` subcommand instead of `generate`.
To see all available options for the generate command:
```bash
docker run --rm -v $PWD:/local openapitools/openapi-generator-cli help generate
```
### Viewing the UI
The swagger documents describe the CRS API and the Example Competition API. [Swagger](https://swagger.io/blog/api-strategy/difference-between-swagger-and-openapi/) is an ecosystem built around the
[OpenAPI](https://www.openapis.org/what-is-openapi) specification with which it is possible to render a UI documenting the API, to generate client code to interact with the API, and to generate server code to be the API.
In essence, the OpenAPI specification is a standard way of documenting HTTP APIs. This allows tools built to consume the description to be portable across APIs.
The different components can be used as needed for different applications. The Example Competition API specification is generated using inline code comments and served under the `/swagger/` path. A web framework specific
third-party dependency that included the swagger-ui was used to provide the endpoints.
The provided Example Competition API will serve the swagger UI at `/swagger/index.html`. The spec is served at `/swagger/doc.json` Competitors may find it useful to do something similar for their CRS.
Most webserver frameworks will have a package that can be used to help serve the UI. If this is not possible or you do not wish to use a third-party package, the source can be downloaded from
[https://github.com/swagger-api/swagger-ui/releases](https://github.com/swagger-api/swagger-ui/releases). After unpacking the archive using tar or ZIP, the precompiled source can be found in the `dist` directory.
If you do not wish to integrate the UI directly into the CRS system it is still possible to use for development.
- Using the same method described above, download and unpack the release.
- Place any number of swagger specs inside the `swagger-ui/dist` folder.
- Start a basic webserver from the `swagger-ui/dist` folder. For example the built-in Python 3 webserver:
```bash
# PWD: swagger-ui/dist
python -m http.server
```
- Change the path to the swagger file at the top of your page to the desired file. For example, `/competition-swagger-v0.1.json` if you placed a swagger spec called `competition-swagger-v0.1.json` at
`swagger-ui/dist/competition-swagger-v0.1.json`.
- This will only allow viewing the documentation. Experimenting with the API endpoints requires a running API server serving the spec file. If the spec is served from the API server and it supports CORS, it is possible
to specify the full URL to the spec in the box. This will allow you to experiment with the API using the `Try it Out` button.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,860 @@
definitions:
types.Architecture:
enum:
- x86_64
type: string
x-enum-varnames:
- ArchitectureX8664
types.Assessment:
enum:
- correct
- incorrect
type: string
x-enum-varnames:
- AssessmentCorrect
- AssessmentIncorrect
types.BundleSubmission:
properties:
broadcast_sarif_id:
format: uuid
type: string
description:
description:
optional plaintext description of the components of the bundle,
such as would be found in a pull request description or bug report
type: string
freeform_id:
format: uuid
type: string
patch_id:
format: uuid
type: string
pov_id:
format: uuid
type: string
submitted_sarif_id:
format: uuid
type: string
type: object
types.BundleSubmissionResponse:
properties:
bundle_id:
format: uuid
type: string
status:
allOf:
- $ref: "#/definitions/types.SubmissionStatus"
description:
Schema-compliant submissions will only ever receive the statuses
accepted or deadline_exceeded
required:
- bundle_id
- status
type: object
types.BundleSubmissionResponseVerbose:
properties:
broadcast_sarif_id:
format: uuid
type: string
bundle_id:
format: uuid
type: string
description:
type: string
freeform_id:
format: uuid
type: string
patch_id:
format: uuid
type: string
pov_id:
format: uuid
type: string
status:
allOf:
- $ref: "#/definitions/types.SubmissionStatus"
description:
Schema-compliant submissions will only ever receive the statuses
accepted or deadline_exceeded
submitted_sarif_id:
format: uuid
type: string
required:
- bundle_id
- status
type: object
types.Error:
properties:
fields:
additionalProperties:
type: string
type: object
message:
type: string
required:
- message
type: object
types.FreeformResponse:
properties:
freeform_id:
format: uuid
type: string
status:
allOf:
- $ref: "#/definitions/types.SubmissionStatus"
description:
Schema-compliant submissions will only ever receive the statuses
accepted or deadline_exceeded
required:
- freeform_id
- status
type: object
types.FreeformSubmission:
properties:
submission:
description: |-
Base64 encoded arbitrary data
2MiB max size before Base64 encoding
type: string
required:
- submission
type: object
types.POVSubmission:
properties:
architecture:
$ref: "#/definitions/types.Architecture"
engine:
description: |-
Fuzz Tooling Engine that exercises this vuln. Allowable engine values are specified in project.yaml.
4KiB max size
maxLength: 4096
type: string
fuzzer_name:
description: |-
Fuzz Tooling fuzzer that exercises this vuln
4KiB max size
maxLength: 4096
type: string
sanitizer:
description: |-
Fuzz Tooling Sanitizer that exercises this vuln
4KiB max size
maxLength: 4096
type: string
testcase:
description: |-
Base64 encoded vuln trigger
2MiB max size before Base64 encoding
format: base64
type: string
required:
- architecture
- engine
- fuzzer_name
- sanitizer
- testcase
type: object
types.POVSubmissionResponse:
properties:
pov_id:
format: uuid
type: string
status:
$ref: "#/definitions/types.SubmissionStatus"
required:
- pov_id
- status
type: object
types.PatchSubmission:
properties:
patch:
description: |-
Base64 encoded patch in unified diff format
100KiB max size before Base64 encoding
format: base64
type: string
required:
- patch
type: object
types.PatchSubmissionResponse:
properties:
functionality_tests_passing:
description: null indicates the tests have not been run
type: boolean
patch_id:
format: uuid
type: string
status:
$ref: "#/definitions/types.SubmissionStatus"
required:
- patch_id
- status
type: object
types.PingResponse:
properties:
status:
type: string
required:
- status
type: object
types.RequestSubmission:
properties:
duration_secs:
description:
Time in seconds until a task should expire. If not provided,
defaults to 3600.
type: integer
type: object
types.SARIFSubmission:
properties:
sarif:
description: SARIF object compliant with the provided schema
type: object
required:
- sarif
type: object
types.SARIFSubmissionResponse:
properties:
status:
allOf:
- $ref: "#/definitions/types.SubmissionStatus"
description:
Schema-compliant submissions will only ever receive the statuses
accepted or deadline_exceeded
submitted_sarif_id:
format: uuid
type: string
required:
- status
- submitted_sarif_id
type: object
types.SarifAssessmentResponse:
properties:
status:
$ref: "#/definitions/types.SubmissionStatus"
required:
- status
type: object
types.SarifAssessmentSubmission:
properties:
assessment:
$ref: "#/definitions/types.Assessment"
description:
description: |-
Plain text reasoning for the assessment
128KiB max size
maxLength: 131072
type: string
required:
- assessment
- description
type: object
types.SubmissionStatus:
enum:
- accepted
- passed
- failed
- deadline_exceeded
- errored
type: string
x-enum-comments:
SubmissionStatusAccepted: Successfully submitted
SubmissionStatusDeadlineExceeded:
Task deadline exceeded. All submissions marked
accepted before the deadline will be evaluated.
SubmissionStatusErrored: Server side error when testing submission
SubmissionStatusFailed: Submission failed testing
SubmissionStatusPassed: Successfully evaluated submission
x-enum-varnames:
- SubmissionStatusAccepted
- SubmissionStatusPassed
- SubmissionStatusFailed
- SubmissionStatusDeadlineExceeded
- SubmissionStatusErrored
info:
contact: {}
title: Example Competition API
version: 1.2.0
paths:
/v1/ping/:
get:
consumes:
- application/json
description: Test authentication creds and network connectivity
responses:
"200":
description: OK
schema:
$ref: "#/definitions/types.PingResponse"
security:
- BasicAuth: []
summary: Test authentication creds and network connectivity
tags:
- ping
/v1/request/delta/:
post:
consumes:
- application/json
description: Send a delta task to the source of this request
parameters:
- description: Submission Body
in: body
name: payload
required: true
schema:
$ref: "#/definitions/types.RequestSubmission"
produces:
- application/json
responses:
"200":
description: No Content
schema:
type: string
"400":
description: Bad Request
schema:
$ref: "#/definitions/types.Error"
"401":
description: Unauthorized
schema:
$ref: "#/definitions/types.Error"
"404":
description: Not Found
schema:
$ref: "#/definitions/types.Error"
"500":
description: Internal Server Error
schema:
$ref: "#/definitions/types.Error"
security:
- BasicAuth: []
summary: Send a delta task to the source of this request
tags:
- request
/v1/task/{task_id}/broadcast-sarif-assessment/{broadcast_sarif_id}/:
post:
consumes:
- application/json
description: Submit a SARIF assessment
parameters:
- description: Task ID
format: uuid
in: path
name: task_id
required: true
type: string
- description: Broadcast SARIF ID
format: uuid
in: path
name: broadcast_sarif_id
required: true
type: string
- description: Submission body
in: body
name: payload
required: true
schema:
$ref: "#/definitions/types.SarifAssessmentSubmission"
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: "#/definitions/types.SarifAssessmentResponse"
"400":
description: Bad Request
schema:
$ref: "#/definitions/types.Error"
"401":
description: Unauthorized
schema:
$ref: "#/definitions/types.Error"
"404":
description: Not Found
schema:
$ref: "#/definitions/types.Error"
"500":
description: Internal Server Error
schema:
$ref: "#/definitions/types.Error"
security:
- BasicAuth: []
summary: Submit a SARIF Assessment
tags:
- broadcast-sarif-assessment
/v1/task/{task_id}/bundle/:
post:
consumes:
- application/json
description: submits a bundle
parameters:
- description: Task ID
format: uuid
in: path
name: task_id
required: true
type: string
- description: Submission Body
in: body
name: payload
required: true
schema:
$ref: "#/definitions/types.BundleSubmission"
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: "#/definitions/types.BundleSubmissionResponse"
"400":
description: Bad Request
schema:
$ref: "#/definitions/types.Error"
"401":
description: Unauthorized
schema:
$ref: "#/definitions/types.Error"
"404":
description: Not Found
schema:
$ref: "#/definitions/types.Error"
"500":
description: Internal Server Error
schema:
$ref: "#/definitions/types.Error"
security:
- BasicAuth: []
summary: Submit Bundle
tags:
- bundle
/v1/task/{task_id}/bundle/{bundle_id}/:
delete:
consumes:
- application/json
description: delete a bundle
parameters:
- description: Task ID
format: uuid
in: path
name: task_id
required: true
type: string
- description: Bundle ID
format: uuid
in: path
name: bundle_id
required: true
type: string
produces:
- application/json
responses:
"204":
description: No Content
schema:
type: string
"400":
description: Bad Request
schema:
$ref: "#/definitions/types.Error"
"401":
description: Unauthorized
schema:
$ref: "#/definitions/types.Error"
"404":
description: Not Found
schema:
$ref: "#/definitions/types.Error"
"500":
description: Internal Server Error
schema:
$ref: "#/definitions/types.Error"
security:
- BasicAuth: []
summary: Delete Bundle
tags:
- bundle
get:
consumes:
- application/json
description: get a bundle
parameters:
- description: Task ID
format: uuid
in: path
name: task_id
required: true
type: string
- description: Bundle ID
format: uuid
in: path
name: bundle_id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: "#/definitions/types.BundleSubmissionResponseVerbose"
"400":
description: Bad Request
schema:
$ref: "#/definitions/types.Error"
"401":
description: Unauthorized
schema:
$ref: "#/definitions/types.Error"
"404":
description: Not Found
schema:
$ref: "#/definitions/types.Error"
"500":
description: Internal Server Error
schema:
$ref: "#/definitions/types.Error"
security:
- BasicAuth: []
summary: Get Bundle
tags:
- bundle
patch:
consumes:
- application/json
description: updates a bundle
parameters:
- description: Task ID
format: uuid
in: path
name: task_id
required: true
type: string
- description: Bundle ID
format: uuid
in: path
name: bundle_id
required: true
type: string
- description: Submission Body
in: body
name: payload
required: true
schema:
$ref: "#/definitions/types.BundleSubmission"
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: "#/definitions/types.BundleSubmissionResponseVerbose"
"400":
description: Bad Request
schema:
$ref: "#/definitions/types.Error"
"401":
description: Unauthorized
schema:
$ref: "#/definitions/types.Error"
"404":
description: Not Found
schema:
$ref: "#/definitions/types.Error"
"500":
description: Internal Server Error
schema:
$ref: "#/definitions/types.Error"
security:
- BasicAuth: []
summary: Update Bundle
tags:
- bundle
/v1/task/{task_id}/freeform/:
post:
consumes:
- application/json
description: submits a freeform pov
parameters:
- description: Task ID
format: uuid
in: path
name: task_id
required: true
type: string
- description: Submission Body
in: body
name: payload
required: true
schema:
$ref: "#/definitions/types.FreeformSubmission"
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: "#/definitions/types.FreeformResponse"
"400":
description: Bad Request
schema:
$ref: "#/definitions/types.Error"
"401":
description: Unauthorized
schema:
$ref: "#/definitions/types.Error"
"404":
description: Not Found
schema:
$ref: "#/definitions/types.Error"
"500":
description: Internal Server Error
schema:
$ref: "#/definitions/types.Error"
security:
- BasicAuth: []
summary: Submit Freeform
tags:
- freeform
/v1/task/{task_id}/patch/:
post:
consumes:
- application/json
description: submit a patch for testing
parameters:
- description: Task ID
format: uuid
in: path
name: task_id
required: true
type: string
- description: Payload
in: body
name: payload
required: true
schema:
$ref: "#/definitions/types.PatchSubmission"
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: "#/definitions/types.PatchSubmissionResponse"
"400":
description: Bad Request
schema:
$ref: "#/definitions/types.Error"
"401":
description: Unauthorized
schema:
$ref: "#/definitions/types.Error"
"404":
description: Not Found
schema:
$ref: "#/definitions/types.Error"
"500":
description: Internal Server Error
schema:
$ref: "#/definitions/types.Error"
security:
- BasicAuth: []
summary: Submit Patch
tags:
- patch
/v1/task/{task_id}/patch/{patch_id}/:
get:
consumes:
- application/json
description: yield the status of patch testing
parameters:
- description: Task ID
format: uuid
in: path
name: task_id
required: true
type: string
- description: Patch ID
format: uuid
in: path
name: patch_id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: "#/definitions/types.PatchSubmissionResponse"
"400":
description: Bad Request
schema:
$ref: "#/definitions/types.Error"
"401":
description: Unauthorized
schema:
$ref: "#/definitions/types.Error"
"404":
description: Not Found
schema:
$ref: "#/definitions/types.Error"
"500":
description: Internal Server Error
schema:
$ref: "#/definitions/types.Error"
security:
- BasicAuth: []
summary: Patch Status
tags:
- patch
/v1/task/{task_id}/pov/:
post:
consumes:
- application/json
description: submit a vulnerability for testing
parameters:
- description: Task ID
format: uuid
in: path
name: task_id
required: true
type: string
- description: Submission body
in: body
name: payload
required: true
schema:
$ref: "#/definitions/types.POVSubmission"
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: "#/definitions/types.POVSubmissionResponse"
"400":
description: Bad Request
schema:
$ref: "#/definitions/types.Error"
"401":
description: Unauthorized
schema:
$ref: "#/definitions/types.Error"
"404":
description: Not Found
schema:
$ref: "#/definitions/types.Error"
"500":
description: Internal Server Error
schema:
$ref: "#/definitions/types.Error"
security:
- BasicAuth: []
summary: Submit Vulnerability
tags:
- pov
/v1/task/{task_id}/pov/{pov_id}/:
get:
consumes:
- application/json
description: yield the status of vuln testing
parameters:
- description: Task ID
format: uuid
in: path
name: task_id
required: true
type: string
- description: POV ID
format: uuid
in: path
name: pov_id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: "#/definitions/types.POVSubmissionResponse"
"400":
description: Bad Request
schema:
$ref: "#/definitions/types.Error"
"401":
description: Unauthorized
schema:
$ref: "#/definitions/types.Error"
"404":
description: Not Found
schema:
$ref: "#/definitions/types.Error"
"500":
description: Internal Server Error
schema:
$ref: "#/definitions/types.Error"
security:
- BasicAuth: []
summary: Vulnerability Status
tags:
- pov
/v1/task/{task_id}/submitted-sarif/:
post:
consumes:
- application/json
description: Submit a CRS generated SARIF
parameters:
- description: Task ID
format: uuid
in: path
name: task_id
required: true
type: string
- description: Submission body
in: body
name: payload
required: true
schema:
$ref: "#/definitions/types.SARIFSubmission"
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: "#/definitions/types.SARIFSubmissionResponse"
"400":
description: Bad Request
schema:
$ref: "#/definitions/types.Error"
"401":
description: Unauthorized
schema:
$ref: "#/definitions/types.Error"
"404":
description: Not Found
schema:
$ref: "#/definitions/types.Error"
"500":
description: Internal Server Error
schema:
$ref: "#/definitions/types.Error"
security:
- BasicAuth: []
summary: Submit a CRS generated SARIF
tags:
- submitted-sarif
securityDefinitions:
BasicAuth:
type: basic
swagger: "2.0"
+91 -5
View File
@@ -3,7 +3,7 @@
"info": {
"title": "Example Competition API",
"contact": {},
"version": "1.1.1"
"version": "1.2.0"
},
"paths": {
"/v1/ping/": {
@@ -27,6 +27,63 @@
}
}
},
"/v1/request/delta/": {
"post": {
"security": [
{
"BasicAuth": []
}
],
"description": "Send a delta task to the source of this request",
"consumes": ["application/json"],
"produces": ["application/json"],
"tags": ["request"],
"summary": "Send a delta task to the source of this request",
"parameters": [
{
"description": "Submission Body",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/types.RequestSubmission"
}
}
],
"responses": {
"200": {
"description": "No Content",
"schema": {
"type": "string"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/types.Error"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/types.Error"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/types.Error"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/types.Error"
}
}
}
}
},
"/v1/task/{task_id}/broadcast-sarif-assessment/{broadcast_sarif_id}/": {
"post": {
"security": [
@@ -800,7 +857,12 @@
"format": "uuid"
},
"status": {
"$ref": "#/definitions/types.SubmissionStatus"
"description": "Schema-compliant submissions will only ever receive the statuses accepted or deadline_exceeded",
"allOf": [
{
"$ref": "#/definitions/types.SubmissionStatus"
}
]
}
}
},
@@ -832,7 +894,12 @@
"format": "uuid"
},
"status": {
"$ref": "#/definitions/types.SubmissionStatus"
"description": "Schema-compliant submissions will only ever receive the statuses accepted or deadline_exceeded",
"allOf": [
{
"$ref": "#/definitions/types.SubmissionStatus"
}
]
},
"submitted_sarif_id": {
"type": "string",
@@ -864,7 +931,12 @@
"format": "uuid"
},
"status": {
"$ref": "#/definitions/types.SubmissionStatus"
"description": "Schema-compliant submissions will only ever receive the statuses accepted or deadline_exceeded",
"allOf": [
{
"$ref": "#/definitions/types.SubmissionStatus"
}
]
}
}
},
@@ -963,6 +1035,15 @@
}
}
},
"types.RequestSubmission": {
"type": "object",
"properties": {
"duration_secs": {
"description": "Time in seconds until a task should expire. If not provided, defaults to 3600.",
"type": "integer"
}
}
},
"types.SARIFSubmission": {
"type": "object",
"required": ["sarif"],
@@ -978,7 +1059,12 @@
"required": ["status", "submitted_sarif_id"],
"properties": {
"status": {
"$ref": "#/definitions/types.SubmissionStatus"
"description": "Schema-compliant submissions will only ever receive the statuses accepted or deadline_exceeded",
"allOf": [
{
"$ref": "#/definitions/types.SubmissionStatus"
}
]
},
"submitted_sarif_id": {
"type": "string",
+69 -5
View File
@@ -42,7 +42,11 @@ definitions:
format: uuid
type: string
status:
$ref: "#/definitions/types.SubmissionStatus"
allOf:
- $ref: "#/definitions/types.SubmissionStatus"
description:
Schema-compliant submissions will only ever receive the statuses
accepted or deadline_exceeded
required:
- bundle_id
- status
@@ -67,7 +71,11 @@ definitions:
format: uuid
type: string
status:
$ref: "#/definitions/types.SubmissionStatus"
allOf:
- $ref: "#/definitions/types.SubmissionStatus"
description:
Schema-compliant submissions will only ever receive the statuses
accepted or deadline_exceeded
submitted_sarif_id:
format: uuid
type: string
@@ -92,7 +100,11 @@ definitions:
format: uuid
type: string
status:
$ref: "#/definitions/types.SubmissionStatus"
allOf:
- $ref: "#/definitions/types.SubmissionStatus"
description:
Schema-compliant submissions will only ever receive the statuses
accepted or deadline_exceeded
required:
- freeform_id
- status
@@ -191,6 +203,14 @@ definitions:
required:
- status
type: object
types.RequestSubmission:
properties:
duration_secs:
description:
Time in seconds until a task should expire. If not provided,
defaults to 3600.
type: integer
type: object
types.SARIFSubmission:
properties:
sarif:
@@ -202,7 +222,11 @@ definitions:
types.SARIFSubmissionResponse:
properties:
status:
$ref: "#/definitions/types.SubmissionStatus"
allOf:
- $ref: "#/definitions/types.SubmissionStatus"
description:
Schema-compliant submissions will only ever receive the statuses
accepted or deadline_exceeded
submitted_sarif_id:
format: uuid
type: string
@@ -257,7 +281,7 @@ definitions:
info:
contact: {}
title: Example Competition API
version: 1.1.1
version: 1.2.0
paths:
/v1/ping/:
get:
@@ -274,6 +298,46 @@ paths:
summary: Test authentication creds and network connectivity
tags:
- ping
/v1/request/delta/:
post:
consumes:
- application/json
description: Send a delta task to the source of this request
parameters:
- description: Submission Body
in: body
name: payload
required: true
schema:
$ref: "#/definitions/types.RequestSubmission"
produces:
- application/json
responses:
"200":
description: No Content
schema:
type: string
"400":
description: Bad Request
schema:
$ref: "#/definitions/types.Error"
"401":
description: Unauthorized
schema:
$ref: "#/definitions/types.Error"
"404":
description: Not Found
schema:
$ref: "#/definitions/types.Error"
"500":
description: Internal Server Error
schema:
$ref: "#/definitions/types.Error"
security:
- BasicAuth: []
summary: Send a delta task to the source of this request
tags:
- request
/v1/task/{task_id}/broadcast-sarif-assessment/{broadcast_sarif_id}/:
post:
consumes:
@@ -0,0 +1,404 @@
{
"swagger": "2.0",
"info": {
"title": "Example CRS API",
"contact": {},
"version": "1.2.0"
},
"paths": {
"/status/": {
"get": {
"security": [
{
"BasicAuth": []
}
],
"description": "report the status of the CRS",
"consumes": ["application/json"],
"produces": ["application/json"],
"tags": ["status"],
"summary": "CRS Status",
"responses": {
"200": {
"description": "Status Obect",
"schema": {
"$ref": "#/definitions/types.Status"
}
}
}
},
"delete": {
"security": [
{
"BasicAuth": []
}
],
"description": "Reset all stats in the status endpoint.",
"consumes": ["application/json"],
"produces": ["application/json"],
"tags": ["status"],
"summary": "Reset status stats",
"responses": {
"200": {
"description": "No Content",
"schema": {
"type": "string"
}
}
}
}
},
"/v1/sarif/": {
"post": {
"security": [
{
"BasicAuth": []
}
],
"description": "submit a sarif broadcast",
"consumes": ["application/json"],
"produces": ["application/json"],
"tags": ["sarif"],
"summary": "Submit Sarif Broadcast",
"parameters": [
{
"description": "Vulnerability Broadcast",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/types.SARIFBroadcast"
}
}
],
"responses": {
"200": {
"description": "No Content",
"schema": {
"type": "string"
}
}
}
}
},
"/v1/task/": {
"post": {
"security": [
{
"BasicAuth": []
}
],
"description": "submit a task for work",
"consumes": ["application/json"],
"produces": ["application/json"],
"tags": ["task"],
"summary": "Submit Task",
"parameters": [
{
"description": "Submission body",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/types.Task"
}
}
],
"responses": {
"202": {
"description": "No Content",
"schema": {
"type": "string"
}
}
}
},
"delete": {
"security": [
{
"BasicAuth": []
}
],
"description": "Cancel all previously submitted tasks. This is meant for edge case recovery in case something goes wrong.",
"consumes": ["application/json"],
"produces": ["application/json"],
"tags": ["task"],
"summary": "Cancel Tasks",
"responses": {
"200": {
"description": "No Content",
"schema": {
"type": "string"
}
}
}
}
},
"/v1/task/{task_id}/": {
"delete": {
"security": [
{
"BasicAuth": []
}
],
"description": "Cancel a task by id. This is meant for edge case recovery in case something goes wrong.",
"consumes": ["application/json"],
"produces": ["application/json"],
"tags": ["task"],
"summary": "Cancel Task",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Task ID",
"name": "task_id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "No Content",
"schema": {
"type": "string"
}
}
}
}
}
},
"definitions": {
"types.SARIFBroadcast": {
"type": "object",
"required": ["broadcasts", "message_id", "message_time"],
"properties": {
"broadcasts": {
"type": "array",
"items": {
"$ref": "#/definitions/types.SARIFBroadcastDetail"
}
},
"message_id": {
"description": "Unique ID for this message. The system will retry sending messages if it does not receive a 200 response code.\nUse this to determine if you have already processed a message.",
"type": "string",
"format": "uuid"
},
"message_time": {
"description": "UNIX millisecond timestamp for when the message was sent",
"type": "integer"
}
}
},
"types.SARIFBroadcastDetail": {
"type": "object",
"required": ["metadata", "sarif", "sarif_id", "task_id"],
"properties": {
"metadata": {
"description": "String to string map containing data that should be attached to outputs like log messages and OpenTelemetry trace attributes for traceability",
"type": "object"
},
"sarif": {
"description": "SARIF Report compliant with provided schema",
"type": "object"
},
"sarif_id": {
"type": "string",
"format": "uuid"
},
"task_id": {
"type": "string",
"format": "uuid"
}
}
},
"types.SourceDetail": {
"type": "object",
"required": ["sha256", "type", "url"],
"properties": {
"sha256": {
"description": "Integrity hash of the gzipped tarball",
"type": "string"
},
"type": {
"$ref": "#/definitions/types.SourceType"
},
"url": {
"description": "URL to fetch the source gzipped tarball",
"type": "string"
}
}
},
"types.SourceType": {
"type": "string",
"enum": ["repo", "fuzz-tooling", "diff"],
"x-enum-varnames": [
"SourceTypeRepo",
"SourceTypeFuzzTooling",
"SourceTypeDiff"
]
},
"types.Status": {
"type": "object",
"required": ["ready", "since", "state", "version"],
"properties": {
"details": {
"description": "This is optional arbitrary content that may be logged in error cases, but is mainly for interactive troubleshooting.",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"ready": {
"description": "Boolean indicating if the CRS is prepared to work on tasks. Do not return true unless you have successfully tested connectivity to the Competition API via /v1/ping/",
"type": "boolean"
},
"since": {
"description": "Last time task and submission stats were reset. Unix timestamp at millisecond resolution.",
"type": "integer"
},
"state": {
"description": "State of the currently running tasks and submissions",
"allOf": [
{
"$ref": "#/definitions/types.StatusState"
}
]
},
"version": {
"description": "Version string for verification and reproducibility.\n\n- git commit\n\n- SemVer\n\n- etc",
"type": "string"
}
}
},
"types.StatusState": {
"type": "object",
"required": ["tasks"],
"properties": {
"tasks": {
"$ref": "#/definitions/types.StatusTasksState"
}
}
},
"types.StatusTasksState": {
"type": "object",
"required": [
"canceled",
"errored",
"failed",
"pending",
"processing",
"succeeded",
"waiting"
],
"properties": {
"canceled": {
"description": "Number of tasks that competition infrastructure has cancelled",
"type": "integer"
},
"errored": {
"description": "Number of tasks that the CRS encountered an unrecoverable issue for",
"type": "integer"
},
"failed": {
"description": "Number of submissions that the competition infrastructure marked failed",
"type": "integer"
},
"pending": {
"description": "Number of tasks that the CRS has not started work on",
"type": "integer"
},
"processing": {
"description": "Number of tasks that the CRS is currently processing",
"type": "integer"
},
"succeeded": {
"description": "Number of submissions that the competition infrastructure marked passed",
"type": "integer"
},
"waiting": {
"description": "Number of submissions that the competition infrastructure is currently testing",
"type": "integer"
}
}
},
"types.Task": {
"type": "object",
"required": ["message_id", "message_time", "tasks"],
"properties": {
"message_id": {
"description": "Unique ID for this message. The system will retry sending messages if it does not receive a 200 response code.\nUse this to determine if you have already processed a message.",
"type": "string",
"format": "uuid"
},
"message_time": {
"description": "UNIX millisecond timestamp for when the message was sent",
"type": "integer"
},
"tasks": {
"type": "array",
"items": {
"$ref": "#/definitions/types.TaskDetail"
}
}
}
},
"types.TaskDetail": {
"type": "object",
"required": [
"deadline",
"focus",
"harnesses_included",
"metadata",
"project_name",
"source",
"task_id",
"type"
],
"properties": {
"deadline": {
"description": "UNIX millisecond timestamp by which any submissions for this task must be in",
"type": "integer"
},
"focus": {
"description": "Because the challenge task may contain multiple repositories, the folder in the type repo source tarball containing the main project.\nIt is still set when there is only one repository.\n\nThis is the project the CRS is meant to submit vulns, patches, and SARIF assessments against.",
"type": "string"
},
"harnesses_included": {
"type": "boolean"
},
"metadata": {
"description": "String to string map containing data that should be attached to outputs like log messages and OpenTelemetry trace attributes for traceability",
"type": "object"
},
"project_name": {
"description": "OSS Fuzz project name",
"type": "string"
},
"source": {
"description": "List of sources needed to evaluate a task",
"type": "array",
"items": {
"$ref": "#/definitions/types.SourceDetail"
}
},
"task_id": {
"type": "string",
"format": "uuid"
},
"type": {
"$ref": "#/definitions/types.TaskType"
}
}
},
"types.TaskType": {
"type": "string",
"enum": ["full", "delta"],
"x-enum-varnames": ["TaskTypeFull", "TaskTypeDelta"]
}
},
"securityDefinitions": {
"BasicAuth": {
"type": "basic"
}
}
}
@@ -0,0 +1,361 @@
definitions:
types.SARIFBroadcast:
properties:
broadcasts:
items:
$ref: "#/definitions/types.SARIFBroadcastDetail"
type: array
message_id:
description: |-
Unique ID for this message. The system will retry sending messages if it does not receive a 200 response code.
Use this to determine if you have already processed a message.
format: uuid
type: string
message_time:
description: UNIX millisecond timestamp for when the message was sent
type: integer
required:
- broadcasts
- message_id
- message_time
type: object
types.SARIFBroadcastDetail:
properties:
metadata:
description:
String to string map containing data that should be attached
to outputs like log messages and OpenTelemetry trace attributes for traceability
type: object
sarif:
description: SARIF Report compliant with provided schema
type: object
sarif_id:
format: uuid
type: string
task_id:
format: uuid
type: string
required:
- metadata
- sarif
- sarif_id
- task_id
type: object
types.SourceDetail:
properties:
sha256:
description: Integrity hash of the gzipped tarball
type: string
type:
$ref: "#/definitions/types.SourceType"
url:
description: URL to fetch the source gzipped tarball
type: string
required:
- sha256
- type
- url
type: object
types.SourceType:
enum:
- repo
- fuzz-tooling
- diff
type: string
x-enum-varnames:
- SourceTypeRepo
- SourceTypeFuzzTooling
- SourceTypeDiff
types.Status:
properties:
details:
additionalProperties:
type: string
description:
This is optional arbitrary content that may be logged in error
cases, but is mainly for interactive troubleshooting.
type: object
ready:
description:
Boolean indicating if the CRS is prepared to work on tasks. Do
not return true unless you have successfully tested connectivity to the
Competition API via /v1/ping/
type: boolean
since:
description:
Last time task and submission stats were reset. Unix timestamp
at millisecond resolution.
type: integer
state:
allOf:
- $ref: "#/definitions/types.StatusState"
description: State of the currently running tasks and submissions
version:
description: |-
Version string for verification and reproducibility.
- git commit
- SemVer
- etc
type: string
required:
- ready
- since
- state
- version
type: object
types.StatusState:
properties:
tasks:
$ref: "#/definitions/types.StatusTasksState"
required:
- tasks
type: object
types.StatusTasksState:
properties:
canceled:
description: Number of tasks that competition infrastructure has cancelled
type: integer
errored:
description:
Number of tasks that the CRS encountered an unrecoverable issue
for
type: integer
failed:
description:
Number of submissions that the competition infrastructure marked
failed
type: integer
pending:
description: Number of tasks that the CRS has not started work on
type: integer
processing:
description: Number of tasks that the CRS is currently processing
type: integer
succeeded:
description:
Number of submissions that the competition infrastructure marked
passed
type: integer
waiting:
description:
Number of submissions that the competition infrastructure is
currently testing
type: integer
required:
- canceled
- errored
- failed
- pending
- processing
- succeeded
- waiting
type: object
types.Task:
properties:
message_id:
description: |-
Unique ID for this message. The system will retry sending messages if it does not receive a 200 response code.
Use this to determine if you have already processed a message.
format: uuid
type: string
message_time:
description: UNIX millisecond timestamp for when the message was sent
type: integer
tasks:
items:
$ref: "#/definitions/types.TaskDetail"
type: array
required:
- message_id
- message_time
- tasks
type: object
types.TaskDetail:
properties:
deadline:
description:
UNIX millisecond timestamp by which any submissions for this
task must be in
type: integer
focus:
description: |-
Because the challenge task may contain multiple repositories, the folder in the type repo source tarball containing the main project.
It is still set when there is only one repository.
This is the project the CRS is meant to submit vulns, patches, and SARIF assessments against.
type: string
harnesses_included:
type: boolean
metadata:
description:
String to string map containing data that should be attached
to outputs like log messages and OpenTelemetry trace attributes for traceability
type: object
project_name:
description: OSS Fuzz project name
type: string
source:
description: List of sources needed to evaluate a task
items:
$ref: "#/definitions/types.SourceDetail"
type: array
task_id:
format: uuid
type: string
type:
$ref: "#/definitions/types.TaskType"
required:
- deadline
- focus
- harnesses_included
- metadata
- project_name
- source
- task_id
- type
type: object
types.TaskType:
enum:
- full
- delta
type: string
x-enum-varnames:
- TaskTypeFull
- TaskTypeDelta
info:
contact: {}
title: Example CRS API
version: 1.2.0
paths:
/status/:
delete:
consumes:
- application/json
description: Reset all stats in the status endpoint.
produces:
- application/json
responses:
"200":
description: No Content
schema:
type: string
security:
- BasicAuth: []
summary: Reset status stats
tags:
- status
get:
consumes:
- application/json
description: report the status of the CRS
produces:
- application/json
responses:
"200":
description: Status Obect
schema:
$ref: "#/definitions/types.Status"
security:
- BasicAuth: []
summary: CRS Status
tags:
- status
/v1/sarif/:
post:
consumes:
- application/json
description: submit a sarif broadcast
parameters:
- description: Vulnerability Broadcast
in: body
name: payload
required: true
schema:
$ref: "#/definitions/types.SARIFBroadcast"
produces:
- application/json
responses:
"200":
description: No Content
schema:
type: string
security:
- BasicAuth: []
summary: Submit Sarif Broadcast
tags:
- sarif
/v1/task/:
delete:
consumes:
- application/json
description:
Cancel all previously submitted tasks. This is meant for edge case
recovery in case something goes wrong.
produces:
- application/json
responses:
"200":
description: No Content
schema:
type: string
security:
- BasicAuth: []
summary: Cancel Tasks
tags:
- task
post:
consumes:
- application/json
description: submit a task for work
parameters:
- description: Submission body
in: body
name: payload
required: true
schema:
$ref: "#/definitions/types.Task"
produces:
- application/json
responses:
"202":
description: No Content
schema:
type: string
security:
- BasicAuth: []
summary: Submit Task
tags:
- task
/v1/task/{task_id}/:
delete:
consumes:
- application/json
description:
Cancel a task by id. This is meant for edge case recovery in case
something goes wrong.
parameters:
- description: Task ID
format: uuid
in: path
name: task_id
required: true
type: string
produces:
- application/json
responses:
"200":
description: No Content
schema:
type: string
security:
- BasicAuth: []
summary: Cancel Task
tags:
- task
securityDefinitions:
BasicAuth:
type: basic
swagger: "2.0"
+7 -3
View File
@@ -3,7 +3,7 @@
"info": {
"title": "Example CRS API",
"contact": {},
"version": "1.1"
"version": "1.2.0"
},
"paths": {
"/status/": {
@@ -179,10 +179,12 @@
}
},
"message_id": {
"description": "Unique ID for this message. The system will retry sending messages if it does not receive a 200 response code.\nUse this to determine if you have already processed a message.",
"type": "string",
"format": "uuid"
},
"message_time": {
"description": "UNIX millisecond timestamp for when the message was sent",
"type": "integer"
}
}
@@ -251,7 +253,7 @@
"type": "boolean"
},
"since": {
"description": "Last time task and submission stats were reset",
"description": "Last time task and submission stats were reset. Unix timestamp at millisecond resolution.",
"type": "integer"
},
"state": {
@@ -324,10 +326,12 @@
"required": ["message_id", "message_time", "tasks"],
"properties": {
"message_id": {
"description": "Unique ID for this message. The system will retry sending messages if it does not receive a 200 response code.\nUse this to determine if you have already processed a message.",
"type": "string",
"format": "uuid"
},
"message_time": {
"description": "UNIX millisecond timestamp for when the message was sent",
"type": "integer"
},
"tasks": {
@@ -352,7 +356,7 @@
],
"properties": {
"deadline": {
"description": "UNIX timestamp by which any submissions for this task must be in",
"description": "UNIX millisecond timestamp by which any submissions for this task must be in",
"type": "integer"
},
"focus": {
+14 -4
View File
@@ -6,9 +6,13 @@ definitions:
$ref: "#/definitions/types.SARIFBroadcastDetail"
type: array
message_id:
description: |-
Unique ID for this message. The system will retry sending messages if it does not receive a 200 response code.
Use this to determine if you have already processed a message.
format: uuid
type: string
message_time:
description: UNIX millisecond timestamp for when the message was sent
type: integer
required:
- broadcasts
@@ -78,7 +82,9 @@ definitions:
Competition API via /v1/ping/
type: boolean
since:
description: Last time task and submission stats were reset
description:
Last time task and submission stats were reset. Unix timestamp
at millisecond resolution.
type: integer
state:
allOf:
@@ -150,9 +156,13 @@ definitions:
types.Task:
properties:
message_id:
description: |-
Unique ID for this message. The system will retry sending messages if it does not receive a 200 response code.
Use this to determine if you have already processed a message.
format: uuid
type: string
message_time:
description: UNIX millisecond timestamp for when the message was sent
type: integer
tasks:
items:
@@ -167,8 +177,8 @@ definitions:
properties:
deadline:
description:
UNIX timestamp by which any submissions for this task must be
in
UNIX millisecond timestamp by which any submissions for this
task must be in
type: integer
focus:
description: |-
@@ -218,7 +228,7 @@ definitions:
info:
contact: {}
title: Example CRS API
version: "1.1"
version: 1.2.0
paths:
/status/:
delete:
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -1,8 +1,8 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "AIXCC Enhanced Static Analysis Results Format (SARIF) Version 2.1.0 JSON Schema",
"id": "https://aixcc.tech/sarif-schema-v0.3.json",
"description": "AIXCC Enhanced Static Analysis Results Format (SARIF) Version 2.1.0 JSON Schema: a standard format for the output of static analysis tools.",
"title": "Static Analysis Results Format (SARIF) Version 2.1.0 JSON Schema",
"id": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json",
"description": "Static Analysis Results Format (SARIF) Version 2.1.0 JSON Schema: a standard format for the output of static analysis tools.",
"additionalProperties": false,
"type": "object",
"properties": {
@@ -2233,7 +2233,7 @@
"$ref": "#/definitions/propertyBag"
}
},
"required": ["message", "ruleId"]
"required": ["message"]
},
"resultProvenance": {
@@ -0,0 +1,68 @@
# Tailnet-accessible hosted Competitor Test Server
The organizers have set up a test competition API server for each team that is reachable from the internet, but tasks your CRS hosted on the tailnet. Telemetry from these runs get forwarded to your teams telemetry
server.
It is intended that competitors may use this to task their CRS with challenges from prior rounds that are available in the aixcc-finals org. There are three changes to your CRS deployment that are required in order for
you to use this server.
1. Change the URL of the Competition API to `https://test-<team-moniker>-api.tail7e9b4c.ts.net`
2. If you are using Kubernetes Tailscale Operator, add another Service for this test URL so that it can be accessible from your cluster. See
[proxies.yaml](https://github.com/aixcc-finals/example-crs-architecture/blob/main/example-crs-architecture/k8s/base/tailscale-connections/proxies.yaml) for context.
3. Append `-testing` to your CRS hostname, for example `team-moniker-testing`. This is the URL that is configured by default.
If you want to use a different CRS URL **for the tailscale-hosted competitor test server**, use the following:
Update CRS hostname used by the hosted competitor testing server
```bash
curl -u 11111111-1111-1111-1111-111111111111:pY8rLk7FvQ2hZm9GwUx3Ej5BnTcV4So0 -X PATCH https://<team-moniker>.tasker.aixcc.tech/crs/url/ -H 'Content-Type: application/json' -d '{"hostname":"team-moniker-testing-1"}'
```
Get CRS hostname used by the hosted competitor testing server
```bash
curl -u 11111111-1111-1111-1111-111111111111:pY8rLk7FvQ2hZm9GwUx3Ej5BnTcV4So0 https://<team-moniker>.tasker.aixcc.tech/crs/url/
```
Kick off tasks with the server
```bash
# Integration Test
curl -u 11111111-1111-1111-1111-111111111111:pY8rLk7FvQ2hZm9GwUx3Ej5BnTcV4So0 -X 'POST' 'https://<team-moniker>.tasker.aixcc.tech/v1/request/delta/'
# LibPNG
curl -u 11111111-1111-1111-1111-111111111111:pY8rLk7FvQ2hZm9GwUx3Ej5BnTcV4So0 -X 'POST' 'https://<team-moniker>.tasker.aixcc.tech/webhook/trigger_task' -H 'Content-Type: application/json' -d '{
"challenge_repo_url": "git@github.com:aixcc-finals/example-libpng.git",
"challenge_repo_base_ref": "0cc367aaeaac3f888f255cee5d394968996f736e",
"challenge_repo_head_ref": "fdacd5a1dcff42175117d674b0fda9f8a005ae88",
"fuzz_tooling_url": "https://github.com/aixcc-finals/oss-fuzz-aixcc.git",
"fuzz_tooling_ref": "d5fbd68fca66e6fa4f05899170d24e572b01853d",
"fuzz_tooling_project_name": "libpng",
"duration": 3600
}'
# Zookeeper
curl -u 11111111-1111-1111-1111-111111111111:pY8rLk7FvQ2hZm9GwUx3Ej5BnTcV4So0 -X 'POST' 'https://<team-moniker>.tasker.aixcc.tech/webhook/trigger_task' -H 'Content-Type: application/json' -d '{
"challenge_repo_url": "git@github.com:aixcc-finals/afc-zookeeper.git",
"challenge_repo_base_ref": "d19cef9ca254a4c1461490ed8b82ffccfa57461d",
"challenge_repo_head_ref": "5ee4f185d0431cc88f365ce779aa04a87fe7690f",
"fuzz_tooling_url": "https://github.com/aixcc-finals/oss-fuzz-aixcc.git",
"fuzz_tooling_ref": "challenge-state/zk-ex1-delta-01",
"fuzz_tooling_project_name": "zookeeper",
"duration": 3600
}'
# Libxml2
curl -u 11111111-1111-1111-1111-111111111111:pY8rLk7FvQ2hZm9GwUx3Ej5BnTcV4So0 -X 'POST' 'https://<team-moniker>.tasker.aixcc.tech/webhook/trigger_task' -H 'Content-Type: application/json' -d '{
"challenge_repo_url": "git@github.com:aixcc-finals/afc-libxml2.git",
"challenge_repo_base_ref": "792cc4a1462d4a969d9d38bd80a52d2e4f7bd137",
"challenge_repo_head_ref": "9d1cb67c31933ee5ae3ee458940f7dbeb2fde8b8",
"fuzz_tooling_url": "https://github.com/aixcc-finals/oss-fuzz-aixcc.git",
"fuzz_tooling_ref": "challenge-state/lx-ex1-delta-01",
"fuzz_tooling_project_name": "libxml2",
"duration": 3600
}'
```
## IMPORTANT NOTE
If you are using this test server, dont forget to change your Competition API URL back to `https://api.tail7e9b4c.ts.net` prior to the start of the round.
@@ -0,0 +1,44 @@
# Tailscale Management Tools
Each team has access to a set of APIs to manage their Tailscale devices. You can use this tool to list devices, delete devices, and update hostnames of tailscale devices. Each endpoint uses the Competition API
credentials for your team.
List Devices
Lists devices on the tailnet for your team.
```bash
curl -u 11111111-1111-1111-1111-111111111111:pY8rLk7FvQ2hZm9GwUx3Ej5BnTcV4So0 https://<team-moniker>.tasker.aixcc.tech/tailscale/device/
```
Show Device
Shows information about a single device by name
```bash
curl -u 11111111-1111-1111-1111-111111111111:pY8rLk7FvQ2hZm9GwUx3Ej5BnTcV4So0 https://<team-moniker>.tasker.aixcc.tech/tailscale/device/team-moniker-foo
```
Rename Device
Change the hostname of a device on the tailnet
```bash
curl -u 11111111-1111-1111-1111-111111111111:pY8rLk7FvQ2hZm9GwUx3Ej5BnTcV4So0 -X PATCH https://<team-moniker>.tasker.aixcc.tech/tailscale/device/team-moniker-foo -H 'Content-Type: application/json' -d '{"hostname":"team-moniker-exhibition2"}'
```
Delete Device
Deletes a specific device on the tailnet
```bash
curl -u 11111111-1111-1111-1111-111111111111:pY8rLk7FvQ2hZm9GwUx3Ej5BnTcV4So0 -X DELETE https://<team-moniker>.tasker.aixcc.tech/tailscale/device/team-moniker-foo
```
Delete all Devices
**WARNING** This deletes all devices on your teams tailnet.
```bash
curl -u 11111111-1111-1111-1111-111111111111:pY8rLk7FvQ2hZm9GwUx3Ej5BnTcV4So0 -X DELETE https://<team-moniker>.tasker.aixcc.tech/tailscale/device/
```
+2
View File
@@ -1,5 +1,7 @@
# CRS Telemetry Specification v1.0
For Telemetry Best Practices, see [here](telemetry_best_practices.md)
Telemetry generated by a CRS is submitted in two distinct ways:
1. GenAI (LLM requests and responses)
@@ -0,0 +1,29 @@
# General telemetry guidance
1. All work performed on challenges should be described by spans, and these spans must specify `round.id`, `task.id`, `crs.action.category` and `crs.action.name` (teams are free to choose their own names for
crs.action.name, but it should be descriptive of the kind of work being performed)
1. Spans/Traces should not be used for general-purpose logging. Use OpenTelemetry Logs for this purpose.
1. All LLM requests' telemetry must be linked to a parent span that describes a unit of work that the LLM request contributes towards.
## Improving the structure of OTEL data
1. A trace is made up of spans that share a `trace_id` , and a trace must describe a distinct non-trivial effort that is mapped to a single `task.id`. Said differently: a trace should not span across multiple tasks, but
a single task may have multiple traces.
1. A trace may have one or more “root spans” (a span with no `parent_span_id`), but a single root span per trace is preferred.
1. A trace should encompass a non-trivial number of spans. A small number of spans in a trace should indicate unusual or exceptional behavior.
1. Spans must be linked to indicate relationships between spans either via parent/child relationships or via span links
1. A parent/child relationship indicated by `parent_span_id` is expected for all units of work that are nested under a larger unit of work (e.g. function calls under an API endpoint, or LLM queries to service an
asynchronous task).
1. In cases where a multiple spans contribute to a new unit of work, or spans have a causal relationship (asynchronous, sequential, etc), span `links` must be used instead of parent/child relationships.
1. In order for the telemetry data to be meaningful, the links between spans must reflect the order and hierarchy of work performed. This means that span relationships should generally form tree-like graphs rather
than unlinked spans. Accomplishing this may require adding a telemetry span at a higher logical level in order to properly describe the hierarchy of work performed.
## Verifying telemetry
On your team's SigNoz server, you may check for the following attributes: `crs.action.category`, `round.id`, `task.id`, & `gen_ai.request.model` as a general means to verify the requested telemetry is being provided.
These attributes provide the following:
- `crs.action.category` attribute confirms CRS telemetry spans were sent
- `round.id/task.id` are examples of attributes from a Task's `metadata` field. These need to be supplied in the spans. (confirms correct task metadata field passthrough)
- `gen_ai.request.model` attribute existing confirms LLM telemetry
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "orchestrator"
version = "0.1.3"
version = "0.1.4"
description = "Buttercup orchestrator"
readme = "README.md"
authors = [{ name = "Trail of Bits", email = "opensource@trailofbits.com" }]
@@ -1 +1 @@
7.13.0-SNAPSHOT
7.14.0-SNAPSHOT
@@ -26,7 +26,7 @@ class TypesArchitecture(str, Enum):
"""
allowed enum values
"""
X86_64 = 'x86_64'
ArchitectureX8664 = 'x86_64'
@classmethod
def from_json(cls, json_str: str) -> Self:
@@ -26,8 +26,8 @@ class TypesAssessment(str, Enum):
"""
allowed enum values
"""
CORRECT = 'correct'
INCORRECT = 'incorrect'
AssessmentCorrect = 'correct'
AssessmentIncorrect = 'incorrect'
@classmethod
def from_json(cls, json_str: str) -> Self:
@@ -26,11 +26,11 @@ class TypesSubmissionStatus(str, Enum):
"""
allowed enum values
"""
ACCEPTED = 'accepted'
PASSED = 'passed'
FAILED = 'failed'
DEADLINE_EXCEEDED = 'deadline_exceeded'
ERRORED = 'errored'
SubmissionStatusAccepted = 'accepted'
SubmissionStatusPassed = 'passed'
SubmissionStatusFailed = 'failed'
SubmissionStatusDeadlineExceeded = 'deadline_exceeded'
SubmissionStatusErrored = 'errored'
@classmethod
def from_json(cls, json_str: str) -> Self:
@@ -14,12 +14,12 @@ from buttercup.common.datastructures.msg_pb2 import (
Patch,
)
from buttercup.common.sarif_store import SARIFStore
from buttercup.common.constants import ARCHITECTURE
from buttercup.common.queues import QueueFactory, QueueNames
from buttercup.common.telemetry import set_crs_attributes, CRSActionCategory
from buttercup.orchestrator.scheduler.sarif_matcher import match
from buttercup.orchestrator.registry import TaskRegistry
from buttercup.orchestrator.competition_api_client.models.types_architecture import TypesArchitecture
from buttercup.orchestrator.competition_api_client.models.types_pov_submission import TypesPOVSubmission
from buttercup.orchestrator.competition_api_client.models.types_patch_submission import TypesPatchSubmission
from buttercup.orchestrator.competition_api_client.models.types_bundle_submission import TypesBundleSubmission
@@ -142,7 +142,7 @@ class CompetitionAPI:
# Create submission payload from crash data
submission = TypesPOVSubmission(
architecture=ARCHITECTURE,
architecture=TypesArchitecture.ArchitectureX8664,
engine=crash.crash.target.engine,
fuzzer_name=crash.crash.harness_name,
sanitizer=crash.crash.target.sanitizer,
@@ -168,7 +168,10 @@ class CompetitionAPI:
payload=submission,
)
logger.debug(f"[{crash.crash.target.task_id}] POV submission response: {response}")
if response.status not in [TypesSubmissionStatus.ACCEPTED, TypesSubmissionStatus.PASSED]:
if response.status not in [
TypesSubmissionStatus.SubmissionStatusAccepted,
TypesSubmissionStatus.SubmissionStatusPassed,
]:
logger.error(
f"[{crash.crash.target.task_id}] POV submission rejected (status: {response.status}) for harness: {crash.crash.harness_name}"
)
@@ -179,7 +182,7 @@ class CompetitionAPI:
return response.pov_id, response.status
except Exception as e:
logger.error(f"[{crash.crash.target.task_id}] Failed to submit vulnerability: {e}")
return None, TypesSubmissionStatus.ERRORED
return None, TypesSubmissionStatus.SubmissionStatusErrored
def get_pov_status(self, task_id: str, pov_id: str) -> TypesSubmissionStatus:
"""
@@ -190,7 +193,7 @@ class CompetitionAPI:
pov_id: POV ID from submit_vulnerability
Returns:
TypesSubmissionStatus: Current status (ACCEPTED, PASSED, FAILED, ERRORED, DEADLINE_EXCEEDED)
TypesSubmissionStatus: Current status (SubmissionStatusAccepted, SubmissionStatusPassed, SubmissionStatusFailed, SubmissionStatusErrored, SubmissionStatusDeadlineExceeded)
"""
assert task_id
assert pov_id
@@ -232,7 +235,10 @@ class CompetitionAPI:
task_id=task_id, payload=submission
)
logger.debug(f"[{task_id}] Patch submission response: {response}")
if response.status not in [TypesSubmissionStatus.ACCEPTED, TypesSubmissionStatus.PASSED]:
if response.status not in [
TypesSubmissionStatus.SubmissionStatusAccepted,
TypesSubmissionStatus.SubmissionStatusPassed,
]:
logger.error(f"[{task_id}] Patch submission rejected (status: {response.status}) for harness: {patch}")
span.set_status(Status(StatusCode.ERROR))
return (None, response.status)
@@ -249,7 +255,7 @@ class CompetitionAPI:
patch_id: Patch ID from submit_patch
Returns:
TypesSubmissionStatus: Current status (ACCEPTED, PASSED, FAILED, ERRORED, DEADLINE_EXCEEDED)
TypesSubmissionStatus: Current status (SubmissionStatusAccepted, SubmissionStatusPassed, SubmissionStatusFailed, SubmissionStatusErrored, SubmissionStatusDeadlineExceeded)
"""
assert task_id
assert patch_id
@@ -300,7 +306,10 @@ class CompetitionAPI:
task_id=task_id, payload=submission
)
logger.debug(f"[{task_id}] Bundle submission response: {response}")
if response.status not in [TypesSubmissionStatus.ACCEPTED, TypesSubmissionStatus.PASSED]:
if response.status not in [
TypesSubmissionStatus.SubmissionStatusAccepted,
TypesSubmissionStatus.SubmissionStatusPassed,
]:
logger.error(
f"[{task_id}] Bundle submission rejected (status: {response.status}) for harness: {pov_id} {patch_id}"
)
@@ -344,7 +353,10 @@ class CompetitionAPI:
task_id=task_id, bundle_id=bundle_id, payload=submission
)
logger.debug(f"[{task_id}] Bundle patch submission response: {response}")
if response.status not in [TypesSubmissionStatus.ACCEPTED, TypesSubmissionStatus.PASSED]:
if response.status not in [
TypesSubmissionStatus.SubmissionStatusAccepted,
TypesSubmissionStatus.SubmissionStatusPassed,
]:
logger.error(
f"[{task_id}] Bundle patch submission rejected (status: {response.status}) for harness: {pov_id} {patch_id} {sarif_id}"
)
@@ -371,7 +383,7 @@ class CompetitionAPI:
# TODO: The description is the most basic I could think of. I don't know if we wanted to do something more fancy.
submission = TypesSarifAssessmentSubmission(
assessment=TypesAssessment.CORRECT,
assessment=TypesAssessment.AssessmentCorrect,
description="Overlapping with our POV/patch",
)
@@ -391,7 +403,10 @@ class CompetitionAPI:
task_id=task_id, broadcast_sarif_id=sarif_id, payload=submission
)
logger.debug(f"[{task_id}] Matching SARIF submission response: {response}")
if response.status not in [TypesSubmissionStatus.ACCEPTED, TypesSubmissionStatus.PASSED]:
if response.status not in [
TypesSubmissionStatus.SubmissionStatusAccepted,
TypesSubmissionStatus.SubmissionStatusPassed,
]:
logger.error(
f"[{task_id}] Matching SARIF submission rejected (status: {response.status}) for sarif_id: {sarif_id}"
)
@@ -594,7 +609,7 @@ class Submissions:
)
# If the API returned ERRORED, we want to retry.
stop = status != TypesSubmissionStatus.ERRORED
stop = status != TypesSubmissionStatus.SubmissionStatusErrored
if stop:
logger.info(f"Competition API returned {status}, will not retry.")
@@ -696,7 +711,7 @@ class Submissions:
"""
status = self.competition_api.get_pov_status(_task_id(e), e.pov_id)
match status:
case TypesSubmissionStatus.FAILED | TypesSubmissionStatus.DEADLINE_EXCEEDED:
case TypesSubmissionStatus.SubmissionStatusFailed | TypesSubmissionStatus.SubmissionStatusDeadlineExceeded:
e.state = SubmissionEntry.STOP
self._persist(self.redis, i, e)
log_structured(
@@ -707,7 +722,7 @@ class Submissions:
state_change=("WAIT_POV_PASS", "STOP"),
msg=f"POV failed (status={status}), stopping",
)
case TypesSubmissionStatus.PASSED:
case TypesSubmissionStatus.SubmissionStatusPassed:
e.state = SubmissionEntry.SUBMIT_PATCH
self._persist(self.redis, i, e)
log_structured(
@@ -718,7 +733,7 @@ class Submissions:
state_change=("WAIT_POV_PASS", "SUBMIT_PATCH"),
msg="POV passed, ready to submit patch when the patch is ready",
)
case TypesSubmissionStatus.ERRORED:
case TypesSubmissionStatus.SubmissionStatusErrored:
log_structured(logger.info, _task_id(e), index=i, pov_id=e.pov_id, msg="POV errored, will resubmit")
pov_id, status = self.competition_api.submit_pov(e.crash)
@@ -730,7 +745,7 @@ class Submissions:
)
# If the API returned ERRORED, we want to retry.
if status != TypesSubmissionStatus.ERRORED:
if status != TypesSubmissionStatus.SubmissionStatusErrored:
e.state = SubmissionEntry.STOP
self._persist(self.redis, i, e)
log_structured(
@@ -749,7 +764,7 @@ class Submissions:
)
case _:
assert status == TypesSubmissionStatus.ACCEPTED, f"Unexpected POV status: {status}"
assert status == TypesSubmissionStatus.SubmissionStatusAccepted, f"Unexpected POV status: {status}"
def _submit_patch(self, i, e):
"""
@@ -794,7 +809,10 @@ class Submissions:
)
else:
match status:
case TypesSubmissionStatus.FAILED | TypesSubmissionStatus.DEADLINE_EXCEEDED:
case (
TypesSubmissionStatus.SubmissionStatusFailed
| TypesSubmissionStatus.SubmissionStatusDeadlineExceeded
):
# Deadline exceeded or failed, move on to next patch (for exceeded we won't try again due to deadline check)
e.patch_idx += 1
self._persist(self.redis, i, e)
@@ -806,7 +824,7 @@ class Submissions:
patch_idx=e.patch_idx,
msg=f"Patch submission failed ({status}), will not attempt this patch again.",
)
case TypesSubmissionStatus.ERRORED:
case TypesSubmissionStatus.SubmissionStatusErrored:
if have_more_patches and e.patch_submission_attempt >= self.patch_submission_retry_limit:
_advance_patch_idx(e)
self._persist(self.redis, i, e)
@@ -847,9 +865,9 @@ class Submissions:
"""
status = self.competition_api.get_patch_status(_task_id(e), e.patch_id)
match status:
case TypesSubmissionStatus.ACCEPTED:
case TypesSubmissionStatus.SubmissionStatusAccepted:
return # No change.
case TypesSubmissionStatus.FAILED | TypesSubmissionStatus.DEADLINE_EXCEEDED:
case TypesSubmissionStatus.SubmissionStatusFailed | TypesSubmissionStatus.SubmissionStatusDeadlineExceeded:
_advance_patch_idx(e)
e.state = SubmissionEntry.SUBMIT_PATCH
self._persist(self.redis, i, e)
@@ -862,7 +880,7 @@ class Submissions:
state_change=("WAIT_PATCH_PASS", "SUBMIT_PATCH"),
msg=f"Patch submission failed ({status}), will not attempt this patch again, moving on to next patch.",
)
case TypesSubmissionStatus.ERRORED:
case TypesSubmissionStatus.SubmissionStatusErrored:
e.state = SubmissionEntry.SUBMIT_PATCH
self._persist(self.redis, i, e)
log_structured(
@@ -874,7 +892,7 @@ class Submissions:
state_change=("WAIT_PATCH_PASS", "SUBMIT_PATCH"),
msg=f"Patch submission errored ({status}), will attempt this patch again.",
)
case TypesSubmissionStatus.PASSED:
case TypesSubmissionStatus.SubmissionStatusPassed:
e.state = SubmissionEntry.SUBMIT_BUNDLE
self._persist(self.redis, i, e)
log_structured(
@@ -904,7 +922,10 @@ class Submissions:
bundle_id, status = self.competition_api.submit_bundle(_task_id(e), e.pov_id, e.patch_id)
if not bundle_id:
match status:
case TypesSubmissionStatus.FAILED | TypesSubmissionStatus.DEADLINE_EXCEEDED:
case (
TypesSubmissionStatus.SubmissionStatusFailed
| TypesSubmissionStatus.SubmissionStatusDeadlineExceeded
):
e.state = SubmissionEntry.STOP
self._persist(self.redis, i, e)
log_structured(
@@ -916,7 +937,7 @@ class Submissions:
state_change=("SUBMIT_BUNDLE", "STOP"),
msg=f"Bundle submission failed ({status}), not much else to do but stop.",
)
case TypesSubmissionStatus.ERRORED:
case TypesSubmissionStatus.SubmissionStatusErrored:
log_structured(
logger.info,
_task_id(e),
@@ -1002,7 +1023,10 @@ class Submissions:
)
else:
match status:
case TypesSubmissionStatus.FAILED | TypesSubmissionStatus.DEADLINE_EXCEEDED:
case (
TypesSubmissionStatus.SubmissionStatusFailed
| TypesSubmissionStatus.SubmissionStatusDeadlineExceeded
):
e.state = SubmissionEntry.STOP
self._persist(self.redis, i, e)
log_structured(
@@ -1056,7 +1080,10 @@ class Submissions:
)
else:
match status:
case TypesSubmissionStatus.FAILED | TypesSubmissionStatus.DEADLINE_EXCEEDED:
case (
TypesSubmissionStatus.SubmissionStatusFailed
| TypesSubmissionStatus.SubmissionStatusDeadlineExceeded
):
e.state = SubmissionEntry.STOP
self._persist(self.redis, i, e)
log_structured(
@@ -1,3 +1,3 @@
# generated by fastapi-codegen:
# filename: /tmp/tmp.4yUHNSUOxp/openapi.json
# timestamp: 2025-03-19T10:11:57+00:00
# filename: /tmp/tmp.0oVFmuzKjt/openapi.json
# timestamp: 2025-05-02T05:12:50+00:00
@@ -1,6 +1,6 @@
# generated by fastapi-codegen:
# filename: /tmp/tmp.4yUHNSUOxp/openapi.json
# timestamp: 2025-03-19T10:11:57+00:00
# filename: /tmp/tmp.0oVFmuzKjt/openapi.json
# timestamp: 2025-05-02T05:12:50+00:00
from __future__ import annotations
@@ -1,6 +1,6 @@
# generated by fastapi-codegen:
# filename: openapi.json
# timestamp: 2025-02-18T14:23:33+00:00
# timestamp: 2025-05-02T05:12:50+00:00
from __future__ import annotations
+49 -30
View File
@@ -83,7 +83,7 @@ def mock_redis():
def mock_competition_api(mock_task_registry):
mock = Mock(spec=CompetitionAPI)
# Add the missing method that's needed in tests
mock.submit_bundle_patch = Mock(return_value=(True, TypesSubmissionStatus.ACCEPTED))
mock.submit_bundle_patch = Mock(return_value=(True, TypesSubmissionStatus.SubmissionStatusAccepted))
return mock
@@ -122,7 +122,9 @@ class TestCompetitionAPI:
mock_lopen.return_value.__enter__.return_value = mock_file
# Setup API response
mock_response = TypesPOVSubmissionResponse(status=TypesSubmissionStatus.ACCEPTED, pov_id="test-pov-123")
mock_response = TypesPOVSubmissionResponse(
status=TypesSubmissionStatus.SubmissionStatusAccepted, pov_id="test-pov-123"
)
# Setup API client mock
mock_pov_api = Mock()
@@ -135,7 +137,7 @@ class TestCompetitionAPI:
# Verify result first
assert result[0] == "test-pov-123"
assert result[1] == TypesSubmissionStatus.ACCEPTED
assert result[1] == TypesSubmissionStatus.SubmissionStatusAccepted
# Verify file was read correctly
mock_lopen.assert_called_once_with(sample_crash.crash.crash_input_path, "rb")
@@ -160,7 +162,7 @@ class TestCompetitionAPI:
mock_lopen.return_value.__enter__.return_value = mock_file
# Setup API response
mock_response = TypesPOVSubmissionResponse(status=TypesSubmissionStatus.FAILED, pov_id="")
mock_response = TypesPOVSubmissionResponse(status=TypesSubmissionStatus.SubmissionStatusFailed, pov_id="")
# Setup API client mock
mock_pov_api = Mock()
@@ -173,12 +175,14 @@ class TestCompetitionAPI:
# Verify result is a tuple with (None, FAILED)
assert result[0] is None
assert result[1] == TypesSubmissionStatus.FAILED
assert result[1] == TypesSubmissionStatus.SubmissionStatusFailed
def test_get_pov_status(self, competition_api):
# Setup mock
mock_pov_api = Mock()
mock_pov_api.v1_task_task_id_pov_pov_id_get.return_value = Mock(status=TypesSubmissionStatus.PASSED)
mock_pov_api.v1_task_task_id_pov_pov_id_get.return_value = Mock(
status=TypesSubmissionStatus.SubmissionStatusPassed
)
# Patch the PovApi constructor
with patch("buttercup.orchestrator.scheduler.submissions.PovApi", return_value=mock_pov_api):
@@ -187,7 +191,7 @@ class TestCompetitionAPI:
# Verify call and result
mock_pov_api.v1_task_task_id_pov_pov_id_get.assert_called_once_with(task_id="task-123", pov_id="pov-456")
assert status == TypesSubmissionStatus.PASSED
assert status == TypesSubmissionStatus.SubmissionStatusPassed
def test_submit_patch_successful(self, competition_api):
# Setup test data
@@ -196,7 +200,9 @@ class TestCompetitionAPI:
expected_encoded_patch = base64.b64encode(patch_content.encode()).decode()
# Setup mock response
mock_response = TypesPatchSubmissionResponse(status=TypesSubmissionStatus.ACCEPTED, patch_id="patch-123")
mock_response = TypesPatchSubmissionResponse(
status=TypesSubmissionStatus.SubmissionStatusAccepted, patch_id="patch-123"
)
# Setup patch API mock
mock_patch_api = Mock()
@@ -215,12 +221,14 @@ class TestCompetitionAPI:
# Verify result
assert result[0] == "patch-123"
assert result[1] == TypesSubmissionStatus.ACCEPTED
assert result[1] == TypesSubmissionStatus.SubmissionStatusAccepted
def test_get_patch_status(self, competition_api):
# Setup mock
mock_patch_api = Mock()
mock_patch_api.v1_task_task_id_patch_patch_id_get.return_value = Mock(status=TypesSubmissionStatus.PASSED)
mock_patch_api.v1_task_task_id_patch_patch_id_get.return_value = Mock(
status=TypesSubmissionStatus.SubmissionStatusPassed
)
# Patch the PatchApi constructor
with patch("buttercup.orchestrator.scheduler.submissions.PatchApi", return_value=mock_patch_api):
@@ -231,7 +239,7 @@ class TestCompetitionAPI:
mock_patch_api.v1_task_task_id_patch_patch_id_get.assert_called_once_with(
task_id="task-123", patch_id="patch-456"
)
assert status == TypesSubmissionStatus.PASSED
assert status == TypesSubmissionStatus.SubmissionStatusPassed
def test_submit_bundle_successful(self, competition_api):
# Setup test data
@@ -240,7 +248,9 @@ class TestCompetitionAPI:
patch_id = "patch-789"
# Setup mock response
mock_response = TypesBundleSubmissionResponse(status=TypesSubmissionStatus.ACCEPTED, bundle_id="bundle-123")
mock_response = TypesBundleSubmissionResponse(
status=TypesSubmissionStatus.SubmissionStatusAccepted, bundle_id="bundle-123"
)
# Setup bundle API mock
mock_bundle_api = Mock()
@@ -260,7 +270,7 @@ class TestCompetitionAPI:
# Verify result
assert result[0] == "bundle-123"
assert result[1] == TypesSubmissionStatus.ACCEPTED
assert result[1] == TypesSubmissionStatus.SubmissionStatusAccepted
def test_submit_matching_sarif_successful(self, competition_api):
# Setup test data
@@ -268,7 +278,7 @@ class TestCompetitionAPI:
sarif_id = "sarif-456"
# Setup mock response
mock_response = Mock(status=TypesSubmissionStatus.ACCEPTED)
mock_response = Mock(status=TypesSubmissionStatus.SubmissionStatusAccepted)
# Setup sarif API mock
mock_sarif_api = Mock()
@@ -286,14 +296,14 @@ class TestCompetitionAPI:
# Verify result
assert result[0] is True
assert result[1] == TypesSubmissionStatus.ACCEPTED
assert result[1] == TypesSubmissionStatus.SubmissionStatusAccepted
# Tests for the Submissions class
class TestSubmissions:
def test_submit_vulnerability_successful(self, submissions, mock_competition_api, sample_crash, mock_redis):
# Setup mock return value for submit_vulnerability
mock_competition_api.submit_pov.return_value = ("test-pov-123", TypesSubmissionStatus.ACCEPTED)
mock_competition_api.submit_pov.return_value = ("test-pov-123", TypesSubmissionStatus.SubmissionStatusAccepted)
mock_redis.rpush.return_value = 1 # Index of the inserted entry
# Call the method
@@ -315,7 +325,7 @@ class TestSubmissions:
def test_submit_vulnerability_failed(self, submissions, mock_competition_api, sample_crash):
# Setup mock to return an error
mock_competition_api.submit_pov.return_value = (None, TypesSubmissionStatus.FAILED)
mock_competition_api.submit_pov.return_value = (None, TypesSubmissionStatus.SubmissionStatusFailed)
# Call the method
result = submissions.submit_vulnerability(sample_crash)
@@ -331,7 +341,7 @@ class TestSubmissions:
def test_submit_vulnerability_errored(self, submissions, mock_competition_api, sample_crash):
# Setup mock to return an error
mock_competition_api.submit_pov.return_value = (None, TypesSubmissionStatus.ERRORED)
mock_competition_api.submit_pov.return_value = (None, TypesSubmissionStatus.SubmissionStatusErrored)
# Call the method
result = submissions.submit_vulnerability(sample_crash)
@@ -396,7 +406,7 @@ class TestStateTransitions:
submissions.entries = [sample_submission_entry]
# Mock competition API to return PASSED
mock_competition_api.get_pov_status.return_value = TypesSubmissionStatus.PASSED
mock_competition_api.get_pov_status.return_value = TypesSubmissionStatus.SubmissionStatusPassed
# Simulate process_cycle
submissions.process_cycle()
@@ -410,7 +420,7 @@ class TestStateTransitions:
submissions.entries = [sample_submission_entry]
# Mock competition API to return FAILED
mock_competition_api.get_pov_status.return_value = TypesSubmissionStatus.FAILED
mock_competition_api.get_pov_status.return_value = TypesSubmissionStatus.SubmissionStatusFailed
# Simulate process_cycle
submissions.process_cycle()
@@ -424,8 +434,8 @@ class TestStateTransitions:
submissions.entries = [sample_submission_entry]
# Mock competition API to return ERRORED then successful resubmission
mock_competition_api.get_pov_status.return_value = TypesSubmissionStatus.ERRORED
mock_competition_api.submit_pov.return_value = ("new-pov-456", TypesSubmissionStatus.ACCEPTED)
mock_competition_api.get_pov_status.return_value = TypesSubmissionStatus.SubmissionStatusErrored
mock_competition_api.submit_pov.return_value = ("new-pov-456", TypesSubmissionStatus.SubmissionStatusAccepted)
# Simulate process_cycle
submissions.process_cycle()
@@ -445,7 +455,10 @@ class TestStateTransitions:
submissions.entries = [sample_submission_entry]
# Mock competition API to return successful patch submission
mock_competition_api.submit_patch.return_value = ("test-patch-123", TypesSubmissionStatus.ACCEPTED)
mock_competition_api.submit_patch.return_value = (
"test-patch-123",
TypesSubmissionStatus.SubmissionStatusAccepted,
)
# Mock the _have_more_patches function to avoid AttributeError
with patch("buttercup.orchestrator.scheduler.submissions._have_more_patches", return_value=True):
@@ -464,7 +477,7 @@ class TestStateTransitions:
submissions.entries = [sample_submission_entry]
# Mock competition API to return PASSED
mock_competition_api.get_patch_status.return_value = TypesSubmissionStatus.PASSED
mock_competition_api.get_patch_status.return_value = TypesSubmissionStatus.SubmissionStatusPassed
# Simulate process_cycle
submissions.process_cycle()
@@ -484,7 +497,7 @@ class TestStateTransitions:
submissions.entries = [sample_submission_entry]
# Mock competition API to return FAILED
mock_competition_api.get_patch_status.return_value = TypesSubmissionStatus.FAILED
mock_competition_api.get_patch_status.return_value = TypesSubmissionStatus.SubmissionStatusFailed
# Mock the _advance_patch_idx function to avoid AttributeError
with patch("buttercup.orchestrator.scheduler.submissions._advance_patch_idx") as mock_advance:
@@ -505,7 +518,10 @@ class TestStateTransitions:
submissions.entries = [sample_submission_entry]
# Mock competition API to return successful bundle submission
mock_competition_api.submit_bundle.return_value = ("test-bundle-789", TypesSubmissionStatus.ACCEPTED)
mock_competition_api.submit_bundle.return_value = (
"test-bundle-789",
TypesSubmissionStatus.SubmissionStatusAccepted,
)
# Simulate process_cycle
submissions.process_cycle()
@@ -522,7 +538,7 @@ class TestStateTransitions:
submissions.entries = [sample_submission_entry]
# Mock competition API to return failed bundle submission
mock_competition_api.submit_bundle.return_value = (None, TypesSubmissionStatus.FAILED)
mock_competition_api.submit_bundle.return_value = (None, TypesSubmissionStatus.SubmissionStatusFailed)
# Simulate process_cycle
submissions.process_cycle()
@@ -538,7 +554,7 @@ class TestStateTransitions:
submissions.entries = [sample_submission_entry]
# Mock competition API to return error
mock_competition_api.submit_bundle.return_value = (None, TypesSubmissionStatus.ERRORED)
mock_competition_api.submit_bundle.return_value = (None, TypesSubmissionStatus.SubmissionStatusErrored)
# Simulate process_cycle
submissions.process_cycle()
@@ -581,7 +597,7 @@ class TestStateTransitions:
submissions.entries = [sample_submission_entry]
# Mock competition API to return successful bundle patch
mock_competition_api.patch_bundle.return_value = (True, TypesSubmissionStatus.ACCEPTED)
mock_competition_api.patch_bundle.return_value = (True, TypesSubmissionStatus.SubmissionStatusAccepted)
# Simulate process_cycle
submissions.process_cycle()
@@ -665,7 +681,10 @@ class TestStateTransitions:
# Mock the _task_id function to verify it's being called with the right arguments
with patch("buttercup.orchestrator.scheduler.submissions._task_id", return_value=task_id) as mock_task_id:
# Mock competition API to return successful patch submission
mock_competition_api.submit_patch.return_value = ("test-patch-123", TypesSubmissionStatus.ACCEPTED)
mock_competition_api.submit_patch.return_value = (
"test-patch-123",
TypesSubmissionStatus.SubmissionStatusAccepted,
)
# Simulate process_cycle
submissions.process_cycle()