mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 18:21:32 +00:00
ci(codeql): rewire local model pack discovery — fix 1122f5a silent no-op
Two CodeQL runs (commits1122f5a+c4157fd) since the initial Option A landing both completed with conclusion=success but failed to dismiss alert #23 (go/request-forgery on scep_probe.go:232). Root cause: the local pack never loaded. The bug was in codeql-config.yml — `packs: { go: ['./'] }` looked plausible (the path is relative to the config file's directory) but the `packs:` field requires pack NAMES, not paths. Discovery of unpublished local packs goes through the codeql-action `init` step's `additional-packs:` input, not through `packs:`. Verified pattern by reading github/vscode-codeql's working .github/codeql/ setup. The supported chain: workflow init step passes additional-packs: <parent-dir> ↓ CodeQL CLI registers each pack under the parent ↓ codeql-config.yml names the pack in `packs: go: [name]` ↓ CodeQL CLI resolves the name → pack on disk ↓ pack's qlpack.yml declares extensionTargets: codeql/go-all ↓ data extension YAML auto-loads, applies the barrier rows Restructure to match this chain: Before After -------- ----- .github/codeql/qlpack.yml .github/codeql/codeql-config.yml .github/codeql/models/ .github/codeql/certctl-models/ request-forgery-sanitizers.model.yml qlpack.yml .github/codeql/codeql-config.yml models/ request-forgery-sanitizers.model.yml The new `.github/codeql/certctl-models/` is the pack directory, named to match `name: shankar0123/certctl-models` in qlpack.yml. Its parent `.github/codeql/` is what additional-packs points at. The action discovers the pack by walking the parent dir, sees the qlpack.yml, registers the name, and `packs:` lookup succeeds. Three concrete changes: - Pack moves from .github/codeql/{qlpack.yml, models/} into the sibling subdirectory .github/codeql/certctl-models/. - codeql-config.yml's packs: directive now uses the pack NAME (`shankar0123/certctl-models`) instead of the broken `./` path. - codeql.yml's Initialize CodeQL step gains `additional-packs: .github/codeql` so the CLI's resolver knows where to find unpublished packs. Belt-and-suspenders correctness fix: the model row's `subtypes` column now uses `False` (Python-style capitalized) instead of `false` to match every shipped CodeQL Go .model.yml convention. SnakeYAML accepts lowercase too — this is a hedge against any strict-format tooling in the path. Why this matters: alert #23 is rated Critical with CWE-918 + CWE-180. The runtime defense is correct (validate-then-pin via ValidateSafeURL + SafeHTTPDialContext), but the analyzer doesn't know it. With the pack actually loading this time, the next CodeQL run will see the barrier and dismiss the alert at source. Same fix implicitly applies to the webhook notifier's outbound client.Do (the second site that uses ValidateSafeURL). Operator: push and watch the next CodeQL run dismiss alert #23. If it doesn't, the next iteration will be on the YAML row's column shape — most likely a one-line tweak, not another redesign.
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
# Models-as-Data sanitizer rows for the go/request-forgery query.
|
||||
#
|
||||
# Each row in `data` is a 9-tuple matching the `barrierModel` extensible
|
||||
# predicate signature for Go:
|
||||
#
|
||||
# (package, type, subtypes, name, signature, ext, output, kind, provenance)
|
||||
#
|
||||
# Where:
|
||||
# - package — Go import path of the sanitizer
|
||||
# - type — receiver type ("" for package-level functions)
|
||||
# - subtypes — false for non-method functions; true to apply to subtypes
|
||||
# - name — function/method name
|
||||
# - signature — empty for Go (the column is not used)
|
||||
# - ext — empty (reserved)
|
||||
# - output — access path that becomes the barrier; "Argument[N]" means
|
||||
# the Nth argument is sanitized after the call
|
||||
# - kind — taint kind the barrier applies to ("request-forgery" for
|
||||
# the go/request-forgery query)
|
||||
# - provenance — origin tag for the model row ("manual" — hand-authored)
|
||||
#
|
||||
# References:
|
||||
# - https://codeql.github.com/docs/codeql-language-guides/customizing-library-models-for-go/
|
||||
# - https://github.blog/changelog/2026-04-21-codeql-now-supports-sanitizers-and-validators-in-models-as-data/
|
||||
|
||||
extensions:
|
||||
# ---------------------------------------------------------------------------
|
||||
# validation.ValidateSafeURL — primary egress URL validator.
|
||||
# ---------------------------------------------------------------------------
|
||||
# Signature: func ValidateSafeURL(rawURL string) error
|
||||
#
|
||||
# Rejects (returns non-nil error for):
|
||||
# - non-http/https schemes (file://, gopher://, ftp://, data:, etc.)
|
||||
# - URLs missing a host
|
||||
# - literal-IP hosts in: 127/8 + ::1 (loopback), 169.254.0.0/16 (link-
|
||||
# local incl. AWS/Azure/GCP cloud metadata at 169.254.169.254),
|
||||
# 224.0.0.0/4 + ff00::/8 (multicast), 255.255.255.255 (broadcast),
|
||||
# 0.0.0.0 + :: (unspecified), fe80::/10 (IPv6 link-local)
|
||||
# - DNS names whose A/AAAA resolution returns any IP in the set above
|
||||
#
|
||||
# Source of truth: internal/validation/ssrf.go (ValidateSafeURL +
|
||||
# IsReservedIP + isReservedIPForDial). RFC 1918 (10/8, 172.16/12,
|
||||
# 192.168/16) is intentionally NOT blocked — see the comment block at
|
||||
# ssrf.go:17-21 for the design rationale.
|
||||
#
|
||||
# The companion runtime defense is SafeHTTPDialContext (installed on the
|
||||
# http.Transport via http.Transport.DialContext) which re-resolves the
|
||||
# host at dial time and pins the dial to a literal non-reserved IP,
|
||||
# defeating DNS rebinding. SafeHTTPDialContext returns a closure rather
|
||||
# than acting as a direct sanitizer in dataflow terms, so it isn't
|
||||
# modeled here — but ValidateSafeURL alone is sufficient to dismiss the
|
||||
# request-forgery alerts at the call sites that use it (scep_probe.go,
|
||||
# webhook.go).
|
||||
- addsTo:
|
||||
pack: codeql/go-all
|
||||
extensible: barrierModel
|
||||
data:
|
||||
# subtypes uses Python-style `False` (capitalized) to match every
|
||||
# shipped CodeQL Go .model.yml — see e.g. github/codeql/go/ql/lib/ext/
|
||||
# database.sql.model.yml. SnakeYAML accepts lowercase too, but
|
||||
# capitalized matches the canonical convention.
|
||||
- ["github.com/shankar0123/certctl/internal/validation", "", False, "ValidateSafeURL", "", "", "Argument[0]", "request-forgery", "manual"]
|
||||
@@ -0,0 +1,55 @@
|
||||
# certctl CodeQL model pack — extends the standard Go queries with project-
|
||||
# specific data-flow knowledge (sanitizers, sinks, summaries).
|
||||
#
|
||||
# Why this exists: CodeQL's standard `go/request-forgery` query is a syntactic
|
||||
# taint-tracking rule. It traces operator-supplied URLs into HTTP egress sinks
|
||||
# (`http.Client.Do`) and reports — but it has no built-in knowledge of
|
||||
# certctl's `internal/validation.ValidateSafeURL` SSRF guard. The validator
|
||||
# IS a sanitizer (rejects loopback, link-local incl. cloud metadata
|
||||
# 169.254.169.254, multicast, broadcast, unspecified, IPv6 link-local;
|
||||
# rejects DNS names whose A/AAAA records resolve into any of those ranges)
|
||||
# but CodeQL doesn't know that, so the analyzer reports a finding the
|
||||
# runtime defense already mitigates.
|
||||
#
|
||||
# This pack uses Models-as-Data (MaD) extensions to declare the validator as
|
||||
# a barrier for the request-forgery query. After this pack is loaded:
|
||||
# - The alert at internal/service/scep_probe.go:232 (CodeQL #23) is
|
||||
# dismissed at source, not via per-line `// codeql[...]` suppression.
|
||||
# - The same model applies to the second site of this shape — webhook
|
||||
# notifier's outbound `client.Do` (internal/connector/notifier/webhook/
|
||||
# webhook.go) — without per-line annotations.
|
||||
# - Future code that flows operator URLs through ValidateSafeURL gets the
|
||||
# same treatment automatically.
|
||||
#
|
||||
# Pack-loading mechanism (post-correction in commit fix-up):
|
||||
# - This pack lives at .github/codeql/certctl-models/. Its NAME is the
|
||||
# `name:` field below.
|
||||
# - .github/workflows/codeql.yml's Initialize CodeQL step passes
|
||||
# `additional-packs: .github/codeql` to the action — that's the parent
|
||||
# directory the CodeQL CLI's resolver searches for unpublished packs.
|
||||
# - .github/codeql/codeql-config.yml then references this pack by NAME
|
||||
# in `packs: { go: [shankar0123/certctl-models] }`. The CLI looks the
|
||||
# name up against the additional-packs path, finds this qlpack.yml,
|
||||
# loads the pack and its data extensions.
|
||||
#
|
||||
# An earlier draft (commit d8026d5) tried `packs: { go: ['./'] }` in
|
||||
# codeql-config.yml, which is the wrong syntax — that field expects pack
|
||||
# names, not paths. The pack silently never loaded; alert #23 stayed open
|
||||
# across two CodeQL runs (d8026d5 + 4bb7a74). Pack-name + additional-packs
|
||||
# is the supported path; verified against the github/vscode-codeql working
|
||||
# example.
|
||||
#
|
||||
# MaD `barrierModel` extension was added for Go in CodeQL 2.25.2 (2026-04-21).
|
||||
# `github/codeql-action@v3` (pinned in .github/workflows/codeql.yml) pulls a
|
||||
# CLI version >= 2.25.2 by default. If a future analysis fails with
|
||||
# "unknown extensible predicate barrierModel", the action's CLI version has
|
||||
# regressed below 2.25.2 — pin a newer action version rather than reverting
|
||||
# this pack.
|
||||
|
||||
name: shankar0123/certctl-models
|
||||
version: 0.0.1
|
||||
library: true
|
||||
extensionTargets:
|
||||
codeql/go-all: '*'
|
||||
dataExtensions:
|
||||
- models/*.model.yml
|
||||
Reference in New Issue
Block a user