75trust / 100

Ci Workflow Contract Audit

by CI Workflow Audit in Onchain & crypto

x402 APIPassing, checked 4 h ago

Audit a CI workflow for what it leaves movable or undeclared. Reports actions pinned to a tag rather than a commit - a tag can be moved by whoever owns the action, so the code a step runs can change without this file changing - missing permissions and timeouts, a job depending on one that is not declared, a pull_request_target trigger, and an expression interpolated straight into a shell command.

POST
https://api.zfinia.com/x402/v1/ci-workflow-contract-audit
Price
$0.12
per call in USDC
Uptime
100%
last 30 days
Response time
513 ms
759 ms slowest 5%
Last check
Passed
4 h ago, next any minute now

Run a free test callHow to call it

Reliability

All checks passedSome failedAll failedNot checked

Status and outage history · Atom feed · Watch with other listings

How to call it

POST https://api.zfinia.com/x402/v1/ci-workflow-contract-audit

Each call costs $0.12 in USDC on Base, paid with the request over x402.

Run a free test callOne unpaid request, the way an agent sends it. You see the payment challenge it gets back. Nothing is charged.

Example request

From the listing.

POST https://api.zfinia.com/x402/v1/ci-workflow-contract-audit
content-type: application/json

{
  "contract": {
    "required_jobs": [
      "build",
      "publish",
      "lint"
    ]
  },
  "workflow": {
    "jobs": {
      "build": {
        "runs-on": "ubuntu-latest",
        "steps": [
          {
            "uses": "actions/checkout@v4"
          },
          {
            "uses": "actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8"
          },
          {
            "name": "Greet",
            "run": "echo \"Building ${{ github.event.pull_request.title }}\""
          },
          {
            "run": "npm ci && npm test"
          }
        ]
      },
      "notify": {
        "needs": [
          "publish",
          "lint"
        ],
        "runs-on": "ubuntu-latest",
        "steps": [
          {
            "run": "echo done"
          }
        ]
      },
      "publish": {
        "needs": "build",
        "permissions": {
          "contents": "read",
          "packages": "write"
        },
        "runs-on": "ubuntu-latest",
        "steps": [
          {
            "uses": "actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3"
          }
        ],
        "timeout-minutes": 15
      }
    },
    "name": "release",
    "on": {
      "pull_request_target": {},
      "push": {
        "branches": [
          "main"
        ]
      }
    }
  }
}

Parameters

ParameterTypeRequiredDescription
contractobjectNo
workflowobjectYes

Example response

The provider's example. We can't check it without paying.

Example response, 99 lines
{
  "assumptions": [
    "An action pinned to a 40-character commit hash is pinned; a tag or branch can be moved by whoever owns the action, so the code a step runs can change without this file changing.",
    "The on key is read from either on or true, because an unquoted on is a YAML boolean and survives conversion to JSON that way.",
    "Findings describe patterns in the document. Naming pull_request_target or an interpolated expression is reporting what the file says, not demonstrating an exploit."
  ],
  "complete": true,
  "conformant": false,
  "examined_job_count": 3,
  "finding_count": 10,
  "findings": [
    {
      "code": "REQUIRED_JOB_MISSING",
      "detail": "lint is required and not declared.",
      "job": "lint"
    },
    {
      "code": "PULL_REQUEST_TARGET_TRIGGER",
      "detail": "The workflow triggers on pull_request_target, which runs with repository write context against code from a fork. This reports the trigger, not an exploit."
    },
    {
      "code": "WORKFLOW_PERMISSIONS_NOT_DECLARED",
      "detail": "The workflow declares no permissions block, so it inherits whatever the repository default is rather than stating what it needs."
    }
  ],
  "findings_by_code": {
    "ACTION_NOT_PINNED_TO_COMMIT": 1,
    "JOB_DEPENDENCY_NOT_DECLARED": 1,
    "JOB_PERMISSIONS_NOT_DECLARED": 2,
    "JOB_TIMEOUT_MISSING": 2,
    "PULL_REQUEST_TARGET_TRIGGER": 1,
    "REQUIRED_JOB_MISSING": 1,
    "UNTRUSTED_INTERPOLATION_IN_RUN": 1,
    "WORKFLOW_PERMISSIONS_NOT_DECLARED": 1
  },
  "job_count": 3,
  "jobs": [
    {
      "job": "build",
      "needs": [],
      "permissions_declared": false,
      "runs_on": "ubuntu-latest",
      "steps": 4,
      "timeout_minutes": null,
      "unpinned_actions": [
        "actions/checkout@v4"
      ]
    },
    {
      "job": "publish",
      "needs": [
        "build"
      ],
      "permissions_declared": true,
      "runs_on": "ubuntu-latest",
      "steps": 1,
      "timeout_minutes": 15,
      "unpinned_actions": []
    },
    {
      "job": "notify",
      "needs": [
        "publish",
        "lint"
      ],
      "permissions_declared": false,
      "runs_on": "ubuntu-latest",
      "steps": 1,
      "timeout_minutes": null,
      "unpinned_actions": []
    }
  ],
  "limitations": [
    "Observable facts about the workflow supplied. Nothing is run, no action is fetched, and this is not a security assessment or a compliance determination.",
    "conformant is true only when every declared rule was evaluated and none failed."
  ],
  "limits": {
    "maxComponents": 5000,
    "maxDependencies": 5000,
    "maxFindings": 500,
    "maxJobs": 200,
    "maxResources": 2000,
    "maxServices": 200,
    "maxVariables": 1000
  },
  "not_examined": [],
  "observed_at": "2026-10-02T00:00:00.000Z",
  "passed": false,
  "request_id": "example-request",
  "service_id": "ci_workflow_contract_audit",
  "service_version": "1.0.0",
  "step_count": 6,
  "trigger_names": [
    "push",
    "pull_request_target"
  ],
  "unpinned_action_count": 1,
  "workflow_permissions_declared": false
}

Call it from code

The curl command shows the payment challenge. The JavaScript and Python versions pay and call, and never pay more than $0.12.

curl: see the payment challenge
# bash or zsh: see the payment challenge. Nothing is charged.
curl -sS -g -i -X POST 'https://api.zfinia.com/x402/v1/ci-workflow-contract-audit' \
  -H 'content-type: application/json' \
  --data-raw '{"contract":{"required_jobs":["build","publish","lint"]},"workflow":{"jobs":{"build":{"runs-on":"ubuntu-latest","steps":[{"uses":"actions/checkout@v4"},{"uses":"actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8"},{"name":"Greet","run":"echo \"Building ${{ github.event.pull_request.title }}\""},{"run":"npm ci && npm test"}]},"notify":{"needs":["publish","lint"],"runs-on":"ubuntu-latest","steps":[{"run":"echo done"}]},"publish":{"needs":"build","permissions":{"contents":"read","packages":"write"},"runs-on":"ubuntu-latest","steps":[{"uses":"actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3"}],"timeout-minutes":15}},"name":"release","on":{"pull_request_target":{},"push":{"branches":["main"]}}}}'

# x402 v2 sends the challenge base64-encoded in the PAYMENT-REQUIRED header (v1 sends it in the body).
# To read the header:
curl -sS -g -o /dev/null -D - -X POST 'https://api.zfinia.com/x402/v1/ci-workflow-contract-audit' \
  -H 'content-type: application/json' \
  --data-raw '{"contract":{"required_jobs":["build","publish","lint"]},"workflow":{"jobs":{"build":{"runs-on":"ubuntu-latest","steps":[{"uses":"actions/checkout@v4"},{"uses":"actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8"},{"name":"Greet","run":"echo \"Building ${{ github.event.pull_request.title }}\""},{"run":"npm ci && npm test"}]},"notify":{"needs":["publish","lint"],"runs-on":"ubuntu-latest","steps":[{"run":"echo done"}]},"publish":{"needs":"build","permissions":{"contents":"read","packages":"write"},"runs-on":"ubuntu-latest","steps":[{"uses":"actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3"}],"timeout-minutes":15}},"name":"release","on":{"pull_request_target":{},"push":{"branches":["main"]}}}}' \
  | grep -i '^payment-required:' | cut -d' ' -f2 | tr -d '\r' | base64 --decode
JavaScript: pay and call
// npm i @x402/fetch @x402/evm viem
import { wrapFetchWithPayment, x402Client } from "@x402/fetch";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";

// Pays at most $0.12 per call.
const client = new x402Client().setSpendControls({ maxAmountPerPayment: "$0.12" });
registerExactEvmScheme(client, { signer: privateKeyToAccount(process.env.EVM_PRIVATE_KEY) });
const pay = wrapFetchWithPayment(fetch, client);

// Not sure it's safe to pay? ToolVet checks it first for $0.005. Pay for that from a separate
// client: this one is capped at $0.12 per payment. GET https://toolvet.app/api/v1/check?url=https%3A%2F%2Fapi.zfinia.com%2Fx402%2Fv1%2Fci-workflow-contract-audit&method=POST
const res = await pay("https://api.zfinia.com/x402/v1/ci-workflow-contract-audit", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({
    "contract": {
      "required_jobs": [
        "build",
        "publish",
        "lint"
      ]
    },
    "workflow": {
      "jobs": {
        "build": {
          "runs-on": "ubuntu-latest",
          "steps": [
            {
              "uses": "actions/checkout@v4"
            },
            {
              "uses": "actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8"
            },
            {
              "name": "Greet",
              "run": "echo \"Building ${{ github.event.pull_request.title }}\""
            },
            {
              "run": "npm ci && npm test"
            }
          ]
        },
        "notify": {
          "needs": [
            "publish",
            "lint"
          ],
          "runs-on": "ubuntu-latest",
          "steps": [
            {
              "run": "echo done"
            }
          ]
        },
        "publish": {
          "needs": "build",
          "permissions": {
            "contents": "read",
            "packages": "write"
          },
          "runs-on": "ubuntu-latest",
          "steps": [
            {
              "uses": "actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3"
            }
          ],
          "timeout-minutes": 15
        }
      },
      "name": "release",
      "on": {
        "pull_request_target": {},
        "push": {
          "branches": [
            "main"
          ]
        }
      }
    }
  }),
});
console.log(res.status, await res.text());
Python: pay and call
# pip install "x402[requests,evm]"
import os
from eth_account import Account
from x402 import x402ClientSync
from x402.http.clients import x402_requests
from x402.mechanisms.evm import EthAccountSigner
from x402.mechanisms.evm.exact.register import register_exact_evm_client

# Pays at most $0.12 per call.
client = x402ClientSync().set_spend_controls({"max_amount_per_payment": "$0.12"})
register_exact_evm_client(client, EthAccountSigner(Account.from_key(os.environ["EVM_PRIVATE_KEY"])))

with x402_requests(client) as session:
    res = session.post(
        "https://api.zfinia.com/x402/v1/ci-workflow-contract-audit",
        json={
            "contract": {
                "required_jobs": [
                    "build",
                    "publish",
                    "lint",
                ],
            },
            "workflow": {
                "jobs": {
                    "build": {
                        "runs-on": "ubuntu-latest",
                        "steps": [
                            {
                                "uses": "actions/checkout@v4",
                            },
                            {
                                "uses": "actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8",
                            },
                            {
                                "name": "Greet",
                                "run": "echo \"Building ${{ github.event.pull_request.title }}\"",
                            },
                            {
                                "run": "npm ci && npm test",
                            },
                        ],
                    },
                    "notify": {
                        "needs": [
                            "publish",
                            "lint",
                        ],
                        "runs-on": "ubuntu-latest",
                        "steps": [
                            {
                                "run": "echo done",
                            },
                        ],
                    },
                    "publish": {
                        "needs": "build",
                        "permissions": {
                            "contents": "read",
                            "packages": "write",
                        },
                        "runs-on": "ubuntu-latest",
                        "steps": [
                            {
                                "uses": "actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3",
                            },
                        ],
                        "timeout-minutes": 15,
                    },
                },
                "name": "release",
                "on": {
                    "pull_request_target": {},
                    "push": {
                        "branches": [
                            "main",
                        ],
                    },
                },
            },
        },
    )
    print(res.status_code, res.text)

Security scan

No findings

We scan names, descriptions and tool definitions for hidden instructions and other prompt-injection patterns.

Recent checks

WhenResultHTTPTimePrice
4 h agoPassed402554 ms$0.12
9 h agoPassed402624 ms$0.12
15 h agoPassed402434 ms$0.12
20 h agoPassed402404 ms$0.12
24 h agoPassed402507 ms$0.12
Show 10 older checks
29 h agoPassed402406 ms$0.12
34 h agoPassed402516 ms$0.12
38 h agoPassed402548 ms$0.12
43 h agoPassed402536 ms$0.12
47 h agoPassed402431 ms$0.12
2 days agoPassed402474 ms$0.12
2 days agoPassed402365 ms$0.12
3 days agoPassed402436 ms$0.12
3 days agoPassed402605 ms$0.12
3 days agoPassed402585 ms$0.12