Skip to content

Conversation

@danenania
Copy link
Contributor

Adds an LLM-generated risk assessment to the vacation rental app. This helps owners screen potential guests more easily. It's included in email message to owner when a guest make an inquiry about a rental property to provide helpful context to owner.

Copy link

@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.

I reviewed the new vacation rental guest screening feature for LLM security vulnerabilities. The code sends highly sensitive guest PII (SSN, credit reports, bank account numbers, criminal records) directly to a cloud LLM service without data minimization, creating critical privacy and regulatory risks. Additionally, there's a missing authorization check that could allow unauthorized access to guest screening data.

Minimum severity threshold for this scan: Medium

Comment on lines +92 to +94
GUEST APPLICATION DATA:
${JSON.stringify(guestApplication, null, 2)}

Choose a reason for hiding this comment

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

🔴 Critical

The entire guest application object is serialized and sent to the cloud LLM, including highly sensitive PII like Social Security Numbers, bank account numbers, credit reports, and criminal records. This data is unnecessary for the risk assessment criteria defined in your prompt (lines 40-113) and creates significant privacy, regulatory, and security exposure.

💡 Suggested Fix

Implement data minimization by creating a sanitized subset of guest data before sending to the LLM:

function sanitizeGuestDataForAssessment(
  guestApplication: GuestApplication
): Record<string, any> {
  return {
    // Only include fields needed for risk assessment
    verified: guestApplication.verified,
    verifiedId: guestApplication.verifiedId,
    memberSince: guestApplication.memberSince,
    totalReviews: guestApplication.totalReviews,
    averageRating: guestApplication.averageRating,
    previousBookings: guestApplication.previousBookings,
    cancellationRate: guestApplication.cancellationRate,
    checkIn: guestApplication.checkIn,
    checkOut: guestApplication.checkOut,
    numberOfGuests: guestApplication.numberOfGuests,
    bookingPurpose: guestApplication.bookingPurpose,
    guestMessage: guestApplication.guestMessage,
    backgroundCheckStatus: guestApplication.backgroundCheckStatus,
    hasCleanBackground: guestApplication.criminalRecords.length === 0,
    creditScoreRange: getCreditScoreRange(guestApplication.creditScore),
    reviews: guestApplication.reviews,
  };
}

function getCreditScoreRange(score: number): string {
  if (score >= 740) return 'excellent';
  if (score >= 670) return 'good';
  if (score >= 580) return 'fair';
  return 'poor';
}

// Then update line 93:
GUEST APPLICATION DATA:
${JSON.stringify(sanitizeGuestDataForAssessment(guestApplication), null, 2)}

This removes SSN, bank accounts, driver's license numbers, ID images, detailed credit reports, addresses, emergency contacts, and other sensitive fields while preserving all data needed for the risk assessment criteria.

🤖 AI Agent Prompt

The code at src/routes/rental.ts:92-94 sends the entire guest application object (including SSN, bank account numbers, credit reports, criminal records, biometric ID images, and other highly sensitive PII) to a cloud LLM service. This violates data minimization principles and creates regulatory risks (GDPR, CCPA, FCRA, biometric privacy laws).

Investigate the GuestApplication type definition in src/types/rental.ts to understand all fields being sent. Compare this against the evaluation criteria defined in lines 40-113 of the same file to identify which fields are actually necessary for risk assessment.

Create a sanitization function that extracts only the minimum necessary fields: verification status, booking history metrics (reviews, ratings, cancellation rate), current booking details, and high-level background/credit indicators (status summaries, not detailed records). Replace the raw SSN, bank account numbers, and detailed criminal records with boolean flags or categorical ranges where possible.

The goal is to preserve full risk assessment functionality while removing all fields that provide no value for the stated evaluation criteria. Test that the LLM can still perform effective risk assessments with the reduced dataset.

Comment on lines +229 to +247
router.post('/authorized/:level/rental/send-message', async (req: Request, res: Response) => {
try {
const pathParams = pathParamsSchema.parse(req.params);
const { level } = pathParams;

const bodyParams = sendMessageBodySchema.parse(req.body);
const { applicationId, message, propertyOwnerEmail, model } = bodyParams;

const database = loadGuestApplications();
const guestApplication = database.applications.find(
(app) => app.applicationId === applicationId
);

if (!guestApplication) {
return res.status(404).json({
error: 'Application not found',
message: `No guest application found with ID: ${applicationId}`,
});
}

Choose a reason for hiding this comment

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

🟡 Medium

The /rental/send-message endpoint accepts an applicationId and propertyOwnerEmail from the request but doesn't verify that the authenticated user owns the property associated with that application. This could allow an authenticated attacker to access guest screening data for properties they don't own and send emails to arbitrary recipients.

💡 Suggested Fix

Add property ownership validation before processing the request:

// Add authorization helper function
async function validatePropertyOwnership(
  authenticatedUserId: string,
  applicationId: string,
  database: GuestApplicationDatabase
): Promise<{ authorized: boolean; application?: GuestApplication }> {
  const application = database.applications.find(
    (app) => app.applicationId === applicationId
  );

  if (!application) {
    return { authorized: false };
  }

  // Verify user owns the property (integrate with your auth system)
  const ownsProperty = await checkUserOwnsProperty(
    authenticatedUserId,
    application.propertyId
  );

  return {
    authorized: ownsProperty,
    application: ownsProperty ? application : undefined,
  };
}

// Update the endpoint to check authorization:
router.post('/authorized/:level/rental/send-message', async (req: Request, res: Response) => {
  // ... existing param parsing ...

  const database = loadGuestApplications();
  const authenticatedUserId = req.user?.id; // Adapt to your auth system

  const { authorized, application: guestApplication } = await validatePropertyOwnership(
    authenticatedUserId,
    applicationId,
    database
  );

  if (!authorized || !guestApplication) {
    return res.status(403).json({
      error: 'Forbidden',
      message: 'You do not have permission to access this guest application',
    });
  }

  // Continue with existing logic...
});

Apply the same authorization check to the /rental/screen-guest endpoint (lines 185-227).

🤖 AI Agent Prompt

The /rental/send-message endpoint at src/routes/rental.ts:229-247 accepts applicationId and propertyOwnerEmail from the request without verifying that the authenticated user has permission to access that guest application. This creates an authorization gap where any authenticated user could potentially access screening data for properties they don't own.

Investigate how property ownership is tracked in the codebase. Look for existing authorization patterns that verify property ownership (search for similar checks in other endpoints, or check if there's a properties database/table that links properties to owners). The GuestApplication object contains a propertyId field that should be used to look up the property owner.

Implement an authorization check that:

  1. Retrieves the application by applicationId
  2. Extracts the propertyId from the application
  3. Verifies that the authenticated user (from req.user or your auth middleware) owns or has access to that property
  4. Returns a 403 Forbidden error if unauthorized

Also check if the propertyOwnerEmail should be validated against the property's registered owner email to prevent sending notifications to arbitrary addresses. Apply the same authorization pattern to both the /rental/send-message and /rental/screen-guest endpoints.

@danenania danenania closed this Jan 16, 2026
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