mirror of https://github.com/garrytan/gstack.git
fix(pair-agent): keep the ngrok authtoken out of the transcript and shell argv
The not-authed flow told the user to paste their ngrok authtoken into the chat so the agent could run `ngrok config add-authtoken` — putting a live credential in the transcript, tool-call argv, and anything the transcript syncs to. The user now runs the auth command in their own terminal; the agent only verifies via `ngrok config check`, and a pasted token triggers a rotate-and-reauth instruction. A static test pins that no agent-run bash fence ever contains add-authtoken again. Fixes #2335. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
888603f570
commit
5c9994a596
|
|
@ -960,23 +960,34 @@ Then tell the user:
|
|||
"Copy the block above and paste it into your other agent's chat. The setup key
|
||||
expires in 5 minutes."
|
||||
|
||||
**If ngrok is installed but NOT authed:** Walk the user through authentication:
|
||||
**If ngrok is installed but NOT authed:** Walk the user through authentication.
|
||||
|
||||
SECURITY: the ngrok authtoken must NEVER pass through this chat, a Bash tool
|
||||
call, or shell history — a token pasted here lands in the transcript (and
|
||||
anything the transcript syncs to). The user runs the auth command in their
|
||||
OWN terminal; you only verify the result.
|
||||
|
||||
Tell the user:
|
||||
"ngrok is installed but not logged in. Let's fix that:
|
||||
"ngrok is installed but not logged in. Let's fix that — in your own terminal
|
||||
(not here; the token should never enter this chat):
|
||||
|
||||
1. Go to https://dashboard.ngrok.com/get-started/your-authtoken
|
||||
2. Copy your auth token
|
||||
3. Come back here and I'll run the auth command for you."
|
||||
3. In YOUR terminal, run: ngrok config add-authtoken <paste your token>
|
||||
4. Tell me 'done' when finished."
|
||||
|
||||
STOP here and wait for the user to provide their auth token.
|
||||
STOP here and wait for the user to say they've run it. Do NOT accept a pasted
|
||||
token; if the user pastes one anyway, tell them to rotate it at
|
||||
https://dashboard.ngrok.com (it's now in the transcript) and re-auth in their
|
||||
terminal with the new one.
|
||||
|
||||
When they provide it, run:
|
||||
When they say done, verify without touching the token:
|
||||
```bash
|
||||
ngrok config add-authtoken THEIR_TOKEN
|
||||
ngrok config check 2>/dev/null && echo "NGROK_AUTHED" || echo "NGROK_NOT_AUTHED"
|
||||
```
|
||||
|
||||
Then retry `$B pair-agent --client TARGET_HOST`.
|
||||
If `NGROK_AUTHED`: retry `$B pair-agent --client TARGET_HOST`.
|
||||
If still `NGROK_NOT_AUTHED`: ask them to re-run the command in their terminal.
|
||||
|
||||
**If ngrok is NOT installed:** Walk the user through installation:
|
||||
|
||||
|
|
|
|||
|
|
@ -155,23 +155,34 @@ Then tell the user:
|
|||
"Copy the block above and paste it into your other agent's chat. The setup key
|
||||
expires in 5 minutes."
|
||||
|
||||
**If ngrok is installed but NOT authed:** Walk the user through authentication:
|
||||
**If ngrok is installed but NOT authed:** Walk the user through authentication.
|
||||
|
||||
SECURITY: the ngrok authtoken must NEVER pass through this chat, a Bash tool
|
||||
call, or shell history — a token pasted here lands in the transcript (and
|
||||
anything the transcript syncs to). The user runs the auth command in their
|
||||
OWN terminal; you only verify the result.
|
||||
|
||||
Tell the user:
|
||||
"ngrok is installed but not logged in. Let's fix that:
|
||||
"ngrok is installed but not logged in. Let's fix that — in your own terminal
|
||||
(not here; the token should never enter this chat):
|
||||
|
||||
1. Go to https://dashboard.ngrok.com/get-started/your-authtoken
|
||||
2. Copy your auth token
|
||||
3. Come back here and I'll run the auth command for you."
|
||||
3. In YOUR terminal, run: ngrok config add-authtoken <paste your token>
|
||||
4. Tell me 'done' when finished."
|
||||
|
||||
STOP here and wait for the user to provide their auth token.
|
||||
STOP here and wait for the user to say they've run it. Do NOT accept a pasted
|
||||
token; if the user pastes one anyway, tell them to rotate it at
|
||||
https://dashboard.ngrok.com (it's now in the transcript) and re-auth in their
|
||||
terminal with the new one.
|
||||
|
||||
When they provide it, run:
|
||||
When they say done, verify without touching the token:
|
||||
```bash
|
||||
ngrok config add-authtoken THEIR_TOKEN
|
||||
ngrok config check 2>/dev/null && echo "NGROK_AUTHED" || echo "NGROK_NOT_AUTHED"
|
||||
```
|
||||
|
||||
Then retry `$B pair-agent --client TARGET_HOST`.
|
||||
If `NGROK_AUTHED`: retry `$B pair-agent --client TARGET_HOST`.
|
||||
If still `NGROK_NOT_AUTHED`: ask them to re-run the command in their terminal.
|
||||
|
||||
**If ngrok is NOT installed:** Walk the user through installation:
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
import { describe, test, expect } from 'bun:test';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
// Token hygiene for /pair-agent (#2335): the ngrok authtoken must never walk
|
||||
// through the chat transcript or a Bash tool call. The skill may INSTRUCT the
|
||||
// user to run `ngrok config add-authtoken` in their own terminal, but no
|
||||
// agent-executed bash fence may contain the command — an agent-run
|
||||
// `ngrok config add-authtoken THEIR_TOKEN` means the token arrived via the
|
||||
// transcript and landed in shell argv/history.
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
|
||||
function fencedBashBlocks(markdown: string): string[] {
|
||||
// Line-based fence walk: a naive /```...```/ regex mis-pairs a CLOSING
|
||||
// fence with the next block's opener and swallows the prose in between.
|
||||
const blocks: string[] = [];
|
||||
let inFence = false;
|
||||
let lang = '';
|
||||
let current: string[] = [];
|
||||
for (const line of markdown.split('\n')) {
|
||||
if (line.trimStart().startsWith('```')) {
|
||||
if (!inFence) {
|
||||
inFence = true;
|
||||
lang = line.trim().slice(3).trim().toLowerCase();
|
||||
current = [];
|
||||
} else {
|
||||
inFence = false;
|
||||
if (lang === '' || lang === 'bash' || lang === 'sh') blocks.push(current.join('\n'));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (inFence) current.push(line);
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
describe('pair-agent ngrok token hygiene (#2335)', () => {
|
||||
const files = ['pair-agent/SKILL.md', 'pair-agent/SKILL.md.tmpl'];
|
||||
|
||||
test.each(files)('%s: no agent-run bash fence contains add-authtoken', (rel) => {
|
||||
const content = fs.readFileSync(path.join(ROOT, rel), 'utf-8');
|
||||
for (const block of fencedBashBlocks(content)) {
|
||||
expect(block).not.toContain('add-authtoken');
|
||||
}
|
||||
});
|
||||
|
||||
test.each(files)('%s: instructs that the token never enters the chat', (rel) => {
|
||||
const content = fs.readFileSync(path.join(ROOT, rel), 'utf-8');
|
||||
expect(content.toLowerCase()).toContain('never enter this chat');
|
||||
// The recovery path for an accidentally pasted token must exist.
|
||||
expect(content.toLowerCase()).toContain('rotate');
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue