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
10 changes: 9 additions & 1 deletion bin/contributor-relay.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2301,7 +2301,15 @@ function handleMessage(data, hub) {
injectGhToken(msg.github_token);
tokenExpiresAt = msg.token_expires_at ? new Date(msg.token_expires_at).getTime() : null;
}
fs.writeFileSync(TASK_FILE, JSON.stringify(msg, null, 2));
// TASK_FILE is observability/debug state with no reader that needs the
// credential; the live token's one legitimate on-disk home is the 0600
// GH_TOKEN_CACHE written by injectGhToken above. Strip it and keep the
// file owner-only (chmod covers overwriting a pre-existing 0644 file)
// so a task-scoped GitHub token never sits world-readable under /tmp
// (kubestellar/hive#5065).
const { github_token: _omittedToken, ...taskFileRecord } = msg;
fs.writeFileSync(TASK_FILE, JSON.stringify(taskFileRecord, null, 2), { mode: 0o600 });
try { fs.chmodSync(TASK_FILE, 0o600); } catch (_) { /* content is already token-free */ }
send({ type: 'task_accepted', seq: nextSeq(), task_id: msg.task_id, task_gen: msg.task_gen });
if (CONTRIBUTOR_MODE === MODE_HEADLESS) {
// Non-interactive path (kubestellar/hive#2538): drive a one-shot CLI
Expand Down
29 changes: 29 additions & 0 deletions bin/contributor-relay.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,35 @@ test('task_assign queues rather than typing when the CLI is not ready', () => {
} finally { teardown(relay); }
});

test('task_assign never persists github_token to the task file (kubestellar/hive#5065)', () => {
const relay = loadRelay({ backend: 'copilot' });
try {
relay.setCliReady(false);
relay.setPendingTask(null);
relay.handleMessage(JSON.stringify({
type: 'task_assign',
task_id: 'ct-token-1',
kind: 'issue',
repo: 'foo/bar',
number: 422,
title: 'token hygiene',
prompt: 'do a thing',
github_token: `ghs_${'a'.repeat(36)}`,
token_expires_at: '2099-01-01T00:00:00Z',
}));

const taskFile = path.join(relay.__tmpDir, 'contributor-task.json');
const raw = fs.readFileSync(taskFile, 'utf8');
assert.ok(!raw.includes('ghs_'), 'task file must not contain the credential value');
const persisted = JSON.parse(raw);
assert.ok(!('github_token' in persisted), 'github_token key must be stripped from the task file');
assert.strictEqual(persisted.token_expires_at, '2099-01-01T00:00:00Z',
'non-secret task fields must survive the strip');
const mode = fs.statSync(taskFile).mode & 0o777;
assert.strictEqual(mode, 0o600, `task file must be owner-only, got 0o${mode.toString(8)}`);
} finally { teardown(relay); }
});

test('auth_response includes optional HIVE_AGENT_ROLE', () => {
const relay = loadRelay({ env: { HIVE_AGENT_ROLE: 'scanner' } });
try {
Expand Down
Loading