Releases: toiroakr/karinto
v0.9.0
Minor Changes
-
#62
c50ee67Thanks @toiroakr! - Honourzizmor.ymlconfigs and fix five parity divergences found by diffing
karinto against zizmor / actionlint on real OSS workflows.- New:
zizmor.ymlconfig support. A zizmor config'srules.<id>.disable
andrules.<id>.ignore(filename[:line[:col]]) opt-outs are now honoured,
alongside the existing inline-comment and ghalint-config opt-outs. Pass it via
the CLI's--zizmor-configor the HTTPzizmorparameter. known-vulnerable-actionsno longer carries a hardcoded action list. It
could not track GHSA's per-advisory version ranges and false-flagged fixed
releases (e.g.tj-actions/changed-files@v47). Vulnerability is now decided
solely by the online advisory path (OSV.dev viaosv=1/ the companion
action) — the mechanism zizmor uses. Withoutosv=1the rule no longer fires.context-availabilityno longer flagsinputsin workflow-levelenv/
concurrencyforworkflow_call/workflow_dispatchworkflows, where the
inputscontext is in fact available (matching actionlint).expression-syntaxno longer reports a stray}}: a literal{{ … }}
template (e.g. docker/metadata-action'spattern={{version}}) is not an
expression. Only an unterminated${{is an error, as in actionlint.bot-conditionsnow fires only on an==comparison ofgithub.actor/
github.triggering_actoragainst a[bot]login (and now also covers
triggering_actor), matching zizmor; the!=/endsWith(...)exclude forms
are no longer flagged.excessive-permissionspersona gating: the workflow-level "no
permissions:block" finding is nowpedantic(the per-job "default
permissions used" finding staysregular), matching zizmor's per-persona
behaviour.
- New:
Patch Changes
- #56
19e61a5Thanks @toiroakr! - Refactorcheck_unknown_contextto reuse the sharedextract_expr_bodies
andstrip_expr_string_literalshelpers instead of its own inline${{ }}
extraction and string-literal skipping. The duplicated expression-scanning
logic is removed and the single-quoted-literal false-positive fix is
preserved.extract_expr_bodiesnow skips single-quoted literals while
locating the terminating}}, so a}}inside a literal (e.g.
${{ hashFiles('a}}b') && github.ref }}) no longer truncates the extracted
body — this preserves the previouscheck_unknown_contextbehaviour and also
hardens the sharedtext_references_regular_contextscan against the same
edge case. The context-access lookahead incheck_unknown_contextnow skips
tabs and newlines (not just spaces) before the., matching theexpr.mbt
lexer, so a typo like${{ githab<TAB>.ref }}is still flagged.
v0.8.4
Patch Changes
-
#51
733a800Thanks @toiroakr! - Honour author-written opt-outs from the upstream tools karinto consolidates:- Inline ignore comments —
# karinto: ignore[rule-id]and
# zizmor: ignore[rule-id]suppress findings on the same line (line-scoped),
supporting comma-separated rule lists and a trailing free-form note. Mirrors
zizmor's inline-ignore syntax. actionlint and ghalint have no inline form, so
only thekarintoandzizmorprefixes are recognised. - ghalint config — a
ghalint.yamlexcludes:list is honoured. Each
entry'spolicy_namemaps onto the karinto rule(s) that absorbed it (via the
catalogueorigins), with theworkflow_file_path/job_name/
action_name/step_idscope fields applied. Available through the CLI's
--ghalint-configflag and the Worker'sghalintHTTP parameter (with
path/ per-file paths resolving theworkflow_file_pathscope).
actionlint config support is intentionally deferred (tracked in #50): it ignores
by regex against actionlint's own error messages, which do not map onto
karinto's findings. - Inline ignore comments —
-
#55
82ad616Thanks @toiroakr! - Fixunknown-context-or-functionfalse positive on single-quoted string
literals containing dots. The scanner walked expression bodies char by char
without skipping string literals, so a dotted literal like
hashFiles('replay-summary.md')or a bare'a.b'was misread as
<head>.<member>context access and reported the head as an unknown context.
Single-quoted literals are now skipped during the scan, matching the GitHub
Actions expression language.
v0.5.0
Minor Changes
- #24
a13d4a3Thanks @toiroakr! - Diagnostics now carry location context. Each finding may include ajobfield
(the offending job's ID) and astepfield ({ index, id }, whereindexis
the 0-based position in the steps list andidis the step'sid:when
declared). Job-scoped and step-scoped rules attach this automatically. Rules
that walk jobs/steps themselves also populate it:duplicate-job-step-ids,
anonymous-definition, andjob-step-id-naming. Step-specific findings raised
from job-scoped rules (artipacked,superfluous-actions,shell-name-per-os)
now carry the offending step too, not just the job. The YAML parser drops source
layout, so diagnostics still have no line/column positions —job/stepare
the location handles instead.
v0.4.0
Minor Changes
-
#21
bf23c93Thanks @toiroakr! - Make the engine pinnable for reproducible CI. Every response now carries an
engine_versionfield (on both success and error paths) so callers can assert
the deployed engine hasn't drifted.Each release additionally deploys two extra Workers alongside the always-latest
one:karinto-vX-Y-Z.toiroakr.workers.dev— an immutable snapshot frozen to
that release's bundle (dots → dashes). The maximally-strict pin for CI.karinto-vX.toiroakr.workers.dev— a major alias that tracks the latest
patch within majorX. Rolls forward across patches and minors, shielded
from breaking-change bumps.
Exact-version snapshots are auto-pruned on each release down to (the latest
patch within every major) ∪ (the topPINNED_KEEP_RECENTversions by
SemVer, default 50) ∪ (the just-released version), so the latest patch
of every released major remains available as an exact pin indefinitely.
Major aliases are never auto-deleted.Each release also pushes a dashed lightweight git tag (
v0-3-2pointing at
the same commit asv0.3.2) used by the new shareable Renovate preset
(github>toiroakr/karinto:pin), which auto-PRs version bumps for
karinto-vX-Y-Z.toiroakr.workers.devURL pins andengine_version
assertions in downstream repos.Full pinning guide — including the 404 risk for long-untouched exact pins,
self-hosting on your own Cloudflare account, and the Renovate preset — in
docs/pinning.md.
v0.3.1
Patch Changes
- #18
567f6c7Thanks @toiroakr! - Add apreview-pagesworkflow that uploadsdocs/as an unzipped artifact
(actions/upload-artifact@v7witharchive: false, Feb 2026 feature) on
every PR touching the docs. The sticky PR comment links straight to the
artifact so reviewers can openindex.htmlin the browser without a
GitHub Pages deploy.
v0.2.1
Patch Changes
-
#14
0b4fa3aThanks @toiroakr! - Promote everyPlannedzizmor rule in the catalogue toImplementedand drive
per-rule upstream parity againstzizmor --pedantic --no-online-auditsto
missing=0 / extra=0 on the vendored fixtures (engine-wide divergences are
recorded in the parity allowlist instead of being silently absorbed).Promoted (zizmor):
anonymous-definition,undocumented-permissions,
forbidden-uses,github-app,dependabot-execution,archived-uses,
impostor-commit,ref-version-mismatch,overprovisioned-secrets,
insecure-commands,unsound-condition,unredacted-secrets,misfeature,
unpinned-tools,unpinned-images,self-hosted-runner,
superfluous-actions,github-env,obfuscation,use-trusted-publishing,
dependabot-cooldown,artipacked,cache-poisoning,excessive-permissions,
template-injection.Promoted (actionlint):
yaml-anchor-issuesships a full implementation
(re-scans the raw source for&name/*nametokens since the YAML parser
resolves them away).Preview implementations were added for the actionlint-side
matrix-values
anddeprecated-action-inputsrules (stillPlannedin the catalogue
pending broader coverage work).Known engine-wide divergences absorbed via the parity allowlist:
# zizmor: ignore[…]is parsed file-wide instead of per-node, so
fixtures that mute a single step also drop neighbouring findings.- Local
zizmor.ymlconfig discovery is not implemented, so
config-scenarios/*/hackme.ymlreports the unconfigured baseline. self-hosted-runneris gated to--persona=auditorupstream but fires
unconditionally in karinto.
v0.2.0
Minor Changes
-
#5
db305e6Thanks @toiroakr! - Catalog overhaul:- Add
rules_catalog.mdas a human-readable mirror ofrules_catalog.mbt,
with upstream documentation links, status, severity, and per-rule notes
on the five consolidations and on every planned/not-planned rule. - Introduce a third
Statusvariant,NotPlanned, for rules that are
documented but deliberately out of scope. Demoteshellcheckand
pyflakes(Cloudflare Workers cannot ship the native binaries),
ref-confusion(cannot occur onceunpinned-usesmandates SHA pins),
andstale-action-refs(GitHub API cost not worth the informational
signal) toNotPlannedand drop their#skip("not implemented yet")
fixtures. Coverage test now only requires fixtures for
Implemented/Plannedentries. AGENTS.md(andCLAUDE.md, now a symlink to it) gains a "Rule catalog
discipline" section requiringrules_catalog.mbtandrules_catalog.md
to be updated together.
- Add
-
#8
bfa0c9cThanks @toiroakr! - Allow specifyingorg/repo/commit[/target/path/...]directly in the request
URL path (e.g.GET /actions/checkout/<sha>/action.yml, or with nested
targets like.github/workflows/ci.yml), and require a commit SHA whenever
repomode is used. Thecommitparameter accepts 7–64 hex characters,
so non-hex branch/tag names (e.g.main,v1.2.3) are rejected outright.
Hex-shaped refs are still accepted at face value, so an all-hex branch or
tag (e.g.deadbee) can collide with a short-SHA-shaped commit; callers
needing guaranteed immutability should pass the full 40-char SHA. Path
segments that don't match the repo-mode shape are ignored, so the Worker
can be served under arbitrary path prefixes (/api/...,/favicon.ico,
etc.) without bricking unrelated requests. Responses inrepomode now
include the resolvedcommitalongsiderepoandtargets.Path-based targets bypass the comma-delimited
targets=parsing, so a
literal,in a path no longer splits one file into two. Each target path
is also validated to reject.., absolute, backslash, and percent-encoded
forms that could escape the pinned<commit>prefix once interpolated
into theraw.githubusercontent.comURL.
Patch Changes
-
#9
800c4f6Thanks @toiroakr! - Harden DoS/ReDoS surface:- Replace the recursive backtracking glob matcher behind the
disable=
parameter with a two-pointer "last-star backtrack" algorithm, so
adversarial patterns such as*a*a*…*bagainst long inputs can no
longer cause exponential CPU usage. (The matcher is not strictly
linear — worst case isO(m·n)— but thedisable=caps below keep
the bound small enough that DoS via this path is not feasible.) - Cloudflare Worker now enforces a 1 MiB cap on request bodies and on
files fetched inrepomode. Oversized direct payloads short-circuit
with413 Payload Too Largebefore reaching the parser / rules.
Inrepomode the request still returns200with the per-file
error surfaced underfiles[].errorso a single oversized file does
not invalidate results for the rest of the batch. disable=patterns are limited to one*each (more than one returns
400), and capped at 64 patterns × 128 characters per pattern.targets=(inrepomode) is capped at 50 paths. Requests over the
cap are rejected with400rather than silently truncated, so clients
don't get anok:trueresponse that quietly skipped files.- Add a 60 req/min per-IP rate limit via the Workers Rate Limiting
binding. Traffic from GitHub-hosted Actions runners is exempt
because runners share egress IPs across unrelated tenants; the
allow-list is sourced fromapi.github.com/metaand refreshed daily
by a Cron Trigger into a KV namespace, with the request path reading
from KV (memoized per isolate) and a one-shot direct-fetch fallback
for the cold-deploy case. Over-limit requests get429. - Deploy note: this introduces a
KVnamespace and atriggers.crons
entry incf/wrangler.jsonc. Runnpx wrangler kv namespace create karinto-metaonce and paste the returned id into both the top-level
andenv.stagingkv_namespacesblocks — production and staging
share the namespace because the/metapayload is GitHub-published
and identical across envs. - Regression test exercises the previously catastrophic pattern.
- Replace the recursive backtracking glob matcher behind the
v0.1.0
0.1.0
Minor Changes
-
#1
4f4267cThanks @toiroakr! - Initial release.- MoonBit lint engine (
karinto.mbt+rules.mbt) with 62 implemented rules
derived from actionlint, zizmor, and ghalint. 22 additional rules are
scaffolded as#skipspecs. - Cloudflare Worker (
cf/index.js) acceptingGET/POSTwith workflow YAML,
form-encoded params, JSON body, or repo-mode dispatch. - OSV.dev integration for
known-vulnerable-actions(opt-in viaosv=1). - Curl-driven smoke check at
cf/smoke.shrunnable asnpm run smoke. - Changesets-managed Release PR flow that cuts the production deploy and
GitHub Release on merge. - Per-PR preview Workers (
karinto-pr-<N>.toiroakr.workers.dev) auto-deployed
and cleaned up. - Staging Worker (
karinto-staging.toiroakr.workers.dev) auto-deployed on
every push tomain.
- MoonBit lint engine (