Skip to content

Surface broker push failures and keep the token out of argv - #220

Merged
s-hiraoku merged 2 commits into
mainfrom
broker-diagnostics-and-token-file
Aug 10, 2026
Merged

s-hiraoku merged 2 commits into
mainfrom
broker-diagnostics-and-token-file

Conversation

@s-hiraoku

@s-hiraoku s-hiraoku commented Aug 10, 2026 •

Copy link
Copy Markdown
Contributor

Two problems in the publication broker, both found while trying to complete a
smoke run against the pinned toolchain.

Closes #218.

1. A failed push was undiagnosable

The broker answered {"ok":false,"error":"git-failed"} and logged only
{"event":"request-refused","reason":"git-failed"}.

runChild already attaches git's full result to the error:

else if (result.code !== 0) reject(Object.assign(new RequestError('git-failed'), { result }));

…and the handler then threw that away, logging the reason code alone. The one
thing that explains the failure — git's stderr — never reached the operator or
the client.

Establishing anything about a refused push therefore required reading the
broker source, reconstructing the push by hand outside the broker, and
comparing. That exercise showed the push itself works: with the broker's exact
arguments, minimal environment, generated askpass helper and a valid token, the
same push succeeds. So the failure is specific to the broker's own execution
and remains unidentified — precisely because the stderr was dropped.

The handler now logs the exit code plus redacted stderr/stdout:

{"event":"request-refused","reason":"git-failed","exitCode":128,
 "stderr":"remote: Permission to owner/repo.git denied to x-access-token."}

Secrets are stripped first (gh*_, github_pat_, and any https://user@host
userinfo), because this output is exactly what an operator will paste into an
issue. Verified against sample git failures containing a token in the remote
URL — the credential is replaced while the diagnostic text survives.

2. The documented startup leaked the token through argv

ADOPTING.md instructed:

sudo env KAIZEN_PUBLICATION_BROKER_TOKEN="$(gh auth token)" \
  /usr/local/libexec/kaizen-publication-broker …

sudo env VAR=value places the token in the argument vector, which is
world-readable:

root 24320 sudo env KAIZEN_PUBLICATION_BROKER_TOKEN=gho_… /usr/local/libexec/…

This defeats the containment the broker is built to provide. The socket is
correctly restricted to root:<run-gid> and captureGitHubAuthEnv refuses
ambient GH_TOKEN/GITHUB_TOKEN for builder-capable processes — and then the
documented startup published the same credential to every process on the
machine, including the agents that are meant to be excluded.

The broker now accepts:

  • --token-file <path> — must be a regular file, owned by root, with no group
    or other permission bits. Rejected otherwise.
  • --token-stdin — for piping without storing.
  • KAIZEN_PUBLICATION_BROKER_TOKEN — still honoured for compatibility, but the
    broker logs token-source-insecure when it is used.

ADOPTING.md now documents the file-based form, and recommends a fine-grained
token scoped to the target repositories rather than an account-wide one.

Verification

  • onboarding/scripts/test-publication-broker.mjs — all 10 fixtures pass,
    including refused requests do not push or expose credentials.
  • --token-file permission checks exercised directly: a 0644 file and a
    non-root-owned file are both rejected.
  • Redaction exercised against three sample git failures; tokens in a remote URL
    and bare gh*_/github_pat_ values are replaced, and non-secret text such as
    fatal: could not read Username for https://github.com is preserved intact.
  • node --check on the patched file.

Not fixed here

#217 (workspaces created 0755 while the broker requires 0700) is
independent and still open. A run can work around it today by invoking Kaizen
under umask 077, which makes every directory Kaizen creates 0700; the
proper fix belongs in workspace/worktree creation in kaizen-loop.

Summary by CodeRabbit

  • Security Improvements

    • Credentials are now handled through secure token files or standard input.
    • Token files are validated for appropriate ownership and permissions.
    • Sensitive credentials are redacted from command output and error logs.
    • Repository-scoped, least-privilege credentials are recommended.
  • Documentation

    • Added guidance for secure token setup.
    • Marked the environment-based token option as deprecated due to potential credential exposure.
    • Expanded troubleshooting information with sanitized request failure details.

A failed push returned only "git-failed". runChild attaches git's stderr to
the error, but the handler logged nothing but the reason code and returned the
same bare code to the client, so the single most useful description of the
failure was discarded. Diagnosing a refused publication required reading the
broker source and reproducing the push by hand outside the broker. The handler
now logs the exit code and the redacted stderr and stdout.

Secrets are stripped from that output before it is logged, since an operator is
likely to paste it into an issue.

The documented startup used `sudo env KAIZEN_PUBLICATION_BROKER_TOKEN=...`,
which places the credential in the argument vector, where any local process can
read it through ps. That defeats the containment the broker exists to provide:
the socket is correctly restricted and captureGitHubAuthEnv refuses ambient
tokens for builder-capable processes, and then the startup command published
the same credential to every process on the machine. The broker now reads the
token from a root-owned 0600 file (--token-file) or stdin (--token-stdin), and
warns when the deprecated environment variable is used.

Refs #217, #218
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@s-hiraoku, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 49 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository: kaizen-agents-org/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4365feff-b40a-4fa6-88cf-b52a37b6d68e

📥 Commits

Reviewing files that changed from the base of the PR and between 33a3a99 and 562c69b.

📒 Files selected for processing (2)
  • onboarding/ADOPTING.md
  • onboarding/scripts/kaizen-publication-broker.mjs
📝 Walkthrough

Walkthrough

The broker now reads GitHub tokens from a root-owned file or stdin. It retains the environment variable as a deprecated fallback, validates token content, and redacts credentials from Git and refusal diagnostics. Onboarding instructions use the protected file method.

Changes

Secure token handling

Layer / File(s) Summary
Token source interface
onboarding/ADOPTING.md, onboarding/scripts/kaizen-publication-broker.mjs
The broker accepts --token-file and --token-stdin. Documentation uses a root-owned token file, describes stdin input, and deprecates KAIZEN_PUBLICATION_BROKER_TOKEN.
Token loading and redacted diagnostics
onboarding/scripts/kaizen-publication-broker.mjs
Token files must be root-owned, regular, and inaccessible to group and other users. Token validation rejects empty values, line breaks, and NUL characters. Git output and refusal diagnostics redact credentials and limit sanitized output to 4000 characters.

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

Possibly related PRs

🚥 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 The title clearly summarizes the main changes: safer token handling and improved broker push failure reporting.
Linked Issues check ✅ Passed The documentation and broker changes address issue #218 by removing sudo env usage and adding file and stdin token sources.
Out of Scope Changes check ✅ Passed The token validation, secret redaction, failure diagnostics, and documentation updates support the linked issue and PR objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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 broker-diagnostics-and-token-file

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.

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
onboarding/scripts/kaizen-publication-broker.mjs (1)

75-94: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject conflicting explicit token sources.

Lines 75 and 94 allow --token-stdin and --token-file together. readBrokerToken() then silently selects the file. Reject this combination so the operator cannot believe that stdin supplies the active token.

Proposed fix
   if (options.socketGid === undefined) options.socketGid = options.runGid;
+  if (options.tokenFile && options.tokenStdin) {
+    throw new Error('--token-file and --token-stdin are mutually exclusive');
+  }
   if (!options.testMode && options.socketGid !== options.runGid) {

As per path instructions, prioritize correctness, async error handling, CLI behavior, test coverage, and backward-compatible public contracts.

🤖 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 `@onboarding/scripts/kaizen-publication-broker.mjs` around lines 75 - 94,
Update the argument parsing logic to reject using --token-stdin together with
--token-file, regardless of their order. Add the conflict validation around the
existing options.tokenStdin and options.tokenFile handling, while preserving
each option’s standalone behavior and the existing readBrokerToken contract.

Source: Path instructions

🤖 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 `@onboarding/ADOPTING.md`:
- Around line 97-98: Update the --token-stdin example in ADOPTING.md to invoke
the installed broker at /usr/local/libexec/kaizen-publication-broker, matching
the installation path documented earlier while preserving the existing
token-piping usage.

In `@onboarding/scripts/kaizen-publication-broker.mjs`:
- Around line 403-410: Update the token-file handling around options.tokenFile
to open the path once with O_NOFOLLOW, reject symbolic links, and validate
ownership, regular-file status, and permissions using fstatSync() on that same
descriptor before reading from it. Replace the separate statSync/readFileSync
path with descriptor-based validation and reading, while preserving the existing
token trimming and error messages.

---

Outside diff comments:
In `@onboarding/scripts/kaizen-publication-broker.mjs`:
- Around line 75-94: Update the argument parsing logic to reject using
--token-stdin together with --token-file, regardless of their order. Add the
conflict validation around the existing options.tokenStdin and options.tokenFile
handling, while preserving each option’s standalone behavior and the existing
readBrokerToken contract.
🪄 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: Repository: kaizen-agents-org/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4cca1c19-9a26-4fd9-9d99-85e265693f63

📥 Commits

Reviewing files that changed from the base of the PR and between 0cd27d7 and 33a3a99.

📒 Files selected for processing (2)
  • onboarding/ADOPTING.md
  • onboarding/scripts/kaizen-publication-broker.mjs

Comment thread onboarding/ADOPTING.md Outdated
Comment thread onboarding/scripts/kaizen-publication-broker.mjs Outdated
statSync follows symlinks and readFileSync re-resolves the path, so the file
that was validated need not be the file that is read. Open once with
O_NOFOLLOW, validate that descriptor with fstat, and read from it.

Also use the installed broker path in the --token-stdin example.
@s-hiraoku

Copy link
Copy Markdown
Contributor Author

PR Guardian

Mergeable, with one caveat recorded below.

Feedback addressed

# finding disposition
1 --token-file validated with statSync then re-read by path (TOCTOU + symlink follow) — 🟠 Major fixed in 562c69b
2 --token-stdin example used a bare command name, not the installed path fixed in 562c69b

Both replied to in-thread and resolved. The first was a genuine security defect in code I added in this PR: validating the path and then reading it again checks one file and reads another if the path is swapped in between, and statSync would follow a planted symlink. Now opened once with O_RDONLY | O_NOFOLLOW, validated via fstatSync on that descriptor, and read from the same descriptor.

Verified after the fix:

  • a symlink pointing at a valid root-owned 0600 file is rejected with ELOOP
  • a 0644 file is still rejected on the ownership check
  • all 10 fixtures in test-publication-broker.mjs pass
  • node --check clean

Final gate

Two snapshots 35s apart, identical:

head        562c69b (unchanged)
isDraft     false
mergeable   MERGEABLE
mergeState  CLEAN
checks      CodeRabbit=SUCCESS, Verify organization contracts=SUCCESS
unresolved  0

Base is current with main (0cd27d7), which matters because this branch protection sets strict: true.

Caveat: CodeRabbit did not re-review the fix commit

Its check reports SUCCESS but with the message "Review rate limited", so it reported success without actually examining 562c69b — the commit containing the security fix it asked for. The two findings it raised on 33a3a99 are fixed and resolved, but its confirmation of those fixes is absent rather than positive.

Flagging rather than treating a rate-limited pass as a clean review. If you would prefer a fresh bot review before merging, @coderabbitai review once its limit resets. Codex is also unavailable (account usage limit), so neither bot has inspected the current head.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

@s-hiraoku: Acknowledged. I will perform a fresh review of the current head, including the descriptor-based --token-file validation and the updated startup documentation.


✏️ Learnings added
Learnt from: s-hiraoku
URL: https://github.com/kaizen-agents-org/.github/pull/220

Timestamp: 2026-08-10T09:15:00.724Z
Learning: In `onboarding/scripts/kaizen-publication-broker.mjs`, secure `--token-file` handling must open the token file with `O_RDONLY | O_NOFOLLOW`, validate the opened descriptor with `fstatSync`, and read from that same descriptor. Do not validate a path and then re-open or re-read that path, because the path can be replaced between validation and use.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

⚠️ 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.

@s-hiraoku
s-hiraoku merged commit 349d019 into main Aug 10, 2026
2 checks passed
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.

Documented broker startup leaks the GitHub token through argv (visible in ps)

1 participant