Skip to content

Releases: toiroakr/karinto

v0.9.0

18 Jun 04:34
950d0f5

Choose a tag to compare

Minor Changes

  • #62 c50ee67 Thanks @toiroakr! - Honour zizmor.yml configs and fix five parity divergences found by diffing
    karinto against zizmor / actionlint on real OSS workflows.

    • New: zizmor.yml config support. A zizmor config's rules.<id>.disable
      and rules.<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-config or the HTTP zizmor parameter.
    • known-vulnerable-actions no 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 via osv=1 / the companion
      action) — the mechanism zizmor uses. Without osv=1 the rule no longer fires.
    • context-availability no longer flags inputs in workflow-level env /
      concurrency for workflow_call / workflow_dispatch workflows, where the
      inputs context is in fact available (matching actionlint).
    • expression-syntax no longer reports a stray }}: a literal {{ … }}
      template (e.g. docker/metadata-action's pattern={{version}}) is not an
      expression. Only an unterminated ${{ is an error, as in actionlint.
    • bot-conditions now fires only on an == comparison of github.actor /
      github.triggering_actor against a [bot] login (and now also covers
      triggering_actor), matching zizmor; the != / endsWith(...) exclude forms
      are no longer flagged.
    • excessive-permissions persona gating: the workflow-level "no
      permissions: block" finding is now pedantic (the per-job "default
      permissions used" finding stays regular), matching zizmor's per-persona
      behaviour.

Patch Changes

  • #56 19e61a5 Thanks @toiroakr! - Refactor check_unknown_context to reuse the shared extract_expr_bodies
    and strip_expr_string_literals helpers 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_bodies now 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 previous check_unknown_context behaviour and also
    hardens the shared text_references_regular_context scan against the same
    edge case. The context-access lookahead in check_unknown_context now skips
    tabs and newlines (not just spaces) before the ., matching the expr.mbt
    lexer, so a typo like ${{ githab<TAB>.ref }} is still flagged.

v0.8.4

16 Jun 02:46
adfc634

Choose a tag to compare

Patch Changes

  • #51 733a800 Thanks @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 the karinto and zizmor prefixes are recognised.
    • ghalint config — a ghalint.yaml excludes: list is honoured. Each
      entry's policy_name maps onto the karinto rule(s) that absorbed it (via the
      catalogue origins), with the workflow_file_path / job_name /
      action_name / step_id scope fields applied. Available through the CLI's
      --ghalint-config flag and the Worker's ghalint HTTP parameter (with
      path / per-file paths resolving the workflow_file_path scope).

    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.

  • #55 82ad616 Thanks @toiroakr! - Fix unknown-context-or-function false 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

29 May 14:15
5c78043

Choose a tag to compare

Minor Changes

  • #24 a13d4a3 Thanks @toiroakr! - Diagnostics now carry location context. Each finding may include a job field
    (the offending job's ID) and a step field ({ index, id }, where index is
    the 0-based position in the steps list and id is the step's id: 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, and job-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/step are
    the location handles instead.

v0.4.0

29 May 04:15
4fa8e51

Choose a tag to compare

Minor Changes

  • #21 bf23c93 Thanks @toiroakr! - Make the engine pinnable for reproducible CI. Every response now carries an
    engine_version field (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 major X. 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 top PINNED_KEEP_RECENT versions 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-2 pointing at
    the same commit as v0.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.dev URL pins and engine_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

24 May 09:53
4c2eecc

Choose a tag to compare

Patch Changes

  • #18 567f6c7 Thanks @toiroakr! - Add a preview-pages workflow that uploads docs/ as an unzipped artifact
    (actions/upload-artifact@v7 with archive: false, Feb 2026 feature) on
    every PR touching the docs. The sticky PR comment links straight to the
    artifact so reviewers can open index.html in the browser without a
    GitHub Pages deploy.

v0.2.1

23 May 13:03
3c7da1c

Choose a tag to compare

Patch Changes

  • #14 0b4fa3a Thanks @toiroakr! - Promote every Planned zizmor rule in the catalogue to Implemented and drive
    per-rule upstream parity against zizmor --pedantic --no-online-audits to
    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-issues ships a full implementation
    (re-scans the raw source for &name / *name tokens since the YAML parser
    resolves them away).

    Preview implementations were added for the actionlint-side matrix-values
    and deprecated-action-inputs rules (still Planned in 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.yml config discovery is not implemented, so
      config-scenarios/*/hackme.yml reports the unconfigured baseline.
    • self-hosted-runner is gated to --persona=auditor upstream but fires
      unconditionally in karinto.

v0.2.0

19 May 12:29
f1c8b7f

Choose a tag to compare

Minor Changes

  • #5 db305e6 Thanks @toiroakr! - Catalog overhaul:

    • Add rules_catalog.md as a human-readable mirror of rules_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 Status variant, NotPlanned, for rules that are
      documented but deliberately out of scope. Demote shellcheck and
      pyflakes (Cloudflare Workers cannot ship the native binaries),
      ref-confusion (cannot occur once unpinned-uses mandates SHA pins),
      and stale-action-refs (GitHub API cost not worth the informational
      signal) to NotPlanned and drop their #skip("not implemented yet")
      fixtures. Coverage test now only requires fixtures for
      Implemented/Planned entries.
    • AGENTS.md (and CLAUDE.md, now a symlink to it) gains a "Rule catalog
      discipline" section requiring rules_catalog.mbt and rules_catalog.md
      to be updated together.
  • #8 bfa0c9c Thanks @toiroakr! - Allow specifying org/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
    repo mode is used. The commit parameter 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 in repo mode now
    include the resolved commit alongside repo and targets.

    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 the raw.githubusercontent.com URL.

Patch Changes

  • #9 800c4f6 Thanks @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*…*b against long inputs can no
      longer cause exponential CPU usage. (The matcher is not strictly
      linear — worst case is O(m·n) — but the disable= 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 in repo mode. Oversized direct payloads short-circuit
      with 413 Payload Too Large before reaching the parser / rules.
      In repo mode the request still returns 200 with the per-file
      error surfaced under files[].error so 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= (in repo mode) is capped at 50 paths. Requests over the
      cap are rejected with 400 rather than silently truncated, so clients
      don't get an ok:true response 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 from api.github.com/meta and 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 get 429.
    • Deploy note: this introduces a KV namespace and a triggers.crons
      entry in cf/wrangler.jsonc. Run npx wrangler kv namespace create karinto-meta once and paste the returned id into both the top-level
      and env.staging kv_namespaces blocks — production and staging
      share the namespace because the /meta payload is GitHub-published
      and identical across envs.
    • Regression test exercises the previously catastrophic pattern.

v0.1.0

18 May 05:01

Choose a tag to compare

0.1.0

Minor Changes

  • #1 4f4267c Thanks @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 #skip specs.
    • Cloudflare Worker (cf/index.js) accepting GET/POST with workflow YAML,
      form-encoded params, JSON body, or repo-mode dispatch.
    • OSV.dev integration for known-vulnerable-actions (opt-in via osv=1).
    • Curl-driven smoke check at cf/smoke.sh runnable as npm 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 to main.