Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ea29ee9
Fix CSV formula injection NUL byte bypass
seonghobae Aug 31, 2026
f344b11
Fix CSV formula injection NUL byte bypass
seonghobae Aug 31, 2026
df8b85d
Trigger CI retry
seonghobae Aug 31, 2026
447629a
Trigger CI retry
seonghobae Aug 31, 2026
3bf327f
test(security): cover repeated NUL CSV prefixes
seonghobae Aug 31, 2026
42d8ee3
Trigger CI retry
seonghobae Aug 31, 2026
51c8b28
Trigger CI retry
seonghobae Aug 31, 2026
15a274a
Trigger CI retry
seonghobae Aug 31, 2026
c60de01
Trigger CI retry
seonghobae Aug 31, 2026
4ddc5c6
Trigger CI retry
seonghobae Aug 31, 2026
e732ff6
Trigger CI retry
seonghobae Aug 31, 2026
2195e67
Trigger CI retry
seonghobae Aug 31, 2026
2e56581
Trigger CI retry
seonghobae Aug 31, 2026
2839a6b
Trigger CI retry
seonghobae Aug 31, 2026
f72c4ca
Trigger CI retry
seonghobae Sep 1, 2026
08dbef7
Trigger CI retry
seonghobae Sep 1, 2026
fcb7a8c
test(security): preserve NUL-only CSV regression
seonghobae Sep 1, 2026
d9842bd
docs(security): date NUL CSV lesson to current repair
seonghobae Sep 1, 2026
801e01b
test(security): preserve full-width CSV operator regressions
seonghobae Sep 1, 2026
910636e
fix(security): preserve full-width CSV operator guard
seonghobae Sep 1, 2026
2eb9bb2
chore(stack): refresh CSV security protected base
seonghobae Sep 4, 2026
754fa51
test(security): reject C0-prefixed CSV fields
seonghobae Sep 5, 2026
12043a9
fix(security): reject leading C0 CSV controls
seonghobae Sep 5, 2026
419651f
merge(develop): adopt protected workflow backstops
seonghobae Sep 5, 2026
71c2772
docs(security): record C0 CSV control boundary
seonghobae Sep 5, 2026
b1328c3
Trigger CI retry
seonghobae Sep 5, 2026
5c431fc
Trigger CI retry
seonghobae Sep 5, 2026
694018a
Trigger CI retry
seonghobae Sep 5, 2026
91eace8
Trigger CI retry
seonghobae Sep 6, 2026
2cb0672
test(security): restore CSV C0 and full-width regressions
seonghobae Sep 6, 2026
4ed51b2
fix(security): restore CSV control-prefix boundary
seonghobae Sep 6, 2026
15b90b2
docs(security): restore CSV C0 trust-boundary note
seonghobae Sep 6, 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
35 changes: 35 additions & 0 deletions .github/workflows/bandit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: bandit

on:
push:
branches:
- develop
- main
pull_request:
branches:
- develop
- main

permissions:
contents: read

env:
GIT_CONFIG_COUNT: "1"
GIT_CONFIG_KEY_0: init.defaultBranch
GIT_CONFIG_VALUE_0: develop

jobs:
bandit-scan:
name: Bandit Security Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
version: "0.8.6"
enable-cache: false
- name: Sync Python dependencies
run: uv sync --project services/analysis-engine --group dev --frozen
- name: Run Bandit
working-directory: services/analysis-engine
run: uv run bandit -c pyproject.toml -r src
22 changes: 6 additions & 16 deletions .github/workflows/build-baseline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,6 @@ on:
tags:
- "v*"

concurrency:
group: >-
${{ github.workflow }}-${{ github.repository }}-${{
github.event_name == 'pull_request' && github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read

Expand Down Expand Up @@ -294,11 +288,9 @@ jobs:
- name: Explain non-blocking macOS amd64 artifact upload failure
if: ${{ steps.upload-macos-amd64.outcome == 'failure' }}
run: |
{
echo "Artifact upload failed after the macOS amd64 bundle was packaged."
echo "Pull request builds keep artifact upload non-blocking because GitHub artifact service or DNS failures do not invalidate the build evidence."
echo "Tag and release builds remain blocking because release publication requires uploaded artifacts."
} >> "$GITHUB_STEP_SUMMARY"
echo "Artifact upload failed after the macOS amd64 bundle was packaged." >> "$GITHUB_STEP_SUMMARY"
echo "Pull request builds keep artifact upload non-blocking because GitHub artifact service or DNS failures do not invalidate the build evidence." >> "$GITHUB_STEP_SUMMARY"
echo "Tag and release builds remain blocking because release publication requires uploaded artifacts." >> "$GITHUB_STEP_SUMMARY"

build-macos-arm64:
name: build / macos / arm64
Expand Down Expand Up @@ -358,11 +350,9 @@ jobs:
- name: Explain non-blocking macOS arm64 artifact upload failure
if: ${{ steps.upload-macos-arm64.outcome == 'failure' }}
run: |
{
echo "Artifact upload failed after the macOS arm64 bundle was packaged."
echo "Pull request builds keep artifact upload non-blocking because GitHub artifact service or DNS failures do not invalidate the build evidence."
echo "Tag and release builds remain blocking because release publication requires uploaded artifacts."
} >> "$GITHUB_STEP_SUMMARY"
echo "Artifact upload failed after the macOS arm64 bundle was packaged." >> "$GITHUB_STEP_SUMMARY"
echo "Pull request builds keep artifact upload non-blocking because GitHub artifact service or DNS failures do not invalidate the build evidence." >> "$GITHUB_STEP_SUMMARY"
echo "Tag and release builds remain blocking because release publication requires uploaded artifacts." >> "$GITHUB_STEP_SUMMARY"

gate-macos:
name: gate / build / macos
Expand Down
6 changes: 0 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,6 @@ on:
- develop
- main

concurrency:
group: >-
${{ github.workflow }}-${{ github.repository }}-${{
github.event_name == 'pull_request' && github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read

Expand Down
39 changes: 39 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: codeql

on:
push:
branches:
- develop
- main
workflow_dispatch:

permissions:
actions: read
contents: read

env:
GIT_CONFIG_COUNT: "1"
GIT_CONFIG_KEY_0: init.defaultBranch
GIT_CONFIG_VALUE_0: develop

jobs:
analyze:
name: codeql
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language:
- javascript-typescript
- python
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
with:
languages: ${{ matrix.language }}
- uses: github/codeql-action/autobuild@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
- uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
4 changes: 0 additions & 4 deletions .github/workflows/ossf-scorecard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ on:
- develop
- main

concurrency:
group: ${{ github.workflow }}-${{ github.repository }}-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: false

permissions: read-all

jobs:
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
name: release

on:
pull_request:
branches:
- develop
- main
push:
branches:
- develop
Expand All @@ -9,10 +13,6 @@ on:
- "v*"
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.repository }}-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: false

permissions:
contents: read

Expand Down
6 changes: 0 additions & 6 deletions .github/workflows/sbom.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,6 @@ on:
types:
- published

concurrency:
group: >-
${{ github.workflow }}-${{ github.repository }}-${{
github.event_name == 'pull_request' && github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read

Expand Down
29 changes: 29 additions & 0 deletions .github/workflows/secret-scan-gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: secret-scan-gate

on:
pull_request:
branches:
- develop
- main
push:
branches:
- develop
- main

permissions:
contents: read

env:
GIT_CONFIG_COUNT: "1"
GIT_CONFIG_KEY_0: init.defaultBranch
GIT_CONFIG_VALUE_0: develop

jobs:
secret-scan:
name: secret-scan-gate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Scan for common hardcoded secrets
run: |
! git grep -nE '(g[h]p_|g[h]o_|A[K]IA[0-9A-Z]{16}|A[I]za[0-9A-Za-z\-_]{35}|BEGIN (R[S]A|E[C]|OPENS[S]H|P[G]P) PRIVATE KEY)' -- . ':(exclude)package-lock.json' ':(exclude)node_modules/**'
53 changes: 7 additions & 46 deletions .github/workflows/security-audit.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
name: security-backstop
name: security-audit

on:
pull_request:
branches:
- develop
- main
push:
branches:
- develop
- main
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.repository }}-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: false

permissions:
contents: read
Expand All @@ -20,12 +19,9 @@ env:
GIT_CONFIG_VALUE_0: develop

jobs:
security-backstop:
name: security-backstop
audit:
name: security-audit
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
Expand Down Expand Up @@ -53,45 +49,10 @@ jobs:
run: uv sync --project services/analysis-engine --group dev --frozen
- name: Audit Python dependencies
run: uv run --project services/analysis-engine --with pip-audit==2.8.0 pip-audit --local --strict
- name: Run Bandit
working-directory: services/analysis-engine
run: uv run bandit -c pyproject.toml -r src
- name: Install stable Rust toolchain
run: rustup toolchain install stable --profile minimal
- name: Install cargo-audit
run: cargo +stable install cargo-audit --locked
- name: Audit Rust dependencies
working-directory: apps/desktop/src-tauri
run: cargo +stable audit
- name: Scan for common hardcoded secrets
run: |
! git grep -nE '(g[h]p_|g[h]o_|A[K]IA[0-9A-Z]{16}|A[I]za[0-9A-Za-z\-_]{35}|BEGIN (R[S]A|E[C]|OPENS[S]H|P[G]P) PRIVATE KEY)' -- . ':(exclude)package-lock.json' ':(exclude)node_modules/**'
- name: Run Trivy filesystem scan summary
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
scan-type: fs
scan-ref: .
version: v0.71.2
format: table
severity: CRITICAL,HIGH,MEDIUM
exit-code: "0"
skip-dirs: services/analysis-engine/.venv
trivyignores: ./.trivyignore
- name: Run Trivy filesystem scan
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
scan-type: fs
scan-ref: .
version: v0.71.2
format: sarif
output: trivy-results.sarif
severity: CRITICAL,HIGH,MEDIUM
limit-severities-for-sarif: true
exit-code: "1"
skip-dirs: services/analysis-engine/.venv
trivyignores: ./.trivyignore
- name: Upload Trivy scan results to GitHub Security tab
if: always()
uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
with:
sarif_file: trivy-results.sarif
54 changes: 54 additions & 0 deletions .github/workflows/trivy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: trivy

on:
push:
branches:
- develop
- main

permissions:
contents: read

env:
GIT_CONFIG_COUNT: "1"
GIT_CONFIG_KEY_0: init.defaultBranch
GIT_CONFIG_VALUE_0: develop

jobs:
trivy-fs-scan:
name: trivy-fs-scan
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Run Trivy filesystem scan summary
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0; SHA pinning retained as supply-chain attack mitigation, do not replace with tag.
with:
scan-type: fs
scan-ref: .
version: v0.71.2
format: table
severity: CRITICAL,HIGH,MEDIUM
exit-code: '0'
skip-dirs: 'services/analysis-engine/.venv'
trivyignores: ./.trivyignore
- name: Run Trivy filesystem scan
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0; SHA pinning retained as supply-chain attack mitigation, do not replace with tag.
with:
scan-type: fs
scan-ref: .
version: v0.71.2
format: sarif
output: trivy-results.sarif
severity: CRITICAL,HIGH,MEDIUM
limit-severities-for-sarif: true
exit-code: '1'
skip-dirs: 'services/analysis-engine/.venv'
trivyignores: ./.trivyignore
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 peeled commit; SHA pinning retained as supply-chain attack mitigation.
if: always()
with:
sarif_file: trivy-results.sarif
7 changes: 6 additions & 1 deletion .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
## 2024-07-07 - Unsanitized Directory Input Paths API Validation
**Vulnerability:** The API logic allowed user-controlled local data directory paths (`cacheRoot` and `tempRoot`) to be directly used without mitigating cross-platform path traversal vulnerabilities.
**Learning:** Checking for '..' sequences in untrusted paths fails to parse cross-platform separators reliably for untrusted inputs (e.g., Windows backslashes on POSIX). Relying solely on `os.sep` or `os.altsep` is inadequate because absolute paths can bypass restrictions if not resolved correctly, or if `os.altsep` is None.
**Prevention:** Manually replace backslashes with forward slashes and split by forward slash (e.g., `if '..' in path.replace('\\', '/').split('/')`) to enforce path traversal protections explicitly for restricted directory inputs provided via the API. Do not block `~` for user-selected input files.
**Prevention:** Manually replace backslashes with forward slashes and split by forward slash (e.g. `if '..' in path.replace('\\', '/').split('/')`) to enforce path traversal protections explicitly for restricted directory inputs provided via the API. Do not block `~` for user-selected input files.

## 2024-05-20 - Python Path Traversal Mitigation bypass
**Vulnerability:** Path traversal detection in Python backend APIs relied solely on checking the input path string or basic parsed parts which might not adequately catch sequences like `..` when intermixed with different path separators.
Expand All @@ -28,3 +28,8 @@
**Vulnerability:** The Rust backend (`apps/desktop/src-tauri/src/main.rs`) did not enforce a maximum URL length limit when processing YouTube URLs via `import_youtube_url`. While the frontend enforced `MAX_YOUTUBE_URL_LENGTH = 2000` via the input element, this could be bypassed by an attacker sending requests directly to the Tauri backend API, potentially causing a Denial of Service (DoS) due to unbounded URL parsing and regex matching.
**Learning:** Input validation must occur at the entry point of untrusted data on the backend, even if it is also validated on the frontend. Relying solely on frontend validation for constraints like string length can expose the backend to resource exhaustion vulnerabilities.
**Prevention:** Always enforce constraints like maximum length, format validation, and sanitization at the earliest possible point on the backend, typically at the API boundary, regardless of frontend safeguards.

## 2026-09-05 - CSV Formula Injection C0 Control Prefix Bypass
**Vulnerability:** CSV formula-injection mitigation was incomplete when a cell began with a C0 control character (`\x00`-`\x1F`) that could be interpreted differently by downstream spreadsheet or parser implementations before a formula token.
**Learning:** NUL is only one member of the parser-disagreement boundary. Security policy must not depend on every downstream consumer preserving leading control bytes exactly, and executable regressions must include non-whitespace controls such as ESC as well as NUL.
**Prevention:** In `escapeCsvField`, treat any leading C0 control after permitted whitespace/BOM/NBSP as dangerous, prefix the entire original field before structural CSV quoting, and retain regressions for NUL-only, repeated NUL, whitespace+control, ESC-prefixed formula-shaped values, and full-width formula operators. Keep the lint exception scoped only to the intentional control-character regular expression.
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

### Changed

- Consolidated Bandit, dependency audits, supplemental secret checks, and Trivy into one trusted-branch security backstop, delegated CodeQL to GitHub default setup, and removed duplicate local PR security and release-preflight runs.
- Pinned npm `10.9.9` as the approved lockfile generator, activated it through Node-bundled Corepack before dependency consumption, and fail closed unless its bundled `tar` is at least `7.5.19`; primary CI still consumes the committed lock only through frozen `npm ci` validation, rejects mutable npm resolution in the lock gate, requires integrity evidence for public-registry lock entries, and preserves generator-sensitive root `@esbuild/*` peer metadata.

### Fixed
Expand Down Expand Up @@ -75,4 +74,4 @@

- `ChordsFeature` (코드 분석) 화면에서 각 파트(Role)의 `transpositionPlan`(이조/조옮김 계획)을 표시하는 기능을 추가했습니다.
- `RangesFeature` (음역대 분석) 화면에서 겹침 경고(Overlap warning) 외에 해당 파트의 채보(Transcription) 가능 노드 수를 요약하여 보여주는 기능을 추가했습니다.
- 신규 UI 요소에 대한 단위 테스트를 추가했습니다 (`apps/desktop/src/features/chords/index.test.tsx`, `apps/desktop/src/features/ranges/index.test.tsx`).
- 신규 UI 요소에 대한 단위 테스트를 추가했습니다 (`apps/desktop/src/features/chords/index.test.tsx`, `apps/desktop/src/features/ranges/index.test.tsx`).
4 changes: 2 additions & 2 deletions apps/desktop/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading