fix(windows,tui): DPAPI over stdin, browser opener without cmd, tag capability check #12

Merged
james.maes merged 3 commits from fix/windows-dpapi-browser-tags-20261106 into main 2026-11-06 15:32:20 +00:00
3 Commits
Author SHA1 Message Date
james.maesandClaude Fable 5.1 2c3114e06b fix(tui): check the record's tag feature and capability before the tag editor
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 1e25bb5a243dfdcd95d4b9a4f780200ff8c8778a] (pull_request) Successful in 8s
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 4s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: Original QQQ CLI verification and package capture / secrets (pull_request) Successful in 18s
QRun-IO/ci-policy: Original QQQ CLI verification and package capture / verify (pull_request) Successful in 1m50s
QRun-IO/ci-policy: Original QQQ CLI verification and package capture / gate (pull_request) Successful in 16s
QRun-IO/ci-policy: ci / original-cli (pull_request) Successful in 2m8s
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 6s
The record screen opened the TagOverlay for any record whenever the
collaboration extension was present. On a table without the TAGS
feature, or for a role without the `tag` capability, the editor opened
and the save failed only when the server refused it.

openTags now calls getRecordFeatures for the record and opens the
overlay only when `features` contains TAGS and `capabilities.tag` is
true; otherwise it flashes "Tags are not enabled for this table." or
"Your role cannot tag this record.". The answer is ignored when the
screen has moved to another record meanwhile, and a request error is
flashed like any other.

Tests: the fake server answers /table/person/<id>/features from a
`recordTagPolicy` (enabled, allowed; reset with the rest of the
collaboration state), and enterprise.test.tsx presses t with the
feature off and with the capability denied and expects the message and
no overlay. The existing tag editor test keeps its defaults.

Carried locally by GG Forge since cli-v1.0.0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-11-06 08:45:03 -06:00
james.maesandClaude Fable 5.1 b126064144 fix(cli): open the browser on Windows without cmd /c start splitting the URL
openInBrowser used `cmd /c start "" <url>` on Windows. cmd treats `&`
as a command separator even inside an argument that was passed as one
string, so an OAuth authorization URL was cut at its first query
parameter: the browser opened the authorization endpoint without
client_id, state or redirect_uri and the loopback callback never
arrived. Only `qqq login --browser` was affected; the printed URL
worked when pasted.

The URL is now never shell text. The opener runs PowerShell
(QQQ_POWERSHELL, else powershell.exe) with a fixed -EncodedCommand,
`Start-Process -FilePath $env:QQQ_BROWSER_URL`, and the URL in that
environment variable; the child is detached, stdio ignored and
windowsHide set so no console window flashes.

Test (win32 only): stubs child_process.spawn to capture the call,
checks the command is not cmd, the URL is in the environment and
windowsHide is set, then runs the captured encoded command in a real
PowerShell with Start-Process shadowed to print its -FilePath, and
expects the full URL back, `&` and all.

Carried locally by GG Forge since cli-v1.0.0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-11-06 08:44:33 -06:00
james.maesandClaude Fable 5.1 a7767ba76c fix(core): pass DPAPI secrets to PowerShell over stdin, not the command line
The Windows keyring (DpapiFileStore) interpolated the secret into the
PowerShell -Command string with JSON.stringify. A token was therefore
visible in the process list and in process-creation auditing for the
lifetime of the call, and PowerShell's own parsing could still mangle
quotes, backticks and $() inside the quoted value.

The secret now goes over stdin: the fixed command sets UTF-8 on both
console streams, reads stdin to the end, runs the protect or unprotect
step on that value and writes the result as compact JSON, which the
caller parses. Unprotect zeroes and frees the BSTR and disposes the
SecureString after reading. Any subprocess failure, non-zero exit or
non-string result is reported as "Windows credential protection failed;
check PowerShell and your Windows profile", never with the arguments.

Tests (win32 only): a round trip of a value with fake JSON, shell
syntax, Unicode and surrounding whitespace that must not appear in the
secrets file in clear, and a corrupt blob that must fail with the
redacted message. Run with QQQ_POWERSHELL=pwsh where Windows PowerShell
5.1 cannot load Microsoft.PowerShell.Security.

Carried locally by GG Forge since cli-v1.0.0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-11-06 08:44:17 -06:00