Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5261356
security: rebuild audit pseudonymization on current main
seonghobae Aug 5, 2026
28966e6
test(security): require strong policy override keys
seonghobae Aug 5, 2026
2e9c4a2
fix(security): reject weak policy override keys
seonghobae Aug 5, 2026
ac70dd7
docs(security): document policy override key strength
seonghobae Aug 5, 2026
27106a5
fix: trigger CI due to strix timeout
seonghobae Aug 5, 2026
b3ad882
chore: remove stray CI trigger script
seonghobae Aug 5, 2026
0e7770b
test: cover disabled policy signing startup path
seonghobae Aug 5, 2026
a86ab46
docs: consolidate unreleased changelog entries
seonghobae Aug 5, 2026
eb34323
build: enforce zero missed production lines and branches
seonghobae Aug 5, 2026
164aecf
ci: verify exact PR head with coverage gates
seonghobae Aug 5, 2026
294b30b
ci: fuzz the exact pull request head
seonghobae Aug 5, 2026
febb3fd
docs: record exact-head and coverage gates
seonghobae Aug 5, 2026
9cf1ff8
test: add shared security provider fixture
seonghobae Aug 5, 2026
3214bc0
test: cover null policy secret normalization
seonghobae Aug 5, 2026
62dac96
test: cover tenant delete fail-closed branches
seonghobae Aug 5, 2026
0416068
test: cover validation security edge cases
seonghobae Aug 5, 2026
6b61330
test: cover conversion filename boundary
seonghobae Aug 5, 2026
70f3399
test: cover artifact deletion failure
seonghobae Aug 5, 2026
d73571b
test: cover repository delete edge cases
seonghobae Aug 5, 2026
36254ee
test: cover exception log sanitization
seonghobae Aug 5, 2026
a1533de
test: cover download filename and digest edges
seonghobae Aug 5, 2026
31c4b4d
fix: redact rejected parameter values
seonghobae Aug 5, 2026
c0c0c45
docs: record rejected-value redaction
seonghobae Aug 5, 2026
a308f0f
test: cover every override header separator
seonghobae Aug 5, 2026
39dc005
refactor: trust normalized policy secret contract
seonghobae Aug 5, 2026
6e311ed
refactor: remove unreachable blank filename branch
seonghobae Aug 5, 2026
8ca8958
fix(ci): isolate exact-head evidence finalization runs
seonghobae Aug 5, 2026
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
78 changes: 76 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,83 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Verify exact checked-out revision
env:
EXPECTED_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: test "$(git rev-parse HEAD)" = "$EXPECTED_SHA"
- name: Use preinstalled Temurin JDK 21
# Uses the runner image's bundled JDK instead of actions/setup-java to
# keep every workflow dependency hash-pinned (Scorecard Pinned-Dependencies).
run: |
echo "JAVA_HOME=$JAVA_HOME_21_X64" >> "$GITHUB_ENV"
echo "$JAVA_HOME_21_X64/bin" >> "$GITHUB_PATH"
- name: Run tests
run: mvn -B --no-transfer-progress test
- name: Run tests and coverage acceptance gates
shell: bash
run: |
if ! mvn -B --no-transfer-progress verify; then
if [[ -f target/site/jacoco/jacoco.csv ]]; then
echo "::group::JaCoCo CSV diagnostics"
cat target/site/jacoco/jacoco.csv
echo "::endgroup::"
fi
if [[ -f target/site/jacoco/jacoco.xml ]]; then
echo "::group::JaCoCo uncovered line diagnostics"
python3 - <<'PY'
import xml.etree.ElementTree as ET
from pathlib import Path

report = Path("target/site/jacoco/jacoco.xml")
root = ET.parse(report).getroot()
gaps = []
for package in root.findall("package"):
package_name = package.get("name", "")
for source_file in package.findall("sourcefile"):
source_name = source_file.get("name", "")
source_path = f"{package_name}/{source_name}" if package_name else source_name
for line in source_file.findall("line"):
missed_instructions = int(line.get("mi", "0"))
missed_branches = int(line.get("mb", "0"))
if missed_instructions or missed_branches:
gaps.append(
(
source_path,
int(line.get("nr", "0")),
missed_instructions,
missed_branches,
)
)

for source_path, line_number, missed_instructions, missed_branches in gaps:
print(
f"{source_path}:{line_number}: "
f"missed_instructions={missed_instructions} "
f"missed_branches={missed_branches}"
)
PY
echo "::endgroup::"
fi
exit 1
fi

merge-compatibility:
name: Maven merge compatibility
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Verify merge revision
env:
EXPECTED_SHA: ${{ github.sha }}
run: test "$(git rev-parse HEAD)" = "$EXPECTED_SHA"
- name: Use preinstalled Temurin JDK 21
run: |
echo "JAVA_HOME=$JAVA_HOME_21_X64" >> "$GITHUB_ENV"
echo "$JAVA_HOME_21_X64/bin" >> "$GITHUB_PATH"
- name: Verify merged result
run: mvn -B --no-transfer-progress verify

script-checks:
name: Buyer-readiness script tests
Expand All @@ -33,6 +102,11 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Verify exact checked-out revision
env:
EXPECTED_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: test "$(git rev-parse HEAD)" = "$EXPECTED_SHA"
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/fuzz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ jobs:
- TenantClaimsFuzzTest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
ref: ${{ github.event.pull_request.head.sha || github.sha }}

- name: Verify exact checked-out revision
env:
EXPECTED_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: test "$(git rev-parse HEAD)" = "$EXPECTED_SHA"

- name: Set up JDK 21
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961
Expand Down
173 changes: 173 additions & 0 deletions .github/workflows/one-shot-netty-sbom-finalize.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
name: One-shot Netty SBOM finalize

on:
pull_request:
branches:
- main
paths:
- .github/workflows/one-shot-netty-sbom-finalize.yml

permissions:
contents: read

concurrency:
group: one-shot-netty-sbom-finalize-artifact-v2-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }}
cancel-in-progress: false

jobs:
finalize-evidence:
if: >-
github.repository == 'ContextualWisdomLab/clearfolio'
&& github.actor == 'seonghobae'
&& github.event.pull_request.head.repo.full_name == github.repository
&& github.event.pull_request.head.ref == 'fix/pii-logging-16240128950440010639'
name: Ingest verified evidence and self-remove
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
actions: read
contents: write
steps:
- name: Checkout exact pull request head
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 1

- name: Verify immutable trigger and live branch identity
env:
EXPECTED_SHA: ${{ github.event.pull_request.head.sha }}
HEAD_BRANCH: ${{ github.event.pull_request.head.ref }}
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "$EXPECTED_SHA"
live_sha="$(git ls-remote origin "refs/heads/${HEAD_BRANCH}" | awk '{print $1}')"
test "$live_sha" = "$EXPECTED_SHA"

- name: Ingest the immutable successful read-only artifact
env:
ARTIFACT_ID: "8929593015"
EXPECTED_ARCHIVE_SHA256: 07a0325e08157f00dda28c58ed4e41af51863cccb2ceea2c4e378ead77dc337f
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
archive="$RUNNER_TEMP/netty-sbom-refresh.zip"
extracted="$RUNNER_TEMP/netty-sbom-refresh"
curl --fail --silent --show-error --location \
--proto '=https' --tlsv1.2 \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
-o "$archive" \
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/actions/artifacts/${ARTIFACT_ID}/zip"
printf '%s %s\n' "$EXPECTED_ARCHIVE_SHA256" "$archive" | sha256sum -c -
mkdir -p "$extracted"
unzip -q "$archive" -d "$extracted"
test "$(find "$extracted" -mindepth 1 -maxdepth 1 -type f | wc -l)" -eq 3
test -s "$extracted/generation-manifest.json"
test -s "$extracted/sbom-cyclonedx.json"
test -s "$extracted/third-party-attribution.md"
cp "$extracted/sbom-cyclonedx.json" \
docs/qa/evidence/2026-07-02-krw2b-sale-readiness/sbom-cyclonedx.json
cp "$extracted/third-party-attribution.md" \
docs/legal/2026-07-03-third-party-attribution.md

- name: Use preinstalled Temurin JDK 21
run: |
echo "JAVA_HOME=$JAVA_HOME_21_X64" >> "$GITHUB_ENV"
echo "$JAVA_HOME_21_X64/bin" >> "$GITHUB_PATH"

- name: Verify complete generated graph and attribution
env:
EXPECTED_SBOM_SHA256: e138a9263edb40c613d5f159acba8fa89ee848a7cef4b6619e095c48451b095c
EXPECTED_ATTRIBUTION_SHA256: e19a3767a545bd059e50003882d8ff2f8a3ff4d3b8fd28d3f305eead61261da9
run: |
set -euo pipefail
python3 - <<'PY'
import hashlib
import json
import os
from pathlib import Path

sbom_path = Path(
"docs/qa/evidence/2026-07-02-krw2b-sale-readiness/sbom-cyclonedx.json"
)
attribution_path = Path(
"docs/legal/2026-07-03-third-party-attribution.md"
)
sbom_bytes = sbom_path.read_bytes()
attribution_bytes = attribution_path.read_bytes()
sbom = json.loads(sbom_bytes)
components = sbom.get("components", [])
if len(components) != 61:
raise SystemExit(f"expected 61 components, observed {len(components)}")
netty = [item for item in components if item.get("group") == "io.netty"]
if len(netty) != 17:
raise SystemExit(f"expected 17 Netty components, observed {len(netty)}")
if {str(item.get("version", "")) for item in netty} != {"4.1.136.Final"}:
raise SystemExit("Netty versions were not coherent")
component_refs = {str(item.get("bom-ref", "")) for item in netty}
dependency_refs = set()
for dependency in sbom.get("dependencies", []):
ref = str(dependency.get("ref", ""))
if "io.netty/" in ref:
dependency_refs.add(ref)
dependency_refs.update(
str(item)
for item in dependency.get("dependsOn", [])
if "io.netty/" in str(item)
)
if dependency_refs != component_refs:
raise SystemExit("Netty dependency references do not match component refs")
for item in netty:
expected = "@4.1.136.Final"
if expected not in str(item.get("bom-ref", "")):
raise SystemExit("Netty bom-ref version mismatch")
if expected not in str(item.get("purl", "")):
raise SystemExit("Netty purl version mismatch")
if b"4.1.135.Final" in sbom_bytes or b"4.1.135.Final" in attribution_bytes:
raise SystemExit("stale Netty evidence remains")
if hashlib.sha256(sbom_bytes).hexdigest() != os.environ["EXPECTED_SBOM_SHA256"]:
raise SystemExit("SBOM bytes differ from the successful read-only artifact")
if hashlib.sha256(attribution_bytes).hexdigest() != os.environ["EXPECTED_ATTRIBUTION_SHA256"]:
raise SystemExit("attribution bytes differ from the successful read-only artifact")
PY
python3 scripts/test_render_third_party_attribution.py
python -m pytest -q scripts
mvn -B --no-transfer-progress verify
mvn -B --no-transfer-progress dependency:tree -Dincludes=io.netty

- name: Commit only generated evidence and helper removal
env:
EXPECTED_SHA: ${{ github.event.pull_request.head.sha }}
HEAD_BRANCH: ${{ github.event.pull_request.head.ref }}
PUSH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "$EXPECTED_SHA"
live_sha="$(git ls-remote origin "refs/heads/${HEAD_BRANCH}" | awk '{print $1}')"
test "$live_sha" = "$EXPECTED_SHA"
rm .github/workflows/one-shot-netty-sbom-finalize.yml
git add \
docs/qa/evidence/2026-07-02-krw2b-sale-readiness/sbom-cyclonedx.json \
docs/legal/2026-07-03-third-party-attribution.md \
.github/workflows/one-shot-netty-sbom-finalize.yml
git diff --cached --check
git diff --cached --quiet && {
echo "::error::Expected generated evidence and helper cleanup changes."
exit 1
}
changed_paths="$(git diff --cached --name-only)"
expected_paths="$(printf '%s\n' \
.github/workflows/one-shot-netty-sbom-finalize.yml \
docs/legal/2026-07-03-third-party-attribution.md \
docs/qa/evidence/2026-07-02-krw2b-sale-readiness/sbom-cyclonedx.json)"
test "$changed_paths" = "$expected_paths"
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git commit -m 'docs(evidence): ingest verified Netty 4.1.136 evidence'
auth_header="$(printf 'x-access-token:%s' "$PUSH_TOKEN" | base64 | tr -d '\n')"
echo "::add-mask::$auth_header"
git -c http.https://github.com/.extraheader="AUTHORIZATION: basic $auth_header" \
push origin "HEAD:${HEAD_BRANCH}"
36 changes: 23 additions & 13 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
## [Unreleased]
### Added
- **UI UX ๊ฐœ์„ **: 'Details' ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ, ์ž‘์—… ์ƒ์„ธ ์ •๋ณด ๋กœ๋“œ ์ค‘์— ์‚ฌ์šฉ์ž๊ฐ€ ๋ช…์‹œ์ ์ธ ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก 'Loading...' ํ…์ŠคํŠธ์™€ ๋น„ํ™œ์„ฑํ™” ์ƒํƒœ๋ฅผ ํ‘œ์‹œํ•˜๋„๋ก ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.

### Changed
- PDF.js WebJar๋ฅผ `6.1.200`์œผ๋กœ ์˜ฌ๋ฆฌ๊ณ , Clearfolio๊ฐ€ ๋™์ผ ๋ฒ„์ „์˜ `pdf.mjs`์™€ `pdf.worker.mjs`๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•ด ์„œ๋ช…๋œ same-origin artifact์˜ ์ฒซ ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋งํ•˜๋„๋ก ํ†ตํ•ฉํ–ˆ์Šต๋‹ˆ๋‹ค. ํŒจํ‚ค์ง•ยท์…ธ ๊ฒฝ๋กœยท์„œ๋ช…๋œ `artifactToken` ํ๋ฆ„์„ ํšŒ๊ท€ ํ…Œ์ŠคํŠธ๋กœ ๊ณ ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

# Changelog

## [Unreleased]

### ์ถ”๊ฐ€๋œ ๊ธฐ๋Šฅ (Added)
### Added

- **UI UX ๊ฐœ์„ **: 'Details' ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ, ์ž‘์—… ์ƒ์„ธ ์ •๋ณด ๋กœ๋“œ ์ค‘์— ์‚ฌ์šฉ์ž๊ฐ€ ๋ช…์‹œ์ ์ธ ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก 'Loading...' ํ…์ŠคํŠธ์™€ ๋น„ํ™œ์„ฑํ™” ์ƒํƒœ๋ฅผ ํ‘œ์‹œํ•˜๋„๋ก ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.
- **๊ด€๋ฆฌ์ž์šฉ ๋‹จ๊ฑด ์ž‘์—… ์‚ญ์ œ ๋ฐ ์žฌ์‹œ๋„ API ์ถ”๊ฐ€**
- ํŠน์ • ๋ณ€ํ™˜ ์ž‘์—…์„ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋Š” `DELETE /api/v1/admin/convert/jobs/{jobId}` ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.
- ์‹คํŒจ(dead-lettered) ์ƒํƒœ์ธ ์ž‘์—…์„ ๊ด€๋ฆฌ์ž๊ฐ€ ์žฌ์‹œ๋„ ํ์— ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋Š” `POST /api/v1/admin/convert/jobs/{jobId}/retry` ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.

- **๋น„๋™๊ธฐ ๋ฒ„ํŠผ ๋กœ๋”ฉ ํ”ผ๋“œ๋ฐฑ ๋ฐ ์ƒํƒœ ๋ณต์› ๊ฐœ์„ **
- KPI ์Šค๋ƒ…์ƒท ์ฆ๊ฑฐ๋ฅผ ๋‹ค์‹œ ๋ถˆ๋Ÿฌ์˜ค๋Š” `refreshKpiEvidence` ๋™์ž‘ ์ค‘์— "Refresh evidence" ๋ฒ„ํŠผ์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ณ  "Refreshing..." ์ด๋ผ๋Š” ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•˜์—ฌ ์‚ฌ์šฉ์ž์˜ ์ค‘๋ณต ํด๋ฆญ์„ ๋ฐฉ์ง€ํ–ˆ์Šต๋‹ˆ๋‹ค.
- KPI ์Šค๋ƒ…์ƒท ์ฆ๊ฑฐ๋ฅผ ๋‹ค์‹œ ๋ถˆ๋Ÿฌ์˜ค๋Š” `refreshKpiEvidence` ๋™์ž‘ ์ค‘์— "Refresh evidence" ๋ฒ„ํŠผ์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ณ  "Refreshing..."์ด๋ผ๋Š” ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•˜์—ฌ ์‚ฌ์šฉ์ž์˜ ์ค‘๋ณต ํด๋ฆญ์„ ๋ฐฉ์ง€ํ–ˆ์Šต๋‹ˆ๋‹ค.
- ๋ฒ„ํŠผ ์ƒํƒœ ๋ณ€๊ฒฝ ์‹œ ๋‚ด๋ถ€ DOM ๊ตฌ์กฐ๋ฅผ ๋ณด์กดํ•˜๊ธฐ ์œ„ํ•ด `Array.from(button.childNodes)`๋กœ ์›๋ž˜ ๋…ธ๋“œ๋ฅผ ์ €์žฅํ•˜๊ณ , ์„ฑ๊ณต ๋ฐ ์‹คํŒจ ํ›„ `finally` ๋ธ”๋ก์—์„œ `replaceChildren(...)`์œผ๋กœ ์•ˆ์ „ํ•˜๊ฒŒ ๋ณต์›ํ•˜๋„๋ก ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

### Changed

- PDF.js WebJar๋ฅผ `6.1.200`์œผ๋กœ ์˜ฌ๋ฆฌ๊ณ , Clearfolio๊ฐ€ ๋™์ผ ๋ฒ„์ „์˜ `pdf.mjs`์™€ `pdf.worker.mjs`๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•ด ์„œ๋ช…๋œ same-origin artifact์˜ ์ฒซ ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋งํ•˜๋„๋ก ํ†ตํ•ฉํ–ˆ์Šต๋‹ˆ๋‹ค. ํŒจํ‚ค์ง•ยท์…ธ ๊ฒฝ๋กœยท์„œ๋ช…๋œ `artifactToken` ํ๋ฆ„์„ ํšŒ๊ท€ ํ…Œ์ŠคํŠธ๋กœ ๊ณ ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.
- CI๊ฐ€ pull request์˜ ์ •ํ™•ํ•œ head SHA๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ฒดํฌ์•„์›ƒํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋ฉฐ, ํ•ฉ์„ฑ merge revision์€ ๋ณ„๋„ ํ˜ธํ™˜์„ฑ ์ž‘์—…์—์„œ ๊ฒ€์ฆํ•˜๋„๋ก ๋ถ„๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.
- Maven `verify` ๋‹จ๊ณ„์—์„œ JaCoCo production line ๋ฐ branch missed count๊ฐ€ ๊ฐ๊ฐ 0์ธ์ง€ ๊ฐ•์ œํ•˜๊ณ , ์‹คํŒจ ์‹œ ๋ˆ„๋ฝ ์œ„์น˜ ์ง„๋‹จ์„ ์ถœ๋ ฅํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.
- Jazzer fuzzing๋„ pull request์˜ ์ •ํ™•ํ•œ head SHA๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ฒดํฌ์•„์›ƒํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋„๋ก ๊ฐ•ํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค.

### Security

- ์ •์ฑ… ์žฌ์ •์˜ ์Šน์ธ์ž์˜ ์›๋ฌธ ์‹๋ณ„์ž๋ฅผ ๊ฐ์‚ฌ ๋กœ๊ทธ์—์„œ ์ œ๊ฑฐํ•˜๊ณ , ์ „์šฉ ํšŒ์ „ํ˜• ํ‚ค์™€ ๋„๋ฉ”์ธ ๋ถ„๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” HMAC ๊ธฐ๋ฐ˜ `approverFingerprint`๋กœ ๋Œ€์ฒดํ–ˆ์Šต๋‹ˆ๋‹ค. ์ „์šฉ ํ‚ค๊ฐ€ ์—†์œผ๋ฉด ์›๋ฌธ์ด๋‚˜ ๋น„ํ‚ค ํ•ด์‹œ๋กœ ํด๋ฐฑํ•˜์ง€ ์•Š๊ณ  ๋น„์ƒ๊ด€ `unavailable` ํ‘œ์‹์„ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค.
- ๊ฐ์‚ฌ ๊ฐ€๋ช…ํ™” ํ‚ค์˜ ์†Œ์œ ๊ถŒ, ํšŒ์ „, ๋ณด์กด, ์‚ฌ๊ณ  ๋Œ€์‘ ๋ฐ GDPR์ƒ ๊ฐ€๋ช…์ •๋ณด์˜ ๊ฐœ์ธ์ •๋ณด ์ง€์œ„๋ฅผ ๋ฌธ์„œํ™”ํ•˜๊ณ , ์›๋ฌธ ์Šน์ธ์ž ์‹๋ณ„์ž์™€ ์Šน์ธ ํ† ํฐ์ด ๋กœ๊ทธ์— ๋‚จ์ง€ ์•Š๋Š” ํšŒ๊ท€ ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.
- ๊ฒฝ๋กœยท์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž… ๋ณ€ํ™˜ ์‹คํŒจ ์‘๋‹ต์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ œ์ถœํ•œ ๊ฑฐ๋ถ€ ๊ฐ’์„ ๊ณ ์ •๋œ `[redacted]` ํ‘œ์‹์œผ๋กœ ๋Œ€์ฒดํ•ด ์˜ค๋ฅ˜ ์‘๋‹ต์„ ํ†ตํ•œ ๊ฐœ์ธ์ •๋ณดยท๋น„๋ฐ€๊ฐ’ ๋ฐ˜์‚ฌ๋ฅผ ์ฐจ๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ฐ’์ด ์‹ค์ œ๋กœ ์—†์—ˆ๋˜ ๊ฒฝ์šฐ์—๋งŒ `null` ์ง„๋‹จ์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.

Comment thread
coderabbitai[bot] marked this conversation as resolved.
### Fixed

- ๋ทฐ์–ด UI์˜ ์žฌ์‹œ๋„ ๋ฒ„ํŠผ ๋กœ๋”ฉ ์ƒํƒœ๊ฐ€ ๋‚ด๋ถ€ DOM์„ ์†์ƒ์‹œํ‚ค์ง€ ์•Š๊ณ  ์•ˆ์ „ํ•˜๊ฒŒ ๋ณต์›๋˜๋„๋ก ์ˆ˜์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

## [0.1.0] - 2026-06-25

### ์ถ”๊ฐ€๋œ ๊ธฐ๋Šฅ (Added)

- **๋น„๋™๊ธฐ ๋ฒ„ํŠผ ๋กœ๋”ฉ ์ƒํƒœ UX ๊ฐœ์„  (Async Button Loading States)**
- ๋ฌธ์„œ ์ œ์ถœ(`submitDocument`), ๋ฐ๋ชจ ๋ฐ์ดํ„ฐ ๋กœ๋“œ(`loadDemoData`), ์‹คํŒจ ์ž‘์—… ์žฌ์‹œ๋„(`retryActiveJob`) ๋“ฑ ๋น„๋™๊ธฐ ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฒ„ํŠผ๋“ค์— ๋Œ€ํ•ด ์ฒ˜๋ฆฌ ์ค‘ ๋ช…์‹œ์ ์ธ ๋กœ๋”ฉ ์ƒํƒœ(Loading, Submitting, Retrying ๋“ฑ)๋ฅผ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.
- ์‚ฌ์šฉ์ž์˜ ์ค‘๋ณต ํด๋ฆญ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์ž‘์—… ์ค‘์—๋Š” ๋ฒ„ํŠผ์ด ๋น„ํ™œ์„ฑํ™”๋˜๋„๋ก ์ˆ˜์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.
Expand All @@ -37,9 +48,11 @@
- ๊ด€๋ จ `AdminJobListResponse` DTO ๋ชจ๋ธ๊ณผ ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” Repository ๋ฐ Service ๊ณ„์ธต์˜ `findAll`/`getAllJobs` ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.

### ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ (Tests)

- ์‹ ๊ทœ ๊ตฌํ˜„๋œ Repository, Service, Controller ๊ณ„์ธต์— ๋Œ€ํ•œ ์œ ๋‹› ํ…Œ์ŠคํŠธ(Unit Tests)๋ฅผ ์ž‘์„ฑํ•˜์—ฌ JaCoCo ๊ธฐ์ค€ ๋ผ์ธ ๋ฐ ๋ธŒ๋žœ์น˜ ์ปค๋ฒ„๋ฆฌ์ง€ 100%๋ฅผ ๋‹ฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

### ๋ณด์•ˆ (Security)

- **์˜์กด์„ฑ ์ทจ์•ฝ์  ์ผ๊ด„ ์ •๋ฆฌ (trivy-fs / osv-scan ๋Œ€์‘)**: Spring Boot ๋ถ€๋ชจ POM์„ `3.5.0`์—์„œ `3.5.16`์œผ๋กœ ์˜ฌ๋ ค Spring Framework, Netty, Reactor Netty, logback ๊ด€๋ จ ๋‹ค์ˆ˜์˜ HIGH/MEDIUM ๊ถŒ๊ณ ๋ฅผ ํ•ด์†Œํ–ˆ์Šต๋‹ˆ๋‹ค.
- Jackson ๊ณ„์—ด์„ `jackson-bom` import๋กœ `2.22.1`์— ๊ณ ์ •ํ•˜์—ฌ jackson-databind case-insensitive deserialization bypass ๊ถŒ๊ณ (GHSA-5jmj-h7xm-6q6v / CVE-2026-54515)๋ฅผ ์ œ๊ฑฐํ–ˆ์Šต๋‹ˆ๋‹ค.
- Apache Tika ํ‘œ์ค€ ํŒŒ์„œ๋ฅผ ํ†ตํ•ด ์œ ์ž…๋˜๋˜ ์ „์ด ์˜์กด์„ฑ์„ `dependencyManagement`๋กœ ๊ณ ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค: junrar `7.6.0`(๊ฒฝ๋กœ ์ˆœํšŒ RCE/ํŒŒ์ผ ์“ฐ๊ธฐ), commons-io `2.20.0`(XmlStreamReader DoS), commons-lang3 `3.18.0`, BouncyCastle `bcprov-jdk18on 1.84` ๋ฐ `bcpkix-jdk18on 1.84`(CRITICAL/Medium). ์ „์ฒด 347๊ฐœ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ๋ฅผ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.
Expand All @@ -48,6 +61,3 @@
- ๋ฃจํŠธ `LICENSE`์™€ Maven license metadata๋ฅผ ์ถ”๊ฐ€ํ•ด Scorecard License alert๊ฐ€ ํ‘œ์ค€ Apache-2.0 ํŒŒ์ผ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ–ˆ์Šต๋‹ˆ๋‹ค.
- logback-core ์‹ ๊ทœ ๊ถŒ๊ณ (GHSA-jhq6-gfmj-v8fx) ๋Œ€์‘์„ ์œ„ํ•ด Logback ๊ด€๋ฆฌ ๋ฒ„์ „์„ `1.5.35`๋กœ ๊ณ ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.
- ์ €์žฅ์†Œ ๋ณด์•ˆ ์ •์ฑ…, Maven/GitHub Actions Dependabot ์„ค์ •, ๊ธฐ๋ณธ CodeQL/์ค‘์•™ SAST ์šด์˜ ์ง€์นจ, ๋‹ค์šด๋กœ๋“œ ํŒŒ์ผ๋ช… ์ •๊ทœํ™” Jazzer fuzz target์„ ์ถ”๊ฐ€ํ•ด Scorecard ๋ณด์•ˆ ๊ฑฐ๋ฒ„๋„Œ์Šค ์‹ ํ˜ธ๋ฅผ ๋ณด๊ฐ•ํ–ˆ์Šต๋‹ˆ๋‹ค.

### Fixed
- ๋ทฐ์–ด UI์˜ ์žฌ์‹œ๋„ ๋ฒ„ํŠผ ๋กœ๋”ฉ ์ƒํƒœ๊ฐ€ ๋‚ด๋ถ€ DOM์„ ์†์ƒ์‹œํ‚ค์ง€ ์•Š๊ณ  ์•ˆ์ „ํ•˜๊ฒŒ ๋ณต์›๋˜๋„๋ก ์ˆ˜์ •
4 changes: 3 additions & 1 deletion docs/diagrams/submit-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ sequenceDiagram
V->>P: getBlockedExtensions()
alt Override headers valid
V-->>V: validate override=true + token + approver
V-->>V: emit audit-safe log(extension, approver, tokenFingerprint)
V-->>V: emit audit-safe log(extension, approverFingerprint, tokenFingerprint)
V-->>Svc: validation ok
else Override missing/invalid
V-->>Svc: UnsupportedDocumentFormatException or IllegalArgumentException
Expand Down Expand Up @@ -94,6 +94,8 @@ sequenceDiagram
end
```

`approverFingerprint` is the versioned, domain-separated keyed audit pseudonym. The raw approver identifier is accepted only as validation input and is never emitted by the audit-safe log.

## Exception paths covered

- Missing or empty file
Expand Down
Loading
Loading