@firefunc-agent/runner 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/job.js CHANGED
@@ -1,4 +1,4 @@
1
- import { mkdtempSync, rmSync, existsSync, mkdirSync, writeFileSync, copyFileSync } from 'node:fs';
1
+ import { mkdtempSync, rmSync, existsSync, mkdirSync, writeFileSync, copyFileSync, appendFileSync, chmodSync, readdirSync, } from 'node:fs';
2
2
  import { tmpdir } from 'node:os';
3
3
  import { join, basename, dirname } from 'node:path';
4
4
  import { exec, execOut, spawnBackground, killProcessTree } from './exec.js';
@@ -13,9 +13,20 @@ const gitMutex = createKeyedMutex();
13
13
  function saveSessionLog(runId, stdout, stderr) {
14
14
  try {
15
15
  const dir = join(dirname(configPath()), 'logs');
16
- mkdirSync(dir, { recursive: true });
16
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
17
+ try {
18
+ chmodSync(dir, 0o700);
19
+ for (const f of readdirSync(dir)) {
20
+ if (f.endsWith('.log'))
21
+ chmodSync(join(dir, f), 0o600);
22
+ }
23
+ }
24
+ catch {
25
+ }
17
26
  const p = join(dir, `${runId}.log`);
18
- writeFileSync(p, `=== agent stdout ===\n${stdout}\n\n=== agent stderr ===\n${stderr}\n`);
27
+ writeFileSync(p, redact(`=== agent stdout ===\n${stdout}\n\n=== agent stderr ===\n${stderr}\n`), {
28
+ mode: 0o600,
29
+ });
19
30
  return p;
20
31
  }
21
32
  catch {
@@ -221,9 +232,20 @@ export async function runJob(job, cfg, api, viewer, opts) {
221
232
  log(`session dev-server PORT=${opts.devServerPort} (parallel-safe)`);
222
233
  }
223
234
  if (cfg.envFile) {
235
+ try {
236
+ const gitDir = (await execOut('git', ['-C', worktree, 'rev-parse', '--git-dir'])).trim();
237
+ if (gitDir) {
238
+ const abs = gitDir.startsWith('/') ? gitDir : join(worktree, gitDir);
239
+ mkdirSync(join(abs, 'info'), { recursive: true });
240
+ appendFileSync(join(abs, 'info', 'exclude'), '\n# FireFunc: never commit the injected env file\n.env\n');
241
+ }
242
+ }
243
+ catch (e) {
244
+ log(`could not write git exclude for .env: ${e.message}`);
245
+ }
224
246
  try {
225
247
  copyFileSync(cfg.envFile, join(worktree, '.env'));
226
- log(`copied ${cfg.envFile} → worktree/.env`);
248
+ log(`copied ${cfg.envFile} → worktree/.env (excluded from commits)`);
227
249
  }
228
250
  catch (e) {
229
251
  log(`envFile copy failed (${cfg.envFile}): ${e.message}`);
@@ -337,7 +359,7 @@ export async function runJob(job, cfg, api, viewer, opts) {
337
359
  : `${engineName} exited abnormally (code ${engineRun.code}). ${engineRun.stderr.slice(0, 300)}`);
338
360
  }
339
361
  const title = job.title ? `fix: ${job.title}` : `fix: ${job.externalId ?? 'FireFunc auto-fix'}`;
340
- await exec('git', ['-C', worktree, 'add', '-A']);
362
+ await exec('git', ['-C', worktree, 'add', '-A', '--', '.', ':(exclude).env']);
341
363
  const vsBase = await exec('git', [
342
364
  '-C',
343
365
  worktree,
@@ -369,6 +391,22 @@ export async function runJob(job, cfg, api, viewer, opts) {
369
391
  const gitCred = job.repo ? await api.gitCredential(job.runId) : null;
370
392
  if (gitCred)
371
393
  log(`using a FireFunc-issued git credential for ${gitCred.repo}`);
394
+ const staged = await execOut('git', [
395
+ '-C',
396
+ worktree,
397
+ 'show',
398
+ '--name-only',
399
+ '--format=',
400
+ 'HEAD',
401
+ ]);
402
+ const leaked = staged
403
+ .split('\n')
404
+ .map((l) => l.trim())
405
+ .filter((l) => l === '.env' || l.endsWith('/.env'));
406
+ if (leaked.length > 0) {
407
+ return fail(`refusing to push: the commit contains ${leaked.join(', ')}, which can hold live credentials. ` +
408
+ `This is a FireFunc bug — please report it. Nothing was pushed.`);
409
+ }
372
410
  const push = gitCred
373
411
  ? await pushBranch(worktree, branch, gitCred.repo, gitCred)
374
412
  : await exec('git', ['-C', worktree, 'push', '--force-with-lease', 'origin', `${branch}:${branch}`], { timeoutMs: 120_000 });
package/dist/viewer.js CHANGED
@@ -11,6 +11,10 @@ export function createSessionViewer(opts) {
11
11
  const port = opts.port ?? 8787;
12
12
  let url = null;
13
13
  const server = createServer((req, res) => {
14
+ const host = req.headers.host;
15
+ if (host && host !== `127.0.0.1:${port}` && host !== `localhost:${port}`) {
16
+ return void res.writeHead(403, { 'content-type': 'text/plain' }).end('forbidden');
17
+ }
14
18
  const path = new URL(req.url ?? '/', 'http://127.0.0.1').pathname;
15
19
  if (path === '/healthz')
16
20
  return void res.writeHead(200).end('ok');
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@firefunc-agent/runner",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "private": false,
5
5
  "type": "module",
6
- "description": "FireFunc self-hosted runner an always-on daemon that runs YOUR local coding agent (Claude Code, Codex, Gemini CLI, Cursor CLI) on work FireFunc routes to it, opens pull requests, and reports back. Your machine, your subscriptions.",
6
+ "description": "FireFunc self-hosted runner \u2014 an always-on daemon that runs YOUR local coding agent (Claude Code, Codex, Gemini CLI, Cursor CLI) on work FireFunc routes to it, opens pull requests, and reports back. Your machine, your subscriptions.",
7
7
  "keywords": [
8
8
  "firefunc",
9
9
  "runner",
@@ -35,7 +35,7 @@
35
35
  "dev": "tsx src/cli.ts",
36
36
  "start": "tsx src/cli.ts start",
37
37
  "build": "npm run clean && tsc -p .",
38
- "prepack": "npm run build && node -e \"const{existsSync}=require('fs');if(!existsSync('dist/cli.js')){console.error('dist/cli.js is missing refusing to pack a package with no code in it');process.exit(1)}\"",
38
+ "prepack": "npm run build && node -e \"const{existsSync}=require('fs');if(!existsSync('dist/cli.js')){console.error('dist/cli.js is missing \u2014 refusing to pack a package with no code in it');process.exit(1)}\"",
39
39
  "lint": "echo '[runner] lint TBD'",
40
40
  "typecheck": "tsc -p . --noEmit",
41
41
  "test": "vitest run --passWithNoTests",