Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ jobs:
echo "This marker exposes github/codeql-action usage to Scorecard without uploading SARIF."

- name: Checkout repository for manual diagnostics
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Initialize CodeQL for manual diagnostics
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
languages: javascript-typescript

Expand All @@ -30,6 +32,6 @@ jobs:

- name: Document advanced CodeQL analyze action without running it
if: ${{ false }}
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
category: "/language:javascript-typescript"
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# CHANGELOG

## [Unreleased]
- **CI 공급망 보안**: 수동 CodeQL 진단 워크플로의 `actions/checkout`을 불변 v7.0.1 커밋으로 갱신하고 인증 정보 지속을 비활성화했으며, CodeQL `init`·`analyze` 참조를 하나의 검증된 v4.37.6 커밋으로 통합했습니다.
- **UX/접근성 개선**: 프로젝트 카드의 클릭 영역을 카드 전체로 확장하여 사용자 편의성을 높였습니다. <a> 태그를 확장하는 대신 가상 요소(pseudo-element) 겹침 방식을 사용하여 스크린 리더 접근성을 유지했습니다.
- **보안 개선**: 컴포넌트 갤러리의 인라인 스크립트와 스타일을 외부 파일로 분리하고, 엄격한 Content-Security-Policy를 적용해 XSS 방어를 강화했습니다.
- **성능 회귀 복원**: 오프스크린 `.section` 렌더링을 `content-visibility: auto`로 지연하고, 일반 섹션은 600px·콘텐츠가 큰 DIKW/projects 섹션은 1000px의 `contain-intrinsic-size` placeholder를 유지해 초기 렌더링 비용과 스크롤바 이동을 함께 줄였습니다.
Expand Down
56 changes: 56 additions & 0 deletions docs/doctoring/codeql-action-pin-consolidation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# CodeQL action pin consolidation

## Decision

The repository's manual CodeQL marker workflow uses one reviewed immutable release identity for both `github/codeql-action/init` and the disabled documentation-only `github/codeql-action/analyze` step:

- `github/codeql-action` v4.37.6: `5595ccaf912efad79be6eef63a5619ff05969be3`;
- `actions/checkout` v7.0.1: `3d3c42e5aac5ba805825da76410c181273ba90b1`.

The checkout step explicitly sets `persist-credentials: false`. The workflow remains manually dispatched, read-only, and does not upload SARIF. GitHub CodeQL default setup or the organization-owned required checks continue to own analysis publication.

## Why this consolidation is necessary

Three independent dependency pull requests proposed overlapping updates to checkout, CodeQL initialization, and the disabled analyze marker. Keeping those changes separate would leave temporary version skew and multiple competing merge paths. The consolidated change applies the newest reviewed CodeQL release to both CodeQL action references and includes the checkout credential hardening already validated in the predecessor branch.

The CodeQL v4.37.6 tag resolves exactly to commit `5595ccaf912efad79be6eef63a5619ff05969be3`. The checkout v7.0.1 release commit is `3d3c42e5aac5ba805825da76410c181273ba90b1`. Full-length commit pins prevent a mutable tag or branch from changing the code executed by the workflow without a reviewed repository change.

## Trust and behavior boundaries

This change does not:

- enable the disabled `analyze` step;
- transfer SARIF ownership from default or central setup;
- add write permissions, tokens, model credentials, caches, or artifacts;
- execute pull-request-controlled code with retained Git credentials; or
- alter branch protection, review identities, or required status contexts.

The manual workflow still receives the ordinary runner environment. Disabling persisted checkout credentials removes automatic authenticated Git access from later steps, but it does not remove every GitHub-provided environment variable. Repository permissions therefore remain explicitly read-only.

## Test-first evidence

Commit `496474275cb3fdb8f8a54dee02a4767acdb0b8f8` introduced the permanent regression contract before the production workflow changed. Against the protected-base workflow, that contract fails because checkout is still v7.0.0, credential persistence is not disabled, and CodeQL is still v4.37.0. The implementation commit then updates only the reviewed action references and credential setting required to satisfy the contract.

## Verification

For each candidate exact head:

1. Run the complete repository test suite, including `tests/test_codeql_workflow_security.py`.
2. Confirm the workflow contains each required action SHA exactly where expected.
3. Confirm `persist-credentials: false` remains attached to the checkout step.
4. Confirm repository security, static-analysis, and policy checks complete successfully on that exact head.
5. Confirm all actionable review threads are resolved and an independent non-last-pusher approval applies to that exact head before merge.

Queued, pending, skipped-required, cancelled, absent, failed, or predecessor-head evidence is not accepted.

## Rollback

Rollback requires a reviewed commit that replaces both CodeQL action references together and updates the regression contract. Do not move one CodeQL component independently or replace a full commit SHA with a tag. If v4.37.6 causes a verified regression, pin both CodeQL references to the last known-good immutable commit, retain `persist-credentials: false`, rerun the exact-head checks, and document the incident in this record and the changelog.

## References

Actions. (2026, July 17). *Checkout v7.0.1* [Software release]. GitHub. https://github.com/actions/checkout/releases/tag/v7.0.1

GitHub. (2026, August 4). *CodeQL Action v4.37.6* [Software release]. GitHub. https://github.com/github/codeql-action/releases/tag/v4.37.6

GitHub. (n.d.). *Secure use reference*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions
128 changes: 128 additions & 0 deletions tests/test_codeql_workflow_security.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
"""Security contracts for the repository's manual CodeQL marker workflow."""

from __future__ import annotations

from pathlib import Path
from typing import Any


ROOT = Path(__file__).resolve().parents[1]
CODEQL_WORKFLOW = ROOT / ".github" / "workflows" / "codeql.yml"
CHECKOUT_V7_0_1_SHA = "3d3c42e5aac5ba805825da76410c181273ba90b1"
CODEQL_V4_37_6_SHA = "5595ccaf912efad79be6eef63a5619ff05969be3"


def _parse_scalar(raw_value: str) -> str | bool:
"""Parse the small set of YAML scalars used by the workflow contract."""
value = raw_value.split(" #", 1)[0].strip()
if value == "false":
return False
if value == "true":
return True
if len(value) >= 2 and value[0] == value[-1] and value[0] in {"'", '"'}:
return value[1:-1]
return value


def _workflow_structure() -> dict[str, Any]:
"""Parse the workflow subset needed to bind permissions and action steps."""
permissions: dict[str, str | bool] = {}
jobs: dict[str, dict[str, Any]] = {}
current_job: dict[str, Any] | None = None
current_step: dict[str, Any] | None = None
current_section: str | None = None
in_step_with = False

for raw_line in CODEQL_WORKFLOW.read_text(encoding="utf-8").splitlines():
if not raw_line.strip() or raw_line.lstrip().startswith("#"):
continue
indent = len(raw_line) - len(raw_line.lstrip(" "))
line = raw_line.strip()

if indent == 0:
current_job = None
current_step = None
in_step_with = False
current_section = line[:-1] if line.endswith(":") else None
continue

if current_section == "permissions" and indent == 2:
key, separator, value = line.partition(":")
assert separator, f"malformed permissions entry: {line!r}"
permissions[key] = _parse_scalar(value)
continue

if current_section != "jobs":
continue

if indent == 2 and line.endswith(":"):
job_name = line[:-1]
current_job = {"steps": []}
jobs[job_name] = current_job
current_step = None
in_step_with = False
continue

if current_job is None:
continue

if indent == 6 and line.startswith("- "):
current_step = {}
current_job["steps"].append(current_step)
in_step_with = False
first_key, separator, value = line[2:].partition(":")
if separator:
current_step[first_key] = _parse_scalar(value)
continue

if current_step is None:
continue

if indent == 8:
key, separator, value = line.partition(":")
if not separator:
continue
if key == "with" and not value.strip():
current_step["with"] = {}
in_step_with = True
else:
current_step[key] = _parse_scalar(value)
in_step_with = False
continue

if indent == 10 and in_step_with:
key, separator, value = line.partition(":")
assert separator, f"malformed step input: {line!r}"
current_step["with"][key] = _parse_scalar(value)

return {"permissions": permissions, "jobs": jobs}


def _single_action_step(workflow: dict[str, Any], action: str) -> dict[str, Any]:
"""Return the sole workflow step whose action belongs to ``action``."""
steps = [
step
for job in workflow["jobs"].values()
for step in job["steps"]
if str(step.get("uses", "")).startswith(f"{action}@")
]
assert len(steps) == 1, f"expected exactly one {action} step, found {len(steps)}"
return steps[0]


def test_codeql_workflow_uses_current_immutable_action_pins() -> None:
"""Bind each reviewed action and security control to its exact YAML step."""
workflow = _workflow_structure()

assert workflow["permissions"] == {"contents": "read"}

checkout = _single_action_step(workflow, "actions/checkout")
assert checkout["uses"] == f"actions/checkout@{CHECKOUT_V7_0_1_SHA}"
assert checkout.get("with", {}).get("persist-credentials") is False

init = _single_action_step(workflow, "github/codeql-action/init")
assert init["uses"] == f"github/codeql-action/init@{CODEQL_V4_37_6_SHA}"

analyze = _single_action_step(workflow, "github/codeql-action/analyze")
assert analyze["uses"] == f"github/codeql-action/analyze@{CODEQL_V4_37_6_SHA}"
assert analyze.get("if") == "${{ false }}"
Loading