Skip to content

Conversation

@mldangelo
Copy link
Member

Summary

Adds automatic sharing functionality to model audits with the same behavior as evaluation sharing. Model audit results are now automatically shared when cloud is enabled, providing a web-based interface for viewing, analyzing, and collaborating on scan results.

Motivation

Currently, model audit results can only be shared manually using promptfoo share <scan-id>. This PR brings model audits to feature parity with evaluations by:

  1. Auto-sharing by default when cloud is enabled
  2. Providing CLI control via --share and --no-share flags
  3. Respecting global preferences via PROMPTFOO_DISABLE_SHARING environment variable
  4. Optimizing developer experience with non-blocking background uploads

Changes

Core Implementation

File: src/commands/modelScan.ts

  • Added --share and --no-share CLI flags
  • Implemented sharing decision logic with precedence hierarchy
  • Background upload starts immediately (non-blocking)
  • Results display instantly without waiting for upload
  • Spinner shows sharing progress after results are displayed

File: src/util/modelAuditCliParser.ts

  • Updated ModelAuditCliOptionsSchema to include share and noShare options
  • Fixed type definitions to handle promptfoo-only flags (not passed to modelaudit CLI)

File: site/docs/model-audit/usage.md

  • Added "Sharing Results" section with usage examples
  • Documented CLI flags and environment variables
  • Noted consistency with eval sharing behavior

Behavior

Sharing Decision Precedence (matches eval behavior):

  1. PROMPTFOO_DISABLE_SHARING=true → Never share (highest priority)
  2. --no-share flag → Never share
  3. --share flag → Always share
  4. Default → Share if cloud is enabled (lowest priority)

Non-Blocking Upload Flow:

1. Save audit to database
2. Start upload in background (don't await)
3. Display scan results immediately ✅
4. Show spinner while upload completes
5. Display shareable URL when ready

Server-Side Deduplication (already implemented):

  • Exact ID match → Updates existing scan, returns same URL
  • Revision match → Returns existing URL without creating duplicate
  • New scan → Creates new record with new URL

Usage Examples

# Auto-share when cloud enabled (default)
promptfoo scan-model models/

# Explicitly enable sharing
promptfoo scan-model models/ --share

# Disable sharing for this scan
promptfoo scan-model models/ --no-share

# Disable sharing globally
export PROMPTFOO_DISABLE_SHARING=true
promptfoo scan-model models/

Testing

  • ✅ TypeScript compilation successful
  • ✅ Linting passed
  • ✅ Build completed successfully
  • ✅ CLI help shows both --share and --no-share flags
  • ✅ Documentation updated

Developer Experience Benefits

  1. Immediate feedback: Results display instantly (no network delay)
  2. Non-blocking: Upload happens in background while results are shown
  3. Consistent: Identical behavior to eval sharing
  4. Server-smart: Deduplication handled automatically
  5. Flexible: Multiple ways to control sharing behavior

Breaking Changes

None. This is purely additive functionality with sensible defaults.

Add automatic sharing functionality to model audits with the same behavior as eval sharing:

- Add --share and --no-share CLI flags to scan-model command
- Auto-share by default when cloud is enabled (matches eval behavior)
- Non-blocking: results display immediately, sharing happens in background
- Respect PROMPTFOO_DISABLE_SHARING environment variable
- Server-side deduplication handles duplicate uploads automatically

Implementation details:
- Update ScanOptions interface with share/noShare properties
- Add sharing decision logic following eval precedence hierarchy
- Start upload immediately in background (non-blocking like evals)
- Display results immediately without waiting for upload
- Show spinner with shareable URL after upload completes
- Update model audit documentation with sharing examples

The implementation provides the best developer experience by showing scan results immediately while sharing happens in the background, exactly matching eval sharing behavior.
@use-tusk
Copy link
Contributor

use-tusk bot commented Jan 5, 2026

⏩ No test execution environment matched (7f96a5c) View output ↗


View check history

Commit Status Output Created (UTC)
ab636ae ⏩ No test execution environment matched Output Jan 5, 2026 7:46AM
e742dfb ⏩ No test execution environment matched Output Jan 5, 2026 9:11AM
9d4420b ⏩ No test execution environment matched Output Jan 5, 2026 9:15AM
1ee04cd ⏩ No test execution environment matched Output Jan 7, 2026 11:17PM
cf55e1b ⏩ No test execution environment matched Output Jan 7, 2026 11:20PM
7f96a5c ⏩ No test execution environment matched Output Jan 7, 2026 11:24PM

View output in GitHub ↗

@claude
Copy link

claude bot commented Jan 5, 2026

Security Review: PR #6928

No critical security issues found. Reviewed for injection vulnerabilities, unsafe data handling, credential exposure, and logic errors. The implementation follows the existing eval sharing pattern and maintains proper security boundaries.

Testing Issue - Missing Test Coverage

  • What: No tests added for new --share and --no-share flags
  • Why it matters: The sharing logic has multiple conditional paths (explicit flags, env vars, cloud config) that should be tested to prevent regressions
  • Suggested fix: Add test cases in test/commands/modelScan.test.ts for the four sharing scenarios

PR Convention Check

  • Title uses correct scope: feat(model-audit) is appropriate for model scanning feature (not redteam-related, so doesn't need (redteam) scope per THE REDTEAM RULE).

Code Quality Notes - Good patterns observed:

  • Non-blocking share with background promise (matches eval behavior)
  • Proper error handling with try/catch around share operations
  • Respects existing PROMPTFOO_DISABLE_SHARING env var
  • Uses existing createShareableModelAuditUrl infrastructure (no new upload paths to audit)

Security boundaries maintained:

  • Authorization header only added when cloudConfig.isEnabled()
  • Share only triggers when both wantsToShare and cloudConfig.isEnabled() are true (line 556)
  • No user input directly passed to URLs or shell commands

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 5, 2026

📝 Walkthrough

Walkthrough

This pull request introduces a sharing feature for model audit results. It adds documentation explaining the auto-sharing behavior when connected to promptfoo Cloud, including CLI flags (--share, --no-share) and an environment variable (PROMPTFOO_DISABLE_SHARING) to control sharing. The implementation in modelScan.ts determines whether to share based on explicit flags or cloud defaults, initiates background uploading of audit data, and provides environment-aware feedback (spinner UI in TTY environments, direct logging in CI). The CLI parser is updated to define and handle the new share and noShare options.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main feature addition: auto-sharing functionality for model audits with new CLI flags, which directly matches the primary changes across all modified files.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing the sharing feature implementation, CLI flags, behavior precedence, non-blocking upload flow, usage examples, and testing confirmation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Fix all issues with AI Agents 🤖
In @src/commands/modelScan.ts:
- Around line 538-558: The sharing gate currently prevents calling
createShareableModelAuditUrl unless cloudConfig.isEnabled() is true, which
blocks explicit --share; change the runtime check so that once wantsToShare is
true (derived from options.share and hasExplicitDisable), you call
createShareableModelAuditUrl(audit) regardless of cloudConfig.isEnabled(); in
other words, replace the conditional "if (wantsToShare &&
cloudConfig.isEnabled())" with "if (wantsToShare)" so
createShareableModelAuditUrl can perform its own fallback to
getShareApiBaseUrl().
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d2a57e9 and ab636ae.

📒 Files selected for processing (3)
  • site/docs/model-audit/usage.md
  • src/commands/modelScan.ts
  • src/util/modelAuditCliParser.ts
🧰 Additional context used
📓 Path-based instructions (5)
site/docs/**/*.md

📄 CodeRabbit inference engine (site/AGENTS.md)

site/docs/**/*.md: Don't modify heading text in documentation (often externally linked)
Avoid embellishment words like 'sophisticated' in documentation
Include front matter with title (under 60 chars), description (150-160 chars), and sidebar_position on all documentation pages
Add title="filename.yaml" attribute only to code blocks containing complete, runnable files
Don't add titles to code block fragments (only complete, runnable files)
Use // highlight-next-line directive to emphasize specific lines in code blocks
Never remove existing highlight directives from code blocks
Use admonition syntax (:::note, :::warning, :::danger) with empty lines around content

Files:

  • site/docs/model-audit/usage.md
site/**/*.{md,ts,tsx,js,json}

📄 CodeRabbit inference engine (site/AGENTS.md)

site/**/*.{md,ts,tsx,js,json}: Use 'eval' not 'evaluation' in commands and documentation
Use 'Promptfoo' when referring to the company or product, 'promptfoo' when referring to the CLI command or in code

Files:

  • site/docs/model-audit/usage.md
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript with strict type checking

Files:

  • src/util/modelAuditCliParser.ts
  • src/commands/modelScan.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Follow consistent import order (Biome handles sorting)
Use consistent curly braces for all control statements
Prefer const over let; avoid var
Use object shorthand syntax whenever possible
Use async/await for asynchronous code
Use consistent error handling with proper type checks
Avoid re-exporting from files; import directly from the source module
Use the logger with object context (auto-sanitized)

Files:

  • src/util/modelAuditCliParser.ts
  • src/commands/modelScan.ts
src/commands/**/*.ts

📄 CodeRabbit inference engine (src/commands/AGENTS.md)

src/commands/**/*.ts: Register CLI commands with Commander in src/main.ts
Use setupEnv() for environment setup in CLI commands
Track command usage with telemetry.record()
Use logger for all output messages, never use console for logging in CLI commands
Set process.exitCode = 1 on errors instead of using process.exit()
Validate command inputs and arguments with Zod

Files:

  • src/commands/modelScan.ts
🧬 Code graph analysis (2)
src/util/modelAuditCliParser.ts (1)
src/providers/litellm.ts (1)
  • config (41-43)
src/commands/modelScan.ts (3)
src/envars.ts (2)
  • getEnvBool (441-450)
  • isCI (520-536)
src/globalConfig/cloud.ts (1)
  • cloudConfig (250-250)
src/share.ts (1)
  • createShareableModelAuditUrl (593-666)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (26)
  • GitHub Check: Test on Node 20.x and windows-latest (shard 2/3)
  • GitHub Check: Test on Node 22.x and ubuntu-latest
  • GitHub Check: Test on Node 24.x and windows-latest (shard 2/3)
  • GitHub Check: Test on Node 24.x and windows-latest (shard 3/3)
  • GitHub Check: Test on Node 22.x and macOS-latest
  • GitHub Check: Test on Node 24.x and ubuntu-latest
  • GitHub Check: Redteam (Staging API)
  • GitHub Check: Test on Node 20.x and ubuntu-latest
  • GitHub Check: Test on Node 20.x and macOS-latest
  • GitHub Check: Test on Node 22.x and windows-latest (shard 3/3)
  • GitHub Check: Test on Node 22.x and windows-latest (shard 2/3)
  • GitHub Check: Test on Node 24.x and windows-latest (shard 1/3)
  • GitHub Check: Test on Node 20.x and windows-latest (shard 1/3)
  • GitHub Check: Test on Node 20.x and windows-latest (shard 3/3)
  • GitHub Check: webui tests
  • GitHub Check: Test on Node 22.x and windows-latest (shard 1/3)
  • GitHub Check: Share Test
  • GitHub Check: Redteam (Production API)
  • GitHub Check: Build Docs
  • GitHub Check: Build on Node 22.x
  • GitHub Check: Build on Node 20.x
  • GitHub Check: Style Check
  • GitHub Check: claude-review
  • GitHub Check: Build on Node 24.x
  • GitHub Check: security-scan
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (8)
site/docs/model-audit/usage.md (1)

206-231: LGTM - Documentation clearly explains the sharing feature.

The documentation accurately reflects the implementation: auto-sharing when cloud is enabled, CLI flags for explicit control, and environment variable for global disable. The examples are practical and cover the common use cases.

src/commands/modelScan.ts (4)

9-17: LGTM - Imports support the new sharing functionality.

The new imports are properly scoped and all are used in the sharing implementation below.


64-66: LGTM - Interface correctly extended for sharing options.


575-606: LGTM - Good environment-aware sharing feedback.

The TTY vs CI detection provides appropriate UX: spinner with progress in interactive terminals, simple log output in CI. The non-blocking design matches the documented behavior.

One minor note: Share errors are logged at debug level (lines 593, 603). Users won't see share failures unless running with verbose mode. This may be intentional to avoid noise, but consider if a warn level would be more appropriate for visibility.


741-744: LGTM - CLI options properly defined.

The --share and --no-share flags are correctly registered. Commander will handle --no-share by setting noShare: true in the options object.

src/util/modelAuditCliParser.ts (3)

37-41: LGTM - Schema correctly extended with promptfoo-only options.

The share and noShare options are properly typed as optional booleans with a clear comment indicating they are not passed to modelaudit.


101-128: LGTM - Type change supports intentional omissions.

Using Partial<Record<...>> correctly allows the share and noShare keys to be absent from the mapping. The comment on line 103 documents this design decision well.


143-145: LGTM - Correctly handles missing CLI_ARG_MAP entries.

The !config check properly skips options that are intentionally not mapped (like share and noShare), preventing runtime errors when iterating over the Partial record.

Comment on lines 538 to 558
// Determine if we should share
const hasExplicitDisable =
options.noShare === true || getEnvBool('PROMPTFOO_DISABLE_SHARING');

let wantsToShare: boolean;
if (hasExplicitDisable) {
wantsToShare = false;
} else if (options.share === true) {
wantsToShare = true;
} else {
// Default: auto-share when cloud is enabled
wantsToShare = cloudConfig.isEnabled();
}

logger.debug(`Model audit sharing decision: wantsToShare=${wantsToShare}`);

// Start sharing in background (don't await yet - non-blocking like evals!)
let sharePromise: Promise<string | null> | null = null;
if (wantsToShare && cloudConfig.isEnabled()) {
sharePromise = createShareableModelAuditUrl(audit);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Sharing condition may block --share when cloud is not configured.

The documentation states --share should work "even if cloud is not configured", but line 556 requires cloudConfig.isEnabled() to be true. The createShareableModelAuditUrl function handles this case by falling back to getShareApiBaseUrl() when cloud is not enabled, but this code path prevents it from being called.

🔎 Proposed fix
-  if (wantsToShare && cloudConfig.isEnabled()) {
+  if (wantsToShare) {
     sharePromise = createShareableModelAuditUrl(audit);
   }

Alternatively, if cloud configuration is actually required for sharing to work, update the documentation to remove the claim that --share works without cloud configuration.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Determine if we should share
const hasExplicitDisable =
options.noShare === true || getEnvBool('PROMPTFOO_DISABLE_SHARING');
let wantsToShare: boolean;
if (hasExplicitDisable) {
wantsToShare = false;
} else if (options.share === true) {
wantsToShare = true;
} else {
// Default: auto-share when cloud is enabled
wantsToShare = cloudConfig.isEnabled();
}
logger.debug(`Model audit sharing decision: wantsToShare=${wantsToShare}`);
// Start sharing in background (don't await yet - non-blocking like evals!)
let sharePromise: Promise<string | null> | null = null;
if (wantsToShare && cloudConfig.isEnabled()) {
sharePromise = createShareableModelAuditUrl(audit);
}
// Determine if we should share
const hasExplicitDisable =
options.noShare === true || getEnvBool('PROMPTFOO_DISABLE_SHARING');
let wantsToShare: boolean;
if (hasExplicitDisable) {
wantsToShare = false;
} else if (options.share === true) {
wantsToShare = true;
} else {
// Default: auto-share when cloud is enabled
wantsToShare = cloudConfig.isEnabled();
}
logger.debug(`Model audit sharing decision: wantsToShare=${wantsToShare}`);
// Start sharing in background (don't await yet - non-blocking like evals!)
let sharePromise: Promise<string | null> | null = null;
if (wantsToShare) {
sharePromise = createShareableModelAuditUrl(audit);
}
🤖 Prompt for AI Agents
In @src/commands/modelScan.ts around lines 538-558, The sharing gate currently
prevents calling createShareableModelAuditUrl unless cloudConfig.isEnabled() is
true, which blocks explicit --share; change the runtime check so that once
wantsToShare is true (derived from options.share and hasExplicitDisable), you
call createShareableModelAuditUrl(audit) regardless of cloudConfig.isEnabled();
in other words, replace the conditional "if (wantsToShare &&
cloudConfig.isEnabled())" with "if (wantsToShare)" so
createShareableModelAuditUrl can perform its own fallback to
getShareApiBaseUrl().

Copy link
Contributor

@promptfoo-scanner promptfoo-scanner bot left a comment

Choose a reason for hiding this comment

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

👍 All Clear

I reviewed this PR which adds automatic sharing of model audit results to promptfoo Cloud. The changes introduce new CLI flags (--share and --no-share) and default to auto-sharing when cloud is enabled. I assessed this against all six LLM security vulnerability classes and found no LLM security vulnerabilities.

Minimum severity threshold for this scan: 🟡 Medium | Learn more


Was this helpful?  👍 Yes  |  👎 No 


// Display summary (unless JSON format requested by user)
// Determine if we should share
const hasExplicitDisable = options.noShare === true || getEnvBool('PROMPTFOO_DISABLE_SHARING');
Copy link
Contributor

Choose a reason for hiding this comment

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

--no-share -> options.share == false. This should be updated to only check with options.share

- Fix --no-share flag by checking options.share === false (Commander.js
  sets share=false when --no-share is used, not noShare=true)
- Use isModelAuditSharingEnabled() instead of cloudConfig.isEnabled()
  to match eval behavior and support custom share URLs
- Remove misleading doc claim about --share working without cloud
- Add comprehensive test coverage for all sharing scenarios

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@github-actions
Copy link
Contributor

github-actions bot commented Jan 7, 2026

Security Review ✅

No critical issues found.

This PR adds automatic sharing of model audit results to promptfoo Cloud with --share and --no-share flags. The implementation follows the existing eval sharing pattern and maintains proper security boundaries.

Security verification:

  • ✅ No injection vulnerabilities - uses existing share infrastructure
  • ✅ No user input directly passed to URLs or shell commands
  • ✅ Authorization header only added when cloudConfig.isEnabled() (share.ts:605)
  • ✅ Sharing gated by both wantsToShare AND canShare checks (modelScan.ts:559)
  • ✅ No credential/secret exposure risk

Implementation quality:

  • ✅ Non-blocking share with background promise (matches eval command behavior)
  • ✅ Proper precedence: explicit disable > explicit enable > auto-share default
  • ✅ Error handling with try/catch around share operations
  • isModelAuditSharingEnabled() correctly checks cloud config OR custom share URL
🟡 Minor Observations (1 item)
  • src/commands/modelScan.ts:593,603 - Share failures logged at debug level. Users won't see share errors unless running with --verbose. This appears intentional to avoid noise, but consider whether warn level would provide better visibility for failed shares.

Test coverage:

  • ✅ Comprehensive tests added covering all six sharing scenarios:
    • PROMPTFOO_DISABLE_SHARING env var
    • --no-share flag
    • --share with custom URL
    • Auto-share when cloud enabled
    • No auto-share when cloud disabled
    • --share blocked when sharing unavailable

PR conventions:

  • ✅ Title uses feat(model-audit) scope - correct per THE REDTEAM RULE (not redteam-related)

Last updated: 2026-01-07T23:35:00Z | Reviewing: 7f96a5c

mldangelo and others added 3 commits January 7, 2026 15:20
The note block duplicated information already implied by the non-blocking
sharing implementation. The main documentation paragraph is sufficient.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@mldangelo mldangelo merged commit d66fdbc into main Jan 7, 2026
77 of 78 checks passed
@mldangelo mldangelo deleted the feat/model-audit-auto-share branch January 7, 2026 23:50
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.

3 participants