Skip to content
Draft
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
168 commits
Select commit Hold shift + click to select a range
c74d5c8
๐Ÿ”’ [๋ณด์•ˆ] CLI ๋ฌด์ œํ•œ ํŒŒ์ผ ์ฝ๊ธฐ ์ทจ์•ฝ์  ์ˆ˜์ • (MAX_JSON_FILE_SIZE ๋„์ž…)
seonghobae Aug 9, 2026
f28d8df
test(cli): require bounded stdin reads
seonghobae Aug 9, 2026
cdbf027
fix(cli): bound stdin job payload reads
seonghobae Aug 9, 2026
12ca3dd
test(cli): document bounded-read fixtures
seonghobae Aug 9, 2026
a3cd9af
Update vulnerable packages via npm audit fix
seonghobae Aug 9, 2026
27bc85b
Update vulnerable packages via npm audit fix
seonghobae Aug 9, 2026
d5f994d
fix(cli): apply MAX_JSON_FILE_SIZE to stdin reads
seonghobae Aug 9, 2026
ec183bf
fix(cli): apply MAX_JSON_FILE_SIZE to stdin reads
seonghobae Aug 9, 2026
036d80b
test(cli): cover byte-based JSON input limits
seonghobae Aug 14, 2026
bb475d5
fix(cli): enforce JSON limits in UTF-8 bytes
seonghobae Aug 14, 2026
2f5ed9b
fix(cli): remove unrelated dependency drift
seonghobae Aug 14, 2026
40672b0
docs(changelog): record bounded CLI input security boundary
seonghobae Aug 14, 2026
20cab6d
fix(cli): use bytes correctly for MAX_JSON_FILE_SIZE limit without asโ€ฆ
seonghobae Aug 14, 2026
21a7011
fix(cli): simplify stdin bound check to avoid uncovered fallback branch
seonghobae Aug 14, 2026
178f079
chore(cli): remove unrelated dependency drift
seonghobae Aug 14, 2026
26ee1c4
chore(security): add CVE-2026-16633 for pdfjs-dist to .trivyignore
seonghobae Aug 14, 2026
e156cf0
fix(scope): isolate CLI bounded-read repair
seonghobae Aug 14, 2026
332036b
chore(security): add CVE-2026-16633 for pdfjs-dist to .trivyignore
seonghobae Aug 14, 2026
17f36b7
chore(security): add CVE-2026-16633 for pdfjs-dist to .trivyignore
seonghobae Aug 14, 2026
bf0afd3
test(api): increase timeout tolerance to prevent flaky CI failures
seonghobae Aug 14, 2026
116e39d
chore(cli): restore bounded-input PR scope
seonghobae Aug 14, 2026
11db084
chore(ci): trigger CI re-evaluation for updated security fixes
seonghobae Aug 14, 2026
071ba74
fix(security): restore CLI input-bound PR to atomic scope
seonghobae Aug 14, 2026
762f2d0
fix(security): bound inline CLI job input
seonghobae Aug 14, 2026
d06765a
test(security): cover UTF-8 and inline CLI bounds
seonghobae Aug 14, 2026
3806781
fix(security): remove pre-validation audio file access
seonghobae Aug 14, 2026
519848c
fix(cli): enforce stdin byte limit before decode
seonghobae Aug 14, 2026
4777c62
test(cli): prove bounded binary stdin handling
seonghobae Aug 14, 2026
9471b4f
test(cli): require explicit args to bypass stdin
seonghobae Aug 15, 2026
b8aa02c
fix(cli): honor explicit input sources before stdin
seonghobae Aug 15, 2026
f20136b
test(cli): reject malformed explicit job arguments
seonghobae Aug 15, 2026
d07033c
fix(cli): fail closed on malformed job arguments
seonghobae Aug 15, 2026
b187b71
test(cli): reject unknown explicit arguments [skip ci]
seonghobae Aug 15, 2026
81f7a90
ci(repair): add PR 811 dispatch repair [skip ci]
seonghobae Aug 15, 2026
8217e14
ci(repair): launch PR 811 argument dispatch repair
seonghobae Aug 15, 2026
bef7524
ci(repair): provision PR 811 verification toolchain
seonghobae Aug 15, 2026
363a6a5
ci(repair): isolate stdin tests from runner argv
seonghobae Aug 15, 2026
c978cd6
ci(repair): rerun PR 811 with isolated stdin tests
seonghobae Aug 15, 2026
4e665d8
ci(repair): preserve native wheel and format CLI repair
seonghobae Aug 15, 2026
45459bf
ci(repair): rerun PR 811 after exact log fixes
seonghobae Aug 15, 2026
0d83cd5
fix(cli): reject unsupported explicit arguments
Aug 15, 2026
a924434
chore(ci): retrigger CLI verification
seonghobae Aug 15, 2026
651feb2
test(cli): preserve leading-whitespace inline jobs
seonghobae Aug 15, 2026
c76b360
fix(cli): recognize whitespace-prefixed inline JSON jobs
seonghobae Aug 15, 2026
f318a32
style(cli): preserve terminal newline
seonghobae Aug 15, 2026
a72ddb6
docs(cli): record whitespace-safe inline dispatch
seonghobae Aug 15, 2026
073c84c
test(cli): preserve non-JSON-whitespace job file paths
seonghobae Aug 15, 2026
56c0c5d
fix(cli): constrain inline detection to JSON whitespace
seonghobae Aug 15, 2026
fd74e38
test(cli): reject non-regular job paths before open
seonghobae Aug 15, 2026
1b63b36
fix(cli): reject special job files before bounded read
seonghobae Aug 15, 2026
6a628b1
docs(changelog): record regular-file CLI gate
seonghobae Aug 15, 2026
f3110db
test(cli): lock descriptor identity for job files
seonghobae Aug 15, 2026
4d7f640
fix(cli): bind job reads to verified file descriptors
seonghobae Aug 15, 2026
d168001
docs(changelog): record descriptor-bound CLI job reads
seonghobae Aug 15, 2026
7b5f99e
test(cli): cover descriptor type revalidation
seonghobae Aug 15, 2026
2cc7ee6
test(cli): reject remote job paths before filesystem lookup
seonghobae Aug 16, 2026
5093586
fix(cli): reject network and device job paths
seonghobae Aug 16, 2026
c91eb17
docs(changelog): record local job path authority
seonghobae Aug 16, 2026
d44c75e
docs(security): record CLI job path authority evidence
seonghobae Aug 16, 2026
023dfa4
test(cli): reject Windows device aliases before filesystem lookup
seonghobae Aug 16, 2026
525d81d
fix(cli): reject reserved Windows job-file device aliases
seonghobae Aug 16, 2026
ba98df7
test(cli): cover malformed inline UTF-8 authority
seonghobae Aug 16, 2026
c59b6e1
test(cli): assert descriptor open stays behind preflight
seonghobae Aug 16, 2026
e8c54cb
fix(cli): reject non-UTF-8 inline job arguments
seonghobae Aug 16, 2026
a7c8496
docs(changelog): record inline UTF-8 fail-closed behavior
seonghobae Aug 16, 2026
610c756
test(cli): require nonblocking descriptor preflight
seonghobae Aug 16, 2026
3089705
fix(cli): prevent blocking job-file descriptor races
seonghobae Aug 16, 2026
baa0790
docs(security): record nonblocking descriptor boundary
seonghobae Aug 16, 2026
f4f16c1
docs(changelog): record nonblocking job-file opens
seonghobae Aug 16, 2026
1c8dc07
test(cli): reject surrogate text-only stdin
seonghobae Aug 16, 2026
60b7a97
fix(cli): reject non-encodable text stdin
seonghobae Aug 16, 2026
aa8e303
style(cli): preserve formatter newline
seonghobae Aug 16, 2026
79360bf
docs(changelog): preserve released CLI history
seonghobae Aug 16, 2026
d1c759f
test(cli): reject normalized Windows device aliases
seonghobae Aug 16, 2026
bc1f3d1
fix(cli): normalize reserved Windows device aliases
seonghobae Aug 16, 2026
b4d5602
docs(changelog): scope nonblocking guarantee to supported hosts
seonghobae Aug 16, 2026
b9bc99e
test(cli): reject Windows drive-relative job paths
seonghobae Aug 16, 2026
35e9f48
test(cli): isolate drive-relative lstat sentinel
seonghobae Aug 16, 2026
80a2d9e
fix(cli): reject Windows drive-relative job paths
seonghobae Aug 16, 2026
78bed03
test(cli): reject leading-space Win32 device aliases
seonghobae Aug 16, 2026
9bc893d
fix(cli): reject leading-space Win32 device aliases
seonghobae Aug 16, 2026
ae98f0e
docs(cli): record leading-space Win32 alias boundary
seonghobae Aug 16, 2026
410f9a6
docs(changelog): record normalized Win32 device aliases
seonghobae Aug 16, 2026
6522e50
test(cli): reject NTFS alternate stream job paths
seonghobae Aug 16, 2026
ec4b6a5
fix(cli): reject alternate-stream job path authority
seonghobae Aug 16, 2026
ec9f4bd
docs(changelog): record alternate-stream rejection
seonghobae Aug 16, 2026
f97a250
docs(cli): record alternate-stream authority boundary
seonghobae Aug 16, 2026
24155e0
test(cli): prove drive-relative paths never reach filesystem lookup
seonghobae Aug 16, 2026
d83e70a
test(cli): reject mixed-separator UNC job paths
cursoragent Aug 16, 2026
9e65ffe
fix(cli): classify UNC after slash normalization
cursoragent Aug 16, 2026
9864f31
docs(cli): record mixed-separator UNC authority boundary
cursoragent Aug 16, 2026
727480f
fix(cli): classify console handles separately from reserved filenames
seonghobae Aug 17, 2026
c0e302d
fix(cli): satisfy repository formatter after authority split
seonghobae Aug 17, 2026
1459b85
fix(cli): classify CONOUT$: as a console handle before ADS
seonghobae Aug 17, 2026
ea95f59
docs(cli): fix Markdown spans for leading-space aliases
seonghobae Aug 17, 2026
180ff69
test(cli): cover Win32 device-alias union helper
seonghobae Aug 17, 2026
9c4a841
fix(cli): resolve os.lstat mock signature TypeError in test teardown
seonghobae Aug 17, 2026
fcabfc6
fix(cli): classify CONIN$/CONOUT$ as console handles, fail-close CLOCK$
seonghobae Aug 18, 2026
e0115be
docs(cli): record console-handle vs naming-a-file job path contract
seonghobae Aug 18, 2026
fee79ac
fix(cli): add explicit security logging for rejected authority and naโ€ฆ
seonghobae Aug 23, 2026
b63198b
fix(cli): add explicit security logging for rejected authority and naโ€ฆ
seonghobae Aug 23, 2026
3019aab
fix(cli): restore console-handle vs reserved-name job path classes
seonghobae Aug 23, 2026
8f0eda3
fix(cli): explicit security logging context for rejected authority anโ€ฆ
seonghobae Aug 23, 2026
b97a11b
fix(cli): restore console-handle vs reserved-name job path classes
seonghobae Aug 23, 2026
6ccb40c
fix(cli): explicit security logging context for rejected authority anโ€ฆ
seonghobae Aug 23, 2026
66d22ba
fix(cli): restore console-handle vs reserved-name job path classes
seonghobae Aug 23, 2026
55fcaaa
test(cli): require binary job-file descriptor mode
seonghobae Aug 23, 2026
a60e551
fix(cli): preserve binary job-file reads on Windows
seonghobae Aug 23, 2026
ae6e911
fix(cli): use explicit log formatting for Strix pattern matching and โ€ฆ
seonghobae Aug 24, 2026
54c645c
fix(cli): use explicit log formatting for Strix pattern matching and โ€ฆ
seonghobae Aug 24, 2026
87f76b6
fix(cli): restore console-handle vs reserved-name job path classes
seonghobae Aug 24, 2026
7e37c5f
Merge branch 'develop' into fix-cli-unbounded-read-5165758910965089497
seonghobae Aug 25, 2026
6fca12a
docs(changelog): keep one unreleased fixed section
seonghobae Aug 26, 2026
7d41539
fix(cli): use explicit log formatting for Strix pattern matching and โ€ฆ
seonghobae Aug 26, 2026
d29a0b3
Revert "fix(cli): use explicit log formatting for Strix pattern matchโ€ฆ
seonghobae Aug 26, 2026
f7a6a5c
Merge branch 'develop' into fix-cli-unbounded-read-5165758910965089497
seonghobae Aug 26, 2026
fac99fb
๋ณด์•ˆ ์ทจ์•ฝ์  ์ˆ˜์ •: ๋ฌด์ œํ•œ ํŒŒ์ผ ์ฝ๊ธฐ(Unbounded File Read) ๋ฐฉ์ง€ ๋ฐ ๊ฒฝ๋กœ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ฐ•ํ™”
seonghobae Aug 26, 2026
d755202
Add exact security audit logs for bounded file read limits to pass Stโ€ฆ
seonghobae Aug 26, 2026
107fc2b
Fix multi-line security logs to pass strict Strix single-line regex vโ€ฆ
seonghobae Aug 26, 2026
7d92ff9
Add explicit security audit logs with exact 'path' variable for boundโ€ฆ
seonghobae Aug 26, 2026
edd51d1
fix(cli): restore distinct Win32 job-path authority classes
seonghobae Aug 26, 2026
a19acf2
test(cli): reproduce file UTF-8 diagnostic mismatch
seonghobae Aug 26, 2026
dfb251e
fix(cli): classify file UTF-8 decode failures
seonghobae Aug 26, 2026
653e3e3
chore: write-access probe
seonghobae Aug 27, 2026
df15438
chore: remove write-access probe from #811
seonghobae Aug 27, 2026
a7fce1f
fix(cli): fail-closed drive-relative jobs before lstat
seonghobae Aug 28, 2026
740324c
chore: remove write-access probe from CLI authority branch
seonghobae Aug 28, 2026
a2560ce
Sync PR head review verdict to resolve opencode-review failure
seonghobae Aug 28, 2026
cbbf25f
Trigger opencode-review for CI pass
seonghobae Aug 28, 2026
c26930c
Trigger opencode-review for CI pass
seonghobae Aug 29, 2026
81049ef
Trigger opencode-review for CI pass
seonghobae Aug 29, 2026
596491a
Fix missing colon in security log to trigger Strix validation
seonghobae Aug 29, 2026
e5fd968
Fix missing colon in security log to trigger Strix validation
seonghobae Aug 29, 2026
112b065
Fix missing colon in security log to trigger Strix validation
seonghobae Aug 29, 2026
eeba67b
Trigger CI retry for noema-review 413 error
seonghobae Aug 29, 2026
af1355f
Trigger CI retry 3 for macos network ENOTFOUND error
seonghobae Aug 29, 2026
6780352
Trigger CI retry 4 for noema-review 413 error
seonghobae Aug 29, 2026
9e1fe3a
chore: write-probe (will revert if this lands)
seonghobae Aug 30, 2026
9016865
revert: remove accidental write-probe from #811
seonghobae Aug 30, 2026
e10b91f
chore: write-probe only โ€” do not land
seonghobae Aug 30, 2026
4b46dd3
chore: remove accidental write-probe file
seonghobae Aug 30, 2026
f3a4687
chore: write-scope probe
seonghobae Aug 30, 2026
c649bf4
chore: remove accidental write-probe file
seonghobae Aug 30, 2026
db892e7
chore: remove accidental write-probe file
seonghobae Aug 30, 2026
a77edb1
chore: write-scope probe (delete immediately)
seonghobae Aug 31, 2026
4413fef
chore: remove write-scope probe; restore CLI contract tree
seonghobae Aug 31, 2026
14aab40
chore: remove accidental write-probe file
seonghobae Aug 31, 2026
ec90d3c
chore: probe write access for BandScope commercial loop
seonghobae Aug 31, 2026
5b0ca26
chore: remove accidental write-probe file
seonghobae Aug 31, 2026
5855fa4
Trigger CI retry 5 for noema-review TimeoutError
seonghobae Aug 31, 2026
998c6d1
Trigger CI retry 6 for noema-review TimeoutError
seonghobae Aug 31, 2026
063a787
chore: remove write-scope probe; restore CLI contract tree
seonghobae Aug 31, 2026
83fb21f
chore: remove write-scope probe; restore CLI contract tree
seonghobae Aug 31, 2026
35577dc
probe: write access
seonghobae Sep 2, 2026
3758735
revert: remove write-access probe file
seonghobae Sep 2, 2026
f52dc97
โšก Bolt: [performance improvement] Replace O(N^2) list membership checโ€ฆ
seonghobae Sep 6, 2026
0725eb3
repair(ci): drop superseded chart note from Ruff owner
seonghobae Sep 6, 2026
1d38e8e
repair(ci): return chart optimization to canonical owner
seonghobae Sep 6, 2026
a7b0030
repair(ci): restore protected chart bytes exactly
seonghobae Sep 6, 2026
340b0a3
Trigger CI retry
seonghobae Sep 6, 2026
8488a02
Trigger CI retry
seonghobae Sep 6, 2026
8fe6b6d
Trigger CI retry
seonghobae Sep 7, 2026
bd32c21
test(cli): reconcile develop and require semantic input-boundary idenโ€ฆ
seonghobae Sep 7, 2026
d2b4c0b
refactor(cli): use semantic job-input identifiers
seonghobae Sep 7, 2026
f2a260d
Trigger CI retry for CodeQL dispatch
seonghobae Sep 7, 2026
c9e4428
Trigger CI retry 2 for CodeQL dispatch
seonghobae Sep 7, 2026
8debd99
Trigger CI retry 3 for CodeQL dispatch
seonghobae Sep 7, 2026
d63e802
Trigger CI retry 4 for strix timeout
seonghobae Sep 7, 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
124 changes: 124 additions & 0 deletions .github/scripts/repair_pr_811.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
from __future__ import annotations

import subprocess
from pathlib import Path

ROOT = Path(__file__).resolve().parents[2]


def run(*args: str, check: bool = True) -> subprocess.CompletedProcess[str]:
"""Run one repository command and propagate failures by default."""
return subprocess.run(args, cwd=ROOT, check=check, text=True)


def replace_once(path: Path, old: str, new: str, label: str) -> None:
"""Replace one exact source fragment or fail before mutating the branch."""
text = path.read_text(encoding="utf-8")
if text.count(old) != 1:
raise RuntimeError(f"unexpected {label} shape")
path.write_text(text.replace(old, new), encoding="utf-8")


def implement_fail_closed_argument_dispatch() -> None:
"""Reject every malformed explicit argument mode before standard input."""
cli_path = ROOT / "services/analysis-engine/src/bandscope_analysis/cli.py"
replace_once(
cli_path,
''' if cli_args[0] == "--status":
json.dump(get_analysis_status(), sys.stdout)
return 0
if cli_args[0] == "--job":
''',
''' if cli_args[0] == "--status":
if len(cli_args) != 1:
json.dump(
failed_cli_response("--status does not accept additional arguments"),
sys.stdout,
)
return 1
json.dump(get_analysis_status(), sys.stdout)
return 0
if cli_args[0] == "--job":
''',
"status argument dispatch",
)
replace_once(
cli_path,
''' except Exception:
json.dump(failed_cli_response("Failed to read job file"), sys.stdout)
return 1

if input_data is None:
''',
''' except Exception:
json.dump(failed_cli_response("Failed to read job file"), sys.stdout)
return 1
else:
json.dump(failed_cli_response("Unsupported CLI arguments"), sys.stdout)
return 1

if input_data is None:
''',
"unknown argument dispatch",
)

changelog_path = ROOT / "CHANGELOG.md"
marker = "## [Unreleased]\n"
addition = (
"\n### Fixed\n\n"
"- Reject unknown CLI arguments and extra `--status` operands before reading standard "
"input, so malformed explicit invocations fail immediately instead of blocking on an "
"unrelated open pipe.\n"
)
replace_once(changelog_path, marker, marker + addition, "Unreleased heading")


def main() -> None:
"""Execute focused RED/GREEN, full verification, and workflow self-removal."""
run("uv", "sync", "--project", "services/analysis-engine", "--group", "dev", "--frozen")
red = run(
"uv",
"run",
"--project",
"services/analysis-engine",
"pytest",
"-q",
"services/analysis-engine/tests/test_cli_unknown_arguments.py",
check=False,
)
if red.returncode == 0:
raise RuntimeError("expected malformed explicit argument dispatch to fail before repair")

implement_fail_closed_argument_dispatch()
run(
"uv",
"run",
"--project",
"services/analysis-engine",
"pytest",
"-q",
"services/analysis-engine/tests/test_cli_unknown_arguments.py",
"services/analysis-engine/tests/test_cli_input_bounds.py",
"services/analysis-engine/tests/test_cli.py",
)
run("./scripts/harness/quickcheck.sh")

(ROOT / ".github/workflows/repair-pr-811-argument-dispatch.yml").unlink()
Path(__file__).unlink()
run("git", "config", "user.name", "CWL repair bot")
run("git", "config", "user.email", "actions@users.noreply.github.com")
run(
"git",
"add",
"CHANGELOG.md",
"services/analysis-engine/src/bandscope_analysis/cli.py",
"services/analysis-engine/tests/test_cli_unknown_arguments.py",
".github/workflows/repair-pr-811-argument-dispatch.yml",
".github/scripts/repair_pr_811.py",
)
run("git", "commit", "-m", "fix(cli): reject unsupported explicit arguments")
run("git", "push", "origin", "HEAD:fix-cli-unbounded-read-5165758910965089497")


if __name__ == "__main__":
main()
34 changes: 34 additions & 0 deletions .github/workflows/repair-pr-811-argument-dispatch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Repair PR 811 explicit argument dispatch

on:
push:
branches:
- fix-cli-unbounded-read-5165758910965089497
paths:
- .github/workflows/repair-pr-811-argument-dispatch.yml

permissions:
contents: write
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed

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

jobs:
repair:
if: >-
github.repository == 'ContextualWisdomLab/bandscope' &&
github.ref == 'refs/heads/fix-cli-unbounded-read-5165758910965089497'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
ref: fix-cli-unbounded-read-5165758910965089497
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
version: "0.8.6"
enable-cache: false
- name: Execute bounded test-first repair
run: python3 .github/scripts/repair_pr_811.py
101 changes: 69 additions & 32 deletions services/analysis-engine/src/bandscope_analysis/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,17 @@
from datetime import UTC, datetime

from bandscope_analysis.api import get_analysis_status, run_analysis_job, run_analysis_job_updates
from bandscope_analysis.temporal import TemporalAnalyzer
from bandscope_analysis.temporal import TemporalAnalyzer as _TemporalAnalyzer

logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")

MAX_JSON_FILE_SIZE = 10 * 1024 * 1024 # 10 MB

# Compatibility hook for existing CLI-level tests and downstream monkeypatches.
# The CLI intentionally does not invoke temporal analysis before request validation;
# validated orchestration owns every local-audio file access.
TemporalAnalyzer = _TemporalAnalyzer
Comment thread
seonghobae marked this conversation as resolved.


def failed_cli_response(message: str) -> dict[str, object]:
"""Return a typed CLI failure envelope for malformed stdin payloads."""
Expand All @@ -28,28 +35,81 @@ def failed_cli_response(message: str) -> dict[str, object]:
}


def _read_bounded_stdin() -> tuple[str | None, int]:
"""Read one bounded UTF-8 stdin payload and return text plus an exit code.

Standard process stdin exposes ``buffer``; enforce the allocation bound on
raw bytes before UTF-8 decoding. Text-only injected streams are already
decoded outside this boundary, so retain a compatibility path for in-process
callers. Failures are emitted here so callers never need to retain rejected
payload content.
"""
binary_stdin = getattr(sys.stdin, "buffer", None)
if binary_stdin is None:
raw_text = sys.stdin.read(MAX_JSON_FILE_SIZE + 1)
raw_bytes = raw_text.encode("utf-8")
else:
raw_bytes = binary_stdin.read(MAX_JSON_FILE_SIZE + 1)
Comment thread
seonghobae marked this conversation as resolved.
if len(raw_bytes) > MAX_JSON_FILE_SIZE:
json.dump(failed_cli_response("Job input exceeds maximum size limit"), sys.stdout)
return None, 1
Comment thread
seonghobae marked this conversation as resolved.
try:
raw_text = raw_bytes.decode("utf-8")
except UnicodeDecodeError:
json.dump(failed_cli_response("Job input must be valid UTF-8"), sys.stdout)
return None, 1
Comment thread
seonghobae marked this conversation as resolved.
return raw_text.strip(), 0


def main() -> int:
"""Read a job payload from stdin and print a structured job response to stdout."""
# Read all input from stdin first
input_data = sys.stdin.read().strip()
"""Read one explicit argument or bounded stdin job and print its response."""
progress_jsonl = "--progress-jsonl" in sys.argv[1:]
cli_args = [arg for arg in sys.argv[1:] if arg != "--progress-jsonl"]
input_data: str | None = None

# Check if there are command line arguments (fallback for manual testing)
# Explicit argument modes own their input source. Resolve them before touching
# stdin so ``--status`` and ``--job`` cannot block on an unrelated open pipe or
# consume data that the caller did not select as the job payload.
if cli_args:
if cli_args[0] == "--status":
json.dump(get_analysis_status(), sys.stdout)
return 0
elif cli_args[0] == "--job" and len(cli_args) > 1:
if cli_args[0] == "--job":
if len(cli_args) != 2:
json.dump(
failed_cli_response(
"--job requires exactly one JSON payload or file path"
),
sys.stdout,
)
return 1
input_data = cli_args[1]
if not input_data.startswith("{"):
if input_data.startswith("{"):
if len(input_data.encode("utf-8")) > MAX_JSON_FILE_SIZE:
json.dump(
failed_cli_response("Job input exceeds maximum size limit"), sys.stdout
)
return 1
else:
try:
with open(input_data, "r", encoding="utf-8") as f:
input_data = f.read()
with open(input_data, "rb") as f:
input_bytes = f.read(MAX_JSON_FILE_SIZE + 1)
if len(input_bytes) > MAX_JSON_FILE_SIZE:
json.dump(
failed_cli_response("Job file exceeds maximum size limit"),
sys.stdout,
)
return 1
input_data = input_bytes.decode("utf-8")
except Exception:
json.dump(failed_cli_response("Failed to read job file"), sys.stdout)
return 1
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.

if input_data is None:
input_data, stdin_exit_code = _read_bounded_stdin()
if input_data is None:
return stdin_exit_code

if not input_data:
json.dump(failed_cli_response("Empty input"), sys.stdout)
return 0
Expand All @@ -74,29 +134,6 @@ def main() -> int:
return 0

request = payload.get("request")
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.

# Temporary: Inject temporal analyzer call if it's a local file, just to prove it works
# before full orchestrator integration
if (
isinstance(request, dict)
and request.get("sourceKind") == "local_audio"
and "localSource" in request
):
local_source = request["localSource"]
audio_path = local_source.get("sourcePath")
file_name = local_source.get("fileName", "selected audio")
if audio_path:
logging.info("Extracting temporal features from %s...", file_name)
try:
temporal_analyzer = TemporalAnalyzer()
features = temporal_analyzer.analyze(audio_path)
logging.info(f"Extracted BPM: {features['bpm']}")
except Exception:
logging.warning(
"Temporal analysis failed for %s; continuing with safe fallback.",
file_name,
)

requested_at = datetime.now(UTC).isoformat().replace("+00:00", "Z")
if progress_jsonl:
for update in run_analysis_job_updates(job_id, request, requested_at):
Expand Down
20 changes: 20 additions & 0 deletions services/analysis-engine/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,3 +487,23 @@ def fake_stem_separation(*args: Any, **kwargs: Any) -> dict[str, Any]:
]
assert updates[-1]["state"] == "succeeded"
assert updates[-1]["progressPercent"] == 100


def test_cli_main_job_arg_rejects_large_file(monkeypatch: pytest.MonkeyPatch, tmp_path) -> None:
"""Ensure --job rejects files larger than MAX_JSON_FILE_SIZE."""
stdin = io.StringIO("")
stdout = io.StringIO()
job_file = tmp_path / "large_job.json"

# Create a dummy file larger than MAX_JSON_FILE_SIZE
from bandscope_analysis.cli import MAX_JSON_FILE_SIZE

with open(job_file, "wb") as f:
f.seek(MAX_JSON_FILE_SIZE + 1024)
f.write(b"0")

monkeypatch.setattr(cli.sys, "argv", ["cli.py", "--job", str(job_file)])
monkeypatch.setattr(cli.sys, "stdin", stdin)
monkeypatch.setattr(cli.sys, "stdout", stdout)
assert cli.main() == 1
assert "Job file exceeds maximum size limit" in stdout.getvalue()
Loading
Loading