Skip to content

Conversation

@github-actions
Copy link
Contributor

This is an automated pull request to release the candidate branch into production, which will trigger a deployment.
It was created by the [Production PR] action.

Co-authored-by: chasprowebdev <chasgarciaprowebdev@gmail.com>
Co-authored-by: Mariano Fuentes <marfuen98@gmail.com>
@vercel
Copy link

vercel bot commented Oct 16, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
app (staging) Ready Ready Preview Comment Oct 17, 2025 3:23pm
portal (staging) Ready Ready Preview Comment Oct 17, 2025 3:23pm

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

… issue (#1633)

* fix(app): trigger 2 new-policy-email events per second at most due to resend rate limit

* fix(app): create trigger.dev task to send policy email due to resend rate limit issue

* fix(app): remove unused send-policy-email API

---------

Co-authored-by: chasprowebdev <chasgarciaprowebdev@gmail.com>
Co-authored-by: Mariano Fuentes <marfuen98@gmail.com>
@comp-ai-code-review
Copy link

comp-ai-code-review bot commented Oct 16, 2025

🔒 Comp AI - Security Review

🔴 Risk Level: HIGH

No OSV/CVE findings. Code contains concrete injection/data-leak issues: stored XSS (unsanitized comments/metadata), SQL/command/path injection risks from unsanitized domain/ID usage, and log/header injection exposure.


📦 Dependency Vulnerabilities

✅ No known vulnerabilities detected in dependencies.


🛡️ Code Security Analysis

View 19 file(s) with issues

🔴 .github/workflows/trigger-tasks-deploy-main.yml (HIGH Risk)

# Issue Risk Level
1 Secrets passed as env to deploy command (exfiltration risk) HIGH
2 --log-level debug may leak secrets in logs HIGH
3 Running remote package via bunx (supply-chain risk) HIGH
4 Unpinned third-party actions/packages (oven-sh/setup-bun) HIGH
5 Custom/self-hosted runner 'warp-...' may expose secrets to operator HIGH

Recommendations:

  1. Avoid passing secrets directly as environment variables to untrusted/third-party tools. Prefer OIDC or ephemeral tokens where supported (e.g., GitHub OIDC for cloud provider deployments) or inject secrets only into steps that absolutely need them and only from protected environments.
  2. Remove --log-level debug in production deploys or ensure any debug output is scrubbed. Enable masking and avoid printing secrets; audit the invoked tool (trigger.dev@4.0.0) for what it logs at debug level.
  3. Minimize running remote packages in CI. If you must, pin to a specific immutable version and verify its provenance. Consider vendoring/building the tool in a controlled step or using a verified action/runner image.
  4. Pin actions to an immutable reference (commit SHA) rather than a floating tag (e.g., use oven-sh/setup-bun@) and pin any packages or use lockfiles. Enable Dependabot and monitor supply-chain advisories.
  5. If using self-hosted runners, restrict access to the runner hosts, enable least-privilege for secrets, isolate runners per team/project, and require approval for environment use. Prefer GitHub-hosted runners for sensitive workflows if possible.
  6. Restrict workflow permissions (use minimal required permissions), enable environment protection rules (required reviewers, wait timers), and enable secret scanning/audit logs for access to critical secrets.

🔴 .github/workflows/trigger-tasks-deploy-release.yml (HIGH Risk)

# Issue Risk Level
1 Remote package execution with secrets via bunx trigger.dev@4.0.0 deploy HIGH
2 Secrets exposed to shell if printed in step logs HIGH
3 Untrusted/custom runner: warp-ubuntu-latest-arm64-4x may leak secrets HIGH
4 Actions and tools pinned only to major tags, not immutable SHAs HIGH
5 No explicit job permissions or least-privilege scope for tokens HIGH

Recommendations:

  1. Avoid executing remote packages (bunx @ ...) while passing repository secrets. Instead, install a vetted CLI as a dependency (pin to an exact checksum/version) in the repository or use a prebuilt binary from a trusted source.
  2. Pin GitHub Actions and external actions to immutable SHAs (e.g., actions/checkout@) rather than major tags to prevent supply-chain tampering. For third-party tools, pin exact package versions and verify checksums.
  3. Do not pass long-lived secrets directly into steps that run third-party code. Prefer short-lived tokens via OIDC where possible, or create scoped tokens with the minimum necessary permissions.
  4. Add an explicit permissions: block at the workflow or job level to enforce least privilege (e.g., permissions: contents: read, id-token: write) and avoid granting broad default permissions.
  5. Prefer GitHub-hosted runners for sensitive workflows or ensure any self-hosted/third-party runner is fully trusted, hardened, and under your control. If using self-hosted runners, restrict network access and audit runner agents.
  6. Ensure logs do not echo secrets. Avoid commands that print environment variables or outputs that may include secrets. Rely on GitHub Actions secret redaction and additional runner-side protections where applicable.
  7. If you must run third-party CLIs, vendor the CLI into the repository or CI image after verifying its integrity, and run it with the least-privilege credentials necessary.

🔴 apps/api/src/main.ts (HIGH Risk)

# Issue Risk Level
1 CORS allows any origin with credentials (origin: true, credentials: true) HIGH
2 Swagger UI served unconditionally (API docs exposed in production) HIGH
3 Swagger persistAuthorization enabled (storing auth tokens in UI) HIGH
4 OpenAPI file written to repo path in dev (potential info disclosure) HIGH
5 No HTTP security headers configured (use helmet/CSP/HSTS/XFO) HIGH

Recommendations:

  1. CORS: Replace origin: true with a validated list or a dynamic origin-check function that only allows trusted origins (e.g., read allowed origins from env). If possible set credentials: false unless cookies must be shared; if credentials:true is required keep origin restricted to explicit trusted domains.
  2. Swagger exposure: Only mount Swagger in non-production environments or protect the /api/docs route with authentication (e.g., middleware that requires an admin API key or OAuth). Example: wrap SwaggerModule.setup in a NODE_ENV !== 'production' guard or a check for an allowlist flag.
  3. PersistAuthorization: Set swaggerOptions.persistAuthorization to false or remove it, and require explicit authentication to authorize the UI. Consider clearing stored tokens on logout and adding short-lived tokens for UI use.
  4. OpenAPI file writing: Avoid writing OpenAPI JSON into the repository tree that might be accessible. If writing is necessary for dev tooling, ensure it's limited to local developer machines (not CI or shared servers), use a secure path outside the served repository, apply tight file permissions, and ensure it is not committed to VCS (add to .gitignore).
  5. HTTP security headers: Add helmet() (or equivalent) to set HSTS, X-Frame-Options, X-Content-Type-Options, and default CSP. Also consider rate limiting, CSRF protections for state-changing endpoints, and secure cookie flags (HttpOnly, Secure, SameSite) for cookies used in authentication.

🟡 apps/api/src/trust-portal/dto/domain-status.dto.ts (MEDIUM Risk)

# Issue Risk Level
1 DomainVerificationDto fields lack validation (type, domain, value, reason) MEDIUM
2 DomainStatusResponseDto.verification array items are not validated MEDIUM
3 Only GetDomainStatusDto.domain is validated; other inputs unvalidated MEDIUM

Recommendations:

  1. Add class-validator decorators to DomainVerificationDto fields: e.g., @IsString() for type/value, @IsOptional() for reason, and @IsFQDN() or @matches(...) for the domain field.
  2. Validate nested arrays: on DomainStatusResponseDto (or any request DTO that contains verification items) add @ValidateNested({ each: true }) and @type(() => DomainVerificationDto) to the verification property so items are validated.
  3. If 'type' is constrained, use @IsEnum(...) or @isin([...]) to limit allowed verification types (TXT, CNAME, etc.).
  4. Enforce length and content limits: use @Length/@maxlength and @matches to prevent extremely long or malicious payloads.
  5. Enable and configure NestJS ValidationPipe globally (e.g., app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }))) so incoming requests are validated and unwanted properties are stripped/rejected.
  6. Sanitize or escape values before using them in any security-sensitive contexts (database queries, command execution, shell calls). Never assume DTOs alone make inputs safe.
  7. Treat response DTOs differently: responses do not need class-validator decorators for incoming validation, but request DTOs (payloads from clients) must be validated. Ensure all endpoints that accept DomainVerificationDto-like objects validate them.

🟡 apps/api/src/trust-portal/trust-portal.controller.ts (MEDIUM Risk)

# Issue Risk Level
1 No input validation on query DTO before service call MEDIUM
2 User-controlled 'domain' passed to service without sanitization MEDIUM
3 Potential SQL/command injection in trustPortalService via dto MEDIUM
4 Optional X-Organization-Id header may allow cross-org access if unchecked MEDIUM

Recommendations:

  1. Enable validation (class-validator) for GetDomainStatusDto
  2. Sanitize and whitelist domain input (hostname regex)
  3. Use parameterized queries/prepared statements in services
  4. Require and verify X-Organization-Id for org scoping
  5. Add rate limiting and request logging for this endpoint

🟡 apps/api/src/trust-portal/trust-portal.service.ts (MEDIUM Risk)

# Issue Risk Level
1 Missing domain validation (no format/sanitization) MEDIUM
2 Domain used directly in API path (path injection/traversal risk) MEDIUM
3 Vercel access token not enforced; requests sent with empty token MEDIUM
4 Sensitive verification values returned to clients MEDIUM
5 Unsanitized logging of domain and error stack (log injection/leak) MEDIUM
6 No axios timeout or retries configured (DoS/resource hang) MEDIUM

Recommendations:

  1. Validate and sanitize the domain input (e.g., require non-empty FQDN via strict regex) before use and reject or normalize invalid input.
  2. Encode/validate path segments before using in the URL (e.g., use encodeURIComponent(domain) or validate domain characters) to avoid path injection/traversal.
  3. Enforce VERCEL_ACCESS_TOKEN presence at startup (throw/fail if missing) instead of proceeding with an empty Authorization header.
  4. Avoid returning raw verification secrets (e.g., TXT values). Redact or omit sensitive verification values in API responses; return only metadata indicating presence/required steps.
  5. Sanitize or strictly validate values written to logs (strip CR/LF and control characters or use structured logging) to mitigate log injection and accidental leakage of sensitive data.
  6. Configure axios timeouts and consider retry/backoff or circuit-breaker policies (e.g., set timeout, use axios-retry or a resiliency library) to prevent hanging requests and improve availability.

🔴 apps/app/src/actions/policies/accept-requested-policy-changes.ts (HIGH Risk)

# Issue Risk Level
1 Client-supplied approverId used for authorization HIGH
2 No check that acting user is the approver HIGH
3 Stored XSS via unsanitized comment persisted HIGH
4 Potential sensitive info in console.error logs HIGH

Recommendations:

  1. Do not rely on client-supplied approverId for authorization. Remove approverId from client input and fetch/verify the approver server-side (e.g., compare policy.approverId to ctx.user.id or lookup the approver record).
  2. Enforce that the acting user is the approver: check ctx.user.id === policy.approverId (or that the member record for ctx.user.id has the approver role/permission) before performing the publish/update.
  3. Sanitize/escape comment content before saving to the database or store raw content but ensure output encoding on render. Consider using a well-tested sanitizer (or a allowlist) and/or store comments as plain text and escape when rendering.
  4. Introduce role/permission checks for this action (e.g., only users with approver/admin role can call it). Validate membership and role from server-side member records, not client input.
  5. Avoid logging sensitive details: replace console.error('Error...', error) with minimal context in logs and record full errors in a secure logging system if needed. Remove or redact sensitive fields from any log output.

🟡 apps/app/src/app/(app)/[orgId]/people/devices/data/index.ts (MEDIUM Risk)

# Issue Risk Level
1 No validation of fleetDmLabelId/host IDs before using in fleet API paths MEDIUM
2 No error handling for fleet API calls; unhandled rejections may leak errors MEDIUM
3 Unbounded parallel requests via Promise.all may exhaust resources or cause DoS MEDIUM
4 Potential exposure of sensitive device data (returns full Host objects) MEDIUM
5 No explicit RBAC/role checks beyond session.organizationId MEDIUM

Recommendations:

  1. Validate and sanitize fleetDmLabelId and host IDs before building URLs: enforce a strict format (e.g., numeric ID or UUID) and coerce/validate types (Number/parseInt with isFinite, or regex for UUID). Use encodeURIComponent or a URL-building library to avoid injecting path characters.
  2. Verify ownership/association: confirm label IDs and host IDs returned from the DB actually belong to the current organization or to employees the caller is allowed to access before calling fleet endpoints.
  3. Add robust error handling around all fleet API calls: wrap calls in try/catch, handle per-request errors (use Promise.allSettled or handle errors per-promise) and return safe/partial results rather than letting exceptions bubble and leak implementation details.
  4. Limit concurrency and batch requests: do not call fleet.get for hundreds/thousands of hosts concurrently. Use a concurrency limiter (e.g., p-limit) or process in batches (e.g., chunks of 5-20) and add backpressure/rate-limiting to avoid exhausting resources or triggering downstream rate limits.
  5. Avoid returning full Host objects: map results to a minimal DTO containing only fields required by the caller (e.g., id, hostname, platform) or explicitly remove sensitive fields (IP addresses, auth tokens, installed software lists, etc.).
  6. Enforce RBAC and permission checks: beyond checking session.activeOrganizationId, verify the caller has the necessary role/permission to list devices for the organization and that each employee/device is permitted to be seen by this caller.
  7. Use defensive programming for DB-derived values: even though fleetDmLabelId comes from the DB, apply validation/sanitization because DB contents can be corrupted or originate from user-controlled inputs elsewhere.
  8. Log and monitor failures securely: when catching errors, avoid logging sensitive device details; log enough context for diagnosis and implement alerting for repeated failures or large error rates.

🟡 apps/app/src/app/(app)/[orgId]/settings/context-hub/components/table/ContextColumns.tsx (MEDIUM Risk)

# Issue Risk Level
1 Delete action relies on client-sent id without server auth checks MEDIUM
2 Unvalidated id payload can be forged client-side to delete arbitrary entries MEDIUM
3 No CSRF/anti-forgery protection visible for delete action MEDIUM
4 JSON.parse of answer may throw on malformed data causing a UI crash MEDIUM

Recommendations:

  1. Enforce server-side auth and authorization checks in deleteContextEntryAction
  2. Validate id ownership and input server-side before performing deletes
  3. Require CSRF/anti-forgery tokens or authenticated actions for destructive ops
  4. Wrap JSON.parse in try/catch or validate with safe parser before parsing
  5. Ensure output is rendered escaped; avoid dangerouslySetInnerHTML

🟡 apps/app/src/app/(app)/[orgId]/settings/trust-portal/components/TrustPortalDomain.tsx (MEDIUM Risk)

# Issue Risk Level
1 Client-side verification flags in localStorage can be tampered by users MEDIUM
2 DNS check action invoked with unvalidated domain via form.watch() MEDIUM
3 Domain regex is restrictive/inconsistent; may accept malformed or reject valid domains MEDIUM
4 Server error messages forwarded to toast may leak internal info MEDIUM

Recommendations:

  1. Do not trust client-side flags in localStorage for verification state. Always treat server-side verification as authoritative: fetch verification status from the server or require a server-side verification endpoint before treating a domain as verified in the UI or enabling related functionality.
  2. Validate and normalize the domain value before calling checkDnsRecord (and other actions). Even though the UI condition limits the Check DNS button to the current initialDomain, calling checkDnsRecord with form.watch('domain') is still passing a client-side value. Add explicit validation at the call site (e.g., run the same zod schema / normalization) and enforce server-side validation in the action handler.
  3. Replace the custom regex with a robust domain validation approach that supports modern TLDs and IDNs (punycode). Consider using a well-maintained library (for example, validator.js, publicsuffix-list-based validators, or a dedicated domain parsing library) or expand the regex to match current TLD lengths and allow punycode. Normalize/trim input (lowercase host labels, convert Unicode to punycode where appropriate) before validation.
  4. Do not display raw server/internal error strings directly to users. Show generic, user-friendly messages in the UI and log full internal errors server-side. If some server error details are safe to show (validation errors), structure and sanitize them explicitly. For example: toast.error('Failed to update custom domain. Please check the domain and try again.') and send detailed error info to logs/monitoring.
  5. On the server/action side, always re-verify DNS ownership before performing any sensitive operations. Do not rely on client-supplied indicators (localStorage, client UI state) to make authorization/verification decisions.
  6. Consider removing or cryptographically signing any client-side state that influences security-related UI flows if you absolutely need to cache verification state on the client (prefer short TTLs and server-backed authoritative checks).

🔴 apps/app/src/app/(app)/onboarding/actions/complete-onboarding.ts (HIGH Risk)

# Issue Risk Level
1 publicAccessToken set as cookie without Secure/HttpOnly/SameSite HIGH
2 publicAccessToken included in JSON response (token leakage) HIGH
3 Stored XSS: user answers saved without sanitization HIGH
4 RevalidatePath uses attacker-controlled headers (referer/x-pathname) HIGH
5 No input size limits for free-text fields (DB/storage abuse) HIGH
6 Error logging may leak sensitive info to logs HIGH

Recommendations:

  1. When setting cookies, always include security flags. For Next.js cookies().set(...) add HttpOnly: true, Secure: true (in production), and SameSite: 'Strict' or 'Lax' as appropriate.
  2. Do not return sensitive tokens in JSON responses. Keep publicAccessToken server-side (in cookie or server session) and only return minimal data required by the client (or return a non-sensitive status).
  3. Sanitize or escape user-provided content before storing or (preferably) before rendering. Use a well-vetted HTML-escaping library or sanitize on render, and treat any stored answers as untrusted input to prevent stored XSS.
  4. Do not call revalidatePath() with raw header values. Validate and normalize the path against an allowlist or derive the path server-side from known-safe values (e.g., only allow '/', '/:orgId', or other explicit routes). Parse and canonicalize any input path and reject suspicious values.
  5. Enforce length limits and validation on all free-text inputs in the schema (e.g., add max length constraints to industry, teamSize, devices, authentication, etc.) and ensure the DB schema has column length constraints to prevent storage abuse.
  6. Avoid logging raw error objects that could contain secrets. Use structured logging with redaction of sensitive fields and log only necessary error metadata. Consider reporting full errors to secure error-tracking (with access controls) rather than stdout.

🟡 apps/app/src/app/(app)/onboarding/hooks/usePostPaymentOnboarding.ts (MEDIUM Risk)

# Issue Risk Level
1 Sensitive data persisted in localStorage (savedAnswers, organizationName) MEDIUM
2 Client trusts and sends localStorage data; can be tampered to forge payloads MEDIUM
3 No full validation of final combined payload before execute MEDIUM
4 Uses server redirectUrl directly in router.push (open redirect risk) MEDIUM
5 Event tracking may leak organization_id to analytics/third parties MEDIUM

Recommendations:

  1. Avoid storing sensitive PII (shipping fullName, address, phone, etc.) in localStorage. Use server-side session storage or an HTTP-only, secure cookie, or encrypt data client-side with keys not accessible to arbitrary JS. At minimum, avoid persisting sensitive fields and minimize retention.
  2. Treat localStorage as untrusted input. Do not rely on it for authoritative state. Perform full server-side validation and authorization of any data sent from the client, and do not assume client-held state is honest.
  3. Validate the full combined payload before sending it to the server or ensure the server enforces strict validation/sanitization. Implement a zod/global schema for the entire CompanyDetails payload and validate it (client-side for UX and server-side for security) before calling completeOnboarding.
  4. Do not blindly navigate to a redirectUrl returned from the server. Validate redirectUrl against an allowlist of trusted internal paths/domains or return only a token/path that the client maps to an internal route. Alternatively, have the server perform the redirect (so it can validate the URL) or return a route key rather than a full URL.
  5. Avoid sending raw organization_id or other PII to third-party analytics. Remove or pseudonymize sensitive identifiers before sending to trackers, or use server-side event forwarding where you can strip or hash PII before it reaches third parties. Document and limit what is sent to analytics.

🟡 apps/app/src/app/(app)/setup/components/OnboardingStepInput.tsx (MEDIUM Risk)

# Issue Risk Level
1 Missing validation on fullName and phone inputs MEDIUM
2 Website input may accept unvalidated URLs MEDIUM
3 Describe textarea lacks strict validation/sanitization MEDIUM
4 SelectPills permits custom values without sanitization MEDIUM
5 Form values set/get used without normalization or checks MEDIUM

Recommendations:

  1. Add explicit client-side validation schemas (e.g., Zod/Yup + react-hook-form resolver) for all form fields; enforce patterns (phone), maxLength, and required where appropriate.
  2. Validate and sanitize all inputs server-side before persistence or use. Never rely solely on client-side checks.
  3. Restrict/validate custom entries from SelectPills (e.g., allow only alphanumeric/limited punctuation, max length) and canonicalize them before saving.
  4. Ensure WebsiteInput validates URLs (scheme, host) and sanitize inputs; additionally validate the submitted URL on the server.
  5. Normalize data (trim, unify casing, remove dangerous characters) before using or persisting; apply consistent normalization on both client and server.
  6. Add defensive checks where form.setValue/getValues are used (e.g., cast/validate before calling) and consider using typed form schemas to prevent unexpected values.

🟡 apps/app/src/app/(app)/setup/hooks/useOnboardingForm.ts (MEDIUM Risk)

# Issue Risk Level
1 Forwarding search/query params in redirects can leak sensitive tokens MEDIUM
2 Raw user form values are sent to third-party analytics/tracking MEDIUM
3 Final submission payload not revalidated before server action MEDIUM

Recommendations:

  1. When redirecting, do not blindly forward all existing search params. Build an allowlist of safe params to forward (e.g., utm_*, non-sensitive flags) and strip anything that looks like tokens (authorization, access_token, session, jwt, api_key, etc.).
  2. If you must forward params, explicitly remove known sensitive keys before appending to the URL, and prefer storing sensitive in server-side session/KV rather than in query strings.
  3. Avoid sending raw form values to third-party analytics. Use an allowlist of fields that are safe to track, remove or anonymize PII (names, emails, phone numbers, addresses, website full paths), and truncate or hash values where applicable.
  4. Ensure server-side validation and sanitization in createOrganizationMinimal (do not rely on client-side checks). Validate types, lengths, allowed characters, and enforce an allowlist for fields like frameworkIds, organizationName, website.
  5. Encode/normalize values used in paths (e.g., router.push) to prevent malformed URIs—use encodeURIComponent or an appropriate router/path builder. Prefer server-returned canonical IDs (e.g., UUIDs) and validate them server-side before reflecting them back to clients.
  6. Log minimal data on analytics events (e.g., counts, categories) rather than raw text; for debugging use internal logs with access controls.

🟡 apps/app/src/hooks/use-domain.ts (MEDIUM Risk)

# Issue Risk Level
1 Unvalidated domain param sent to API query string MEDIUM
2 Domain not URL-encoded before insertion into endpoint MEDIUM

Recommendations:

  1. Validate domain format client-side using a strict allowlist/regex (e.g., RFC-compliant hostname validation) and enforce a max length (<=253). Reject inputs with spaces or control characters.
  2. Encode the domain when building the URL: use encodeURIComponent(domain) or URLSearchParams / URL API to construct query strings.
  3. Strip or reject CRLF and other control characters to avoid header injection or request-splitting attacks.
  4. Always enforce server-side validation and sanitization of the domain parameter — never trust client-side checks alone.
  5. Consider sending sensitive parameters in a JSON body (POST) or using parameterized backend handling if appropriate, and ensure the server ignores unexpected extra query parameters.
  6. Use centralized helper utilities for URL construction (URL and URLSearchParams) and add unit tests to validate edge cases (e.g., inputs containing '&', '=', '?', or control characters).

🟡 apps/app/src/jobs/tasks/email/new-policy-email.ts (MEDIUM Risk)

# Issue Risk Level
1 Missing validation: payload fields used without sanitization MEDIUM

Recommendations:

  1. Validate incoming payloads at the task boundary with a runtime schema (e.g., Zod or Joi). Enforce types, allowed values for notificationType, email format, max lengths for userName and policyName, and that organizationId is a valid UUID/ID format.
  2. Sanitize values before use: trim strings, remove/control disallowed characters, and escape any values that may be inserted into downstream systems.
  3. Do not log raw PII. Redact or hash sensitive fields before logging (e.g., log email as user@redacted or hash(email)). Replace userName and organizationName with redacted or truncated values where possible.
  4. Avoid rethrowing raw error objects/messages to callers. Wrap errors in application-specific errors or sanitize error messages so they do not leak sensitive data.
  5. Ensure the sendPolicyNotificationEmail implementation also validates/sanitizes inputs and does not assume upstream validation. If it already performs strict validation, document that clearly and keep a minimal validation at this boundary as defense-in-depth.
  6. Fix the concurrency/rate limiting mismatch: comment says 1 email/sec but concurrencyLimit is 2. If you need a strict per-second rate limit, use a rate limiter (token bucket, leaky bucket) or set concurrency and pacing to match the intended throughput.
  7. Add tests covering malformed payloads, very large strings, and payloads with unexpected characters to prevent downstream injection or logging leaks.

🟡 apps/portal/src/app/layout.tsx (MEDIUM Risk)

# Issue Risk Level
1 Analytics key exposed via NEXT_PUBLIC_POSTHOG_* env vars MEDIUM
2 Session object may be serialized to client via Providers MEDIUM
3 Passing full request headers() into auth.api.getSession MEDIUM

Recommendations:

  1. Remove NEXT_PUBLIC_ prefix from any secret keys. If the PostHog key is meant to be secret, store it in a server-only env var (no NEXT_PUBLIC_) and initialize server-side only. If it is a public-only key intended for client-side analytics, confirm it has limited scope and rotate it if mistakenly used as a secret.
  2. Sanitize the session object before passing it to client components. Only include minimal user profile fields required by the UI (e.g., id, name, email) and explicitly omit tokens, refresh tokens, raw cookies or any secrets. Alternatively, keep Providers as a server component or fetch session data inside a server component and expose only a safe subset via a dedicated API.
  3. Avoid passing the entire headers() object blindly. Provide only the specific header(s) required by auth.api.getSession (for example the cookie header) or have the auth library read headers server-side itself. Ensure auth.api.getSession does not echo or serialize sensitive headers back to the client.
  4. Audit the Providers implementation and any client components to ensure no sensitive session fields are serialized into the client bundle or exposed to browser-side code or third-party scripts. Apply explicit serialization/whitelisting of session fields.

🟡 apps/portal/src/app/providers.tsx (MEDIUM Risk)

# Issue Risk Level
1 PII/tracking: user email and id passed to analytics MEDIUM
2 Session object may expose sensitive data to client MEDIUM

Recommendations:

  1. Stop sending raw user emails to analytics. Remove userEmail from AnalyticsProvider or send a pseudonymized value (e.g., HMAC or irreversible hash with a server-side secret).
  2. Avoid sending real user IDs to analytics. Use either an analytics-specific anonymous ID or a server-side hashed ID (HMAC with secret) so the original ID cannot be reconstructed client-side.
  3. Require and respect explicit user consent (consent banner/feature-flag) before initializing analytics or sending any PII.
  4. Limit the session data passed into client components: only include the minimal fields required (e.g., boolean isAuthenticated) rather than the full session object. Do not expose tokens or sensitive user attributes to the browser.
  5. Audit AnalyticsProvider to verify it does not log/store raw PII, has appropriate retention controls, and does not forward PII to third parties without consent.
  6. Move any PII-processing (hashing/pseudonymization) to the server where secrets can be kept, and only expose safe identifiers to client-side code.

🔴 packages/docs/openapi.json (HIGH Risk)

# Issue Risk Level
1 IDOR via X-Organization-Id header allowing org access escalation HIGH
2 Clients can modify sensitive fields (hasAccess, fleetDmLabelId) -> privilege escalation HIGH
3 metadata as raw JSON string may enable stored XSS or JSON injection HIGH
4 Unvalidated URL fields (logo, website) may enable SSRF/open redirect HIGH
5 Request body fields may lead to SQL/command injection if unsanitized HIGH

Recommendations:

  1. Enforce server-side authentication and authorization for every request. Do not trust X-Organization-Id alone — verify the authenticated principal (API key or session) is authorized for the requested organization and resource.
  2. Remove or restrict client-editable sensitive fields (e.g., hasAccess, fleetDmLabelId). If they must be changeable, require an elevated role (admin) and audit/approve workflow.
  3. Treat metadata as structured JSON: accept JSON objects (not free-form strings), validate against a strict schema, sanitize stored content, and escape on output to prevent stored XSS.
  4. Validate and allowlist URL inputs (logo, website). Perform server-side URL validation (scheme, host allowlist, no private IPs), disallow localhost/private network addresses to prevent SSRF, and sanitize any user-controlled values used in redirects.
  5. Use strict input validation, parameterized queries/ORMs, and avoid building SQL/command strings with raw user input. Never pass user input to exec/eval; if subprocesses are needed, use safe APIs with argument lists.
  6. Implement per-endpoint authorization checks (resource ownership, role checks) and add logging/auditing on sensitive changes and downloads (attachments, device agents).
  7. Limit the surface of signed URL generation: verify the requester can access the underlying resource and keep short expirations and minimal permissions on generated URLs.

💡 Recommendations

View 3 recommendation(s)
  1. Add strict input validation/sanitization at DTO boundaries (e.g., class-validator/@IsFQDN/@IsEnum, or Zod) and apply @ValidateNested/@type for nested arrays so domain, id, and verification payloads are rejected unless they match an allowlist/format.
  2. Stop concatenating user-controlled values into SQL/command/URL paths. Use parameterized queries/ORM placeholders for DB access, and encode path/query segments (encodeURIComponent or URL APIs) when building outbound URLs to prevent path/traversal/command injection.
  3. Sanitize/encode all persisted or rendered user content and redact sensitive fields in responses and logs. Remove raw verification secrets from API responses, escape stored comments on output, and strip CR/LF/control chars before logging to avoid XSS and log/header injection.

Powered by Comp AI - AI that handles compliance for you. Reviewed Oct 17, 2025

@vercel vercel bot temporarily deployed to staging – portal October 16, 2025 14:50 Inactive
@comp-ai-code-review
Copy link

comp-ai-code-review bot commented Oct 16, 2025

🔒 Comp AI - Security Review

🔴 Risk Level: HIGH

No OSV/CVE findings. Multiple injection risks: unsanitized domains/label IDs and unvalidated query/header inputs are interpolated into API paths/URLs in several files.


📦 Dependency Vulnerabilities

✅ No known vulnerabilities detected in dependencies.


🛡️ Code Security Analysis

View 17 file(s) with issues

🔴 apps/api/src/main.ts (HIGH Risk)

# Issue Risk Level
1 CORS: origin set to true with credentials:true allows credentialed requests from any origin HIGH
2 Public Swagger UI at /api/docs exposes endpoints and auth schemes without protection HIGH
3 Swagger persistAuthorization:true can retain sensitive API keys in browsers HIGH
4 Large JSON/body parser limit (15mb) increases DoS/resource exhaustion risk for uploads HIGH
5 Writing OpenAPI to repo path in dev may leak internal API schema or secrets HIGH

Recommendations:

  1. CORS: Restrict origin to an allowlist of trusted domains (or implement a validation function). Do not use credentials: true together with a reflected/wildcard origin. If cookies/credentials are required, explicitly return the allowed origin for each request rather than enabling reflection for all origins.
  2. Swagger UI: Disable or restrict /api/docs in production. Require authentication (e.g., basic auth, OAuth, or IP allowlist) or mount Swagger only in non-production environments. Alternatively, conditionally enable Swagger based on an explicit env var.
  3. persistAuthorization: Set persistAuthorization to false and avoid exposing API key auth schemes publicly. Advise users not to store API keys in the browser; if needed, document token exchange flows instead of raw API keys in the UI.
  4. Body parser limits: Reduce JSON/urlencoded limits to the minimum necessary (e.g., 1–2MB), validate Content-Length, and enforce server-side checks. For large file uploads, use streaming/multipart upload endpoints that store to disk/cloud directly, and add rate limiting and request size limits at the reverse proxy/load balancer.
  5. OpenAPI write: Avoid writing full OpenAPI to a repository-accessible path or ensure the output directory is excluded from source control and access-controlled. Only generate docs to ephemeral or developer-only locations, strip sensitive schemas/fields before export, or require an explicit opt-in env var to write the file.

🟡 apps/api/src/trust-portal/dto/domain-status.dto.ts (MEDIUM Risk)

# Issue Risk Level
1 DomainVerificationDto fields lack validation and type isn't whitelisted MEDIUM
2 DomainStatusResponseDto verification items are not validated MEDIUM
3 Domain regex is inconsistent and may accept malformed domains MEDIUM

Recommendations:

  1. Add class-validator decorators to DomainVerificationDto fields (e.g., @IsString(), @isnotempty() where applicable).
  2. Restrict type to an enum and validate it with @IsEnum(...) to prevent arbitrary types (e.g., only TXT, CNAME).
  3. For arrays of nested DTOs, add @ValidateNested({ each: true }) and @type(() => DomainVerificationDto) on the verification property and validate the nested DTO fields.
  4. Replace or augment the custom regex with a proven validator (e.g., use validator.isFQDN via @IsFQDN() or a stricter/well-tested regex) and allow modern TLD lengths; ensure the regex is consistent about case and label rules.
  5. Apply input sanitization/encoding before using DTO values in DB queries, shell commands, or templates (use parameterized DB queries, avoid string interpolation into commands).
  6. Add unit tests for domain parsing/validation to ensure edge cases (long TLDs, punycode, mixed case, labels with hyphens) are handled correctly.

🟡 apps/api/src/trust-portal/trust-portal.controller.ts (MEDIUM Risk)

# Issue Risk Level
1 Unvalidated query input passed directly to service (domain) MEDIUM
2 GetDomainStatusDto validation not enforced in controller MEDIUM
3 X-Organization-Id header is optional; org scoping may be bypassed MEDIUM

Recommendations:

  1. Enable Nest validation pipe and use class-validator on GetDomainStatusDto
  2. Validate and sanitize domain input (regex/allowlist) before service call
  3. Enforce server-side authorization for organization scoping
  4. Require or verify X-Organization-Id against user session for session auth
  5. Add logging and rate limiting for the domain status endpoint

🟡 apps/api/src/trust-portal/trust-portal.service.ts (MEDIUM Risk)

# Issue Risk Level
1 Missing validation of domain input MEDIUM
2 Unsanitized domain used directly in API path MEDIUM
3 Unvalidated domain logged (possible log injection) MEDIUM
4 Full error.stack and API error bodies are logged MEDIUM
5 VERCEL_ACCESS_TOKEN not enforced on startup MEDIUM

Recommendations:

  1. Validate the domain input at the controller boundary using DTO validation (class-validator) or a strict whitelist/regex. Prefer a strict FQDN check (e.g. @IsFQDN or a tight regex) and reject or normalize invalid values before calling the service.
  2. URL-encode or otherwise sanitize the domain before interpolating into the API path (e.g. use encodeURIComponent(domain)) so special characters cannot change the request path.
  3. Avoid logging raw user-supplied domains. Redact or sanitize logged domain values (e.g. show only the domain's hostname, or mask parts) to prevent log injection and leakage.
  4. Do not log full error stacks or raw API response bodies to production logs. Log limited, non-sensitive context (error code, safe message). Use structured logging with redaction for any fields that might contain secrets.
  5. Fail fast on missing required secrets: if VERCEL_ACCESS_TOKEN is required, throw during startup rather than only logging a warning. Alternatively, use a safe default only for local/dev environments and document the behavior.
  6. Use NestJS validation pipes and DTO decorators (e.g. @IsFQDN, @IsString, @Length) so invalid inputs are rejected before reaching this service.
  7. When handling axios errors, avoid logging error.response?.data fully. Instead extract/whitelist only non-sensitive fields you need (status code and a short message). Consider centralizing HTTP client error handling and redaction.
  8. Add unit/integration tests for malicious/edge domain inputs (e.g. containing slashes, percent-encoding, newline characters) to confirm encoding/validation works as intended.

🔴 apps/app/src/actions/policies/accept-requested-policy-changes.ts (HIGH Risk)

# Issue Risk Level
1 Insufficient authorization: client-supplied approverId lets any auth user accept HIGH
2 No server-side check that current user is the approver or has permission HIGH
3 Potential runtime error: policy.signedBy may be null before .includes HIGH
4 Verbose error logging may leak sensitive info HIGH

Recommendations:

  1. Do not trust approverId from the client. Derive the approver server-side (e.g., use session/user id) or validate that the requesting user is the approver by checking user.id === policy.approverId or mapping the approver member to the authenticated user.
  2. Enforce explicit authorization: check that the current authenticated user has the permission/role to accept changes (for example, verify user.id equals approver userId or that user has an 'approver' or admin role within the organization).
  3. Wrap related DB operations (status update, clearing signedBy, comment creation) and notification scheduling in a database transaction so state changes and notifications remain consistent on error.
  4. Defensively check signedBy before using .includes: e.g., const signedBy = Array.isArray(policy.signedBy) ? policy.signedBy : []; then use signedBy.includes(...). Also consider DB schema defaults so signedBy is always an array.
  5. Avoid logging full error objects to console in production. Log minimal, non-sensitive error messages and capture full errors in a secure error-tracking system (with access controls).
  6. Remove approverId from the public input schema if not needed, or validate it strictly server-side (ensure its type/format and relation to the current user/member record).
  7. Consider limiting notification recipients to only those who should receive them and ensure email addresses are validated/normalized before sending.

🟡 apps/app/src/app/(app)/[orgId]/people/devices/data/index.ts (MEDIUM Risk)

# Issue Risk Level
1 Unsanitized fleetDmLabelId used in fleet API path MEDIUM
2 Unbounded parallel fleet API calls may cause DoS MEDIUM
3 No pagination/limits when collecting hosts MEDIUM
4 No error handling for fleet or DB requests MEDIUM
5 Returns full host objects; may leak sensitive data MEDIUM

Recommendations:

  1. Validate and sanitize fleetDmLabelId and host IDs before interpolating into API paths. Enforce type checks (e.g., numeric ID or validated UUID) and reject or coerce unexpected values.
  2. Limit concurrency when making many external requests. Use a concurrency limiter (p-limit, Bottleneck, or implement batching) instead of Promise.all on an unbounded array.
  3. Add pagination and/or upper bounds when collecting hosts and when calling Fleet label/hosts endpoints. Respect Fleet API pagination and stop/limit overall collected results to a safe maximum.
  4. Wrap DB and fleet API calls in try/catch and handle errors gracefully. Return sanitized/opaque error messages to callers and consider retry/backoff for transient fleet errors.
  5. Filter/omit sensitive host fields before returning to the client. Define an allowlist of safe host properties to return (or explicitly redact secrets, network identifiers, credentials, or other PII).
  6. Enforce and verify server-side authorization for each request. Ensure the session's activeOrganizationId is validated and the caller is authorized to view each returned host, especially if label IDs or DB values could be tampered with.

🟡 apps/app/src/app/(app)/[orgId]/settings/context-hub/components/table/ContextColumns.tsx (MEDIUM Risk)

# Issue Risk Level
1 No validation of context.id before executing delete action MEDIUM
2 Untrusted JSON parsed and displayed without sanitization MEDIUM

Recommendations:

  1. Enforce server-side validation and authorization for delete actions. The server must verify the requesting user is authorized to delete the resource identified by id, validate the id format/type, and reject malformed or unauthorized requests. Do not rely on client-side checks for authorization.
  2. Sanitize and validate context.id both client- and server-side. On the client, ensure the id is a well-formed identifier before sending; on the server, perform strict type/format validation and use parameterized queries or ORM protections to prevent injection and other misuse.
  3. Harden delete action implementation: check ownership, scope, and permissions; implement rate limiting and CSRF protections for state-changing actions where applicable; return minimal error details.
  4. Make isJSON robust and handle JSON.parse errors and large payloads. Validate the expected schema/shape of parsed JSON (e.g., allowed keys, value types, and maximum sizes) before using it. Fail gracefully if JSON is malformed or exceeds size/complexity limits.
  5. Although React escapes text content by default (so rendering values as JSX text reduces XSS risk), explicitly avoid dangerouslySetInnerHTML and verify that no downstream code inserts raw HTML. Additionally, sanitize or whitelist keys/values from untrusted JSON to avoid unexpected rendering, limit depth/size, and ensure keys used as React keys are safe strings.
  6. Add server-side logging and monitoring for delete operations and malformed/large JSON payloads to detect abuse or attempted attacks.

🟡 apps/app/src/app/(app)/[orgId]/settings/trust-portal/components/TrustPortalDomain.tsx (MEDIUM Risk)

# Issue Risk Level
1 Client-side domain validation only; can be bypassed MEDIUM
2 Untrusted verification flags stored in localStorage can be spoofed MEDIUM
3 Server error details are shown directly in toast (information leak) MEDIUM

Recommendations:

  1. Enforce/duplicate domain validation on the server side (same zod schema or server-side validation), and reject any domain that doesn't pass server validation before persisting or performing DNS checks. Do not rely solely on client-side zodResolver checks.
  2. Treat verification state as server authoritative. Do not rely on localStorage flags for authoritative UI state or granting permissions. Fetch verification status from the server (or verify the signed/tamper-evident token) before showing verified UI or allowing actions that depend on verification.
  3. Do not display raw server error objects to users. Map server errors to user-friendly messages and log full errors server-side. Strip stack traces, internal error messages, and validation internals from responses returned to the client.
  4. If you need client-side caching of verification state, sign/timestamp the cached value server-side (e.g., JWT or HMAC) and validate it on use, or prefer background polling/explicit server checks.
  5. Wrap navigator.clipboard calls in try/catch and handle permission/availability failures gracefully (toast on success only after confirmed write).

🟡 apps/app/src/app/(app)/onboarding/actions/complete-onboarding.ts (MEDIUM Risk)

# Issue Risk Level
1 Public access token set in cookie without HttpOnly/Secure flags MEDIUM
2 Error messages returned to client may leak internal details MEDIUM
3 User input stored verbatim in DB; risk of stored XSS on render MEDIUM
4 Revalidation path derived from untrusted headers (referer/x-pathname) MEDIUM
5 organizationId lacks format validation (no UUID/check) MEDIUM
6 shipping.phone lacks format/length validation MEDIUM

Recommendations:

  1. When setting cookies, explicitly set security attributes: HttpOnly: true, Secure: true, SameSite (e.g., 'Lax' or 'Strict'), and a short expiry. Example: cookies().set({ name: 'publicAccessToken', value: handle.publicAccessToken, httpOnly: true, secure: true, sameSite: 'lax', maxAge: 60 * 60 });
  2. Return generic error messages to clients (e.g., "Failed to complete onboarding"); log full error details server-side. Avoid sending error.message or stack traces in responses.
  3. Sanitize or escape user-supplied text before storing or ensure any rendering path properly HTML-escapes values. Alternatively validate/whitelist allowed inputs and enforce size limits (e.g., max length).
  4. Do not derive revalidation targets directly from untrusted headers. Use a whitelist of allowed paths or derive the path server-side from known safe values (session/org id) instead of referer/x-pathname. Validate paths strictly against expected patterns before calling revalidatePath.
  5. Enforce a stricter organizationId schema (e.g., z.string().uuid()) and validate against expected formats. Continue to verify authorization server-side (as already done).
  6. Validate shipping.phone format and length (e.g., use a phone validation library or regex, enforce min/max length) and possibly normalize/store in E.164 format. Also validate other shipping fields (address length, characters) to limit unexpected input.

🟡 apps/app/src/app/(app)/onboarding/hooks/usePostPaymentOnboarding.ts (MEDIUM Risk)

# Issue Risk Level
1 Sensitive PII stored in localStorage (fullName, address, phone) MEDIUM
2 localStorage accessible to XSS, exposing persisted onboarding data MEDIUM
3 Tracking events include org and step values, risking data leakage to trackers MEDIUM

Recommendations:

  1. Avoid storing PII in localStorage. Do not persist shipping.fullName, shipping.address, shipping.phone client-side. Instead: (a) send these fields directly to the server as soon as collected and persist server-side; or (b) keep them only in short-lived in-memory or secure, server-backed session state. If client-side storage is absolutely required, encrypt with a key only the server can verify/decrypt (but server-side storage is strongly preferred).
  2. Mitigate XSS to protect localStorage access: adopt a strict Content Security Policy (CSP), use secure coding practices (escape user-controlled data in the DOM), perform input validation/encoding, and reduce attack surface (e.g., avoid dangerouslySetInnerHTML or other direct HTML injection). Consider storing sensitive progress in HttpOnly cookies or server session rather than localStorage.
  3. Limit and sanitize data sent to tracking/3rd-party analytics: remove or hash organization identifiers and scrub step values (especially PII) before sending to external trackers. Use an allowlist for what tracker events can contain and consider sending only event-level metadata (event names, non-sensitive counts/flags) instead of raw form answers.
  4. Keep and harden server-side validation and authorization: the server action already applies a Zod schema and checks organization membership (apps/app/src/app/(app)/onboarding/actions/complete-onboarding.ts), which is good. Continue enforcing server-side validation for all onboarding completion flows and reject/normalize unexpected fields. Consider using z.object(...).strict() or explicitly strip unknown keys if you want to reject unknown properties.
  5. Although the redirect in the client (router.push(data.redirectUrl)) uses a server-provided value, it's best practice for the server to return only same-origin or allowlisted redirect targets. Validate/construct redirect URLs server-side (avoid reflecting user-controlled URLs) and, if possible, return a path rather than a full URL.
  6. If client-side progress state must remain, do not trust it as authoritative. Re-validate required fields server-side before marking onboarding complete. Consider using a signed server token to represent progress instead of raw localStorage values to make tampering detectable.
  7. To minimize prototype-pollution concerns: keep the server parsing/whitelisting of keys (as already done when building contextData from known steps) and avoid directly merging untrusted top-level objects into objects used for internal logic without schema validation.

🟡 apps/app/src/app/(app)/setup/hooks/useOnboardingForm.ts (MEDIUM Risk)

# Issue Risk Level
1 Sensitive org ID sent to analytics (trackEvent/sendGTMEvent) MEDIUM
2 User-provided fields sent to third-party analytics MEDIUM
3 Search params appended to redirect URL without validation MEDIUM
4 Saved initialData used directly in form state MEDIUM

Recommendations:

  1. Avoid sending raw identifiers or PII to third-party analytics. For organization_id or setup_id sent in trackEvent, either remove these fields from analytics payloads, use non-identifying/sketch IDs, or hash/anonymize them before sending.
  2. Do not send free-form user input to analytics or GTM. Specifically avoid sending step_value or other user-entered fields to trackOnboardingEvent/trackEvent. If you must, sanitize/normalize and minimize the data (e.g., boolean flags, counts, categories) and avoid full strings such as organization names or website URLs.
  3. Whitelist and validate search params before appending them to redirect URLs. Only copy known-safe keys and validate/encode their values (e.g., allow only expected keys such as 'utm_source', 'utm_medium' and ensure no scriptable content). Consider removing sensitive params from the redirect entirely.
  4. Treat initialData/savedAnswers as untrusted. Apply server-side validation and sanitization before persisting and before using them in analytics or constructing URLs. When rendering values in the UI maintain framework escaping practices and avoid inserting raw HTML. Consider encrypting sensitive KV-stored form data and restricting access.
  5. Where possible, avoid placing long-lived or sensitive IDs in client-visible URLs. Use opaque tokens with limited scope/TTL if you need to pass context to client navigation.
  6. Audit other uses of analytics tracking and GTM calls across the app to ensure no other PII or identifiers are leaking.

🟡 apps/app/src/app/(app)/setup/lib/constants.ts (MEDIUM Risk)

# Issue Risk Level
1 Insecure client-side storage of PII via STORAGE_KEY (shipping, phone, etc.) MEDIUM
2 Freeform fields (describe, address, fullName) lack sanitization; XSS risk MEDIUM
3 Phone field has no format validation; may accept malformed input MEDIUM

Recommendations:

  1. Audit all usages of STORAGE_KEY ('onboarding_answers') and eliminate storing sensitive PII in client-side storage (localStorage/sessionStorage). If you must persist between page loads, persist only non-sensitive state client-side and move PII to server-side storage protected by strong access controls and encryption at rest.
  2. If temporary client-side storage is unavoidable, encrypt the payload with a key not stored in localStorage (prefer server-derived or session-based keys) and limit retention period. Prefer HTTP-only, Secure cookies for session tokens rather than storing PII in browser storage.
  3. Add output escaping or a proven sanitization library (e.g., DOMPurify) where freeform fields may be rendered. In React/Next apps, prefer rendering values as text (JSX escapes by default) and avoid dangerouslySetInnerHTML unless fully sanitized. Audit templates/components that render any of these fields (describe, shipping.fullName, shipping.address, etc.) to ensure they don't inject raw HTML.
  4. Harden input validation in the Zod schema: add regex/format checks for phone numbers (prefer E.164) using libphonenumber-js or zod refinements, and consider additional validation for addresses if applicable. Example: phone: z.string().refine(val => isValidE164(val), 'Invalid phone number').
  5. Sanitize and normalize freeform text on input and/or before persistence (trim, length limits already present for describe; keep and extend to other fields). Log fewer PII details and ensure logging/storage redaction policies are in place.
  6. Define retention and access controls for onboarding data: automatic expiry, user-accessible deletion, minimal privileges for any client- or server-side code that can read these answers.
  7. Add automated tests and code-review checks to flag any future usage of STORAGE_KEY with direct localStorage usage or rendering of unescaped user input.

🟡 apps/app/src/hooks/use-domain.ts (MEDIUM Risk)

# Issue Risk Level
1 Unsanitized user domain inserted into API query parameter MEDIUM
2 No client-side validation allows malformed input enabling backend injection MEDIUM

Recommendations:

  1. Encode the domain when building the URL: use encodeURIComponent(domain) or URLSearchParams to avoid injecting characters that change the query structure.
  2. Add client-side validation: trim input, enforce an allowlist regex for valid hostnames/domains (e.g. ^([a-z0-9-]+.)+[a-z]{2,}$ with case-insensitive flag) and a reasonable max length (e.g. 253 chars). Reject or normalize whitespace-only values.
  3. Always enforce server-side validation and canonicalization: treat the domain parameter as untrusted, validate against the same allowlist, enforce length limits, and reject malformed values.
  4. Limit allowed characters (letters, digits, hyphen, dot) and disallow control characters (CR/LF) to avoid header/CRLF injection or request smuggling vectors.
  5. Use safer URL construction helpers (URL, URLSearchParams) instead of manual string concatenation to prevent accidental injection of special characters.

🟡 apps/app/src/jobs/tasks/email/new-policy-email.ts (MEDIUM Risk)

# Issue Risk Level
1 Missing validation for task payload fields (email, userName, policyName, organizationId) MEDIUM

Recommendations:

  1. Validate and whitelist all payload fields before use (email, userName, policyName, organizationId, organizationName, notificationType). Reject or normalize unexpected values at task enqueue time and again at task run time.
  2. Use a schema validation library (e.g., zod or joi) to enforce types, required fields, allowed enum values, length limits, and sane character sets.
  3. Validate email format (and optionally normalize/canonicalize) before using or logging it; use a well-tested email validation routine.
  4. Sanitize or escape user-supplied values before injecting into email templates to prevent template injection or HTML/script injection in rendered emails.
  5. Avoid logging raw PII. Redact or hash sensitive fields (emails, organizationId, userName) in logs, or log only identifiers that cannot be reversed to personal data.
  6. Ensure the downstream sendPolicyNotificationEmail implementation also validates/escapes inputs and is not assumed to be a safe sink.
  7. Add authorization checks and provenance metadata so only trusted systems/users can enqueue tasks with these payloads.

🟡 apps/portal/src/app/layout.tsx (MEDIUM Risk)

# Issue Risk Level
1 Analytics key in NEXT_PUBLIC_* exposes to clients MEDIUM
2 Forwarding all request headers to auth.api.getSession may leak secrets MEDIUM

Recommendations:

  1. Remove NEXT_PUBLIC prefix from any secret/analytics keys so they are not exposed to the client. Use server-only env vars (no NEXT_PUBLIC) and access them only in server/runtime code.
  2. Rotate the analytics key if it has already been published to clients (or consider revoking and issuing a new key).
  3. Limit forwarded headers to the minimum required by auth.api.getSession (for example, pass only the Cookie header or a specific authorization header). Avoid blindly forwarding all headers from headers() to downstream libraries.
  4. If the auth library requires specific headers, explicitly pick and validate those headers before passing them (e.g., const h = { cookie: headers().get('cookie') } ).
  5. Audit the NuqsAdapter / Providers and any third-party integrations to ensure they do not leak forwarded headers or send client-exposed keys to third parties.
  6. If analytics must run on the client, use a public, scoped key designed for client use and limit its capabilities. Otherwise proxy analytics calls through your server so the server-only key is never exposed.

🟡 apps/portal/src/app/providers.tsx (MEDIUM Risk)

# Issue Risk Level
1 PII leak: sending user email to analytics on client MEDIUM
2 Identifiable tracking: sending raw user ID to analytics MEDIUM
3 Client-side exposure of session/user object to bundle MEDIUM
4 No consent check before initializing analytics MEDIUM

Recommendations:

  1. Do not send raw emails to client-side analytics. Omit email or pseudonymize it (e.g., send only domain, or a server-side computed hash).
  2. Avoid sending raw user IDs from the client. Send an anonymous ID or a server-side HMAC/hashed identifier (compute with a server secret so the client cannot reverse it).
  3. Move identification to the server where possible. Perform analytics identify calls server-side with the provider's secure SDK or a server-side proxy that can enforce policies and redact PII.
  4. Require and check explicit user consent before loading/initializing analytics. Store consent (cookie/localStorage) and initialize AnalyticsProvider lazily only after consent is granted.
  5. Minimize the session object passed to client components. Do not serialize entire session/user objects to the client—only include non-sensitive fields required for UI.
  6. Lazy-load the analytics library after consent, and configure the analytics provider to respect Do Not Track and to disable third-party data sharing where possible.
  7. Audit the analytics provider's data handling and retention policies before sending any PII. Prefer providers that support privacy controls and server-side identification.

🔴 packages/docs/openapi.json (HIGH Risk)

# Issue Risk Level
1 IDOR via X-Organization-Id header allows accessing/modifying other orgs HIGH
2 Missing input validation for name/slug/logo/website/metadata fields HIGH
3 metadata typed as string JSON may enable injection or parsing errors HIGH
4 Client-controllable sensitive flags (hasAccess, isFleetSetupCompleted) may be escalated HIGH
5 Auth mismatch: spec refers to session auth but operation security only lists apikey HIGH

Recommendations:

  1. Enforce server-side ownership/authorization checks: never trust X-Organization-Id from the client. Verify the authenticated session/API key maps to the organization and that the caller is authorized for the requested org before returning or modifying data.
  2. Harden input validation on the server: add explicit schemas/validators for name, slug (pattern + length), logo/website (format: uri), and other string fields. Reject or sanitize unexpected characters and enforce maxLength where appropriate.
  3. Use structured types for metadata: accept/validate JSON objects with a known schema rather than free-form JSON-in-string. If you must accept a string, parse with strict schema validation and reject malformed JSON to avoid parsing-related injection or errors.
  4. Protect sensitive fields: restrict updates to sensitive flags (hasAccess, isFleetSetupCompleted, fleetDmLabelId, etc.) to high-privilege roles only and audit/require additional checks (e.g., admin approval, ACL checks). Consider removing these from client-writable DTOs if not required.
  5. Fix OpenAPI auth documentation and enforcement: document all supported auth methods in components.securitySchemes (e.g., cookie / bearer / session) and reflect them in operation-level 'security' entries. Ensure server enforces the same auth methods as documented so clients/devs are not misled.
  6. Additional measures: add server-side rate limiting and logging for org-scoped operations, perform input sanitization before any downstream use (SQL/OS/exec), and add automated schema validation tests to catch regressions.

💡 Recommendations

View 3 recommendation(s)
  1. Add defensive validation and DTO checks at controller boundaries (e.g., class-validator: @IsFQDN()/@IsString()/@IsUUID()/@ValidateNested()) for domain, fleetDmLabelId and header inputs (trust-portal DTOs/controller, use-domain, devices data) so invalid values are rejected before service calls.
  2. Always URL-encode or use URL/URLSearchParams/axios params instead of string interpolation when building external request paths (e.g., encodeURIComponent(domain) or pass domain as query param) to prevent path/query injection (trust-portal.service, apps/app use-domain, devices data).
  3. Remove client-exposed secrets/keys from client bundles and stop relying on client-supplied sensitive flags/IDs: move analytics/secret keys out of NEXT_PUBLIC vars and treat X-Organization-Id and other client-provided IDs as untrusted (derive/verify server-side).

Powered by Comp AI - AI that handles compliance for you. Reviewed Oct 16, 2025

* feat(app): add shipping info step as onboarding question

* fix(app): increase of line height for step title on onboarding

* fix(app): fix typescript error

* fix(app): update shipping info step on onboarding screen

---------

Co-authored-by: chasprowebdev <chasgarciaprowebdev@gmail.com>
Co-authored-by: Mariano Fuentes <marfuen98@gmail.com>
…for vercel (#1663)

* feat(app): show domain verification status on trust portal settings

* feat(api): create an API to get the vercel trust portal domain status

* fix(app): show domain verification status using api

* fix(api): validate incoming request data using class-validator decorators for domain-status API

* fix(app): prevent API call if the domain is empty

---------

Co-authored-by: chasprowebdev <chasgarciaprowebdev@gmail.com>
…on the portal (#1668)

Co-authored-by: chasprowebdev <chasgarciaprowebdev@gmail.com>
Co-authored-by: Mariano Fuentes <marfuen98@gmail.com>
* fix(app): add resend as a dependency to packages.email

* build(trigger): update trigger deployment workflows to install email package

---------

Co-authored-by: chasprowebdev <chasgarciaprowebdev@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants