Skip to content

Codex/merge security fixes#25

Merged
rishabhcli merged 13 commits intomainfrom
codex/merge-security-fixes
Mar 17, 2026
Merged

Codex/merge security fixes#25
rishabhcli merged 13 commits intomainfrom
codex/merge-security-fixes

Conversation

@rishabhcli
Copy link
Copy Markdown
Owner

@rishabhcli rishabhcli commented Mar 17, 2026

Worked for 9m 18s
I merged the Codex branches into a single integration branch and pushed it to origin as codex/merge-security-fixes at 70eaae5.

I found 6 remote codex/* branches, not 5. One of them, origin/codex/fix-mobile-oauth-exchange-validation, was already patch-equivalent to a fix that had landed on origin/main, but I still merged it so all 6 are now ancestors of the integration branch. I kept main untouched.

Key conflict resolutions:

Combined the JWT secret hardening with the newer session code: production still requires SESSION_SECRET, tests keep a deterministic fallback, and dev gets a per-process random secret.
Preserved the secure, already-merged behavior that disables the deprecated mobile /api/auth/mobile/exchange endpoint.
Kept the run ownership protections for run listing and Browserbase session access.
Carried over the verifier command-injection hardening and aligned its tests.
Applied the untrusted-repo hardening to the current live code path by skipping install scripts and disabling build-script execution during code analysis.

Summary by cubic

Hardened security across verification, analysis, and auth to prevent command injection, path traversal, and untrusted script execution. Enforced run ownership and PKCE, and removed insecure session secret defaults; users now only see and access their own runs.

  • Bug Fixes

    • Verifier: replaced execSync with execFileSync, validated patch file paths stay within the repo, used git add --, and sanitized/limited commit messages; also switched TypeScript checks to execFileSync.
    • Analyzer: added allowBuildScripts flag; ad‑hoc runs set it to false to skip build checks for untrusted repos.
    • Git install: added --ignore-scripts to package manager install to block postinstall scripts.
    • API: GET /api/runs/[runId]/session now requires the requester to own the run; run creation stores ownerId; GET /api/runs filters by ownerId; cloud runs require a GitHub token.
    • OAuth: exchangeCodeForToken supports redirectUri, codeVerifier, and state; mobile app validates PKCE state and sends codeVerifier and state.
  • Migration

    • Set SESSION_SECRET in production; dev and test have safe defaults.

Written for commit 70eaae5. Summary will update on new commits.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added PKCE OAuth support for enhanced mobile authentication security
    • Implemented owner-based access control for runs; runs are now associated with their creators
  • Security & Bug Fixes

    • Added ability to skip build script execution for untrusted repositories
    • Enhanced OAuth to support additional authentication parameters
    • Improved path validation and commit message safety
    • Dependency installation now skips lifecycle scripts by default

rishabhcli and others added 13 commits March 17, 2026 10:58
…-auth-bypass' into codex/merge-security-fixes

# Conflicts:
#	lib/auth/session.ts
#	middleware.ts
…jection-vulnerability' into codex/merge-security-fixes

# Conflicts:
#	agents/verifier/index.ts
…ization-vulnerability' into codex/merge-security-fixes

# Conflicts:
#	app/api/runs/analyze/route.ts
#	app/api/runs/route.ts
…ity-vulnerability' into codex/merge-security-fixes

# Conflicts:
#	app/api/runs/route.ts
…erability' into codex/merge-security-fixes

# Conflicts:
#	app/api/auth/mobile/exchange/route.ts
Copilot AI review requested due to automatic review settings March 17, 2026 18:52
@rishabhcli rishabhcli merged commit 361e48a into main Mar 17, 2026
4 of 8 checks passed
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 17, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ff832e14-fd81-4e39-abb6-7e4c6b244554

📥 Commits

Reviewing files that changed from the base of the PR and between 115ffa8 and 70eaae5.

📒 Files selected for processing (14)
  • agents/analyzer/index.ts
  • agents/verifier/index.ts
  • app/api/runs/[runId]/session/route.ts
  • app/api/runs/analyze/route.ts
  • app/api/runs/route.ts
  • lib/auth/github.ts
  • lib/auth/session-secret.ts
  • lib/auth/session.ts
  • lib/dashboard/run-store.ts
  • lib/git/clone.ts
  • lib/queue/ad-hoc-runner.ts
  • lib/types.ts
  • mobile/lib/auth/context.tsx
  • tests/unit/verifier.test.ts

📝 Walkthrough

Walkthrough

This PR adds owner-based run access control via session authentication, implements PKCE support for OAuth flows, refactors child process execution to use safer execFileSync with path validation, centralizes session secret management, and introduces a configurable allowBuildScripts flag to control build execution during code analysis.

Changes

Cohort / File(s) Summary
Session & Authentication Foundation
lib/auth/session-secret.ts (new), lib/auth/session.ts
Centralizes session secret management with environment-aware fallback logic; replaces in-file secret generation with imported module.
OAuth PKCE Enhancement
lib/auth/github.ts, mobile/lib/auth/context.tsx
Extends exchangeCodeForToken to accept PKCE parameters (codeVerifier, state, redirectUri); mobile auth now validates PKCE state before token exchange.
Run Ownership & Authorization
app/api/runs/[runId]/session/route.ts, app/api/runs/route.ts, app/api/runs/analyze/route.ts
Adds session-based authorization checks to verify run ownership; GET/POST handlers now filter and create runs scoped to authenticated user.
Run Data Model
lib/types.ts, lib/dashboard/run-store.ts
Extends Run interface with optional ownerId field; createRun propagates owner ID into persisted records.
Code Analysis Control
agents/analyzer/index.ts, lib/queue/ad-hoc-runner.ts
Introduces allowBuildScripts boolean flag (default true) to conditionally execute build checks; ad-hoc runner disables build scripts for untrusted repositories.
Process Execution Safety
agents/verifier/index.ts, lib/git/clone.ts
Migrates from execSync to execFileSync for safer subprocess invocation; adds file path validation layer via getValidatedFilePath; sanitizes commit messages with length cap; updates npm install to use --ignore-scripts flag.
Test Updates
tests/unit/verifier.test.ts
Updates test mocks from execSync to execFileSync and adjusts assertions to match command-plus-args invocation pattern.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • weavehacks#21: Aligns on OAuth parameter expansion and PKCE state/codeVerifier handling in both mobile and backend authentication flows.

Suggested labels

codex


🐰 Scripts now skip with a gentle bound,
Sessions keep secrets safe and sound,
Owners claim their runs with pride,
PKCE codes securely ride,
Safety checks hop all around! 🔐✨

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/merge-security-fixes
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rishabhcli rishabhcli deleted the codex/merge-security-fixes branch March 17, 2026 18:52
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR focuses on tightening security across the Git/GitHub automation flows, session secret handling, and run access control by introducing safer process execution, path validation, and per-user run scoping.

Changes:

  • Harden VerifierAgent git operations (move to execFileSync, validate patch paths, sanitize commit messages) and update unit tests accordingly.
  • Introduce per-run ownership (ownerId) and scope /api/runs results and sensitive run session endpoints to the authenticated user.
  • Improve OAuth security by adding state/PKCE validation in the mobile auth flow and supporting optional PKCE/token-exchange params for GitHub token exchange.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/unit/verifier.test.ts Updates mocks/assertions for execFileSync-based git commands.
mobile/lib/auth/context.tsx Adds OAuth state + PKCE verifier validation and sends verifier/state to backend exchange.
lib/types.ts Adds ownerId?: number to Run.
lib/queue/ad-hoc-runner.ts Constructs analyzer with build-script execution disabled for untrusted repos.
lib/git/clone.ts Installs dependencies with --ignore-scripts to reduce supply-chain/script risk.
lib/dashboard/run-store.ts Propagates ownerId into created runs.
lib/auth/session.ts Uses shared getSessionSecret() implementation.
lib/auth/session-secret.ts New env-aware session secret loader (prod-required, dev-random, test-fixed).
lib/auth/github.ts Expands token exchange to accept optional redirectUri/PKCE/state parameters.
app/api/runs/route.ts Scopes run listing to the authenticated user and requires GitHub auth for cloud runs.
app/api/runs/analyze/route.ts Records ownerId on analysis runs.
app/api/runs/[runId]/session/route.ts Restricts Browserbase session debug URLs to the run owner.
agents/verifier/index.ts Security hardening: safer git invocation, path traversal protection, commit message sanitization.
agents/analyzer/index.ts Adds allowBuildScripts switch to skip build checks for untrusted repos.
Comments suppressed due to low confidence (2)

app/api/runs/route.ts:52

  • After scoping runs to the current user, the response still returns stats computed by getRunStatsAsync(repoId) which aggregates across all runs (not owner-scoped). This can leak other users’ activity in a multi-tenant deployment and also makes the stats inconsistent with the returned run list. Consider computing stats from the already-filtered runs, or extending the stats API to accept an ownerId and scope the aggregation accordingly.
  if (userId !== undefined) {
    runs = runs.filter((r) => r.ownerId === userId);
  }

  if (statusFilter) {
    runs = runs.filter((run) => run.status === statusFilter);
  }
  if (repoIdFilter) {
    runs = runs.filter((run) => run.repoId === repoIdFilter);
  }
  if (searchQuery) {
    const normalizedQuery = searchQuery.toLowerCase();
    runs = runs.filter(
      (run) =>
        run.repoName.toLowerCase().includes(normalizedQuery) ||
        run.id.toLowerCase().includes(normalizedQuery)
    );
  }

  const total = runs.length;
  const totalPages = Math.ceil(total / limit);
  const offset = (page - 1) * limit;
  const paginatedRuns = runs.slice(offset, offset + limit);
  const stats = await getRunStatsAsync(repoIdFilter || undefined);

agents/verifier/index.ts:403

  • validateSyntax() previously redirected stderr to stdout (2>&1) before parsing compiler output; with execFileSync('npx', ['tsc', ...]) that redirection is gone, but the catch block still only inspects error.stdout. If TypeScript writes diagnostics to stderr, filteredOutput will be empty and syntax/JSX errors may be missed. Consider merging stdout+stderr when parsing, or explicitly checking error.stderr as well.
        execFileSync('npx', ['tsc', '--noEmit', '--project', 'tsconfig.json'], {
          cwd: this.projectRoot,
          stdio: 'pipe',
        });
      } catch (error) {
        // Log TypeScript output for debugging but check if it's a syntax error
        if (error instanceof Error && 'stdout' in error) {
          const stdout = (error as { stdout?: Buffer }).stdout;
          if (stdout) {
            const output = stdout.toString();
            // Filter out npm warnings and unrelated files
            const filteredOutput = output
              .split('\n')
              .filter((line) =>
                !line.includes('npm warn') &&
                !line.includes('npm WARN') &&
                line.includes(filePath))
              .join('\n')
              .trim();

            // Only fail on actual syntax errors (TS1xxxx and TS17xxx are parse/JSX errors)
            if (filteredOutput.includes('error TS1') || filteredOutput.includes('error TS17')) {
              console.log(`Syntax error in ${filePath}:`, filteredOutput.slice(0, 300));

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread app/api/runs/route.ts
let runs = await getAllRunsAsync();

if (userId !== undefined) {
runs = runs.filter((r) => r.ownerId === userId);
Comment thread agents/verifier/index.ts
Comment on lines +452 to +485
/**
* Resolve and validate that patch file paths stay within the repository.
*/
private getValidatedFilePath(filePath: string): string {
if (!filePath || typeof filePath !== 'string') {
throw new Error('Invalid patch file path');
}

const normalizedPath = path.normalize(filePath);
if (path.isAbsolute(normalizedPath)) {
throw new Error('Absolute file paths are not allowed in patches');
}

const resolvedPath = path.resolve(this.projectRoot, normalizedPath);
const rootPath = path.resolve(this.projectRoot);

if (resolvedPath !== rootPath && !resolvedPath.startsWith(`${rootPath}${path.sep}`)) {
throw new Error('Patch file path resolves outside project root');
}

return resolvedPath;
}

/**
* Keep commit subjects safe, readable, and bounded.
*/
private sanitizeCommitDescription(description: string): string {
const fallback = 'Bug fix';
const normalized = description.trim().replace(/[\r\n]+/g, ' ');
if (!normalized) {
return fallback;
}
return normalized.slice(0, VerifierAgent.COMMIT_MESSAGE_MAX_LENGTH);
}
Comment thread agents/verifier/index.ts
Comment on lines +184 to +191
const safeFilePath = this.getValidatedFilePath(patch.file);
const commitMessage = `fix: ${this.sanitizeCommitDescription(patch.description)}\n\nApplied by QAgent`;

execFileSync('git', ['add', '--', safeFilePath], {
cwd: this.projectRoot,
stdio: 'pipe',
});
execFileSync('git', ['commit', '-m', commitMessage], {
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 issues found across 14 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="lib/git/clone.ts">

<violation number="1" location="lib/git/clone.ts:118">
P1: Skipping install scripts here can leave cloned repos unusable for `setupLocalRepo`. Some projects need `prepare`/`postinstall` or native build steps to generate artifacts before `startDevServer()` runs, so this shared helper should not disable scripts unconditionally.</violation>
</file>

<file name="agents/verifier/index.ts">

<violation number="1" location="agents/verifier/index.ts:468">
P2: Path validation allows the project root directory itself (`"."`) to pass, which in `commitFix` would cause `git add` to stage the entire repository. Since this is a path traversal guard, it should reject paths that don't resolve to a file strictly inside the root.</violation>
</file>

<file name="lib/auth/session.ts">

<violation number="1" location="lib/auth/session.ts:4">
P2: This import changes session signing to use a runtime-local dev fallback secret, which can make middleware reject valid local sessions when `SESSION_SECRET` is unset.</violation>
</file>

<file name="app/api/runs/route.ts">

<violation number="1" location="app/api/runs/route.ts:28">
P1: Reject unauthenticated GET requests here; when `userId` is undefined this branch is skipped and the endpoint still returns every run.</violation>

<violation number="2" location="app/api/runs/route.ts:29">
P2: Scope `stats` to the same owner filter as `runs`; this response still leaks cross-user aggregate counts.</violation>
</file>

<file name="agents/analyzer/index.ts">

<violation number="1" location="agents/analyzer/index.ts:68">
P0: Disabling only the build step still executes untrusted repository code via ESLint config/plugins.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread agents/analyzer/index.ts
// 3. Build check
const buildIssues = await this.runBuildCheck();
issues.push(...buildIssues);
if (this.allowBuildScripts) {
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: Disabling only the build step still executes untrusted repository code via ESLint config/plugins.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At agents/analyzer/index.ts, line 68:

<comment>Disabling only the build step still executes untrusted repository code via ESLint config/plugins.</comment>

<file context>
@@ -63,8 +65,12 @@ export class CodeAnalyzerAgent {
     // 3. Build check
-    const buildIssues = await this.runBuildCheck();
-    issues.push(...buildIssues);
+    if (this.allowBuildScripts) {
+      const buildIssues = await this.runBuildCheck();
+      issues.push(...buildIssues);
</file context>
Fix with Cubic

Comment thread lib/git/clone.ts
const installCommand = pm === 'npm' ? 'npm install' : `${pm} install`;
const installCommand =
pm === 'npm'
? 'npm install --ignore-scripts'
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Skipping install scripts here can leave cloned repos unusable for setupLocalRepo. Some projects need prepare/postinstall or native build steps to generate artifacts before startDevServer() runs, so this shared helper should not disable scripts unconditionally.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/git/clone.ts, line 118:

<comment>Skipping install scripts here can leave cloned repos unusable for `setupLocalRepo`. Some projects need `prepare`/`postinstall` or native build steps to generate artifacts before `startDevServer()` runs, so this shared helper should not disable scripts unconditionally.</comment>

<file context>
@@ -113,7 +113,10 @@ export async function installDependencies(
-  const installCommand = pm === 'npm' ? 'npm install' : `${pm} install`;
+  const installCommand =
+    pm === 'npm'
+      ? 'npm install --ignore-scripts'
+      : `${pm} install --ignore-scripts`;
 
</file context>
Fix with Cubic

Comment thread app/api/runs/route.ts
Comment on lines +28 to +30
if (userId !== undefined) {
runs = runs.filter((r) => r.ownerId === userId);
}
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Reject unauthenticated GET requests here; when userId is undefined this branch is skipped and the endpoint still returns every run.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/api/runs/route.ts, line 28:

<comment>Reject unauthenticated GET requests here; when `userId` is undefined this branch is skipped and the endpoint still returns every run.</comment>

<file context>
@@ -22,6 +25,10 @@ export async function GET(request: NextRequest) {
 
   let runs = await getAllRunsAsync();
 
+  if (userId !== undefined) {
+    runs = runs.filter((r) => r.ownerId === userId);
+  }
</file context>
Suggested change
if (userId !== undefined) {
runs = runs.filter((r) => r.ownerId === userId);
}
if (userId === undefined) {
return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
}
runs = runs.filter((r) => r.ownerId === userId);
Fix with Cubic

Comment thread agents/verifier/index.ts
const resolvedPath = path.resolve(this.projectRoot, normalizedPath);
const rootPath = path.resolve(this.projectRoot);

if (resolvedPath !== rootPath && !resolvedPath.startsWith(`${rootPath}${path.sep}`)) {
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Path validation allows the project root directory itself (".") to pass, which in commitFix would cause git add to stage the entire repository. Since this is a path traversal guard, it should reject paths that don't resolve to a file strictly inside the root.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At agents/verifier/index.ts, line 468:

<comment>Path validation allows the project root directory itself (`"."`) to pass, which in `commitFix` would cause `git add` to stage the entire repository. Since this is a path traversal guard, it should reject paths that don't resolve to a file strictly inside the root.</comment>

<file context>
@@ -441,6 +448,41 @@ export class VerifierAgent implements IVerifierAgent {
+    const resolvedPath = path.resolve(this.projectRoot, normalizedPath);
+    const rootPath = path.resolve(this.projectRoot);
+
+    if (resolvedPath !== rootPath && !resolvedPath.startsWith(`${rootPath}${path.sep}`)) {
+      throw new Error('Patch file path resolves outside project root');
+    }
</file context>
Suggested change
if (resolvedPath !== rootPath && !resolvedPath.startsWith(`${rootPath}${path.sep}`)) {
if (!resolvedPath.startsWith(`${rootPath}${path.sep}`)) {
Fix with Cubic

Comment thread lib/auth/session.ts
import { cookies } from 'next/headers';
import { SignJWT, jwtVerify } from 'jose';
import type { Session, GitHubUser, GitHubRepo, SessionPayload } from '@/lib/types';
import { getSessionSecret } from '@/lib/auth/session-secret';
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This import changes session signing to use a runtime-local dev fallback secret, which can make middleware reject valid local sessions when SESSION_SECRET is unset.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/auth/session.ts, line 4:

<comment>This import changes session signing to use a runtime-local dev fallback secret, which can make middleware reject valid local sessions when `SESSION_SECRET` is unset.</comment>

<file context>
@@ -1,22 +1,9 @@
 import { cookies } from 'next/headers';
 import { SignJWT, jwtVerify } from 'jose';
 import type { Session, GitHubUser, GitHubRepo, SessionPayload } from '@/lib/types';
+import { getSessionSecret } from '@/lib/auth/session-secret';
 
 const SESSION_COOKIE = 'qagent_session';
</file context>
Fix with Cubic

Comment thread app/api/runs/route.ts
let runs = await getAllRunsAsync();

if (userId !== undefined) {
runs = runs.filter((r) => r.ownerId === userId);
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Scope stats to the same owner filter as runs; this response still leaks cross-user aggregate counts.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/api/runs/route.ts, line 29:

<comment>Scope `stats` to the same owner filter as `runs`; this response still leaks cross-user aggregate counts.</comment>

<file context>
@@ -22,6 +25,10 @@ export async function GET(request: NextRequest) {
   let runs = await getAllRunsAsync();
 
+  if (userId !== undefined) {
+    runs = runs.filter((r) => r.ownerId === userId);
+  }
+
</file context>
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants