Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ Each release of Research-Kit, newest first. The version is `KIT_VERSION` in
`research-kit/lib/core.mjs`; a release is tagged `v<version>` (ADR-0116).

## Unreleased
- Rejected collection URLs pass through the existing credential redactor before
MCP tool results or `collect-remote` stderr can echo them (#378). Regression
coverage includes caller credentials in URL userinfo and query parameters.

- Fixed `AGENTS.md` corpus description to use evergreen relative references rather than stale hardcoded entry counts (#299).

- The suite's "every CLI refuses a flag it does not know" table now covers `measure`,
Expand Down
4 changes: 2 additions & 2 deletions docs/ARCHITECTURE.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion research-kit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,7 @@ node research-kit/bin/selftest.mjs # all of it
node research-kit/bin/selftest.mjs gate hook # just these files
```

1789 tests, offline, no key and no network. The runner **awaits** every test, so `ok` means
1791 tests, offline, no key and no network. The runner **awaits** every test, so `ok` means
the assertions settled (ADR-0021), and each test is raced against a watchdog
(`RESEARCH_KIT_TEST_TIMEOUT`, default 60s).

Expand Down
2 changes: 1 addition & 1 deletion research-kit/bin/collect-remote.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ checkFlagValues(flags, {
// token would otherwise hide it, and a dispatched bad URL would be searched as text.
const queriesValue = queriesInput({ queries: flagList(flags.query), urls: flagList(flags.url) });
if (queriesValue.error) {
process.stderr.write(`${queriesValue.error}\n fix: --url takes a page address starting with https://\n`);
process.stderr.write(`${redact(queriesValue.error)}\n fix: --url takes a page address starting with https://\n`);
process.exit(EXIT.CANNOT_START);
}

Expand Down
2 changes: 1 addition & 1 deletion research-kit/lib/mcp.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ async function callTool(message, deps) {
try {
if (name === 'collect') {
const queries = queriesInput({ queries: args.queries ?? [], urls: args.urls ?? [] });
if (queries.error) return ok(message.id, { isError: true, content: [text(`${queries.error}. urls takes page addresses starting with https://`)] });
if (queries.error) return ok(message.id, { isError: true, content: [text(redact(`${queries.error}. urls takes page addresses starting with https://`))] });
const started = await deps.dispatch({
repository: args.repository,
inputs: {
Expand Down
10 changes: 10 additions & 0 deletions research-kit/test/cli.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1581,3 +1581,13 @@ test('install-hooks --dry-run answers what the real run answers, deployed or not
const gitconfig = fs.existsSync(path.join(home, 'gitconfig')) ? fs.readFileSync(path.join(home, 'gitconfig'), 'utf8') : '';
assert.doesNotMatch(gitconfig, /hooksPath/, 'a dry run set core.hooksPath');
});

test('collect-remote redacts credentials in refused URLs', () => {
const credentials = [`ghp_${'z'.repeat(36)}`, `github_pat_${'B2'.repeat(20)}`];
const r = run('collect-remote.mjs', ['--repository', 'o/r', '--topic', 't',
'--url', `ftp://user:${credentials[0]}@a.example/`, '--url', `ftp://a.example/?token=${credentials[1]}`], { root: project() });
assert.equal(r.status, 3);
for (const credential of credentials) assert.ok(!r.all.includes(credential), 'a caller credential leaked');
assert.match(r.err, /redacted/);
assert.match(r.err, /not an http\(s\) URL/);
});
13 changes: 13 additions & 0 deletions research-kit/test/mcp.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -725,3 +725,16 @@ test('the server installs the closed-stdout guard before it writes its first byt
assert.ok(call !== -1, 'mcp-server.mjs no longer tolerates a closed stdout (2026-09-30)');
assert.ok(call < firstWrite, `the guard is installed after the first write (write at ${firstWrite}, guard at ${call})`);
});

test('refused URLs never echo caller credentials in a tool result', async () => {
const credentials = [`ghp_${'z'.repeat(36)}`, `github_pat_${'B2'.repeat(20)}`];
const d = deps();
const r = await handle(call('collect', { repository: 'o/r', topic: 'x', urls: [
`ftp://user:${credentials[0]}@a.example/`, `ftp://a.example/?token=${credentials[1]}`,
] }), d);
assert.equal(r.result.isError, true);
for (const credential of credentials) assert.ok(!JSON.stringify(r).includes(credential), 'a caller credential leaked');
assert.match(r.result.content[0].text, /redacted/);
assert.match(r.result.content[0].text, /not an http\(s\) URL/);
assert.equal(d.seen.dispatch, undefined, 'a refused URL must not start a run');
});
Loading