Repository navigation
feat(security): assess source, image and cluster on a schedule - #30
Merged
Merged
Conversation
Adds tests/workflows/continuous-security.bats, the contract for the scheduled vulnerability assessment gitops spec 009 T146 must provide and T143 must test. It follows tests/workflows/mirror-platform-images.bats: grep the guarantees the task requires, and equally the things it forbids. The contract requires a reusable workflow that its callers schedule, because GitHub never fires schedule for a reusable workflow and a cron here would silently never run; the three surfaces T146 names, with the image surface reading the digests the GitOps repository declares through a read-only App token as mirror-platform-images.yml already does and the cluster surface reusing the lane's existing read-only verify-security.sh; an explicit PASS, FAIL or BLOCKED per surface, so a surface without credentials is never counted as a pass; routing into one deduplicated issue in the repository that owns the artifact, updated rather than reopened when a finding recurs and closed when it clears; OIDC with no static credentials; the declared account rather than a hard-coded one; no cluster-mutating verb; and every action pinned by full SHA. It also requires the self-test that runs this contract on every change that could break it. It fails as required: .github/workflows/continuous-security.yml is missing.
…ot as text Three corrections to the contract added in the previous commit, all found by running it against a real implementation. The schedule check forbade the bare word "schedule:", so it reported the header comment that explains why the schedule belongs in the caller. It now matches a YAML key at the start of a line, which is the thing it meant to forbid. The image surface asserted "scan-type: image", which assumes the Trivy action. The set of declared digests is resolved at run time and an action cannot iterate it, so the implementation installs the checksum-locked Trivy release the toolchain lock records, the way mirror-platform-images.yml installs its tooling. The contract now asserts that release, its checksum, and the verified install instead. The account check required a vars.AWS_ACCOUNT_ID reference. Taking the registry as an input, the shape ci.yml already uses for ecr-repository, is stronger: the workflow then holds no account at all. The contract now requires that input and forbids any twelve-digit registry literal. The contract still fails for the right reason: .github/workflows/continuous-security.yml is missing.
Makes tests/workflows/continuous-security.bats pass (gitops spec 009 T146, FR-035). Trivy in ci.yml scans what a push builds and Trivy Operator scans what a cluster runs; neither re-assesses an artifact that was already published when a vulnerability is disclosed days later. This closes that gap. The schedule lives in each caller, never here: GitHub does not fire schedule for a reusable workflow, so a cron in this file would silently never run. Three surfaces, each recorded as PASS, FAIL or BLOCKED, the vocabulary the lane's collectors already use. Source scans the caller's own checkout and needs no credential, so it works from the first run. Image resolves the digests the GitOps repository declares for the service, through a read-only App token scoped to that one repository, and scans each distinct one with the checksum-locked Trivy release the toolchain lock records. Cluster runs the lane's existing read-only verify-security.sh rather than opening a second path into a cluster. A surface with no credentials is BLOCKED and says why; it is never counted as a pass and never fails the run as though the platform were broken. Findings route into one issue in the repository that owns the artifact. A recurring finding comments on the open issue instead of filing a new one, because a daily schedule that opened an issue per run would bury the signal in its own noise, and a clean run closes it. The registry is an input, the shape ci.yml already uses, so no account literal lives in this file. Like mirror-platform-images.yml, the parts that need infrastructure nobody has built yet are authored and contract-tested but inactive: the image surface needs the reader role, and the cluster surface needs a cluster with Argo CD (T063-T065).
The suite writes no em dash. Replaces the three the previous two commits introduced, in the contract's summary line and the self-test header, with plain punctuation. No assertion and no behavior changes; the contract still passes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes
A scheduled vulnerability assessment that re-checks artifacts which were
already published.
.github/workflows/continuous-security.ymlis a reusableworkflow;
tests/workflows/continuous-security.batsis its contract andcontinuous-security-self-test.ymlruns that contract on every change thatcould break it.
The schedule lives in each caller, never here. GitHub does not fire
schedulefor a reusable workflow, so a cron in this file would silently never run.
Three surfaces, each recorded as PASS, FAIL or BLOCKED, the vocabulary the
observability lane's collectors already use:
Needs no credential, so it works from the first scheduled run.
is what "running artifact" means here. Read through a read-only App token
scoped to that one repository, the same pattern
mirror-platform-images.ymluses for the locked image set, then scanned with the checksum-locked Trivy
release the toolchain lock records.
verify-security.sh, rather than asecond imperative path into a cluster.
A surface without credentials is BLOCKED and says why. It is never counted as a
pass, and it never fails the run as though the platform were broken.
Findings route into one issue in the repository that owns the artifact. A
recurring finding comments on the open issue instead of filing a new one, and a
clean run closes it.
Why
FR-035 requires that running artifacts and their host environments be assessed
continuously for newly disclosed vulnerabilities, with actionable findings
routed into the review process.
Nothing did that.
ci.ymlscans what a push builds, so a repository nobodytouches is never re-checked; a CVE disclosed against an image built last month
is invisible. Trivy Operator scans what a cluster runs and reports to Slack and
a dashboard, but it never looks at source and it opens nothing anyone is
assigned. Between them there was no periodic reassessment and no owner.
Tasks
gitops specs/009-full-platform-rollout T143 (test, delivered here)
gitops specs/009-full-platform-rollout T146 (workflow, partial: callers pending)
The register lives in
microservice-app-gitops, not in this repository, and thesuite's convention is that a task whose artifact lands elsewhere is ticked in a
separate GitOps pull request after this one merges. No task is ticked here.
T146 is partial. It also requires wiring the five service repositories and
the ops and GitOps callers. A caller must pin this workflow by an immutable
commit that exists on
main, which cannot be known until this pull requestmerges, so the seven caller pull requests follow immediately afterwards and the
GitOps register records T143 and T146 together once they are all in.
How it is verified
The contract fails before the implementation and passes after.
test(security): specify the continuous security assessmentwas committed red:After the implementation:
Two corrections to the contract, each in its own commit, both found by
running it against a real implementation rather than against nothing:
schedule:and so reported the header comment thatexplains why the schedule belongs in the caller. It now matches a YAML key at
the start of a line.
scan-type: image, which assumes the Trivy action. The set ofdeclared digests is resolved at run time and an action cannot iterate it, so
the implementation installs the checksum-locked Trivy release and the contract
now asserts that release, its checksum, and the verified install.
vars.AWS_ACCOUNT_IDreference. Taking the registry as aninput, the shape
ci.ymlalready uses forecr-repository, is stronger: theworkflow then holds no account at all, and the contract now forbids any
twelve-digit registry literal.
Every other suite in this repository still passes:
Both workflow files parse as YAML and declare the intended triggers, checked
with PyYAML in a pinned container:
One Actions semantic was removed rather than assumed. An earlier draft read
secrets.gitops-reader-app-idinside a stepif. Thesecretscontext is notavailable there, so whether the read-only credentials were wired is now resolved
in a step, where the context is available, and read back as a step output.
No digest or checksum was invented. The Trivy release and its checksum are
the ones
microservice-app-gitops/scripts/managed/full-profile-toolchain.lockrecords for v0.74.0; every action SHA is one this repository already pins.
Documentation MCP servers.
../microservice-app-ai-agents/scripts/check-mcp.sh gitops .reports
PASS aws-knowledge answers with 5 tools,PASS microsoft-learn answers with 3 tools. This pull request adds no infrastructure manifest and noprovider argument: it is a GitHub Actions workflow and a bash contract. The one
external behavior it depends on, that
scheduledoes not fire for a reusableworkflow and must be declared by the caller, is GitHub Actions behavior that
neither server documents; it is recorded in the workflow header so the next
reader does not have to rediscover it.
Risk and rollback
Nothing runs until a caller schedules it, and no caller exists yet, so merging
this changes no behavior in any repository.
When callers land, the risks are bounded and named:
issues: writein the caller to route a finding. That isthe only write permission it asks for; it has no
contents: write.It cannot block a pull request, because no caller runs it on
pull_request.the same authored-but-inactive state
mirror-platform-images.ymlhas carriedsince it was written. They report BLOCKED rather than failing.
collector. The contract rejects any mutating
kubectlverb in this file.Rollback is
git revertof the merge. Callers pin an immutable commit, so arevert cannot change what an already-merged caller runs.
What this PR does not do
follow in their own pull requests, once there is a merge commit on
maintopin. Until then T146 stays partial and unticked.
microservice-app-gitopsafter the callers merge, in a separate pull request, as the suite's task
convention requires.
the image and cluster surfaces have never run. They are contract-tested, not
observed, and the workflow reports that as BLOCKED rather than as a pass.
ci.yml,release.ymlorpromote.yml. This is an additionbeside them, not a modification of the delivery path.
are the ones
mirror-platform-images.ymlalready defines.Merge method: merge commit. This pull request carries an SDD pair
(
test(security)committed red, thenfeat(security)), andmicroservice-app-docs/docs/Pull request and task tracking conventions.md#40requires the pair to survive.
Approval required: one approval from
EstebanGZamorJuanmadiaz45. Theauthor does not merge this himself, and no AI agent approves it.
🤖 Generated with Claude Code