-
Notifications
You must be signed in to change notification settings - Fork 37
feat: add option to update security assessments data #1642
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
There was a problem hiding this 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/updateto 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', |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
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.
| statusMessage: err instanceof Error ? err.message : 'Failed to trigger security update', | |
| statusMessage: 'Failed to trigger security update', |
| await client.workflow.start(UPSERT_OSPS_BASELINE_WORKFLOW, { | ||
| taskQueue: SECURITY_BEST_PRACTICES_TASK_QUEUE, | ||
| workflowId, | ||
| args: [workflowParams], | ||
| }); |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
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.
| <p | ||
| class="text-xs leading-5" | ||
| v-html="sanitize(slotProps.message.detail)" | ||
| ></p> |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
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.
| export async function getTemporalClient(): Promise<Client> { | ||
| if (temporalClient) { | ||
| return temporalClient; | ||
| } |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
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.
|
|
||
| try { | ||
| await SECURITY_API_SERVICE.triggerSecurityUpdate(route.params.slug as string, { | ||
| repoUrl: currentRepoUrl.value || '', |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
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.
| repoUrl: currentRepoUrl.value || '', | |
| repoUrl: currentRepoUrl.value, |
| 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()}`; |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
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.
| cmDbDatabase: 'crowd-web', | ||
| temporalServerUrl: 'localhost:7233', | ||
| temporalNamespace: 'default', | ||
| temporalCertificate: '', | ||
| temporalPrivateKey: '', | ||
| temporalEncryptionKeyId: 'local', | ||
| temporalEncryptionKey: '', |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
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.
| 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). |
| * - workflowId (string): The Temporal workflow ID | ||
| * - message (string): Success or error message | ||
| */ | ||
| export default defineEventHandler(async (event): Promise<SecurityUpdateResponse | Error> => { |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
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.
| export default defineEventHandler(async (event): Promise<SecurityUpdateResponse | Error> => { | |
| export default defineEventHandler(async (event): Promise<SecurityUpdateResponse | Error> => { | |
| await auth(event); |
No description provided.