Skip to content

Conversation

@gaspergrom
Copy link
Collaborator

No description provided.

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Copy link
Contributor

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 adds functionality to trigger security assessment updates for project repositories through a new API endpoint. The implementation integrates Temporal workflow orchestration to handle asynchronous security scanning operations.

Changes:

  • Added new POST endpoint /api/project/{slug}/security/update to trigger security assessment updates for repositories
  • Integrated Temporal workflow client with encryption support for secure communication with Temporal servers
  • Updated UI to include an "Update results" button for repository-level security views with toast notifications
  • Added required Temporal client libraries and configuration options

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
pnpm-lock.yaml Added Temporal dependencies (~1.11.8), upgraded @gitbeaker packages (40.6.0→43.8.0), and removed @questdb dependencies. Contains suspicious axios version 1.13.4.
frontend/setup/runtime-config.ts Added 7 new Temporal configuration variables and GitHub token config
frontend/server/utils/temporal.ts New utility implementing Temporal client with AES-GCM encryption for payloads
frontend/server/api/project/[slug]/security/update.post.ts New API endpoint to trigger security update workflows via Temporal
frontend/package.json Added @temporalio client, common, and proto packages
frontend/app/components/uikit/toast/toast.vue Enhanced to support HTML content in toast messages with sanitization
frontend/app/components/modules/project/views/security.vue Added "Update results" button with loading state and toast notifications
frontend/app/components/modules/project/services/security.api.service.ts Added triggerSecurityUpdate method to call the new API endpoint
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

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

console.error('Error triggering security update:', err);
return createError({
statusCode: 500,
statusMessage: err instanceof Error ? err.message : 'Failed to trigger security update',
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

The error response at line 107 may leak sensitive internal error information to the client. While error messages can be helpful for debugging, exposing raw error messages from internal systems (like Temporal workflows or database queries) could reveal implementation details to potential attackers. Consider using a generic error message for production environments while logging the detailed error server-side, which you're already doing at line 104.

Suggested change
statusMessage: err instanceof Error ? err.message : 'Failed to trigger security update',
statusMessage: 'Failed to trigger security update',

Copilot uses AI. Check for mistakes.
Comment on lines +92 to +96
await client.workflow.start(UPSERT_OSPS_BASELINE_WORKFLOW, {
taskQueue: SECURITY_BEST_PRACTICES_TASK_QUEUE,
workflowId,
args: [workflowParams],
});
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

There's no rate limiting protection for this endpoint, which could allow users to trigger multiple security update workflows rapidly, potentially overloading the Temporal infrastructure or incurring excessive costs. Unlike the GET endpoint pattern shown in the codebase, this POST endpoint that triggers expensive async operations should have additional rate limiting beyond the global middleware. Consider adding endpoint-specific rate limiting or checking if a workflow is already running for this repository before starting a new one.

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +26
<p
class="text-xs leading-5"
v-html="sanitize(slotProps.message.detail)"
></p>
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

Using raw HTML with v-html directive without any note about the sanitization being performed could be misleading to future developers. While the code does call sanitize() which uses DOMPurify, it would be helpful to add a comment explaining why v-html is necessary here and that the content is being sanitized, especially since this is user-facing toast content that could potentially include dynamic data.

Copilot uses AI. Check for mistakes.
Comment on lines +133 to +136
export async function getTemporalClient(): Promise<Client> {
if (temporalClient) {
return temporalClient;
}
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

The temporal client connection is created once and cached globally, which could cause issues if the connection is lost or becomes stale. The current implementation doesn't handle connection failures or implement reconnection logic. If the Temporal server connection drops after the initial connection, all subsequent workflow starts will fail until the application restarts. Consider implementing connection health checks and reconnection logic, or handling connection errors more gracefully.

Copilot uses AI. Check for mistakes.

try {
await SECURITY_API_SERVICE.triggerSecurityUpdate(route.params.slug as string, {
repoUrl: currentRepoUrl.value || '',
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

The condition currentRepoUrl.value || '' on line 291 is redundant because the function already returns early if !currentRepoUrl.value on line 285. This check guarantees that currentRepoUrl.value is truthy at this point, making the || '' fallback unnecessary.

Suggested change
repoUrl: currentRepoUrl.value || '',
repoUrl: currentRepoUrl.value,

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +90
const { slug } = event.context.params as Record<string, string>;
const body: SecurityUpdateRequest = await readBody(event);

// Validate request body
if (!body.repoUrl) {
return createError({
statusCode: 400,
statusMessage: 'Missing required field: repoUrl is required',
});
}

// Use server-side configured token
const token = config.securityGithubToken;
if (!token) {
return createError({
statusCode: 500,
statusMessage: 'Security GitHub token is not configured on the server',
});
}

try {
// Fetch project details to get the project ID
const projectRes = await fetchFromTinybird<ProjectTinybird[]>('/v0/pipes/projects_list.json', {
slug,
details: false,
});

if (!projectRes.data || projectRes.data.length === 0) {
return createError({ statusCode: 404, statusMessage: 'Project not found' });
}

const project = projectRes.data[0];

// Validate that the repository belongs to this project
if (!project.repositories.includes(body.repoUrl)) {
return createError({
statusCode: 400,
statusMessage: 'Repository does not belong to this project',
});
}

// Get temporal client and start the workflow
const client = await getTemporalClient();

const workflowParams: IUpsertOSPSBaselineSecurityInsightsParams = {
insightsProjectId: project.id,
insightsProjectSlug: slug,
repoUrl: body.repoUrl,
token,
};

const workflowId = `security-update-${slug}-${Date.now()}`;
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

The slug parameter from the URL path is not validated before being used. While it's used to query Tinybird (which provides some indirect validation), it's also directly incorporated into the workflowId without sanitization. If a malicious user passes a slug with special characters or excessive length, it could cause issues with Temporal workflow naming conventions or logging systems. Consider adding validation to ensure the slug matches expected patterns (e.g., alphanumeric with hyphens) and has a reasonable length.

Copilot uses AI. Check for mistakes.
Comment on lines 34 to +40
cmDbDatabase: 'crowd-web',
temporalServerUrl: 'localhost:7233',
temporalNamespace: 'default',
temporalCertificate: '',
temporalPrivateKey: '',
temporalEncryptionKeyId: 'local',
temporalEncryptionKey: '',
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

The new Temporal configuration variables (temporalServerUrl, temporalNamespace, temporalCertificate, temporalPrivateKey, temporalEncryptionKeyId, temporalEncryptionKey, securityGithubToken) are added without any documentation explaining what they are, how to configure them, or what values they should have in different environments. This makes it difficult for developers to set up the application correctly. Consider adding documentation (in README.md or a separate config documentation file) explaining these new configuration requirements, especially for production deployments.

Suggested change
cmDbDatabase: 'crowd-web',
temporalServerUrl: 'localhost:7233',
temporalNamespace: 'default',
temporalCertificate: '',
temporalPrivateKey: '',
temporalEncryptionKeyId: 'local',
temporalEncryptionKey: '',
cmDbDatabase: 'crowd-web',
// Temporal workflow engine configuration:
// - temporalServerUrl: Host:port of the Temporal frontend service.
// * Development: usually "localhost:7233".
// * Production: set to the address of your managed/self-hosted Temporal cluster.
// - temporalNamespace: Temporal namespace to use.
// * Development: "default" (or a local namespace you created).
// * Production: a dedicated namespace for this application.
// - temporalCertificate / temporalPrivateKey: mTLS client certificate and key used
// to authenticate with Temporal when TLS is enabled.
// * Development: leave empty for plain-text local development unless your
// local Temporal instance requires TLS.
// * Production: required if your Temporal cluster is secured via TLS.
// Provide PEM-encoded contents via environment variables or secrets.
// - temporalEncryptionKeyId / temporalEncryptionKey: key identifier and key material
// used for payload or data encryption (if enabled).
// * Development: "local" key id with a test key is acceptable.
// * Production: use a strong key managed by your KMS/HSM or secret store. Do not
// hard-code keys in source; inject via environment variables or secret manager.
temporalServerUrl: 'localhost:7233',
temporalNamespace: 'default',
temporalCertificate: '',
temporalPrivateKey: '',
temporalEncryptionKeyId: 'local',
temporalEncryptionKey: '',
// securityGithubToken:
// GitHub token used for security-related GitHub API calls (for example, to
// access private repositories or security data). This should be a GitHub
// Personal Access Token (PAT) with the minimum required scopes.
// Do not commit real tokens; supply via environment variables or secret
// management in each environment (different tokens for dev/stage/prod as needed).

Copilot uses AI. Check for mistakes.
* - workflowId (string): The Temporal workflow ID
* - message (string): Success or error message
*/
export default defineEventHandler(async (event): Promise<SecurityUpdateResponse | Error> => {
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

This POST endpoint is missing authentication checks. Unlike other POST endpoints in the codebase (e.g., /api/community/[slug].post.ts which calls await auth(event)), this endpoint doesn't verify that the user is authenticated before allowing them to trigger a security update workflow. This could allow unauthenticated users to trigger expensive Temporal workflows, potentially leading to resource abuse and increased costs. Consider adding authentication by calling await auth(event) at the beginning of the handler, or adding this route to the protected routes list in the JWT middleware.

Suggested change
export default defineEventHandler(async (event): Promise<SecurityUpdateResponse | Error> => {
export default defineEventHandler(async (event): Promise<SecurityUpdateResponse | Error> => {
await auth(event);

Copilot uses AI. Check for mistakes.
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