Skip to content

Fix length-1 quote trap in shared settings-reader cleaned() helper#1106

Merged
steipete merged 1 commit into
steipete:mainfrom
m1qaweb:fix/settings-reader-quote-unwrap-trap
May 22, 2026
Merged

Fix length-1 quote trap in shared settings-reader cleaned() helper#1106
steipete merged 1 commit into
steipete:mainfrom
m1qaweb:fix/settings-reader-quote-unwrap-trap

Conversation

@m1qaweb
Copy link
Copy Markdown
Contributor

@m1qaweb m1qaweb commented May 22, 2026

PROBLEM

The provider settings readers in CodexBarCore (29 of them, plus
CodexBarConfig, CLIConfigCommand, and ProviderTokenResolver -- 32 files
total) share a copy-pasted cleaned() helper that strips one layer of
wrapping quotes:

if (value.hasPrefix("\"") && value.hasSuffix("\"")) ||
    (value.hasPrefix("'") && value.hasSuffix("'"))
{
    value.removeFirst()
    value.removeLast()
}

When value is exactly one character (a lone " or '), both hasPrefix and
hasSuffix return true, removeFirst() empties the string, and removeLast()
then traps the process with:

Swift/RangeReplaceableCollection.swift:867: Fatal error:
Can't remove last element from an empty collection

The trap fires deep inside libswiftCore.so's
RangeReplaceableCollection.removeLast() and is not catchable. The whole
process dies (menu bar app or CLI command). Restarting with the same bad
env var or config value crashes again on next probe.

REACHABILITY

Triggered by these user-controllable inputs:

  • env var set to a lone quote (e.g. OLLAMA_API_KEY='"' from a
    misquoted shell script, launchd plist, systemd unit, or paste error)
  • apiKey / cookieHeader values in ~/.codexbar/config.json
  • codexbar config set-api-key --api-key '"' on the CLI

VERIFIED REPRODUCTION

Ubuntu 24.04 x86_64 with Swift 6.2.4, against upstream HEAD 123fe84
(no patches applied). The PR 1098 follow-up fix for FoundationNetworking
was applied first so CodexBarCore could compile, but the cleaned()
helpers were left unpatched. Then a single test:

@Test
func alibabaTokenPlanCookieHeader_lonelyDoubleQuote_shouldNotCrash() {
    let env = ["ALIBABA_TOKEN_PLAN_COOKIE": "\""]
    let result = AlibabaTokenPlanSettingsReader.cookieHeader(
        environment: env)
    #expect(result == nil)
}

Output:

Swift/RangeReplaceableCollection.swift:867: Fatal error:
Can't remove last element from an empty collection
*** Program crashed: Illegal instruction at 0x00007eab486e2d76 ***
Thread 2 crashed:
  1 [ra] 0x00007eab485ee6ac
    RangeReplaceableCollection<>.removeLast() + 443
    in libswiftCore.so

After applying this patch, the same test passes in 0.001s.

FIX

Replace the two-line removeFirst() + removeLast() pair in all 32
locations with:

value = String(value.dropFirst().dropLast())

dropFirst() and dropLast() are empty-safe (they return an empty
Substring on empty input). For value.count >= 2 the result is
byte-identical to the old code. For the single trap case
(value.count == 1 with matching quote prefix/suffix), the new code
returns "" which the surrounding .isEmpty guards convert to nil --
strictly better than a process crash.

REGRESSION COVERAGE

New TestsLinux/SettingsReaderQuoteUnwrapTrapTests.swift exercises two
representative public readers (Alibaba Token Plan, Ollama API key) with
lone-quote and properly-quoted inputs across 7 test cases. 7/7 pass on
the fixed code; the lone-quote tests would have killed the test process
before this patch.

swift build -> Build complete!
swift test -> 21 tests in 4 suites passed (was 14 before this PR)
make check -> no new lint violations introduced by this PR

NOTES

  1. The trigger (lone " or ' as a config value) is unusual but well
    within "user provides unexpected input" territory. The
    disproportion of the consequence (full process crash, repeated on
    restart while the bad value persists) justifies the fix even
    though the trigger is rare.

  2. I chose dropFirst/dropLast over a length guard or a shared helper
    because the diff per file is the smallest possible (2 lines
    removed, 1 line added) and the idiom is natively empty-safe.
    Happy to rework to a different shape (value.count >= 2 guard, or
    extracted CodexBarCore helper) if you prefer either approach.

  3. Scope is 32 files because the pattern is copy-pasted that many
    times. Patching only one copy would leave the other 31 still
    crashable. A future refactor to a shared CodexBarCore helper
    would prevent future copy-paste of the pattern.

A near-identical `cleaned(_:)` helper is copy-pasted across 32 cross-platform-core
files (29 provider settings readers, CodexBarConfig, CLIConfigCommand, and
ProviderTokenResolver). Each one strips a single layer of wrapping quotes:

    if (value.hasPrefix("\"") && value.hasSuffix("\"")) ||
        (value.hasPrefix("'") && value.hasSuffix("'"))
    {
        value.removeFirst()
        value.removeLast()
    }

When `value` is exactly one character — a lone `"` or `'` — both `hasPrefix` and
`hasSuffix` return true, `removeFirst()` empties the string, and `removeLast()` then
traps the process with "Can't remove last element from empty collection." This is
reachable from user-controlled inputs:

  * env vars (e.g. `ALIBABA_TOKEN_PLAN_COOKIE='"'`, `OLLAMA_API_KEY="'"`),
  * the `apiKey`/`cookieHeader` JSON values in `~/.codexbar/config.json`,
  * `codexbar config set-api-key --api-key "'"` (or `\"`) on the CLI.

Any of those crashes the menu-bar app (or the CLI) outright the next time the
affected provider is probed — there is no graceful degradation, no error message,
just an illegal-instruction trap.

Reproduced on Ubuntu 24.04 x86_64 with Swift 6.2.4:

    swift -e 'var v="\""; v.removeFirst(); v.removeLast()'
    # Swift/StringStorage.swift:88: Fatal error: Can't remove last element from empty collection
    # Illegal instruction (core dumped)

The fix replaces the two-line removeFirst/removeLast pair with
`value = String(value.dropFirst().dropLast())` in all 32 locations.
`dropFirst()`/`dropLast()` are empty-safe (both return the empty
substring on empty input), so length-0 and length-1 inputs no longer
trap. For length ≥ 2 inputs the new code produces the same result as the
old code; for length-1 lone-quote inputs the new code returns "" instead
of crashing, which the surrounding `.isEmpty` guards then convert to nil.

Regression coverage: new TestsLinux suite `SettingsReaderQuoteUnwrapTrapTests`
exercises two representative public readers (Alibaba Token Plan and Ollama
API key) with lone `"`, lone `'`, and properly-wrapped values, and asserts
the readers return nil for the trap-inducing inputs and unwrap correctly
for the valid ones. 7/7 tests pass on the fixed code; the lone-quote tests
would have trapped (and killed the test process) on master.

  swift build  -> Build complete!
  swift test   -> 21/21 tests in 4 suites passed (up from 14)
  make check   -> 0 new lint violations introduced by this PR

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@clawsweeper
Copy link
Copy Markdown

clawsweeper Bot commented May 22, 2026

Codex review: needs real behavior proof before merge.

Latest ClawSweeper review: 2026-05-22 12:01 UTC / May 22, 2026, 8:01 AM ET.

Workflow note: Future ClawSweeper reviews update this same comment in place.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

Summary
The PR replaces copied quote-unwrapping helpers in provider/config settings readers with an empty-safe dropFirst().dropLast() form and adds Linux regression coverage for lone quote inputs.

Reproducibility: yes. Current source shows the deterministic hasPrefix/hasSuffix plus removeFirst()/removeLast() path for a one-character quote input; I did not execute it because this review is read-only and avoids generated artifacts.

PR rating
Overall: 🦪 silver shellfish
Proof: 🦪 silver shellfish
Patch quality: 🐚 platinum hermit
Summary: The patch is small and source-correct, but the proof is still test-only and remains a merge gate for an external PR.

Rank-up moves:

  • Add redacted terminal output or a screenshot showing a real codexbar CLI/app path with a lone quote env/config value after the fix does not crash.
  • Rerun CI after Fix Linux build of AlibabaTokenPlanCookieHeader #1105 or an equivalent Linux build fix is on the base branch.
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Real behavior proof
Needs real behavior proof before merge: The PR body includes reproduction details and test/build/lint output, but no after-fix real CLI or app run with the malformed env/config value; contributor action is needed before merge. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Risk before merge

  • The PR body provides test/build/lint output, but not an after-fix real CLI or app run showing a lone quote in env/config no longer crashes; that proof is still needed before merge.
  • Linux CI may remain red until the separate build fix at Fix Linux build of AlibabaTokenPlanCookieHeader #1105 or an equivalent current-main fix lands and this PR is rerun.

Maintainer options:

  1. Decide the mitigation before merge
    Land the narrow mechanical crash fix with focused regression coverage after the contributor adds real CLI or app behavior proof using a bad env/config value, while keeping any later shared-helper refactor separate.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge
No automated repair is needed; the remaining blocker is contributor-provided real behavior proof and normal maintainer review once the separate Linux build issue is resolved.

Security
Cleared: The diff only changes local settings-string normalization and test coverage; I found no new dependency, secret-handling expansion, or supply-chain concern.

Review details

Best possible solution:

Land the narrow mechanical crash fix with focused regression coverage after the contributor adds real CLI or app behavior proof using a bad env/config value, while keeping any later shared-helper refactor separate.

Do we have a high-confidence way to reproduce the issue?

Yes. Current source shows the deterministic hasPrefix/hasSuffix plus removeFirst()/removeLast() path for a one-character quote input; I did not execute it because this review is read-only and avoids generated artifacts.

Is this the best way to solve the issue?

Yes. The replacement preserves the old behavior for properly wrapped values and turns the length-1 trap case into an empty value that existing callers already treat as absent; extracting a shared helper would be a follow-up cleanup, not required for this fix.

Label changes:

  • add P2: This fixes a real crash from malformed user-controlled settings input, but the trigger is narrow and the patch is a normal bounded bug fix.
  • add rating: 🦪 silver shellfish: Current PR rating is 🦪 silver shellfish because proof is 🦪 silver shellfish, patch quality is 🐚 platinum hermit, and The patch is small and source-correct, but the proof is still test-only and remains a merge gate for an external PR.
  • add status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR body includes reproduction details and test/build/lint output, but no after-fix real CLI or app run with the malformed env/config value; contributor action is needed before merge. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Label justifications:

  • P2: This fixes a real crash from malformed user-controlled settings input, but the trigger is narrow and the patch is a normal bounded bug fix.
  • rating: 🦪 silver shellfish: Current PR rating is 🦪 silver shellfish because proof is 🦪 silver shellfish, patch quality is 🐚 platinum hermit, and The patch is small and source-correct, but the proof is still test-only and remains a merge gate for an external PR.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR body includes reproduction details and test/build/lint output, but no after-fix real CLI or app run with the malformed env/config value; contributor action is needed before merge. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

What I checked:

  • Current-main crashable helper: Current main still has the copied quote-unwrapping pattern in AlibabaTokenPlanSettingsReader.cleaned(_:); a lone quote satisfies both prefix and suffix checks, then removeFirst() empties the string before removeLast() is called. (Sources/CodexBarCore/Providers/Alibaba/AlibabaTokenPlanSettingsReader.swift:38, fd4878ba669c)
  • Representative current-main caller: OllamaAPISettingsReader.cleaned(_:) has the same pattern on current main, confirming the issue is not isolated to the new Alibaba reader. (Sources/CodexBarCore/Providers/Ollama/OllamaUsageFetcher.swift:651, fd4878ba669c)
  • Virtual merge keeps the patch scoped: The GitHub virtual merge against current main changes the expected settings-reader files plus the new Linux test file, without the unrelated branch-vs-main drift visible when diffing the raw head commit. (2db11756eea3)
  • Mechanical replacement coverage: The virtual merge replaces each value.removeFirst() / value.removeLast() quote-unwrapping pair in the touched Swift sources with value = String(value.dropFirst().dropLast()). (2db11756eea3)
  • Regression tests: The added Linux test suite covers Alibaba Token Plan and Ollama settings readers for lone double quote, lone apostrophe, and normally quoted values. (TestsLinux/SettingsReaderQuoteUnwrapTrapTests.swift:31, 2db11756eea3)
  • Whitespace check: The virtual merge has no git diff --check whitespace errors. (2db11756eea3)

Likely related people:

  • Peter Steinberger: Git history shows Peter on the config-backed provider settings and CLI config helper path, including cleanConfigSecret and CodexBarConfig helper history. (role: recent area contributor; confidence: high; commits: 23a89fb02a08, 8a8517337b5c, 2db843a4600f; files: Sources/CodexBarCLI/CLIConfigCommand.swift, Sources/CodexBarCore/Config/CodexBarConfig.swift, Sources/CodexBarCore/Providers/Ollama/OllamaUsageFetcher.swift)
  • Misty: Misty introduced and then hardened the Alibaba Token Plan settings reader that is one of the representative crash paths in this PR. (role: recent provider contributor; confidence: high; commits: b2bc921ab8b8, fd4878ba669c; files: Sources/CodexBarCore/Providers/Alibaba/AlibabaTokenPlanSettingsReader.swift, Sources/CodexBarCore/Providers/Alibaba/AlibabaTokenPlanCookieHeader.swift)

Codex review notes: model gpt-5.5, reasoning high; reviewed against fd4878ba669c.

@m1qaweb m1qaweb closed this May 22, 2026
@m1qaweb
Copy link
Copy Markdown
Contributor Author

m1qaweb commented May 22, 2026

The two Linux CI failures here (build-linux-cli on linux-x64 and
linux-arm64) are not caused by this PR.

They are the pre-existing Linux build break introduced by #1098 (Alibaba
Token Plan provider), which added
Sources/CodexBarCore/Providers/Alibaba/AlibabaTokenPlanCookieHeader.swift
but omitted the conditional FoundationNetworking import. HTTPCookie is
unavailable on Linux without that import, so swift build on Linux has
been failing on every commit to main since #1098 landed.

I have a separate one-line PR open at #1105 that fixes exactly
this. Once that PR merges, this PR's CI will turn green on a re-run with
no changes here.

Verified by reproducing on upstream main 123fe84 with no patches
applied -- same error, same files, same exit code 1.

@m1qaweb m1qaweb reopened this May 22, 2026
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal priority bug or improvement with limited blast radius. labels May 22, 2026
@clawsweeper
Copy link
Copy Markdown

clawsweeper Bot commented May 22, 2026

ClawSweeper PR egg

🎁 Pass real behavior proof to wake the egg and unlock a hatchable treat.

Where did the egg go?
  • The egg game starts only after the PR passes the real-behavior proof check.
  • Before that, no creature or rarity is rolled. The treat waits for real proof.
  • This is still just collectible flavor: proof affects review readiness, not creature quality.

@steipete
Copy link
Copy Markdown
Owner

@clawsweeper re-review

Maintainer proof for the real CLI path on the patched branch:

$ tmp_home=$(mktemp -d)
$ HOME="$tmp_home" .build/debug/CodexBarCLI config set-api-key --provider ollama --api-key '"' --json --pretty
[
  {
    "error" : {
      "code" : 1,
      "kind" : "args",
      "message" : "Missing API key. Pass --api-key <key> or pipe it with --stdin."
    },
    "provider" : "cli",
    "source" : "cli"
  }
]
exit=1

That exercises the user-facing codexbar config set-api-key path with a lone double quote. The fixed branch exits cleanly with a normal argument error instead of trapping in removeLast().

Additional local proof:

$ swift test --filter SettingsReaderQuoteUnwrapTrapTests
Test run with 7 tests in 1 suite passed

The earlier Linux failures are superseded by #1104 / fd4878b. The current rerun for this PR has Linux x64 and Linux arm64 green; macOS lint-build-test is still running.

@clawsweeper
Copy link
Copy Markdown

clawsweeper Bot commented May 22, 2026

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@steipete steipete merged commit 97f9ecb into steipete:main May 22, 2026
4 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal priority bug or improvement with limited blast radius. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants