mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 13:41:30 +00:00
3275f9f1e0
CI run on the ecb8896 push surfaced two real failures rooted in the
2026-05-04 docs overhaul:
1. G-3 env-docs-drift caught two phantom CERTCTL_* env vars I'd
introduced in the Phase 4 follow-on connector pages
(CERTCTL_CA_CERT_PATH_NEW in adcs.md was a placeholder I made
up; CERTCTL_EJBCA_POLL_MAX_WAIT_SECONDS in ejbca.md does not
exist in source). Both removed.
2. QA-doc Part-count drift guard tried to grep
docs/qa-test-guide.md and docs/testing-guide.md, both of which
were renamed/deleted in Phase 2/Phase 5. The Part-count drift
class died with testing-guide.md (Phase 5 prune dispersed its
content); the seed-count drift class is still live but pointed
at the wrong path.
Fixes:
- Removed the QA-doc Part-count drift guard from ci.yml (premise
dead) plus its standalone scripts/qa-doc-part-count.sh peer.
- Retargeted the QA-doc seed-count drift guard from
docs/qa-test-guide.md → docs/contributor/qa-test-suite.md (the
Phase 2 target). Updated both ci.yml inline copy and
scripts/qa-doc-seed-count.sh.
- Updated Makefile qa-stats: target to drop the testing-guide.md
Parts metric (file is gone).
- Updated Makefile verify-docs: target to drop the part-count step.
G-3 was also failing in the second direction (env vars defined in
config.go but never documented anywhere). 16 vars surfaced —
features.md (deleted Phase 6) and testing-guide.md (deleted Phase 5)
had been their canonical home. Created
docs/reference/configuration.md as the new home: a compact
operator-facing env-var reference covering scheduler intervals, job
lifecycle, rate limiting, audit, deploy verify, database,
agent-side, and SCEP profile binding. Added to docs/README.md
Reference table.
Doc-side updates to qa-test-suite.md to reframe its references to
the deleted testing-guide.md (it's now self-contained: the
Part-by-Part Coverage Map IS the canonical Part inventory).
Cosmetic comment-only updates in ci.yml + scripts/ci-guards/*.sh +
scripts/dev-setup.sh to point at the new audience-organized doc
paths (docs/operator/security.md, docs/operator/tls.md,
docs/reference/architecture.md, etc.) instead of the pre-Phase-2
flat layout.
Verified: all 24 ci-guards/*.sh pass locally; qa-doc-seed-count.sh
clean. Net diff: 178 additions / 112 deletions across 13 files.
One file deleted (qa-doc-part-count.sh) and one file added
(docs/reference/configuration.md).
scripts/ci-guards/ — Regression-guard scripts
Each <id>.sh script in this directory pins one closed audit finding from
regressing. CI runs the full set on every push via the
Regression guards step in .github/workflows/ci.yml. Operators can
run any script locally:
bash scripts/ci-guards/G-3-env-docs-drift.sh
Contract
Every script in this directory MUST:
- Be exit-code 0 on a clean repo (no regression present).
- Be exit-code non-zero on regression, with a
::error::annotation prefix so PR reviewers see the failing line in the GitHub Actions UI. - Be runnable from repo root via
bash scripts/ci-guards/<id>.shwith NO arguments and NO env-var requirements. The CI loop step (for g in scripts/ci-guards/*.sh; do bash "$g"; done) iterates every.shhere without args; any script that requires an arg or env var WILL fail in that loop. - Carry a head-comment block matching the in-source justification from the original ci.yml entry: the audit-finding reference, the closure rationale, the exempt-surface list (if any).
- Use
set -eearly to fail-fast on internal command errors. - Produce no output on the happy path beyond a final
echo "<id>: clean."confirmation line.
Helpers vs guards
Scripts that consume input artifacts (a test-output log, a
coverage.out file) or env vars (PR_NUMBER, GH_TOKEN) are
HELPERS, not guards. They live in scripts/, NOT scripts/ci-guards/.
Current helpers:
scripts/vendor-e2e-skip-check.sh— consumestest-output.logarg from the deploy-vendor-e2e jobscripts/coverage-pr-comment.sh— consumescoverage.out+PR_NUMBER+GH_TOKENenv from the go-build-and-test jobscripts/check-coverage-thresholds.sh— consumescoverage.out.github/coverage-thresholds.yml
scripts/qa-doc-part-count.sh+scripts/qa-doc-seed-count.sh— invoked viamake verify-docspre-tag, not in CI
Adding a new guard
- Drop a new
<id>.shin this directory with the head-comment block describing the audit finding it closes. - Make it executable:
chmod +x scripts/ci-guards/<id>.sh. - Verify it fails on a deliberate regression and passes on clean repo.
- CI auto-picks up new scripts via the
for g in scripts/ci-guards/*.shloop in theRegression guardsstep — no ci.yml change required.
The 22 guards in this directory
| ID | Finding | Catches |
|---|---|---|
G-1-jwt-auth-literal |
G-1 JWT silent auth downgrade | "jwt" literal in additive auth-type surfaces |
L-001-insecure-skip-verify |
L-001 unjustified InsecureSkipVerify | InsecureSkipVerify: true without //nolint:gosec |
H-001-bare-from |
H-001 (CWE-829) tag-swap attack | Bare FROM line without @sha256 digest pin |
M-012-no-root-user |
M-012 (CWE-250) container-as-root | Dockerfile missing terminal USER <non-root> |
H-009-readme-jwt |
H-009 README JWT advertising | README.md re-introducing JWT-as-supported claim |
G-2-api-key-hash-json |
G-2 cat-s5-apikey_leak | api_key_hash in JSON-emitting surface |
U-2-plaintext-healthcheck |
U-2 healthcheck protocol mismatch | Plaintext http:// in HEALTHCHECK directive |
U-3-migration-mount |
U-3 seed initdb schema drift | Migration file mounted into postgres initdb |
D-1-D-2-statusbadge-phantom |
D-1 + D-2 dead keys + TS phantoms | StatusBadge dead keys + 5 Certificate / 5 Agent / 1 Issuer / 1 Notification phantom fields |
L-1-bulk-action-loop |
L-1 client-side bulk loops | for ... await triggerRenewal/updateCertificate in CertificatesPage |
B-1-orphan-crud |
B-1 orphan-CRUD client fns | 8 update/create/delete fns lose their page consumer |
S-2-strings-contains-err |
S-2 brittle error-dispatch | strings.Contains(err.Error(), "not found"|"violates foreign key") in handlers |
G-3-env-docs-drift |
G-3 env-var docs drift | CERTCTL_* env var defined OR documented but not both |
test-naming-convention |
I-001-extended | func TestXxx (lowercase first letter) — Go silently skips |
S-1-hardcoded-source-counts |
S-1 stale numeric prose | Hardcoded "N issuer connectors" / "N MCP tools" in README + docs |
P-1-documented-orphan-fns |
P-1 documented orphans | 16 read-fn names removed from client.ts exports |
T-1-frontend-page-coverage |
T-1 untested frontend pages | New page in web/src/pages/ without sibling .test.tsx and not on the deferred allowlist |
bundle-8-L-015-target-blank-rel-noopener |
L-015 (CWE-1022) reverse-tabnabbing | target="_blank" without rel="noopener noreferrer" |
bundle-8-L-019-dangerously-set-inner-html |
L-019 (CWE-79) XSS | dangerouslySetInnerHTML outside safeHtml.ts |
bundle-8-M-009-bare-usemutation |
M-009 + M-029 mutation contract | Bare useMutation() outside useTrackedMutation wrapper |
H-1-encryption-key-min-length |
H-1 closure follow-up (post-Phase-5 surfacing) | CERTCTL_CONFIG_ENCRYPTION_KEY literal in any deploy/docker-compose*.yml shorter than the 32-byte floor enforced by internal/config/config.go::Validate() |
test-compose-scep-coherence |
post-Phase-5 surfacing of dead SCEP test config | CERTCTL_SCEP_ENABLED=true in test compose without (a) a CI job that runs the SCEP integration test, (b) the ra.crt + ra.key + intune_trust_anchor.pem fixtures committed to deploy/test/fixtures/, AND (c) the matching volume mount |
Guards explicitly NOT here
QA-doc Part-count drift+QA-doc seed-count drift— these protect docs-the-operator-reads, not anything the product depends on. Moved tomake verify-docs(operator runs pre-tag, not on every push). Seecowork/ci-pipeline-cleanup-prompt.mdPhase 11.
Running the full set locally
for g in scripts/ci-guards/*.sh; do
echo "=== $(basename "$g") ==="
bash "$g" || echo " FAILED"
done