curl-able GitHub Actions linter. POST a workflow or action.yml and get
back JSON diagnostics. Rules are inspired by
actionlint,
zizmor, and
ghalint — see
THIRD_PARTY_LICENSES.md.
Public endpoint: https://karinto.toiroakr.workers.dev
61 of 82 catalogued rules are active. They cover syntax, expression typing
and context availability, permissions hygiene, pinned-uses requirements,
taint analysis for template injection, and a range of security policies
(excessive permissions, self-hosted runners, OIDC migration, dangerous
triggers, and more). The full catalogue with status, severity, and upstream
origins lives in rules_catalog.md (human-readable
mirror of the in-code source of truth at
rules_catalog.mbt).
GET or POST. Parameters can come from the URL path
(/<owner>/<repo>/<commit>[/<target/path/...>] — segments after the
commit are joined into a single nested target path), the query string,
the request body (raw key=value&..., JSON, or a plain YAML blob), or
any mix — body beats query, query beats path on conflict. Paths that
don't match the repo-mode shape are ignored so the Worker can be served
under arbitrary path prefixes.
| Key | Type | Notes |
|---|---|---|
type |
workflow | action | (omit) |
Optional; auto-detected when blank |
content |
string | The YAML source |
disable |
string | Comma-separated glob patterns of rule IDs to skip. At most 64 patterns, 128 characters per pattern, and one * per pattern. |
repo |
owner/name |
Public-repo mode; mutually exclusive with content |
commit |
hex SHA, 7–64 chars | Required whenever repo is set. Non-hex branch/tag names (e.g. main, v1.2.3) are rejected. Hex-shaped refs are accepted at face value — a short SHA can collide with an all-hex branch/tag (e.g. deadbee), so use the full 40-char SHA for guaranteed immutability. |
targets |
string | Comma-separated literal file paths. Required with repo unless a single target is supplied via the URL path (/<owner>/<repo>/<commit>/<target/...>). Globs are not supported — list each file. At most 50 paths; requests over the cap are rejected with 400 rather than silently truncated. |
osv |
1 / true |
Query OSV.dev for known-vulnerable actions (adds 50–300 ms) |
no_capture |
1 / true |
Skip persisting this request to the dark-launch capture store (see Privacy) |
POST with the workflow as the body:
curl -X POST --data-binary @.github/workflows/ci.yml \
https://karinto.toiroakr.workers.devPOST with form data:
curl https://karinto.toiroakr.workers.dev \
--data-urlencode "content@.github/workflows/ci.yml" \
--data "disable=permissions-*"GET with query parameters (small payloads only):
curl -G https://karinto.toiroakr.workers.dev \
--data-urlencode "content=$(cat workflow.yml)" \
--data "type=workflow"GET/POST over a public repo (single target via path):
curl "https://karinto.toiroakr.workers.dev/actions/checkout/b4ffde65f46336ab88eb53be808477a3936bae11/action.yml"Or with explicit query parameters and multiple targets:
curl "https://karinto.toiroakr.workers.dev?repo=actions/checkout&commit=b4ffde65f46336ab88eb53be808477a3936bae11&targets=action.yml,.github/workflows/test.yml"- Request body: 1 MiB. Direct payloads over the cap short-circuit with
413 Payload Too Largebefore reaching the parser. Inrepomode the request still returns200and the oversized file is surfaced asfiles[].errorso the rest of the batch is unaffected. - Per-IP rate limit: 60 requests / minute. Over-limit traffic gets
429. Requests originating from GitHub-hosted Actions runners are exempted (their egress IPs are shared across unrelated tenants), so noisy CI tenants can't collateral-429 other CI traffic.
{
"ok": true,
"result": {
"kind": "workflow",
"stats": { "jobs": 2, "steps": 2, "lines": 11 },
"diagnostics": [
{
"rule": "duplicate-job-step-ids",
"severity": "error",
"message": "duplicate job ID `build` (conflicts with `Build` case-insensitively)"
}
]
}
}In repo mode the result is wrapped:
{
"ok": true,
"repo": "actions/checkout",
"commit": "b4ffde65f46336ab88eb53be808477a3936bae11",
"targets": ["action.yml"],
"files": [ { "path": "action.yml", "ok": true, "result": { ... } } ]
}For private repos pass content directly. The Worker does not handle
GITHUB_TOKEN-authenticated repo-mode fetches.
The production deployment persists successful requests (the content
plus a few non-secret query parameters) and the corresponding response
into a private bucket for up to 30 days. These captures are used to
replay traffic against PR previews and detect regressions before they
reach prod. To opt out per-request, send either:
- query / form parameter
no_capture=1, or - HTTP header
X-Karinto-No-Capture: 1
Requests using osv=1 or repo= are never captured; nor are requests
whose content exceeds the per-deployment cap (default 100 KiB, tunable
via the CAPTURE_CONTENT_LIMIT_KIB Worker variable — see
DEVELOPMENT.md).
Build, test, deploy, and rule-catalog notes live in
DEVELOPMENT.md.