Repository navigation
feat(scripts): workspace-misroute guard (fail loud in the wrong checkout) — #679 D3 - #680
Conversation
…rong checkout (#679 D3) The session launcher bound a TrustShell task to the old `ihh-ica` project while the working dir was the `trustshell-tl` worktree, so `start_session` resolved the wrong repo and work risked landing (or silently failing) against the wrong history. The session-binding itself is app-level config this script cannot change; this is the in-repo assertion so any loop or session can gate its own work at startup and fail LOUDLY instead of silently misrouting: node scripts/check-workspace.cjs # expects origin = DealAppSeo/trustshell node scripts/check-workspace.cjs owner/repo # expects that repo TRUSTSHELL_EXPECT_REPO=owner/repo ... # or via env workspaceMatches() is pure (https/ssh, optional .git / trailing slash, case-insensitive); main() takes an injected shell so the test asserts both exit paths without depending on the checkout it runs in. Exit 0 on match, 1 (loud stderr) on misroute. No deps. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🧾 Receipt1 claim(s) checked against GitHub's own check runs on
What this receipt does not prove
Receipts by TrustShell. Read-only: it reads this PR and its check runs, and writes only this comment. No code leaves the repo. |
Strix Security ReviewTo fix before merging (1)
PR summary: Adds an opt-in guard script that checks the checkout's git origin matches an expected `owner/repo` and exits 1 with a loud message otherwise, plus a unit test and an `npm run check:workspace` alias. Review summaryThis PR adds a small, dependency-free workspace guard ( Ruled out (1)
Fixed the findings? re-run the review, or tag Review 1 on this PR · Reviewed by Strix |
| process.stderr.write( | ||
| `WORKSPACE MISROUTED: origin is "${remote || '(none)'}", expected ${expected}. ` + | ||
| `Refusing repo-specific work in the wrong checkout — cd to the ${expected} worktree first.\n`, | ||
| ); |
There was a problem hiding this comment.
🟢 Workspace guard prints credential-bearing git remote URL verbatim to stderr
Severity: LOW · CWE-532
The new workspace guard writes the raw output of git remote get-url origin into its failure message, so any credentials embedded in the remote URL are disclosed in full. When a checkout is cloned with a token in the URL — the common https://x-access-token:$TOKEN@github.com/owner/repo.git form used by CI runners and agent sandboxes — and the guard fires on a misroute, the token is emitted to stderr, which is captured in terminal scrollback, CI job logs, and agent session transcripts that are read by more parties than the local git config. The script exists precisely to run at task startup in automated sessions, and the misroute path is the one that fires in misconfigured environments, so the leak lands in exactly the logs that get shared and retained. The repository's other tooling already redacts secrets and strips tokens before logging, so this path is inconsistent with the project's own hygiene standard.
| process.stderr.write( | |
| `WORKSPACE MISROUTED: origin is "${remote || '(none)'}", expected ${expected}. ` + | |
| `Refusing repo-specific work in the wrong checkout — cd to the ${expected} worktree first.\n`, | |
| ); | |
| process.stderr.write( | |
| `WORKSPACE MISROUTED: origin is "${redactRemote(remote)}", expected ${expected}. ` + | |
| `Refusing repo-specific work in the wrong checkout — cd to the ${expected} worktree first.\n`, | |
| ); |
Prompt to fix with AI
This is a security vulnerability found during a code review.
Vulnerability: Workspace guard prints credential-bearing git remote URL verbatim to stderr
Severity: LOW
CWE: CWE-532
The new workspace guard writes the raw output of `git remote get-url origin` into its failure message, so any credentials embedded in the remote URL are disclosed in full. When a checkout is cloned with a token in the URL — the common `https://x-access-token:$TOKEN@github.com/owner/repo.git` form used by CI runners and agent sandboxes — and the guard fires on a misroute, the token is emitted to stderr, which is captured in terminal scrollback, CI job logs, and agent session transcripts that are read by more parties than the local git config. The script exists precisely to run at task startup in automated sessions, and the misroute path is the one that fires in misconfigured environments, so the leak lands in exactly the logs that get shared and retained. The repository's other tooling already redacts secrets and strips tokens before logging, so this path is inconsistent with the project's own hygiene standard.
Location: scripts/check-workspace.cjs:36-39
Context: Failure message echoes the raw remote URL, including any embedded credentials
```
// Before:
process.stderr.write(
`WORKSPACE MISROUTED: origin is "${remote || '(none)'}", expected ${expected}. ` +
`Refusing repo-specific work in the wrong checkout — cd to the ${expected} worktree first.\n`,
);
// After:
process.stderr.write(
`WORKSPACE MISROUTED: origin is "${redactRemote(remote)}", expected ${expected}. ` +
`Refusing repo-specific work in the wrong checkout — cd to the ${expected} worktree first.\n`,
);
```
Location: scripts/check-workspace.cjs:19-25
Context: Adds the redaction helper used by the failure message
```
// Before:
/** PURE. Does a git remote URL (https or ssh, optional `.git` / trailing slash) point at `owner/repo`? */
function workspaceMatches(remoteUrl, expected) {
const m = String(remoteUrl)
.trim()
.match(/github\.com[/:]([^/\s]+\/[^/\s]+?)(?:\.git)?\/?$/i);
return m ? m[1].toLowerCase() === String(expected).toLowerCase() : false;
}
// After:
/** PURE. Does a git remote URL (https or ssh, optional `.git` / trailing slash) point at `owner/repo`? */
function workspaceMatches(remoteUrl, expected) {
const m = String(remoteUrl)
.trim()
.match(/github\.com[/:]([^/\s]+\/[^/\s]+?)(?:\.git)?\/?$/i);
return m ? m[1].toLowerCase() === String(expected).toLowerCase() : false;
}
/** Strip any credentials embedded in a remote URL before it is written to a log. */
function redactRemote(remoteUrl) {
const s = String(remoteUrl || '').trim();
return s ? s.replace(/\/\/[^/@\s]+@/, '//<redacted>@') : '(none)';
}
```
How to fix:
Redact the userinfo component of the remote URL before it is written to stderr, or print only the parsed `owner/repo` (which is all the message needs to be actionable). Apply the same redaction to any other diagnostic that echoes a remote URL, and consider a shared helper so future scripts cannot reintroduce the pattern.
Please fix this vulnerability. If you propose a fix, make it concise and minimal.React 👍 / 👎 to tune Strix for this repo. A repo collaborator (or the PR author) can resolve this thread to dismiss the finding.
| /** PURE. Does a git remote URL (https or ssh, optional `.git` / trailing slash) point at `owner/repo`? */ | ||
| function workspaceMatches(remoteUrl, expected) { | ||
| const m = String(remoteUrl) | ||
| .trim() | ||
| .match(/github\.com[/:]([^/\s]+\/[^/\s]+?)(?:\.git)?\/?$/i); | ||
| return m ? m[1].toLowerCase() === String(expected).toLowerCase() : false; | ||
| } |
There was a problem hiding this comment.
Adds the redaction helper used by the failure message
| /** PURE. Does a git remote URL (https or ssh, optional `.git` / trailing slash) point at `owner/repo`? */ | |
| function workspaceMatches(remoteUrl, expected) { | |
| const m = String(remoteUrl) | |
| .trim() | |
| .match(/github\.com[/:]([^/\s]+\/[^/\s]+?)(?:\.git)?\/?$/i); | |
| return m ? m[1].toLowerCase() === String(expected).toLowerCase() : false; | |
| } | |
| /** PURE. Does a git remote URL (https or ssh, optional `.git` / trailing slash) point at `owner/repo`? */ | |
| function workspaceMatches(remoteUrl, expected) { | |
| const m = String(remoteUrl) | |
| .trim() | |
| .match(/github\.com[/:]([^/\s]+\/[^/\s]+?)(?:\.git)?\/?$/i); | |
| return m ? m[1].toLowerCase() === String(expected).toLowerCase() : false; | |
| } | |
| /** Strip any credentials embedded in a remote URL before it is written to a log. */ | |
| function redactRemote(remoteUrl) { | |
| const s = String(remoteUrl || '').trim(); | |
| return s ? s.replace(/\/\/[^/@\s]+@/, '//<redacted>@') : '(none)'; | |
| } |
What
A small, dependency-free guard script + test that lets any loop or session assert it is running in the right checkout before doing repo-specific work, and fail loudly if not.
Why
During the #679 sprint the session launcher bound a TrustShell task to the old
ihh-icaproject while the working directory was thetrustshell-tlworktree — sostart_sessionresolved the wrong repo and work risked landing (or silently failing) against the wrong history. The session-binding itself is app-level config a repo script can't change; this is the durable in-repo assertion so the misroute is caught at task startup instead of discovered later.How it's safe to review
workspaceMatches(remoteUrl, expected)is pure — https/ssh forms, optional.git/ trailing slash, case-insensitive. Unit-tested against the ihh-ica misroute, a different repo, and empty/garbage remotes.main(argv, env, run)takes an injected shell, so the test asserts both exit paths (0 on match, 1 on misroute) without depending on the checkout it runs in.scripts/,tests/, and onepackage.jsonalias (npm run check:workspace). Nodist/, no runtime code.Verification
npx jest tests/check-workspace.test.ts→ 3/3 pass0(origin = trustshell); withDealAppSeo/ihh-icaexpected → exit1with the loud stderr message.Draft — part of the #679 developer-trust sprint (DECISION 3). Independent of the security fixes in #679.
🤖 Generated with Claude Code