-
Notifications
You must be signed in to change notification settings - Fork 1
[Audit] #28 code-review-control #131
Copy link
Copy link
Open
Labels
attestation-auditAttestation evidence audit reviewAttestation evidence audit reviewbuild-flowBuild flow attestationBuild flow attestationpriority:highCompliance-critical controlCompliance-critical control
Description
Attestation Identity
- Slot name(s):
code-review-control - Flow: Build (
agentic-sdlc-demo-GH{issue}-Build) - Level: Artifact-level (bound to
artifact-fingerprinted— source directory SHA256) - Kosli type:
genericin build-template.yml (line 58) — BUT custom typecode-review-controlexists insetup.shwith 5 jq rules and a JSON schema - Schema:
kosli/attestation-types/schemas/code-review-control.json(10 required fields includingreview_flow_name,total_loops,configured_loops,compliant_loops,all_loops_compliant,final_verdict,issue_number,commit_sha,evaluated_at) - jq evaluator rules (custom type, currently UNUSED):
.all_loops_compliant == true.final_verdict == "ACCEPTED".total_loops >= 1.compliant_loops == .total_loops.total_loops == .configured_loops
- Instances per trail: 1
Control Objective
- Risk mitigated: Unreviewed or partially-reviewed code reaching production. Specifically: (a) code merging without completing all review loops, (b) non-compliant review trails being ignored, (c) review process being skipped entirely, (d) Final trail missing (review loop did not complete to conclusion), (e) ticket tampering during review, (f) unresolved review threads persisting into production.
- Auditor question: "Can you demonstrate that every code change underwent a complete, multi-loop agentic review process and that all review trails were fully compliant before the build artifact was produced?"
- Regulatory mapping:
- SOC 2 CC8.1 — Changes to infrastructure, data, software, and procedures are authorized, designed, developed, tested, approved, implemented, and maintained to meet the entity's objectives. This control provides the "approved" gate by verifying all review loops completed compliantly.
- SOC 2 CC6.1 — Logical access security: the Rego policy checks ticket-integrity (issue_locked) to prevent unauthorized modification during review.
- ISO 27001 A.8.25 — Secure development lifecycle: mandates formal review checkpoints before code progresses through pipeline stages.
- ISO 27001 A.8.26 — Application security requirements: this control verifies that security review (multi-persona including Security & Compliance agent) was included in the review process.
- ISO 27001 A.8.32 — Change management: the control bridges the review flow to the build flow, ensuring review evidence is bound to a specific artifact fingerprint.
Evidence Specification
| Field | Type | Source | Required | Validated by |
|---|---|---|---|---|
review_flow_name |
string | CI job (needs.setup.outputs.review_flow_name) |
Yes | Schema only (custom type unused) |
review_trails |
array[string] | kosli list trails --flow + jq parse |
Yes | Schema only |
policy_allowed |
boolean | kosli evaluate trails with Rego policy |
Yes | Rego: allow rule |
violations |
array[string] | Rego policy violations set |
Yes | Rego: 5 violation rules |
policy_file |
string | Hardcoded path kosli/policies/code-review-control.rego |
Yes | None |
commit_sha |
string | git rev-parse HEAD |
Yes | Schema minLength: 7 |
evaluated_at |
string (ISO 8601) | date -u in CI |
Yes | Schema format: date-time |
total_loops |
integer | NOT currently in CI payload | Yes (schema) | jq rule .total_loops >= 1 (unused) |
configured_loops |
integer | NOT currently in CI payload | Yes (schema) | jq rule .total_loops == .configured_loops (unused) |
compliant_loops |
integer | NOT currently in CI payload | Yes (schema) | jq rule .compliant_loops == .total_loops (unused) |
all_loops_compliant |
boolean | NOT currently in CI payload | Yes (schema) | jq rule .all_loops_compliant == true (unused) |
final_verdict |
string (enum) | NOT currently in CI payload | Yes (schema) | jq rule .final_verdict == "ACCEPTED" (unused) |
- Evidence producer: GitHub Actions CI runner (
code-review-controljob,ubuntu-latest) - Producer trust level: MEDIUM — GitHub-hosted runner with Kosli CLI. The
kosli evaluate trailscommand runs client-side; results are parsed via shell and jq. The--compliantflag is set by the CI script based on the Regoallowoutput. - Tamper resistance: LOW-MEDIUM
- The Rego policy file is version-controlled in the repo and checked out at runtime, so PR authors could theoretically modify the policy in the same PR that modifies the code.
- The
--compliantflag is computed client-side and passed tokosli attest generic. A compromised runner or malicious workflow edit could set--compliant=trueregardless of Rego output. - Because the build template uses
type: genericinstead oftype: custom:code-review-control, the 5 jq rules and JSON schema in setup.sh provide zero server-side validation. Any JSON payload is accepted. - The
|| trueon thekosli evaluate trailscommand (line 271) means Rego evaluation failures are silently swallowed — the job continues and defaults toALLOWED=false, but error conditions are not distinctly surfaced.
Compliance Logic
- Compliant when: ALL of the following:
- At least one review trail exists in the review flow (
count(input.trails) > 0) - Every trail has
is_compliant == true(all attestation slots filled, all jq rules passed within the review flow) - A trail ending in
-Finalexists (proves the review loop completed to conclusion) - Every trail has
ticket-integrityattestation withissue_locked == true(prevents ticket tampering during review) - The Final trail has zero unresolved review threads (
threads_left_open == 0) kosli evaluate trailsreturnsallow == true, which is mapped to--compliant=true
- At least one review trail exists in the review flow (
- Non-compliant when: Any violation detected by the Rego policy, including:
- No trails in the review flow
- Any trail is non-compliant
- No
-Finaltrail exists - Ticket was unlocked during any review loop
- Final trail has open/unresolved review threads
- Threshold source: Rego policy at
kosli/policies/code-review-control.rego. No configurable thresholds — all checks are binary pass/fail. The number of expected loops is NOT validated at this layer (theconfigured_loopscheck exists only in the unused custom type jq rules).
Edge Cases & Failure Modes
- Check cannot run:
- If the review flow does not exist or has no trails,
kosli list trailsreturns empty. The CI job setstrail_count=0and skips evaluation. The attestation still fires with--compliant=false. This is correct fail-closed behavior. - If the Kosli API is unreachable, the
kosli evaluate trailscommand fails silently due to|| true.ALLOWEDdefaults tofalse. Attestation may also fail if the API is still down, leaving the slot unfilled (trail non-compliant).
- If the review flow does not exist or has no trails,
- Partial evidence:
- If some trails exist but the Final trail is missing, the Rego policy correctly flags this as a violation.
- If a trail exists but is missing attestation slots (e.g.,
ticket-integritynot attested), the trail-levelis_compliantwill already be false, caught by the "all trails must be compliant" rule.
- Bypass vectors:
- Policy-in-repo: The Rego policy file is in the same repo as the code. A PR that weakens the policy (e.g.,
default allow = true) could self-approve if the CI runs the modified policy. Mitigation: policy changes should require separate approval. - Generic type: Since the template uses
type: generic, Kosli performs no server-side payload validation. An attacker with workflow edit access couldkosli attest generic --compliant=truewith arbitrary or empty user-data. - Review flow name manipulation: If
review_flow_nameis incorrectly computed (wrong issue number), the control could evaluate trails from a different issue's review, leading to a false pass. - Shell injection: Trail names are interpolated directly into shell commands (
${{ steps.trails.outputs.trail_names }}). Malicious trail names with shell metacharacters could inject commands, though trail names are typically controlled by the review orchestrator.
- Policy-in-repo: The Rego policy file is in the same repo as the code. A PR that weakens the policy (e.g.,
- False positive risk: LOW — the Rego policy is strict (5 violation rules, all must pass). A false positive (flagging compliant review as non-compliant) would block the build but is self-correcting (re-run or investigate).
- False negative risk: MEDIUM — the biggest false-negative vector is the
type: genericissue. If an attacker bypasses the Rego evaluation but still callskosli attest generic --compliant=true, nothing server-side will catch this. - TOCTOU gaps:
- The review flow trails are evaluated at a point in time. If new attestations are added to the review flow after evaluation but before the build completes, they are not reflected.
- The commit SHA checked in CI (
git rev-parse HEAD) is the merge commit, not the reviewed commit. If the branch is updated between review and merge, the review evidence may not match the built code. (Theartifact-integrity-controlis designed to catch this.)
Dependencies
- Upstream:
setupjob: providesreview_flow_name,kosli_flow_name,kosli_trail_name,issue_idbuildjob: providesfingerprint(source directory SHA256) that this attestation is bound to- Review flow (cross-flow): all Loop and Final trails must be compliant in the code-review flow
- Downstream:
- This attestation fills the
code-review-controlslot in the build trail. The build trail cannot be compliant until this slot (and all 7 other artifact attestation slots) are filled and compliant. - No other CI job directly depends on this job's outputs, but the overall build trail compliance is the merge gate.
- This attestation fills the
- Cross-flow:
- Reads from:
agentic-sdlc-demo-GH{issue}-CodeReviewflow (review trails, attestation statuses) - Writes to:
agentic-sdlc-demo-GH{issue}-Buildflow (build trail,code-review-controlslot) - The Rego policy inspects nested attestation data within review trails:
ticket-integrity.attestation_data.issue_lockedandresolver-threads-resolved.attestation_data.threads_left_open
- Reads from:
Assessment
- Implementation match: PARTIAL — The Rego policy is well-designed with 5 distinct violation checks covering structural (trails exist), compliance (all compliant), completeness (Final trail), integrity (ticket locked), and resolution (threads resolved). However, the CI pipeline payload does NOT match the custom type schema. The CI constructs a payload with
review_flow_name,review_trails,policy_allowed,violations,policy_file,commit_sha, andevaluated_at— but omitstotal_loops,configured_loops,compliant_loops,all_loops_compliant,final_verdict, andissue_number, all of which are required by the schema. - Evidence sufficiency: INSUFFICIENT — The Rego policy provides strong client-side evaluation, but because the attestation uses
type: generic, the Kosli server performs zero validation. The 5 jq rules and JSON schema defined for thecode-review-controlcustom type are completely inert. The--compliantflag is the only signal, and it is set by a shell script on a CI runner. - Gaps:
- TYPE MISMATCH (Critical):
build-template.ymldeclarestype: genericbutsetup.shcreates a custom typecode-review-controlwith 5 jq rules and a 10-field required schema. The custom type is never used. This means no server-side evidence validation occurs. - PAYLOAD MISMATCH (High): The CI pipeline payload is missing 5 of the 10 required schema fields (
total_loops,configured_loops,compliant_loops,all_loops_compliant,final_verdict). Even if the type were switched tocustom:code-review-control, the attestation would fail schema validation. - POLICY-IN-REPO (Medium): The Rego policy is co-located with the code it gates. A single PR could modify both the policy and the code, weakening the control.
- SILENT ERROR SWALLOWING (Medium): The
|| trueonkosli evaluate trailsmeans errors (API failures, malformed JSON, CLI bugs) are treated identically to policy violations. There is no distinct error-vs-denied signal. - NO CONFIGURED_LOOPS VALIDATION (Medium): The Rego policy does not validate that the number of trails matches the configured review rounds. This check exists only in the unused jq rules. A review that ran 1 loop instead of the configured 3 would still pass if that 1 loop + Final were compliant.
- TYPE MISMATCH (Critical):
- Recommendations:
- Switch to custom type: Change
build-template.ymlline 58 fromtype: generictotype: custom:code-review-controlto activate server-side jq rules and schema validation. - Align CI payload: Update the CI job to construct a payload matching all 10 required schema fields, including computing
total_loops,configured_loops,compliant_loops,all_loops_compliant, andfinal_verdictfrom the evaluation results. - Externalize policy: Move Rego policies to a separate repo or branch protection rule so they cannot be modified in the same PR as the code they gate.
- Distinguish errors from denials: Replace
|| truewith explicit error handling that distinguishes API/CLI errors from legitimate policy denials. Surface errors as distinct attestation metadata. - Add configured_loops to Rego: Add a Rego rule that validates
count(input.trails)matches an expected value, or passconfigured_loopsas external data to the policy.
- Switch to custom type: Change
- Verdict: NEEDS IMPROVEMENT — The Rego policy design is strong, and the 5 violation rules provide good coverage of review completeness and integrity. However, the
type: genericdeclaration nullifies all server-side evidence validation, the CI payload does not match the custom type schema, and the policy-in-repo pattern creates a bypass vector. These gaps must be closed before this control can be relied upon for SOC 2 or ISO 27001 audit evidence.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
attestation-auditAttestation evidence audit reviewAttestation evidence audit reviewbuild-flowBuild flow attestationBuild flow attestationpriority:highCompliance-critical controlCompliance-critical control