This project uses a baseline dependency and static scanning workflow:
npm auditblocks HIGH and CRITICAL npm advisories.- OWASP CVE Lite CLI scans the npm lockfile and blocks HIGH and CRITICAL findings.
- Trivy scans repository dependencies, configuration, and accidental secrets.
- Semgrep runs as report-only static analysis until the initial finding set is reviewed.
MEDIUM and LOW findings should be reviewed during maintenance, but they are not initial CI blockers.
Run the Node dependency checks locally:
npm run security:hardening
npm run security:audit
npm run security:cve
npm run security:scanRun Trivy locally when the CLI is available:
trivy fs --scanners vuln,misconfig,secret --severity HIGH,CRITICAL --exit-code 1 .Run Semgrep locally when the CLI is available:
semgrep scan --config auto .HIGH and CRITICAL dependency vulnerabilities are blocking for pull requests and main branch pushes when the scanner supports severity thresholds reliably.
If a scanner cannot enforce thresholds clearly, keep it in report-only mode and document the follow-up before enabling it as a gate.
- Do not upgrade dependencies automatically as part of vulnerability scanning.
- Prefer the smallest safe patch or minor update that fixes the vulnerability.
- Major upgrades require a focused compatibility review, build verification, and scheduler regression checks.
- Re-run the security scan, scheduler QA, and build before merging dependency changes.
- Never commit Firebase secrets, admin credentials, API keys, private keys, or recovery codes.
- Keep real
.envfiles out of Git. - Use
.env.exampleonly for variable names and non-sensitive defaults. - Rotate any secret immediately if it is accidentally committed or exposed in logs.
- Current pilot compatibility: Only the station admin signs in; the approved product label for that authenticated tenant actor is OWNER.
- Only a tenant OWNER signs in for new MVP tenants.
- Employees do not have app accounts, Firebase Auth identities, role claims, or write access.
- Employee-facing views, where present, must be public-safe and read-only.
- UI hiding is not security. Firestore rules are the enforcement layer.
- Target admin/private access requires Firebase Auth plus an ACTIVE matching tenant membership with role
OWNER. - Existing
ADMINandMANAGERmemberships are temporary runtime compatibility only. Do not create new memberships with these roles; removal requires the approved Phase 2 inventory, migration, tests and rollback. - ShiftOryx Admin uses
platformAdmins/{uid}and remains separate from tenant ownership. Platform status does not automatically bypass tenant isolation. - Public collections may be readable without sign-in only when every field is intentionally sanitized.
- Production tenant membership bootstrapping must happen from a trusted local/admin environment with a Firebase service account, never from frontend code.
- The
admin:bootstraphelper only prepares the Firebase Auth user. It does not grant tenant access. Tenant access comes fromtenantMemberships/{uid}_{tenantId}. - The bootstrap helpers read temporary credentials from environment variables and must not print passwords, private keys, access tokens, or service account objects.
employeesis admin-only because it contains private data and scheduling role fields.employeeAbsencesis admin-only and remains the private source of truth for the scheduler generator.attendance_historyandweek_historyare admin-only because they are loaded through admin-only UI flows.- Public/employee views must read only dedicated sanitized collections.
- Sanitized public collections are readable without sign-in and writable only by admins.
- Do not trust frontend-only validation for production authorization decisions.
- Production admin authorization uses Firebase Auth plus an ACTIVE
tenantMemberships/{uid}_{tenantId}document for the matching tenant. - Email allowlists and Firebase custom claims must not grant tenant admin access.
- Do not put admin passwords in Vite env vars. Vite env vars are included in the frontend bundle.
- Firestore protected data must require tenant membership authorization for reads and writes unless it is a dedicated sanitized public collection.
- Server-side data access must use parameterized queries or a trusted SDK/ORM.
- Passwords must be hashed with a strong password hashing function such as Argon2id or bcrypt.
- JWT/session secrets must be high-entropy values stored only in environment-specific secret storage.
- Login, admin, and write-heavy endpoints must use rate limiting when production backend services are introduced.
- Current limiter: no dedicated backend limiter is present because the app is Firebase/client-heavy.
- Affected features: login/admin entry points, schedule writes, employee writes, audit-log writes, and any future API or Cloud Function endpoints.
- Implemented now: documented the production limiter requirement; no runtime limiter changes were made in this pass.
- Deferred: Firebase App Check enforcement, Cloud Function or backend throttles for write-heavy actions, and Cloudflare/Firebase abuse controls.
- Recommended production path: enable Firebase App Check for supported clients, keep Firestore rules as the authorization boundary, and add server-side rate limiting when privileged writes move behind Cloud Functions or a backend API.
- Removed client-side admin password checks and fallback credentials from the frontend.
- Removed hardcoded demo admin authorization from Firestore rules.
- Added basic Firestore field allowlists and type/date checks for scheduler collections.
- Added Vercel security headers: HSTS, nosniff, frame deny, referrer policy, and permissions policy.
- CSP is intentionally not enabled yet because Firebase Auth, Firestore, Analytics, dynamic chunks, and inline style behavior need a dedicated browser compatibility pass.
- Schedule generation, clear operations, template/history loads, and week finalization use Firestore batch/chunked writes where practical to reduce partial-write risk.
- Generated shifts include a
generationRunIdso a schedule can be tied back to the generation event that created it. - Admin actions write an immutable audit log entry in the
audit_logscollection when practical. - The audit log captures action, actor uid/email, target collection/scope/id, before/after data where practical, metadata, timestamp, and
generationRunIdfor generation-related actions. - Firestore rules allow audit log reads and creates only for authenticated admins.
- Firestore rules deny audit log update and delete from the client.
Remaining limitation: without a backend or Cloud Function, audit log creation is initiated by the trusted admin client. Firestore rules protect who may create/read logs and prevent client edits/deletes, but they cannot guarantee that every allowed write is accompanied by a matching audit entry. A server-side write layer or Firestore trigger is the future hardening path for fully enforced audit logging.
Private source of truth:
employeeAbsencesis admin-only read/write.- It contains full absence data used by admin screens and the scheduler generator, including employee ids, replacement mode, manual replacement employee id, notes, status, and audit metadata.
- The generator uses
employeeAbsences, never the sanitized public view.
Public view:
- Public users must not read absence mirrors, absence labels, absence date ranges, sick leave, leave-related rest days, replacement reasons, notes, or absence status.
- The legacy
employeeAbsencesPubliccollection is no longer anonymous-readable. It remains admin-only for legacy cleanup and must not be used by public UI. - Public/employee schedule views may show only work schedule fields: employee full name, date, shift label/type, start time, and end time.
- Public schedule snapshots must not include employee ids, user ids, emails, phones, tenant memberships, audit metadata, internal notes, absence data, or private archive data.
Approved future Phase 10 boundary:
- The public view may later show only sanitized labels
Άδεια,ΡεπόorΔεν εργάζεταιand an explicit owner-reviewedpublicNote. - The generic
notesfield must never become public automatically. - Absence reasons, medical details, attachments and
privateNoteremain private. - This future boundary does not change current production behavior and requires dedicated schema, rules, preview and leakage tests.
Remaining limitation: without a backend or Cloud Function, the trusted admin client still creates sanitized public schedule snapshots. Firestore rules restrict raw absence and admin data, and validation checks must keep public snapshots limited to work shifts only.
Vercel sends a conservative Content-Security-Policy for the deployed app.
Allowed runtime origins are scoped to:
selffor Vite app shell, chunks, CSS, local images, and local fonts.https://*.googleapis.com,https://*.firebaseio.com,https://*.firebaseapp.com, andhttps://*.appspot.comfor Firebase Auth, Firestore, and related Firebase APIs.- Google Analytics / Firebase Analytics endpoints such as
https://www.googletagmanager.com, Google Analytics hosts, andhttps://app-measurement.com. data:andblob:for browser-generated images/download/export flows and embedded fonts.
Temporary compatibility trade-off:
style-src 'unsafe-inline'remains enabled because the React UI uses inline/dynamic styles in several places.script-srcdoes not allow'unsafe-inline'; scripts are limited toselfand Google Tag Manager for Firebase Analytics compatibility.
Browser QA checklist:
- See
docs/csp-qa-checklist.md. - Re-run browser QA after adding auth providers, external media/fonts, new Firebase products, export libraries, or deployment header changes.
- Check console and network panels for CSP violations or blocked Firebase/Google endpoints.