Skip to content
Open
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
59 changes: 50 additions & 9 deletions app/api/prove/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,58 @@ export async function POST(request: NextRequest) {
try {
const body = await request.json();

const requestBody = {
url: body.url,
headers: body.headers || [
// Check if this is a GraphQL request (has query) or legacy REST request (has url)
let requestBody;

if (body.query) {
// GraphQL request
const graphqlUrl = 'https://api.github.com/graphql';
const graphqlBody = JSON.stringify({
query: body.query,
...(body.variables && Object.keys(body.variables).length > 0 && { variables: body.variables })
});

// Construct headers for GraphQL request
const headers = [
"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
"Content-Type: application/json",
"Accept: application/vnd.github+json"
]
};
];

if (body.githubToken) {
headers.push(`Authorization: Bearer ${body.githubToken}`);
}

requestBody = {
url: graphqlUrl,
method: 'POST',
headers: headers,
body: graphqlBody
};
Comment on lines +21 to +37

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Do not log GitHub PAT in requestBody

requestBody.headers includes Authorization: Bearer ${body.githubToken}, and console.log('Request body:', JSON.stringify(requestBody, null, 2)); will emit that PAT into server logs. This is high‑risk credential leakage; please avoid logging the full request body or explicitly strip/redact the Authorization header before logging.

-    console.log('Request body:', JSON.stringify(requestBody, null, 2));
+    // Avoid logging full requestBody because it may contain sensitive headers (e.g., GitHub PATs).
+    // If needed for debugging, log only non-sensitive metadata:
+    console.debug('Request body summary:', {
+      url: requestBody.url,
+      method: requestBody.method,
+      headerCount: Array.isArray(requestBody.headers) ? requestBody.headers.length : undefined,
+    });

Also applies to: 61-62

🤖 Prompt for AI Agents
In app/api/prove/route.ts around lines 21 to 37 (and also where requestBody is
logged around lines 61-62), the requestBody is built with an Authorization
header containing the GitHub PAT which must not be emitted to logs; update
logging to never print the full requestBody or to redact sensitive headers by
removing or replacing the Authorization header value before any
JSON.stringify/console.log (e.g., clone the requestBody for logging and set
cloned.headers to include a redacted Authorization or filter it out entirely),
and ensure any other logs that could include body.githubToken are removed or
redacted.


console.log('Sending GraphQL request to vlayer API');
console.log('GraphQL URL:', graphqlUrl);
console.log('Variables:', JSON.stringify(body.variables, null, 2));
} else if (body.url) {
// Legacy REST request (for backward compatibility)
requestBody = {
url: body.url,
headers: body.headers || [
"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
"Accept: application/vnd.github+json"
]
};

console.log('Sending REST request to vlayer API');
console.log('URL being proved:', requestBody.url);
} else {
return NextResponse.json(
{ error: 'Either query (GraphQL) or url (REST) must be provided' },
{ status: 400 }
);
}

console.log('Sending to vlayer API:', JSON.stringify(requestBody, null, 2));
console.log('URL being proved:', requestBody.url);
console.log('Headers being sent:', requestBody.headers);
console.log('Request body:', JSON.stringify(requestBody, null, 2));

const response = await fetch('https://web-prover.vlayer.xyz/api/v1/prove', {
method: 'POST',
Expand Down Expand Up @@ -50,7 +91,7 @@ export async function POST(request: NextRequest) {
}

return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Failed to prove URL' },
{ error: error instanceof Error ? error.message : 'Failed to prove request' },
{ status: 500 }
);
}
Expand Down
61 changes: 15 additions & 46 deletions app/api/upload-proof/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { sql } from '@/lib/db';
import { transformGraphQLPRResponse, extractRepoInfo } from '@/lib/graphql-transformer';

// Configure max duration for Vercel (up to 90 seconds)
export const maxDuration = 90;
Expand Down Expand Up @@ -55,33 +56,11 @@ export async function POST(request: NextRequest) {
}

const verifyData = await verifyResponse.json();

console.log('Verify data:', verifyData);

Comment on lines 58 to 61

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Avoid logging full verification payloads (GitHub PAT leakage risk)

console.log('Verify data:', verifyData); combined with the fact that verifyData carries the original request (including headers) means GitHub personal access tokens sent via /api/prove will end up in server logs. That’s a serious secret‑exposure risk; please remove or strictly sanitize these logs (e.g., drop headers, especially Authorization) before logging.

-    console.log('Verify data:', verifyData);
+    // Avoid logging full verifyData because it may contain sensitive headers (e.g., GitHub PATs).
+    // console.debug('Verify summary:', {
+    //   hasRequest: !!verifyData.request,
+    //   hasResponse: !!verifyData.response,
+    // });

Also applies to: 79-83

🤖 Prompt for AI Agents
In app/api/upload-proof/route.ts around lines 58-61 (and also remove or fix
similar logging at 79-83), the code logs the full verifyData object which
contains request headers and can leak GitHub PATs; remove the full console.log
or replace it with a sanitized log that omits headers (especially Authorization)
and any sensitive fields, e.g. log only safe metadata like response status or
specific non‑sensitive properties; ensure any future logging explicitly strips
or redacts headers.Authorization before output.

// Extract repository info from the verified response
let repoOwner: string | null = null;
let repoName: string | null = null;

try {
// Try multiple locations where the URL might be
let urlToMatch = null;

if (verifyData?.response?.url) {
urlToMatch = verifyData.response.url;
} else if (verifyData?.request?.url) {
urlToMatch = verifyData.request.url;
} else if (verifyData?.response?.request) {
urlToMatch = verifyData.response.request;
}

if (urlToMatch) {
const urlMatch = urlToMatch.match(/\/repos\/([^\/]+)\/([^\/]+)\/contributors/);
if (urlMatch) {
repoOwner = urlMatch[1];
repoName = urlMatch[2];
}
}
} catch (e) {
console.error('Could not extract repo info from response:', e);
}
const { owner: repoOwner, name: repoName } = extractRepoInfo(verifyData);

if (!repoOwner || !repoName) {
return NextResponse.json(
Expand All @@ -91,38 +70,28 @@ export async function POST(request: NextRequest) {
}

// Parse the response body to extract contributor data
// Handle GraphQL PR response, contributors API, and commits API (for backward compatibility)
let githubUsername: string | null = null;
let contributionCount: number | null = null;
let avatarUrl: string | null = null;
let githubUrl: string | null = null;

if (verifyData.response && verifyData.response.body) {
try {
const contributorsData = JSON.parse(verifyData.response.body);
const responseBody = verifyData.response.body;
const contributorData = transformGraphQLPRResponse(responseBody, username.trim());

if (Array.isArray(contributorsData)) {
// Find the contributor matching the provided username
const targetContributor = contributorsData.find((c: any) =>
c.login && c.login.toLowerCase() === username.trim().toLowerCase()
);

if (!targetContributor) {
return NextResponse.json(
{ error: `No contributions found for username: ${username.trim()}` },
{ status: 404 }
);
}

githubUsername = targetContributor.login;
contributionCount = targetContributor.contributions;
avatarUrl = targetContributor.avatar_url || null;
githubUrl = targetContributor.html_url || null;
} else {
if (!contributorData) {
return NextResponse.json(
{ error: 'Invalid contributors data format' },
{ status: 400 }
{ error: `No contributions found for username: ${username.trim()}` },
{ status: 404 }
);
}

githubUsername = contributorData.username;
contributionCount = contributorData.contributions;
avatarUrl = contributorData.avatar;
githubUrl = contributorData.githubUrl;
} catch (parseError) {
console.error('Failed to parse contributor data:', parseError);
return NextResponse.json(
Expand Down
85 changes: 21 additions & 64 deletions app/api/verify-all/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NextResponse } from 'next/server';
import { readdir, readFile } from 'fs/promises';
import { join } from 'path';
import { transformGraphQLPRResponse, extractRepoInfo } from '@/lib/graphql-transformer';

// Configure max duration for Vercel
export const maxDuration = 300;
Expand Down Expand Up @@ -78,75 +79,31 @@ export async function GET() {
console.log('Verify response structure:', JSON.stringify(verifyData, null, 2));

// Extract repository info from the verified response
let repoOwner: string | null = null;
let repoName: string | null = null;
try {
// Try multiple locations where the URL might be
let urlToMatch = null;

// Check different possible locations
if (verifyData?.response?.url) {
urlToMatch = verifyData.response.url;
} else if (verifyData?.request?.url) {
urlToMatch = verifyData.request.url;
} else if (verifyData?.response?.request) {
urlToMatch = verifyData.response.request;
}

console.log('URL to match:', urlToMatch);

if (urlToMatch) {
const urlMatch = urlToMatch.match(/\/repos\/([^\/]+)\/([^\/]+)\/contributors/);
if (urlMatch) {
repoOwner = urlMatch[1];
repoName = urlMatch[2];
console.log(`Extracted repo: ${repoOwner}/${repoName}`);
}
}
} catch (e) {
console.log('Could not extract repo info from response:', e);
}
const { owner: repoOwner, name: repoName } = extractRepoInfo(verifyData);
console.log(`Extracted repo: ${repoOwner}/${repoName}`);

Comment on lines 79 to 84

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Sanitize or remove logs that include full verification payloads

Between console.log('Verify response structure:', JSON.stringify(verifyData, null, 2)); and logging of responseBody, you’re likely emitting the original request (including GitHub Authorization headers from the webproof) into server logs. That PAT is sensitive and should not be logged. Recommend either removing these logs or logging only high‑level metadata (e.g., filenames, repo owner/name, counts) without headers.

-        console.log('Verify response structure:', JSON.stringify(verifyData, null, 2));
+        // Avoid logging full verifyData; it may contain sensitive headers (e.g., GitHub PATs).
+        console.debug('Verify response summary:', {
+          file,
+          hasRequest: !!verifyData.request,
+          hasResponse: !!verifyData.response,
+        });
@@
-          const responseBody = verifyData.response.body;
-          const contributorData = transformGraphQLPRResponse(responseBody, targetUsername || undefined);
+          const responseBody = verifyData.response.body;
+          const contributorData = transformGraphQLPRResponse(responseBody, targetUsername || undefined);

Also applies to: 87-91

🤖 Prompt for AI Agents
In app/api/verify-all/route.ts around lines 79-84 (and similarly 87-91), remove
or replace the console.log that prints the full verifyData/responseBody JSON
(which may contain sensitive GitHub Authorization headers/PATs); instead log
only high-level, non-sensitive metadata such as repo owner/name, filenames or
counts, and strip any headers or auth fields before logging. Ensure any helper
that extracts repo info runs before logging and only sanitized fields are
emitted to logs.

// Parse the response body to extract contributor data
// Handle GraphQL PR response, contributors API, and commits API (for backward compatibility)
if (verifyData.response && verifyData.response.body) {
try {
const contributorsData = JSON.parse(verifyData.response.body);
const responseBody = verifyData.response.body;
const contributorData = transformGraphQLPRResponse(responseBody, targetUsername || undefined);

// Extract only the specific contributor that was verified
if (Array.isArray(contributorsData)) {
let targetContributor = null;

console.log(`Found ${contributorsData.length} contributors in data`);

if (targetUsername) {
// Find the contributor matching the username from filename
console.log(`Looking for username: ${targetUsername}`);
targetContributor = contributorsData.find((c: any) =>
c.login && c.login.toLowerCase() === targetUsername.toLowerCase()
);
console.log(`Found contributor:`, targetContributor ? targetContributor.login : 'NOT FOUND');
} else {
// Fallback: use the first contributor if no username in filename
console.log('No username in filename, using first contributor');
targetContributor = contributorsData[0];
}

if (targetContributor && targetContributor.login) {
console.log(`Adding contributor: ${targetContributor.login} with ${targetContributor.contributions} contributions`);
const repoUrl = repoOwner && repoName ? `https://github.com/${repoOwner}/${repoName}` : undefined;
contributors.push({
username: targetContributor.login,
contributions: targetContributor.contributions,
avatar: targetContributor.avatar_url,
githubUrl: targetContributor.html_url,
verified: true,
repoOwner: repoOwner || undefined,
repoName: repoName || undefined,
repoUrl
});
} else {
console.log(`No valid contributor found for ${file}`);
}
if (contributorData) {
console.log(`Adding contributor: ${contributorData.username} with ${contributorData.contributions} contributions`);
const repoUrl = repoOwner && repoName ? `https://github.com/${repoOwner}/${repoName}` : undefined;
contributors.push({
username: contributorData.username,
contributions: contributorData.contributions,
avatar: contributorData.avatar,
githubUrl: contributorData.githubUrl,
verified: true,
repoOwner: repoOwner || undefined,
repoName: repoName || undefined,
repoUrl
});
} else {
console.log(`No valid contributor data found for ${file}`);
}
} catch (parseError) {
console.error(`Failed to parse contributor data from ${file}:`, parseError);
Expand Down
Loading