Audit a CI workflow for what it leaves movable or undeclared
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 timeout…
by api.zfinia.com · discovered, not yet claimed by its owner · updated 2026-10-04
Mirrored from Coinbase's public x402 Bazaar. The description is the provider's own text and has not been reviewed here; the payment requirements are copied as published.
What it does
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.
Tags: configuration · continuous integration · supply chain hygiene · linting · developer tooling · bazaar
Call contract
- Method
- POST
- Input
- json
- Output
- json
- Last paid call
- 2026-10-04
{
"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"
]
}
}
}
}{
"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
}Use with an agent
- Discover. Agents find this listing with
GET https://bazaar.saylorinnovations.com/discovery/search?query=…, the MCP toolsearch_resources, or/discovery/resources. - Inspect. Fetch
/resources/34026.jsonfor the price and payment options. - Pay. Call
GET https://api.zfinia.com/x402/v1/ci-workflow-contract-audit. The provider answers402with its payment requirements. An x402 client signs one of the options below and retries withPAYMENT-SIGNATURE. - Execute. The provider returns
200with the data. Agent Bazaar is not in the request path and never sees your payment.
For humans
You don't need an account or an API key; you need a wallet holding a small amount of USDC on Base, and an x402-capable client (see the examples below). Call the endpoint unpaid first to see exactly what it asks for.
Pricing & payment
- Price
- $0.12 / request
- Protocol
- x402 v2
- Auth
- No account or API key. Pay per request.
- Networks
- Base
- Demand
- 1 calls · 1 unique payers in 30d (public x402 Bazaar data)
x402 payment requirements
| Scheme | Network | Asset | Amount | Pay to | Timeout |
|---|---|---|---|---|---|
| exact | Base eip155:8453 | USDC | $0.12 | 0x25e8245f07d5e4873987293dad666afbe56a9608 | 60s |
accepts[] (raw JSON)
[
{
"scheme": "exact",
"network": "eip155:8453",
"payTo": "0x25e8245f07d5e4873987293dad666afbe56a9608",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "120000",
"amountUsd": 0.12,
"maxTimeoutSeconds": 60
}
]Schemas
Output schema (JSON Schema)
{
"properties": {
"example": {
"type": "object"
},
"type": {
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}Example shape — generated from the schema, not a captured live response
{
"example": {},
"type": "string"
}Examples
curl -X POST 'https://api.zfinia.com/x402/v1/ci-workflow-contract-audit' -H 'Content-Type: application/json' --data '{"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"]}}}}'
# -> 402 Payment Required; read PAYMENT-REQUIRED
# retry the identical request with PAYMENT-SIGNATUREconst res = await fetch("https://api.zfinia.com/x402/v1/ci-workflow-contract-audit", {
"method": "POST",
"headers": {
"content-type": "application/json"
},
"body": "{\"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\"]}}}}"
});
if (res.status === 402) {
const { accepts } = await res.json();
// pay one of accepts[] via an x402 client, then retry with the payment header
}import httpx
import json
res = httpx.request("POST", "https://api.zfinia.com/x402/v1/ci-workflow-contract-audit", json=json.loads("{\"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\"]}}}}"))
if res.status_code == 402:
accepts = res.json()["accepts"]
# pay one of accepts[] via an x402 client, then retry with the payment headerDetails
- Provider
- api.zfinia.com
- Endpoint
- https://api.zfinia.com/x402/v1/ci-workflow-contract-audit
- Machine-readable
/resources/34026.json