Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: CodeQL

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
schedule:
- cron: "30 1 * * 0"

jobs:
analyze:
name: Analyze (javascript-typescript)
runs-on: ubuntu-latest
permissions:
security-events: write
packages: read
actions: read
contents: read

steps:
- name: Checkout repository
uses: actions/checkout@v4
Comment on lines +22 to +23

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify checkout steps explicitly disable credential persistence
rg -nP 'uses:\s*actions/checkout@' .github/workflows -A4 -B1

Repository: hrx01-dev/Servio

Length of output: 1496


🌐 Web query:

GitHub actions checkout persist-credentials false security best practice

💡 Result:

Setting persist-credentials: false is considered a security best practice for GitHub Actions workflows that do not require authenticated Git operations [1][2][3]. By default, the actions/checkout action is configured with persist-credentials: true [4][2]. This setting automatically persists the authentication token (typically GITHUB_TOKEN) into the local Git configuration of the checked-out repository [4][5][3]. This behavior was originally designed to allow subsequent steps in a workflow to perform authenticated Git operations, such as git fetch or git push, without additional configuration [4][6][7]. However, this practice introduces several security risks: - Credential Exposure in Artifacts: If a workflow archives or uploads the repository directory as an artifact, the persisted credentials stored in.git/config may be included and leaked [2][3]. - Unauthorized Access: Any subsequent step or third-party action executed within the same job can read these credentials from the Git configuration and potentially use them to perform unauthorized actions, such as pushing commits or accessing other repository data [5][2][3]. To mitigate these risks, you should explicitly set persist-credentials to false in your workflow definition whenever your job does not need to perform authenticated Git commands [1][2][3]: - uses: actions/checkout@v4 with: persist-credentials: false While modern versions of actions/checkout have improved security by storing credentials in a separate file under $RUNNER_TEMP rather than directly in.git/config [4][6][7], setting persist-credentials: false remains the recommended practice to adhere to the principle of least privilege and minimize the exposure of sensitive tokens [1][2]. If your workflow does require authenticated Git operations, ensure that your GITHUB_TOKEN permissions are scoped as strictly as possible [5].

Citations:


🏁 Script executed:

#!/bin/bash
# Check all workflow files for checkout usage and credential persistence settings
fd '\.yml$' .github/workflows/ | while read file; do
  echo "=== $file ==="
  grep -A 5 'actions/checkout' "$file" || true
done

Repository: hrx01-dev/Servio

Length of output: 892


Add persist-credentials: false to the checkout step.

The actions/checkout step should explicitly disable credential persistence to prevent the GITHUB_TOKEN from being stored in the Git configuration, reducing the risk of token exposure in artifacts or unauthorized access by subsequent steps.

Suggested hardening
       - name: Checkout repository
        uses: actions/checkout@v4
+        with:
+          persist-credentials: false

This is a security best practice recommended by OWASP and aligns with the principle of least privilege.

🧰 Tools
🪛 zizmor (1.25.2)

[warning] 22-23: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 23-23: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/codeql.yml around lines 22 - 23, The actions/checkout@v4
step in the checkout repository task does not explicitly disable credential
persistence, which allows the GITHUB_TOKEN to remain in the Git configuration.
Add the `persist-credentials: false` parameter to the checkout step to ensure
the token is not stored in the Git configuration, reducing the risk of token
exposure in artifacts or subsequent steps.

Source: Linters/SAST tools

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify non-SHA action refs in workflows
rg -nP '^\s*uses:\s*[^@]+@v\d+' .github/workflows

Repository: hrx01-dev/Servio

Length of output: 977


Pin GitHub Actions to commit SHAs instead of mutable version tags.

Lines 23, 26, and 32 use mutable tags (@v4, @v3). This weakens CI supply-chain integrity and introduces risk if these tags are re-released or modified upstream.

Suggested fix
-      - name: Checkout repository
-        uses: actions/checkout@v4
+      - name: Checkout repository
+        uses: actions/checkout@<full_commit_sha>

-      - name: Initialize CodeQL
-        uses: github/codeql-action/init@v3
+      - name: Initialize CodeQL
+        uses: github/codeql-action/init@<full_commit_sha>

-      - name: Perform CodeQL Analysis
-        uses: github/codeql-action/analyze@v3
+      - name: Perform CodeQL Analysis
+        uses: github/codeql-action/analyze@<full_commit_sha>
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 22-23: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 23-23: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/codeql.yml at line 23, Replace all mutable version tags in
the GitHub Actions workflow with their corresponding full commit SHAs.
Specifically, update the `uses` fields that currently reference
`actions/checkout@v4`, and any other action references using `@v3` or `@v4`
tags, by replacing the version tag with the full 40-character commit SHA for
that action version. This ensures the workflow uses immutable references and
prevents supply-chain risks from tag modifications.

Source: Linters/SAST tools


- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: javascript-typescript
build-mode: none

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:javascript-typescript"
14 changes: 13 additions & 1 deletion src/app/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect, useLayoutEffect, useRef } from "react";
import { Routes, Route, BrowserRouter } from "react-router-dom";
import { Routes, Route, useLocation, BrowserRouter } from "react-router-dom";
import { AnimatePresence, motion } from "motion/react";
import { Navbar } from "./components/Navbar";
import { Hero } from "./components/Hero";
Expand All @@ -15,6 +15,8 @@ import { FAQ } from "./components/FAQ";
import { FinalCTA } from "./components/FinalCTA";
import { Footer } from "./components/Footer";
import NotFound from "./components/NotFound";
import { ServiceDetailPage } from "./components/ServiceDetailPage";
import { ThemeProvider } from "./hooks/useTheme";
import { SplashScreen } from "./components/SplashScreen";
import { AuthProvider } from "../Firebase/AuthContext";
import { SignIn } from "../Firebase/SignIn";
Expand All @@ -34,6 +36,12 @@ import { PricingConfig } from "../dashboard/pages/PricingConfig";

const REVEAL_EASE: [number, number, number, number] = [0.4, 0, 0.2, 1];

function ScrollToTop() {
const { pathname } = useLocation();
useEffect(() => { window.scrollTo(0, 0); }, [pathname]);
return null;
Comment on lines +39 to +42

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/app/App.tsx | head -50

Repository: hrx01-dev/Servio

Length of output: 2754


🏁 Script executed:

rg "`#services`|`#quote`" --type jsx --type tsx --type js --type ts

Repository: hrx01-dev/Servio

Length of output: 88


🏁 Script executed:

rg "useLocation|scrollIntoView" -A 3 -B 3

Repository: hrx01-dev/Servio

Length of output: 4279


🏁 Script executed:

rg "href=\"#|navigate.*#" -A 2 -B 2

Repository: hrx01-dev/Servio

Length of output: 3214


🏁 Script executed:

grep -r "scrollToSection\|href=\"#" src/ --include="*.tsx" --include="*.ts"

Repository: hrx01-dev/Servio

Length of output: 2481


ScrollToTop may interfere with hash-based section navigation.

Line 38 calls window.scrollTo(0, 0) whenever pathname changes. This can conflict with hash-based navigation (e.g., /#services, /#contact, /#portfolio) by scrolling users to the top before the browser can scroll to the target element when navigating between pages.

Proposed fix
 function ScrollToTop() {
-  const { pathname } = useLocation();
-  useEffect(() => { window.scrollTo(0, 0); }, [pathname]);
+  const { pathname, hash } = useLocation();
+  useEffect(() => {
+    if (hash) {
+      const id = hash.slice(1);
+      requestAnimationFrame(() => {
+        document.getElementById(id)?.scrollIntoView({ behavior: 'auto' });
+      });
+      return;
+    }
+    window.scrollTo(0, 0);
+  }, [pathname, hash]);
   return null;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function ScrollToTop() {
const { pathname } = useLocation();
useEffect(() => { window.scrollTo(0, 0); }, [pathname]);
return null;
function ScrollToTop() {
const { pathname, hash } = useLocation();
useEffect(() => {
if (hash) {
const id = hash.slice(1);
requestAnimationFrame(() => {
document.getElementById(id)?.scrollIntoView({ behavior: 'auto' });
});
return;
}
window.scrollTo(0, 0);
}, [pathname, hash]);
return null;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/App.tsx` around lines 36 - 39, The ScrollToTop component
unconditionally scrolls to the top whenever the pathname changes, which
interferes with hash-based navigation like /#services or /#contact. Update the
useEffect hook in the ScrollToTop function to check if a hash exists in the URL
using location.hash. Only call window.scrollTo(0, 0) when navigating to a new
pathname that does not have a hash fragment, allowing the browser to naturally
scroll to hash-based anchor elements before ScrollToTop takes effect. You may
also need to update the dependency array to include hash information if
necessary.

}

function LandingPage() {
return (
<>
Expand Down Expand Up @@ -110,11 +118,14 @@ function LandingShell() {
aria-hidden={!loading.isReady || undefined}
>
<BrowserRouter>
<ThemeProvider>
<ScrollToTop />
<AuthProvider>
<Routes>
<Route path="/" element={<LandingPage />} />
<Route path="/signin" element={<SignIn />} />
<Route path="/signup" element={<SignUp />} />
<Route path="/services/:slug" element={<ServiceDetailPage />} />
<Route
path="/dashboard"
element={
Expand All @@ -135,6 +146,7 @@ function LandingShell() {
<Route path="*" element={<NotFound />} />
</Routes>
</AuthProvider>
</ThemeProvider>
</BrowserRouter>
</motion.div>

Expand Down
3 changes: 2 additions & 1 deletion src/app/components/FAQ.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState } from "react";
import { motion, AnimatePresence, useReducedMotion } from "motion/react";
import { TypingText } from "./TypingText";
import { ChevronDown } from "lucide-react";

const faqs = [
Expand Down Expand Up @@ -49,7 +50,7 @@ export function FAQ() {
<h2 id="faq-title" className="text-4xl md:text-5xl font-bold text-gray-900 dark:text-white mt-3 mb-4">
Frequently Asked{" "}
<span className="bg-gradient-to-r from-[#4F46E5] to-[#7C3AED] bg-clip-text text-transparent">
Questions
<TypingText text="Questions" delay={150} cursorColor="bg-indigo-600" />
</span>
</h2>
<p className="text-xl text-gray-600 dark:text-gray-300">
Expand Down
3 changes: 2 additions & 1 deletion src/app/components/Hero.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { motion, useReducedMotion } from 'motion/react';
import { Smartphone, Zap, TrendingUp, Sparkles } from 'lucide-react';
import { useState, useRef } from 'react';
import { TypingText } from './TypingText';

const heroImage = "https://images.unsplash.com/photo-1551288049-bebda4e38f71?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxtb2Rlcm4lMjB3ZWJzaXRlJTIwZGFzaGJvYXJkJTIwZGVzaWdufGVufDF8fHx8MTc4MTcwMjY1OXww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral";

Expand Down Expand Up @@ -63,7 +64,7 @@ export function Hero() {
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900 dark:text-white mb-6">
Your Business Deserves a Website That{' '}
<span className="bg-gradient-to-r from-indigo-600 to-purple-600 bg-clip-text text-transparent">
Converts
<TypingText text="Converts" delay={1200} triggerOnView={false} cursorColor="bg-indigo-600" />
</span>
</h1>

Expand Down
66 changes: 26 additions & 40 deletions src/app/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { useState, useEffect } from 'react';
import { useState } from 'react';
import { Link, useNavigate, useLocation } from 'react-router-dom';
import { Menu, X, Moon, Sun } from 'lucide-react';
import { motion, AnimatePresence, useReducedMotion } from 'motion/react';
import { useThrottledScroll } from '../hooks/useThrottledScroll';
import { useTheme } from '../hooks/useTheme';
import { useAuth } from '../../Firebase/useAuth';
import { auth } from '../../Firebase/firebase';

export function Navbar() {
const [isScrolled, setIsScrolled] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [isDarkMode, setIsDarkMode] = useState(false);
const { isDarkMode, toggleTheme } = useTheme();
const { currentUser } = useAuth();
const location = useLocation();
const navigate = useNavigate();
Expand All @@ -20,33 +21,6 @@ export function Navbar() {
setIsScrolled(scrollY > 20);
}, 150);

useEffect(() => {
// Check for saved theme preference or system preference
const savedTheme = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const isDark = savedTheme ? savedTheme === 'dark' : prefersDark;

setIsDarkMode(isDark);
if (isDark) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}, []);

const toggleTheme = () => {
const newDarkMode = !isDarkMode;
setIsDarkMode(newDarkMode);

if (newDarkMode) {
document.documentElement.classList.add('dark');
localStorage.setItem('theme', 'dark');
} else {
document.documentElement.classList.remove('dark');
localStorage.setItem('theme', 'light');
}
};

const handleSignOut = async () => {
try {
await auth.signOut();
Expand Down Expand Up @@ -143,13 +117,16 @@ export function Navbar() {
)}
</button>
{currentUser ? (
<div className="flex items-center gap-4">
<span className="text-sm text-gray-700 dark:text-gray-200">
Welcome, {currentUser.displayName || currentUser.email}
</span>
<div className="flex items-center gap-3">
<Link
to="/dashboard"
className="px-5 py-2.5 bg-gradient-to-r from-indigo-600 to-purple-600 text-white rounded-lg hover:shadow-lg hover:shadow-indigo-500/50 transition-all duration-300 font-medium text-sm"
>
Dashboard
</Link>
<button
onClick={handleSignOut}
className="px-4 py-2 bg-red-500 text-white rounded-lg text-sm font-medium"
className="px-4 py-2 border border-gray-300 dark:border-slate-600 text-gray-700 dark:text-gray-200 rounded-lg text-sm font-medium hover:bg-gray-100 dark:hover:bg-slate-800 transition-colors"
>
Sign Out
</button>
Expand Down Expand Up @@ -233,12 +210,21 @@ export function Navbar() {
Contact
</button>
{currentUser ? (
<button
onClick={handleSignOut}
className="w-full px-6 py-2.5 bg-red-500 text-white rounded-lg font-medium"
>
Sign Out
</button>
<div className="space-y-2">
<Link
to="/dashboard"
onClick={() => setIsMobileMenuOpen(false)}
className="block w-full text-center px-6 py-2.5 bg-gradient-to-r from-indigo-600 to-purple-600 text-white rounded-lg hover:shadow-lg hover:shadow-indigo-500/50 transition-all duration-300 font-medium"
>
Dashboard
</Link>
<button
onClick={handleSignOut}
className="w-full px-6 py-2.5 border border-gray-300 dark:border-slate-600 text-gray-700 dark:text-gray-200 rounded-lg font-medium hover:bg-gray-100 dark:hover:bg-slate-800 transition-colors"
>
Sign Out
</button>
</div>
) : (
<Link
to="/signin"
Expand Down
3 changes: 2 additions & 1 deletion src/app/components/Pricing.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { motion, useReducedMotion } from 'motion/react';
import { TypingText } from './TypingText';
import { Check, Star } from 'lucide-react';

const plans = [
Expand Down Expand Up @@ -76,7 +77,7 @@ export function Pricing() {
<h2 id="pricing-title" className="text-4xl md:text-5xl font-bold text-gray-900 dark:text-white mt-3 mb-4">
Simple,{' '}
<span className="bg-gradient-to-r from-indigo-600 to-purple-600 bg-clip-text text-transparent">
Transparent Pricing
<TypingText text="Transparent Pricing" delay={150} cursorColor="bg-indigo-600" />
</span>
</h2>
<p className="text-xl text-gray-600 dark:text-gray-300 max-w-2xl mx-auto">
Expand Down
3 changes: 2 additions & 1 deletion src/app/components/Process.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { motion, useReducedMotion } from 'motion/react';
import { TypingText } from './TypingText';
import { ClipboardList, Palette, Code2, Rocket } from 'lucide-react';

const steps = [
Expand Down Expand Up @@ -47,7 +48,7 @@ export function Process() {
<h2 id="process-title" className="text-4xl md:text-5xl font-bold text-gray-900 dark:text-white mt-3 mb-4">
How We{' '}
<span className="bg-gradient-to-r from-indigo-600 to-purple-600 bg-clip-text text-transparent">
Work
<TypingText text="Work" delay={150} cursorColor="bg-indigo-600" />
</span>
</h2>
<p className="text-xl text-gray-600 dark:text-gray-300 max-w-2xl mx-auto">
Expand Down
Loading
Loading