Skip to content

fix(security): enforce production HMAC signing key readiness - #313

Draft
seonghobae wants to merge 8 commits into
mainfrom
fix/production-tenant-claims-key-strength
Draft

fix(security): enforce production HMAC signing key readiness#313
seonghobae wants to merge 8 commits into
mainfrom
fix/production-tenant-claims-key-strength

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Objective

Fail closed when production uses missing, ephemeral, undersized, purpose-reused, or misleadingly normalized HMAC signing material for tenant claims and artifact links.

Fresh exact state — 2026-08-10

  • exact source head: 610f081cc56cfcaa372d674a56f4b21c91123c8f;
  • exact protected main: 55d7ae8647208e301f282350f076eeddaba61d11 after protected merge of fix(security): harden audit pseudonymization and refresh Netty evidence #270;
  • clean reconciliation: two commits, two changed files, no inherited parent/source noise;
  • open, Draft at this metadata update, mergeable;
  • exact-head CI 31387412055: success;
  • exact-head Security Scan 31387411478: success;
  • exact-head SAST Semgrep 31387411422: success;
  • exact-head fuzz 31387412079: success;
  • no qualifying independent approval is inferred from automated checks, comments, statuses, or model output.

The former divergent branch and every predecessor run are historical. #270's mvn verify, zero-missed owned production coverage, strict public-Javadoc and privacy/security substrate are now protected-main behavior.

Test-first security evidence

This bounded slice establishes regressions for:

  • effective tenant-claims HMAC key must be nonblank and at least 16 UTF-8 bytes after the exact NUL-removal/strip() normalization used by TenantAccessService;
  • production rejects a configured tenant secret whose literal bytes would be silently changed by runtime normalization;
  • production requires explicit stable artifact-token signing material rather than the process-random development fallback;
  • artifact-token HMAC material must be at least 16 UTF-8 bytes;
  • effective tenant and artifact signing keys must be byte-distinct by purpose;
  • an explicitly null tenant secret follows the same controlled missing-secret failure rather than escaping the effective-key path.

Clean-base coverage RED → GREEN

Initial clean head 2e1dc916a0b4f5392439da147abf190a18cf3e3c executed 505 Maven tests with zero failures, errors, or skips, but exact CI 31387048868 failed the uncompromised JaCoCo gate: ProductionAuthReadinessConfig.java retained one missed line and one missed branch in the explicit-null normalization path.

Current head 610f081cc56cfcaa372d674a56f4b21c91123c8f adds the focused null-secret regression without changing production behavior or weakening coverage. Exact-head CI, Security Scan, SAST, and fuzz are all GREEN.

Claim boundary

The 16-byte floor is a minimum-strength guard, not proof of entropy, independent generation, KMS custody, rotation, or complete key-management compliance. NIST SP 800-224 remains draft guidance; final key-management authority and operational controls remain distinct. Issue #319 owns migration from direct environment/config secret authority to a provider-neutral credential registry and must not be conflated with this readiness slice. Issue #314 separately owns OIDC/JWT federation.

Scope

Only these files change:

  1. src/main/java/com/clearfolio/viewer/config/ProductionAuthReadinessConfig.java
  2. src/test/java/com/clearfolio/viewer/config/ProductionAuthReadinessConfigTest.java

The slice does not change tenant-claim payload/signature semantics, artifact-token payloads, HMAC algorithm, endpoint authorization, or secret transport.

Merge gate

Move to Ready for ordinary independent review. Merge only if this unchanged exact head continues to satisfy every live required check, zero valid unresolved findings, the counted write-authorized independent approval required by repository policy, and expected-head-safe protection. Do not treat this key-readiness slice as completion of issue #319's credential-source migration.

Summary by CodeRabbit

  • 보안 및 안정성 개선

    • 운영 환경에서 인증 서명 설정을 더욱 엄격하게 검증합니다.
    • 테넌트 인증 및 아티팩트 토큰 서명 키의 누락, 형식 오류, 짧은 키를 사전에 감지합니다.
    • 공백이나 NUL 문자가 포함된 키를 정규화하고, 정규화로 변경된 키는 거부합니다.
    • 두 인증 키에 동일한 값을 사용할 수 없도록 검증합니다.
  • 테스트

    • 다양한 잘못된 설정과 유효한 최소 설정에 대한 검증 범위를 확대했습니다.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 30bf2a51-1a25-41c0-909d-cf4c1d7627f6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Production readiness 검증이 tenant-claims와 artifact-token HMAC 비밀을 모두 확인하도록 변경되었습니다. UTF-8 최소 길이, NUL·유니코드 공백 정규화, 키 분리 조건과 관련 테스트가 추가되었습니다.

Changes

Production HMAC readiness

Layer / File(s) Summary
Production 비밀 검증 구현
src/main/java/com/clearfolio/viewer/config/ProductionAuthReadinessConfig.java
생성자가 두 HMAC 비밀을 받습니다. 검증은 존재 여부, UTF-8 기준 16바이트 이상 길이, 키 중복, tenant 비밀 정규화를 확인합니다.
검증 동작 테스트
src/test/java/com/clearfolio/viewer/config/ProductionAuthReadinessConfigTest.java
누락, 짧은 키, 정규화, 키 재사용 및 유효한 키를 사용한 production 시작 조건을 검증합니다.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 프로덕션 HMAC 서명 키 준비 상태를 강제하는 주요 변경 사항을 정확하고 간결하게 설명합니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/production-tenant-claims-key-strength

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Please review exact current source head bcf9989eaebac435802425708cdc2a48f243d295 against live protected main f3cc09a9838f0f88c81a2ceae22138fab80a2edb. This is a bounded production-readiness change for clearfolio.tenant-claims.hmac-secret: 15 UTF-8 bytes must fail closed; exactly 16 bytes and the existing 17-byte fixture must start successfully. Verify byte-count semantics, fail-closed production-profile behavior, preservation of the missing-secret contract, test boundary coverage, public Javadocs, and that the standards wording does not claim entropy or compliant key generation from length alone. Exact-head CI 31345183533, Security Scan 31345183536, SAST Semgrep 31345183547, and fuzz 31345183539 are successful. Treat bot status as advisory and report only current-head actionable findings.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='bcf9989eaebac435802425708cdc2a48f243d295'
base='f3cc09a9838f0f88c81a2ceae22138fab80a2edb'

printf '%s\n' '== Exact commits =='
git show -s --format='base %H%n%P%n%an <%ae>%n%s' "$base"
git show -s --format='head %H%n%P%n%an <%ae>%n%s' "$head"

printf '%s\n' '== Changed files and diff =='
git diff --name-status "$base" "$head"
git diff --find-renames --find-copies --check "$base" "$head"
git diff --find-renames --find-copies --unified=80 "$base" "$head"

printf '%s\n' '== Candidate source and test files =='
fd -t f -i 'ProductionAuthReadinessConfig' . || true

printf '%s\n' '== Candidate file AST outlines =='
fd -t f -i 'ProductionAuthReadinessConfig' . -x ast-grep outline {} --items all --view expanded || true

printf '%s\n' '== Tenant-claims configuration, guards, and test references =='
rg -n -i -C 8 \
  '(clearfolio\.tenant-claims\.hmac-secret|tenant.?claims|hmac.?secret|ProductionAuthReadinessConfig)' \
  --glob '!**/target/**' --glob '!**/.git/**' .

printf '%s\n' '== Production profile configuration files =='
fd -t f -i 'application*' . -x sh -c '
  case "$1" in
    *.yml|*.yaml|*.properties)
      if rg -q -i "(spring\.config\.activate\.on-profile|spring\.profiles|production|tenant-claims|hmac-secret)" "$1"; then
        echo "--- $1"
        cat -n "$1"
      fi
      ;;
  esac
' sh {} || true

Length of output: 143632


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo='ContextualWisdomLab/clearfolio'
pr='313'
head='bcf9989eaebac435802425708cdc2a48f243d295'
base='f3cc09a9838f0f88c81a2ceae22138fab80a2edb'

printf '%s\n' '== Commit ancestry and changed paths =='
git merge-base --is-ancestor "$base" "$head" && echo 'base is an ancestor of head'
git diff --name-only "$base" "$head"
git diff --check "$base" "$head"

printf '%s\n' '== Fixture UTF-8 byte counts =='
python3 - <<'PY'
for value in ("short-hmac-key!", "0123456789abcdef", "production-secret"):
    print(f"{value!r}: chars={len(value)}, utf8_bytes={len(value.encode('utf-8'))}")
PY

printf '%s\n' '== Exact PR metadata, reviews, and commits =='
gh pr view "$pr" --repo "$repo" --json \
  number,state,isDraft,mergeable,headRefOid,baseRefOid,reviewDecision,reviews,statusCheckRollup,commits \
  --jq '{
    number, state, isDraft, mergeable, headRefOid, baseRefOid, reviewDecision,
    reviews: [.reviews[] | {author: .author.login, state, commit: .commit.oid}],
    checks: [.statusCheckRollup[] | {name, status, conclusion}],
    commits: [.commits[] | {oid: .oid, messageHeadline: .messageHeadline}]
  }'

printf '%s\n' '== Pull-request review threads at the requested head =='
gh api graphql \
  -F owner='ContextualWisdomLab' \
  -F name='clearfolio' \
  -F number="$pr" \
  -f query='
query($owner: String!, $name: String!, $number: Int!) {
  repository(owner: $owner, name: $name) {
    pullRequest(number: $number) {
      reviewThreads(first: 100) {
        nodes {
          isResolved
          comments(first: 20) {
            nodes {
              author { login }
              commit { oid }
              body
              path
              line
            }
          }
        }
      }
    }
  }
}' \
  --jq '.data.repository.pullRequest.reviewThreads.nodes
    | map(select(.isResolved == false)
      | {comments: [.comments.nodes[] | select(.commit.oid == "bcf9989eaebac435802425708cdc2a48f243d295")
          | {author: .author.login, path, line, body}]})'

printf '%s\n' '== Build and JavaDoc configuration =='
fd -t f -g 'pom.xml' -g 'build.gradle' -g 'build.gradle.kts' . -x sh -c '
  echo "--- $1"
  rg -n -i -C 4 "(maven-javadoc|javadoc|doclint|maven-checkstyle|spotless|maven-enforcer)" "$1" || true
' sh {}

Length of output: 6086


@seonghobae I found one actionable test-coverage gap.

  • P2 — Add a non-ASCII UTF-8 boundary fixture in src/test/java/com/clearfolio/viewer/config/ProductionAuthReadinessConfigTest.java, Line 27. The 15-, 16-, and 17-byte fixtures are all ASCII. Their character count equals their UTF-8 byte count. A future regression from getBytes(StandardCharsets.UTF_8).length to String.length() would still pass all current tests. Add a value with 16 UTF-8 bytes but fewer than 16 Java characters, such as eight two-byte characters. Assert that production startup succeeds. This test proves the stated byte-count contract.

I verified the exact requested head and base. The implementation uses UTF-8 bytes in ProductionAuthReadinessConfig.java, Line 37. The production-only profile guard remains in place. The missing or blank secret error contract remains unchanged. The public Javadoc states that length does not establish entropy or approved key generation. No other current-head actionable finding was identified.

⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@seonghobae seonghobae changed the title fix(security): enforce production tenant-claims HMAC key length fix(security): enforce production HMAC signing key readiness Aug 10, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Please review exact current source head 4724bf33637bec6385551280d59b0e14bcb296ab against live protected main f3cc09a9838f0f88c81a2ceae22138fab80a2edb. Verify the bounded production-readiness contract for both tenant-claims and artifact-token HMAC signing material: missing/blank rejection, UTF-8 byte-length floor, explicit stable artifact-token key in production instead of the process-random development fallback, exact 16-byte acceptance boundaries, unchanged token/claim payload and HMAC algorithm semantics, and no overlap with the active #270/#268 security/lifecycle work. Exact-head CI 31346286160 is successful with Maven job 93328727596 executing 439 tests with zero failures/errors/skips; Security Scan 31346286168, SAST Semgrep 31346286175, and fuzz 31346286131 are successful. Treat predecessor evidence as historical. Report only current actionable findings; automated review remains advisory and is not counted as qualifying independent approval.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='4724bf33637bec6385551280d59b0e14bcb296ab'
base='f3cc09a9838f0f88c81a2ceae22138fab80a2edb'
repo='ContextualWisdomLab/clearfolio'

printf '%s\n' '== Exact commits and ancestry =='
git show -s --format='base: %H%nparents: %P%nsubject: %s' "$base"
git show -s --format='head: %H%nparents: %P%nsubject: %s' "$head"
git merge-base --is-ancestor "$base" "$head" && echo 'base is an ancestor of head'

printf '%s\n' '== Changed paths and diff =='
git diff --name-status "$base" "$head"
git diff --check "$base" "$head"
git diff --find-renames --unified=100 "$base" "$head"

printf '%s\n' '== Candidate source and test files =='
fd -t f -i 'ProductionAuthReadinessConfig' . -E target
fd -t f -i 'ArtifactLinkService' . -E target

printf '%s\n' '== Java outlines =='
fd -t f -i 'ProductionAuthReadinessConfig' . -E target -x ast-grep outline {} --items all --view expanded
fd -t f -i 'ArtifactLinkService' . -E target -x ast-grep outline {} --items all --view expanded

printf '%s\n' '== Relevant configuration and signing references =='
rg -n -i -C 8 \
  '(clearfolio\.tenant-claims\.hmac-secret|clearfolio\.artifact-token\.secret|ProductionAuthReadinessConfig|ArtifactLinkService|HmacSHA|random.*key|SecureRandom)' \
  --glob '!**/target/**' --glob '!**/.git/**' .

printf '%s\n' '== Exact focused files at requested head =='
for path in $(git diff --name-only "$base" "$head"); do
  case "$path" in
    *.java)
      echo "--- $path"
      git show "$head:$path" | cat -n
      ;;
  esac
done

printf '%s\n' '== Exact PR and related PR changed paths =='
for pr in 313 270 268; do
  echo "--- PR #$pr"
  gh pr view "$pr" --repo "$repo" --json number,state,isDraft,headRefOid,baseRefOid,files,statusCheckRollup \
    --jq '{number,state,isDraft,headRefOid,baseRefOid,files:[.files[].path],checks:[.statusCheckRollup[]|{name,status,conclusion}]}' || true
done

Length of output: 233156


@seonghobae I found one actionable test-coverage gap.

  • P2 — Add non-ASCII UTF-8 acceptance tests for both secrets in src/test/java/com/clearfolio/viewer/config/ProductionAuthReadinessConfigTest.java, Line 52 and Line 61. All boundary fixtures are ASCII. Therefore, their Java character count equals their UTF-8 byte count. The tests do not prove the documented UTF-8 byte-length contract. Add production startup tests with a secret that has exactly 16 UTF-8 bytes but fewer than 16 Java characters, such as eight two-byte characters. Test it once as clearfolio.tenant-claims.hmac-secret and once as clearfolio.artifact-token.secret. A future replacement of either getBytes(StandardCharsets.UTF_8).length check with String.length() would otherwise pass the current tests.

I verified the requested source head and protected base. The production-only guard rejects missing or blank values and rejects secrets under 16 UTF-8 bytes. It blocks the production use of the process-local artifact-token fallback. The diff does not change the claim payload, artifact-token payload, or HmacSHA256 algorithm. The public Javadocs correctly state that key length alone does not prove entropy or approved key generation. The changed paths do not overlap with the active #270 or #268 changed paths. Automated checks remain advisory and do not constitute independent approval.

⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Please review exact current source head c1455ad0f69ba0ff1d3bb890e83e0dba5b9ad9d2 against live protected main f3cc09a9838f0f88c81a2ceae22138fab80a2edb. Verify the bounded production HMAC readiness contract: missing/blank rejection, 16-UTF-8-byte minimum for both tenant-claims and artifact-token signing material, explicit stable artifact-token key in production instead of the process-random development fallback, exact boundary acceptance, and rejection of byte-identical key reuse across tenant-claim and artifact-delivery purposes without claiming that inequality proves entropy/KMS provenance. Exact-head CI 31346737404 is successful with Maven job 93329944372 executing 440 tests with zero failures/errors/skips; Security Scan 31346737410, SAST Semgrep 31346737433, and fuzz 31346737419 are successful. Treat all predecessor heads and earlier review requests as stale. Report only current actionable findings; automated review remains advisory and is not qualifying independent approval.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo='ContextualWisdomLab/clearfolio'
pr='313'
head='c1455ad0f69ba0ff1d3bb890e83e0dba5b9ad9d2'
base='f3cc09a9838f0f88c81a2ceae22138fab80a2edb'

printf '%s\n' '== Exact commits and ancestry =='
git show -s --format='base %H%nparents %P%nsubject %s' "$base"
git show -s --format='head %H%nparents %P%nsubject %s' "$head"
git merge-base --is-ancestor "$base" "$head" && echo 'base-is-ancestor=yes'

printf '%s\n' '== Changed paths and whitespace check =='
git diff --name-status "$base" "$head"
git diff --check "$base" "$head"

printf '%s\n' '== Changed Java outlines =='
git diff --name-only "$base" "$head" -- '*.java' | while IFS= read -r path; do
  printf '\n--- %s\n' "$path"
  ast-grep outline "$path" --items all --view expanded
done

printf '%s\n' '== Exact changed Java source =='
git diff --name-only "$base" "$head" -- '*.java' | while IFS= read -r path; do
  printf '\n--- %s\n' "$path"
  git show "$head:$path" | cat -n
done

printf '%s\n' '== Relevant implementation and call sites =='
rg -n -C 8 \
  '(ProductionAuthReadinessConfig|ArtifactLinkService|clearfolio\.tenant-claims\.hmac-secret|clearfolio\.artifact-token\.secret|HmacSHA|SecureRandom|MessageDigest\.isEqual|getBytes\(StandardCharsets\.UTF_8\))' \
  --glob '!**/target/**' --glob '!**/.git/**' .

printf '%s\n' '== Relevant file outlines =='
fd -t f -E target -E .git \
  'ProductionAuthReadinessConfig\.java|ProductionAuthReadinessConfigTest\.java|ArtifactLinkService\.java' . \
  -x ast-grep outline {} --items all --view expanded

printf '%s\n' '== PR state, checks, and changed-path overlap =='
gh pr view "$pr" --repo "$repo" \
  --json state,isDraft,mergeable,headRefOid,baseRefOid,reviewDecision,statusCheckRollup \
  --jq '{state,isDraft,mergeable,headRefOid,baseRefOid,reviewDecision,checks:[.statusCheckRollup[]|{name,status,conclusion}]}'

for related_pr in 270 268; do
  printf '\n--- PR #%s paths\n' "$related_pr"
  gh pr view "$related_pr" --repo "$repo" --json files \
    --jq '.files[].path' || true
done

printf '%s\n' '== Unresolved review threads on exact requested head =='
gh api graphql \
  -F owner='ContextualWisdomLab' -F name='clearfolio' -F number="$pr" \
  -f query='
query($owner: String!, $name: String!, $number: Int!) {
  repository(owner: $owner, name: $name) {
    pullRequest(number: $number) {
      reviewThreads(first: 100) {
        nodes {
          isResolved
          comments(first: 20) {
            nodes { author { login } commit { oid } body path line }
          }
        }
      }
    }
  }
}' \
  --jq --arg head "$head" '
    .data.repository.pullRequest.reviewThreads.nodes
    | map(select(.isResolved == false)
      | .comments.nodes[]
      | select(.commit.oid == $head)
      | {author: .author.login, path, line, body})'

Length of output: 203491


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo='ContextualWisdomLab/clearfolio'
pr='313'
head='c1455ad0f69ba0ff1d3bb890e83e0dba5b9ad9d2'
base='f3cc09a9838f0f88c81a2ceae22138fab80a2edb'

printf '%s\n' '== Exact changed paths =='
git diff --name-status "$base" "$head"
git diff --check "$base" "$head"

printf '%s\n' '== Focused test source at exact head =='
git show "$head:src/test/java/com/clearfolio/viewer/config/ProductionAuthReadinessConfigTest.java" | cat -n

printf '%s\n' '== Exact-head unresolved review threads =='
gh api graphql \
  -F owner='ContextualWisdomLab' \
  -F name='clearfolio' \
  -F number="$pr" \
  -f query='
query($owner: String!, $name: String!, $number: Int!) {
  repository(owner: $owner, name: $name) {
    pullRequest(number: $number) {
      reviewThreads(first: 100) {
        nodes {
          isResolved
          comments(first: 20) {
            nodes {
              author { login }
              commit { oid }
              body
              path
              line
            }
          }
        }
      }
    }
  }
}' \
  | jq --arg head "$head" '
      [.data.repository.pullRequest.reviewThreads.nodes[]
       | select(.isResolved == false)
       | .comments.nodes[]
       | select(.commit.oid == $head)
       | {author: .author.login, path, line, body}]'

printf '%s\n' '== UTF-8 fixture counts =='
python3 - <<'PY'
values = {
    "tenant_short": "short-hmac-key!",
    "tenant_minimum": "0123456789abcdef",
    "artifact_short": "short-art-key!",
    "artifact_minimum": "0123456789abcdef",
    "non_ascii_example": "éééééééé",
}
for name, value in values.items():
    print(f"{name}: code_points={len(value)}, utf8_bytes={len(value.encode('utf-8'))}")
PY

Length of output: 7246


@seonghobae I found two current actionable test-coverage gaps.

  • P2 — Add non-ASCII UTF-8 boundary coverage for both secrets. ProductionAuthReadinessConfigTest.java, Line 64 and Line 73 use only ASCII fixtures. Therefore, the Java character count equals the UTF-8 byte count. The tests would not detect a regression from getBytes(StandardCharsets.UTF_8).length to String.length(). Add a production-startup test that uses distinct 16-byte non-ASCII values for both secrets. For example, each value can contain eight two-byte characters.

  • P2 — Add whitespace-only secret rejection tests. ProductionAuthReadinessConfigTest.java, Line 14 and Line 32 test missing values but not blank values. The implementation correctly uses StringUtils.hasText, but the tests do not protect this requirement. Add production-startup failure tests for whitespace-only clearfolio.tenant-claims.hmac-secret and clearfolio.artifact-token.secret. Use at least 16 whitespace characters so a future null-only presence check cannot be masked by the length gate.

I verified the exact requested head and base. The diff changes only ProductionAuthReadinessConfig.java and its focused test. The implementation rejects missing and blank values, uses UTF-8 byte lengths for both minimum checks, requires a configured artifact-token secret in production, and rejects byte-identical key material with MessageDigest.isEqual.

The artifact-token development fallback remains outside the production profile guard. The claim payload, artifact-token payload, and HmacSHA256 algorithm are unchanged. The public Javadocs state that key length and distinct values do not prove entropy, independent generation, custody, rotation, or KMS provenance. The changed paths do not overlap with #270 or #268. The exact-head unresolved-thread query returned no threads. Automated checks remain advisory and are not independent approval.

You are interacting with an AI system.

Copy link
Copy Markdown
Collaborator Author

@opencode-agent @cwl-noema-review

Please review exact current source head d026524bf9d97bebdb420a09616c98a87601b30a against current protected main 83ec6f7fe2b04bdcd28bf98ec350e41e55730a18. Focus on whether production readiness validates the effective tenant HMAC bytes actually used by TenantAccessService, stable artifact-token signing across restart/replicas, purpose separation, fail-closed ambiguity handling, and whether the 16-byte minimum is described without overstating entropy/KMS/compliance. NIST SP 800-224 remains an Initial Public Draft in the current NIST publication catalog; treat draft guidance separately from final FIPS/SP 800-57 authority. Report only current-head source-backed findings. Advisory model review is not qualifying independent approval.

@seonghobae
seonghobae force-pushed the fix/production-tenant-claims-key-strength branch from d026524 to 2e1dc91 Compare August 10, 2026 12:13
@seonghobae
seonghobae marked this pull request as ready for review August 10, 2026 12:20
@seonghobae
seonghobae enabled auto-merge (squash) August 10, 2026 12:21

Copy link
Copy Markdown
Collaborator Author

@opencode-agent @cwl-noema-review Please independently review exact current head 610f081cc56cfcaa372d674a56f4b21c91123c8f against protected main 55d7ae8647208e301f282350f076eeddaba61d11. Exact-head CI 31387412055, Security Scan 31387411478, SAST 31387411422, and fuzz 31387412079 are successful. Verify production rejects missing/null, undersized, normalized/ambiguous, unstable artifact-token, and purpose-reused HMAC material while preserving the explicit boundary that key readiness is not issue #319 credential-registry migration. Review the clean-base coverage RED→GREEN and ensure the test did not weaken production or the 100% gate. Advisory review only; do not mutate, count as human approval, merge, or weaken gates.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/com/clearfolio/viewer/config/ProductionAuthReadinessConfig.java`:
- Around line 52-54: Update ProductionAuthReadinessConfig, TenantAccessService,
and ArtifactLinkService to resolve tenant-claims and artifact-token secrets
through the same KV or credential registry used by the application, removing
their Spring `@Value` environment-placeholder injection. Keep environment
variables limited to bootstrapping that shared secret store, and ensure all
three components consume the resolved values consistently.

In
`@src/test/java/com/clearfolio/viewer/config/ProductionAuthReadinessConfigTest.java`:
- Around line 102-118: Update
productionProfileStartsAtMinimumSignedTenantClaimsSecretLength and
productionProfileStartsAtMinimumArtifactTokenSecretLength to use distinct
non-ASCII secrets that are exactly 16 UTF-8 bytes, replacing the current ASCII
boundary fixtures while keeping the startup-success assertions unchanged.
- Around line 41-79: Extend ProductionAuthReadinessConfigTest with coverage for
both secret types: add a tenant-claims secret containing NUL characters but at
least 16 UTF-8 bytes and assert the “without NUL or surrounding whitespace”
failure, and add whitespace-only tenant and artifact-token secrets whose raw
values are at least 16 bytes while asserting the missing-secret validation
errors. Keep the existing productionRunner and direct-constructor testing
patterns.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: daf91af9-801a-42e7-bc34-4f6ce5ec8738

📥 Commits

Reviewing files that changed from the base of the PR and between b66b692 and ad306bb.

📒 Files selected for processing (2)
  • src/main/java/com/clearfolio/viewer/config/ProductionAuthReadinessConfig.java
  • src/test/java/com/clearfolio/viewer/config/ProductionAuthReadinessConfigTest.java

Comment on lines 52 to +54
public ProductionAuthReadinessConfig(
@Value("${clearfolio.tenant-claims.hmac-secret:}") String tenantClaimsSecret) {
if (!StringUtils.hasText(tenantClaimsSecret)) {
@Value("${clearfolio.tenant-claims.hmac-secret:}") String tenantClaimsSecret,
@Value("${clearfolio.artifact-token.secret:}") String artifactTokenSecret) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

비밀 조회를 Spring 환경 placeholder에서 분리하세요.

Line 53과 Line 54는 HMAC 비밀을 @Value Spring 환경 placeholder에서 읽습니다. 이는 production 비밀의 허용된 소스가 아닙니다. ProductionAuthReadinessConfig, TenantAccessService, ArtifactLinkService가 동일한 KV 또는 credential registry에서 해석한 비밀을 사용하도록 마이그레이션하세요. 환경 변수는 해당 저장소의 bootstrap에만 사용하세요.

As per coding guidelines, 새 비밀과 기존 artifact-token 및 tenant-claims 비밀은 Spring 환경 placeholder 대신 KV 또는 credential registry에서 읽어야 합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/clearfolio/viewer/config/ProductionAuthReadinessConfig.java`
around lines 52 - 54, Update ProductionAuthReadinessConfig, TenantAccessService,
and ArtifactLinkService to resolve tenant-claims and artifact-token secrets
through the same KV or credential registry used by the application, removing
their Spring `@Value` environment-placeholder injection. Keep environment
variables limited to bootstrapping that shared secret store, and ensure all
three components consume the resolved values consistently.

Source: Coding guidelines

@opencode-agent
opencode-agent Bot disabled auto-merge August 12, 2026 09:02
@seonghobae
seonghobae marked this pull request as draft August 12, 2026 11:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant