Purpose of this exercise
AI-assisted Development Exploration
- Goal: Understand capabilities of AI-assisted development
- June: four legacy applications explored, one attendance delivery decision, approximately one week
- July: a full modernization of Timefree — the Rails app rewritten in Laravel, live-validated against real Google and Microsoft calendars, and deployed
- Not a production deployment — a pilot to determine what's learnable before committing to a direction
July — Timefree rewritten and deployed
Timefree — the scheduling app that in June was merely "debugged and restored" — has been fully rewritten in
Laravel/PHP (the stack our team maintains) against its unchanged database. The rewrite was verified to behave
identically to the Rails original by automated comparison (225 of 225 calendar occurrences matched across 1,120 real
synced events), integrates live with both Google Calendar and Microsoft 365, includes the kiosk, email notifications, and
instant-sync webhooks, carries a 101-test suite where the original had effectively none — and was built from first line of code
to deployed demo directed by a non-Rails developer.
Open the Timefree rewrite →
· How it was built (Developer Detail) →
💡
The most useful findings are in the Assessment section — that's where this exercise answers the question it was designed to answer: where does AI help, and where are the risks.
Jump to assessment →
Login required for all demos. Two options:
Option 1 — Google (Pharos staff): Go to
the login page and click
Sign in with Google using a
@pharosresources.com account.
Option 2 — Password: Go to
the login page and enter username
pharos.maintenance / password
Full-gain-quiet1!.
Once logged in, use
Test User Accounts (Administration menu) to switch to
judy.fakey to access faculty features.
Timefree rewrite: separate email-only login at
timefree-next.aidemo.pharos360.com — staff/faculty testers use their
@pharosresources.com address or
judy.fakey@example.edu;
demo.student@example.com for the student booking view. First stop after login:
Settings → your timezone.
🏗️
A copy of Pharos360, its Rails API, and PostgreSQL database running in Docker on an EC2 instance. Fake student data only — production is not involved.
♻️
The Java/GWT Roster app rewritten in Laravel/PHP. Runs as a separate server connected to Pharos360 data, with the same features plus accessibility fixes.
✅
Attendance built directly into the Pharos360 PHP codebase — shares its database and runs inside the existing app rather than a separate deployment. Feature flag controlled by pharos.maintenance via Settings. Email notifications not yet ported.
📊 Compare both approaches →
🤖
An AI interface built into the attendance module. Accepts natural language questions about attendance patterns, flags at-risk students, and generates faculty referral drafts. Responses are markdown-rendered.
📅
The Rails scheduling app, rewritten in Laravel against its unchanged database and deployed. Appointments, availability, recurring schedules, Google + Microsoft 365 calendar sync (validated with real accounts), check-in kiosk, email notifications, and webhook-driven instant sync — verified behavior-identical to the original by automated comparison, with a 101-test suite the original never had. Supersedes the June debugging work on the Rails app.
💬
A standalone discussion facilitation tool — not currently part of the product suite — that was
modernized as an exploratory project. The legacy stack was containerized and a full rewrite in
React was begun, with a working UI and a key session bug resolved.
🔐
Pharos staff can sign in as pharos.maintenance using their @pharosresources.com Google Workspace account — no shared password needed. Client university auth (CAS, SAML, LDAPS) is unchanged. Password fallback still works alongside it.
☁️
Cloud Infrastructure via Terraform
The entire demo environment is provisioned on AWS using Terraform — EC2, VPC, IAM, security
groups, and DNS. Caddy handles HTTPS and wildcard subdomain routing. All apps are accessible
at subdomains of aidemo.pharos360.com.
🔍
Legacy Code Archaeology
Claude Code traced bugs through old PHP and Rails code: broken path resolution, missing database sequences, undefined functions, and misconfigured auth — needs to be verified by developers.
♿
Accessibility (WCAG)
Both Pharos360 and Present received targeted accessibility fixes: keyboard navigation,
screen-reader roles, focus management, contrast improvements, and proper semantic markup.
🎨
Modern Theme Toggle
Pharos360 and Present each gained a live "Classic / Modern" theme switcher — visible in the demo — that updates
typography, spacing, and layout without breaking any existing functionality or client branding.
📋
Documentation
The entire environment — all projects, Docker commands, credentials, known
gotchas, console shortcuts — was captured in a living reference document during the session.
The distinction that matters
- AI reduced the time needed for exploration — four apps running in the cloud, documented, and extended in approximately one week
- It does not replace developer judgment on production decisions, security review, or architecture
- Those still require people
Assessment
Where AI helps — and where to be careful
The framing behind this exercise
- Goal: understand where AI is useful
- Useful in specific, bounded ways
- Requires developer judgment to be safe
🧪
No real data was used
- All work done against fake students, placeholder credentials, and a demo database
- Auth, session handling, and data access carry real risk with actual student records
🔍
No peer review or test suite
- Every change went from AI output directly to running code — a red flag in a team environment
- AI produces complete-looking code regardless of whether a case was handled securely
🏗️
Greenfield is easier than incremental
- Rewriting Present from scratch is a better AI fit than modifying a live production system
- Live systems have downstream effects, real-data edge cases, and behavior that only surfaces under load
⚖️
AI is confidently wrong
- Doesn't understand your security model, compliance obligations, or infrastructure
- Can produce code that looks correct in testing but handles a case insecurely
- Human review is not optional — it's the control that makes this viable
July update — what the Timefree rewrite changed about these conclusions
- "No test suite" is fixable as part of the work, not a precondition. The rewrite was test-first: the old app's behavior was pinned in "characterization tests" before any new code, ported to the new stack as an executable contract, and grown to 101 tests. Three latent bugs in the original Rails recurrence logic were discovered this way and deliberately preserved pending a product decision — with the fix documented.
- There is a middle path between "modify live code" and "greenfield rewrite": rewrite the application, keep the database. Timefree-next runs against the unchanged schema, which made the old and new apps directly comparable — the same queries against the same data had to produce identical answers, and did (verified on 1,120 real synced calendar events).
- Live verification catches what tests cannot. Six bugs were found the same day by exercising real flows — a timezone rendering issue, an OAuth redirect mismatch, a CSRF exemption that passed every test but failed in production. The working method was: test-first, then verify against the running system, never trust either alone.
- The judgment points were human throughout. Which behaviors to preserve vs. fix, whether to drop a legacy integration path, how to handle notifications — each was a same-day decision by the directing developer, not the AI. The speed came from collapsing the implementation time between decisions.
- The original maintenance problem no longer applies. The motivation for the project — a Rails app nobody on a PHP team could maintain — is resolved: the entire system is now in the team's stack, with its behavior documented as tests.
For developers
- AI needed direction throughout — it didn't produce production-ready code autonomously
- Claude Code surfaced the security findings in the Security Findings tab; they still need developer review before any code reaches production
- Production hardening, architecture, and security decisions still require experienced developers
- The value is faster exploration and faster first drafts — not fewer developers
What responsible production use requires
Developer review of every change
- Treat AI output like a PR from a junior developer — read, questioned, approved before production
A testing layer before deployment
- No test suite was the single biggest risk in this pilot
- Automated tests must exist before AI-assisted changes reach production
Defined scope for AI involvement
- Best for: scaffolding, diagnosis, boilerplate, refactoring
- Extra scrutiny for: auth, session handling, data export, anything touching student PII
Staged rollout with a rollback plan
- CI/CD pipeline with staging, automated tests, and a rollback path
- That pipeline does not yet exist for these apps
Technical breakdown
What was built and how
A full account of the Terraform infrastructure, Docker setup, Pharos360 fixes, the AI attendance
assistant, the built-in attendance integration (Option B), the Present rewrite (Option A),
Timefree containerization, and the directory reorganization — with the key decisions and gotchas
that came up along the way. July adds the Timefree rewrite (first block below).
Pharos360 PHP app
AI Attendance Assistant
Attendance in Pharos360
Present Laravel app
Timefree Rails app
Timefree-next Laravel rewrite
Headsup Node app
Docker Compose v2
Terraform on AWS
PostgreSQL 12
-
Provisioned with
Terraform — all AWS resources defined as code and applied in one pass. State stored locally at
Claude/terraform/.
-
EC2 instance
t3.large, Ubuntu 22.04 LTS, 50 GB gp3 encrypted root volume. Instance ID:
i-04cebc4b3ede3cfa7. Managed via AWS SSM (no SSH keys needed).
-
Networking
Dedicated VPC with public subnet. Security group allows 80/443 from anywhere, SSM traffic only. Elastic IP not used — static IP via instance association.
-
IAM
Instance role
pharos-aidemo-role grants SSM access (for remote commands) and S3 read on aidemo/* and demodefaults/* prefixes only.
-
Reverse proxy
Caddy handles HTTPS termination and wildcard subdomain routing. Each app maps to a subdomain of
aidemo.pharos360.com — Caddy proxies to the appropriate Docker container port.
-
DNS
Rackspace A records:
aidemo.pharos360.com and *.aidemo.pharos360.com → 44.197.141.230.
-
Subdomains
p360.aidemo.pharos360.com → Pharos360 + attendance + AI assistant · present.aidemo.pharos360.com → Present (Laravel) · timefree.aidemo.pharos360.com → Timefree (legacy Rails) · timefree-next.aidemo.pharos360.com → Timefree rewrite (Laravel) · headsup.aidemo.pharos360.com → Headsup Next
-
Server startup
Run
docker compose -f docker-compose.server.yml up -d from /opt/pharos/Server/. Do NOT use the base docker-compose.yml on the server — it conflicts with Caddy on port 80.
-
ANTHROPIC_API_KEY
Stored in
/opt/pharos/Server/.env (not in git). Injected into the pharos360 container via docker-compose.server.yml. Key belongs to pharosresources.com Anthropic account.
-
Services
| Service |
Description |
Port |
db | PostgreSQL 12 — shared by Pharos360, Present, Timefree | 5432 |
pharos360 | Ubuntu 22 + PHP 8.3 + Apache + Rails 3.x API (via rbenv) | 80 |
present | PHP 8.3 + Laravel — attendance rewrite | 8080 |
timefree | Rails 4.2 + Ruby 2.7.8 (rbenv) — scheduling web app | 3002 |
timefree-worker | Delayed Job worker — starts after timefree health check passes | — |
timefree-next | PHP 8.4 + Apache + Laravel 13 — Timefree rewrite (July) | 80 |
timefree-next-worker | Laravel queue worker — calendar sync, pushes, emails, webhooks | — |
timefree-next-scheduler | Laravel scheduler — subscription renewal, token keepalive, auto-checkout | — |
headsup | Node 6 + Sencha Touch — legacy facilitation stack | 3000 |
headsup-next | Express 5 + Socket.io 4 — rewrite API server | 3001 |
headsup-client | React 19 + Vite 6 + Tailwind v4 — rewrite frontend | 5173 |
headsup-mongo | MongoDB 3.4 — legacy stack database | internal |
headsup-mongo-next | MongoDB 4.4 — rewrite database (also holds seed data) | internal |
caddy | Reverse proxy — HTTPS termination + subdomain routing (server only) | 80/443 |
-
Volume mounts
Live bind mounts — edit code on the Mac, see changes immediately in the container. No rebuild needed for PHP/Blade edits.
-
DB persistence
Named volume
pgdata survives docker compose down. Only docker compose down -v wipes it.
-
Critical gotcha
configuration.php is generated by the container entrypoint on every startup — it is not a static file. It writes $CLIENT = 'p360' (cloud) or $CLIENT = 'localdev' (local), which all file-path resolution depends on.
-
supervisord
Manages Apache + Rails Thin server inside the pharos360 container. Apache proxies
/api/… → Rails on port 3000, stripping the prefix.
-
What it is
A chat-first AI interface embedded in the Pharos360 attendance module. Faculty ask questions in natural language; the AI queries the database using tool calls and returns markdown-rendered answers with tables, bullet points, and bolded names.
-
Entry point
attendance_ai.php — welcome message on load, four suggested prompt chips (falling behind, consecutive absences, compare classes, class summary). No auto-loaded flagged list — the AI fetches data only when asked.
-
API backend
attendance_ai_api.php — tool-use loop with 6 tools: get_flagged_students, get_student_detail, get_class_summary, get_attendance_timeline, get_consecutive_absences, get_class_comparison.
-
Models
Chat analysis uses
claude-haiku-4-5-20251001 (fast, low cost). Referral draft generation uses claude-sonnet-4-6 (higher quality for the final written output).
-
Referral workflow
Enter a student ID in the "Draft a Referral" section → AI writes a referral note in a modal → "Open Referral Form" opens
referral_create.php in a new tab → faculty reviews and submits → saved to DB.
-
Settings
attendance_ai_settings.php — absence threshold % and consecutive absence minimum (used as the default min_streak in tool calls). attendance_user_settings.php — export format and default status (returns to referring page after save).
-
Demo data
Two summer courses seeded: ART120-01 (32 MWF sessions) and ART121-01 (20 TTh sessions), each with students showing distinct patterns — chronic absences, sudden streaks, tardy patterns, and recovering attendance — so every tool call returns meaningful results.
-
cases/queries.php
Multiple scalar subqueries failed with "more than one row returned." Fixed by converting
= (SELECT …) to EXISTS (SELECT 1 …) in 3 places, and adding LIMIT 1 to 4 others.
-
airbrake.php
The Airbrake PHP library throws a fatal exception if
projectKey is an empty string. Blanked to a no-op stub — notifyAirbrake() still exists but does nothing in demo.
-
photo.php
Fake-user image check (Q-prefix student IDs) moved before the session check so avatar images serve without a valid Pharos session — needed by Present's photo proxy.
-
emails/functions.php
Added missing
user_to_email() function, referenced everywhere but never defined. Looks up email from students table (students) or names table (staff) by username.
-
DB sequences
All PostgreSQL ID sequences were out of sync after bulk import. Reset every
_id_seq to MAX(id) of its table.
-
delayed_jobs table
Table was entirely missing. Created with standard schema plus the custom
client VARCHAR(255) column that Pharos::Delayed::Job requires. Now seeded in docker-entrypoint.sh — survives DB rebuilds.
-
Rails client fallback
PHP calls the Rails API as
http://127.0.0.1/api/… (no subdomain). Added fallback in application_controller.rb: when host matches 127.0.0.1, sets client from DB_NAME env var.
-
Student avatars
Generated 40 placeholder PNG avatars for fake students (Q1001–Q1039) using PHP GD. Script at
Server/generate_avatars.php.
-
What it is
Pharos staff can now sign into Pharos360 as
pharos.maintenance using their @pharosresources.com Google Workspace account instead of (or alongside) the shared password. Client university auth (CAS, SAML, LDAPS) is not affected.
-
Auth flow
Standard OAuth 2.0 authorization code flow — pure PHP, no library. On initiation, a
state token is stored in session for CSRF protection. Google redirects back to /login.php?code=…; the plugin exchanges the code for tokens via curl, fetches the user's email, hard-rejects any non-pharosresources.com domain, then sets $_SESSION['auth'] = 'pharos.maintenance' — identical to password login.
-
New file
site/auth_plugins/google_oauth.php — self-contained handler for both initiation and callback. Follows the same pattern as existing CAS/SAML plugins. Redirect URI is built dynamically from HTTP_X_FORWARDED_PROTO (Caddy sets this; $_SERVER['HTTPS'] is not set inside Docker).
-
login.php changes
One detection block: if
?use_google_auth=true is set, or if a code param is present with a matching session state, include the plugin. One UI block: a "Sign in with Google" button appears below the password form — only when ?use_basic_auth=true is active and google_oauth_client_id is configured. Button is absent on all client login pages.
-
Config
$config['google_oauth_client_id'] and $config['google_oauth_client_secret'] injected into configuration.php by docker-entrypoint.sh from env vars. Added to docker-compose.yml and docker-compose.server.yml. Credentials never committed to git.
-
Password fallback
Username/password form remains active alongside Google auth. Both paths set the same session state. Logout is unchanged — clearing the Pharos session is sufficient; Google's session is separate.
-
How to log in
Navigate to
/login.php?use_basic_auth=true and click Sign in with Google, or enter the shared password as before. Direct navigation to /login.php (no flag) shows the client-facing login page without the button.
-
What it is
Take Attendance, Learn Names, History, and Export built directly into the existing Pharos360 PHP codebase — shares its database and runs inside the app itself rather than a separate deployment. Uses the same
roster_* tables already created by Present.
-
New pages (7)
attendance_courses.php — faculty landing; attendance_take.php — photo card grid; attendance_save.php — POST handler; attendance_history.php — past sessions with status pills; attendance_learn_names.php — shuffled card modal; attendance_event_types.php — event type CRUD; attendance_export.php — CSV download
-
Helper functions (5)
Added to
site/classes/queries.php: userTeachesClass(), getRosterByClass(), getAttendanceStatuses(), getOrCreateDefaultEventType(), buildSparkLines()
-
Feature flag
has_attendance boolean in site_configuration_settings. When false, all attendance pages redirect home and the nav link is hidden. Controlled by pharos.maintenance via Settings page.
-
Files modified
partials/userland.php — faculty nav link (Attendance or Roster depending on which setting is active); settings.php — attendance enable/disable control for pharos.maintenance
-
Not yet built
Email notification endpoints (
attendance_email_notify.php, attendance_email_summary.php) — Present's bulk-email feature is not yet ported. Core attendance recording is complete.
-
Auth / access
All attendance pages call
ensureFaculty(); class-scoped pages verify ownership via userTeachesClass(). The feature flag gate is pharos.maintenance-only — administrators cannot enable/disable it.
-
What it replaced
A Java/GWT application. The rewrite is a Laravel 11 PHP app that shares the existing Pharos360 PostgreSQL database (roster_* tables) — no data migration needed.
-
Auth
Reads
pharos360_session_id cookie first, falls back to PHPSESSID (set by Pharos360 on every page load). Both validated via internal HTTP call to Pharos360's cookie_checker.php. Caddy's X-Forwarded-Proto header trusted via trustProxies(at: '*').
-
Duplicate rows
class_faculty and view_class_list_filtered_with_names contain duplicates. All queries use DISTINCT.
-
phpspreadsheet v5
ByColumnAndRow() methods removed in v5. Replaced with Coordinate::stringFromColumnIndex($col) . $row.
-
Spark lines
Per-student attendance history rendered as colored vertical bars (4px wide). Built in
CourseController::buildSparkLines(), reused by AttendanceController.
-
Features
Course list sidebar, Take Attendance (photo cards, cycle status, bulk email), History, Summary, Export (CSV + XLSX), Learn Names modal, Event type management, per-user Settings.
-
Pharos360
Skip-to-main link,
role="main", role="navigation", role="banner", keyboard-accessible app menu (role="button", tabindex, aria-expanded), flash message roles (role="alert" / "status"), viewport meta tag added globally.
-
Present
Student cards:
role="button", tabindex="0", Enter/Space keyboard handlers, dynamic aria-label as status cycles. Learn Names modal: focus trap, Escape closes, focus returns to trigger. Spark lines: role="img" + aria-label. Contrast: #aaa → #6b7280.
-
Modern theme
Both apps got a floating Classic/Modern toggle (bottom-right, persisted via
localStorage). All modern styles scoped to html[data-theme="modern"]. Anti-FOUC inline script applies saved theme before first paint. Client color system left intact in Pharos360.
-
What it does
Facilitates structured group discussions: a facilitator runs sessions through a state machine (init → started → grouping → discussing → reporting → evaluating → closed), participants join on mobile, and the facilitator drives prompts and evaluations in real time via Socket.io.
-
Legacy stack
Node.js + Express 3.2.4, MongoDB (mongoskin), Socket.io 0.9.14, Sencha Touch (Ext JS mobile) compiled client, Google OAuth — port 3000. Runs in Docker with
platform: linux/amd64 required for Apple Silicon.
-
Rewrite stack
Node.js + Express 5, Socket.io 4, MongoDB driver v6, React 19 + Vite 6 + Tailwind v4, Google OAuth. Server on port 3001, client on port 5173 (served via Caddy on the cloud server).
-
Key bug fixed
Stale device cookie:
middleware/device.js trusted the browser's device cookie without verifying the document existed in MongoDB. After a volume wipe, the cookie pointed to a deleted device, causing group.js to find 0 active sockets and throw "No participants have joined." Fix: middleware now verifies the device in DB before trusting the cookie; falls back to creating a new one. Socket updateOne also uses upsert: true.
-
Styling pass
Full modern UI pass on the React rewrite: Inter font, indigo-600 primary, slate neutrals, card shadows, AdminPanel as a numbered step flow (1–4 with checkmarks), WaitingRoom with prominent join code and pulsing participant dot, mobile-first participant views, SVG icons replacing decorative emojis.
-
Why
Timefree ran on Rails 4.2 / Ruby 2.7 (EOL 2023, patched only via a paid RailsLTS subscription) and its maintaining developer left. Pharos is a PHP shop; this app was about to become the company's only Ruby. Rather than upgrade Rails through four major versions, it was rewritten into the team's stack — a decision made after the audit showed the integration scope was smaller than assumed.
-
Method
1) Characterization tests pinned the Rails app's scheduling math (availability slot-splitting, IceCube recurrence) before any new code — discovering three latent bugs in the original, preserved deliberately pending product decisions. 2) Tests were ported to PHP as the behavioral contract ("fidelity gate"). 3) The database schema was kept unchanged — Eloquent maps the legacy tables, so old and new apps are directly comparable on the same data. 4) Every feature was live-verified through the running app, not just tests.
-
Fidelity proof
The Rails and Laravel engines were run against the same database and diffed: identical booking slots for identical inputs, and 225/225 identical calendar-event occurrences across a full year of 1,120 real synced Google events — recurrence expansion, moved-instance exclusions and all. One deliberate divergence (Rails lost the all-day flag on recurring events) is documented in code.
-
Scope delivered
Pharos360 session auth (multi-tenant) with dev bypass · availability + appointment CRUD with the original validation rules · per-user timezones (calendar renders in the viewer's zone; recurrences repeat on the owner's wall clock across DST) · Google Calendar adapter (incremental pull sync with sync tokens) · Microsoft 365 Graph adapter (live windows, server-side recurrence) · appointment push to external calendars · email notifications with first-party add-to-calendar links and a signed .ics endpoint · company webhooks to Pharos360 · the check-in kiosk (stations, services, Pharos time_amp logging) · webhook subscriptions with automatic renewal for instant sync.
-
Tests
101 automated tests (Pest) — the Rails original had 5 stale specs. Suite includes the ported characterization tests, provider adapters against faked HTTP, auth matrix, kiosk flow, notifications, and webhook subscription lifecycle.
-
Live validation
Both integrations validated against real accounts same-day: 1,527 events pulled from a real Google account; a real Microsoft 365 test tenant (Entra app registration, OAuth, event push read back from Graph). Six bugs found and fixed via live testing that tests alone missed — including a CSRF exemption that passed the entire suite but failed in production.
-
Modernizations
Decisions made against today's landscape rather than ported blindly: legacy pre-Graph Office365 path dropped (users reconnect via Graph at migration) · third-party add-to-calendar service replaced with provider template URLs + first-party .ics (no event data leaves for a third party) · Microsoft refresh-token rotation persisted + weekly keepalive (kills the recurring "broken account" support burden) · Google-style "notify attendees?" control on edits.
-
Where it lives
GitHub
pharosresources/Timefree-next (private; server pulls via read-only deploy key). Demo: timefree-next.aidemo.pharos360.com, database timefree_next, three containers (app / queue worker / scheduler). Email is log-only on the demo pending a dedicated Mailgun credential.
-
Remaining before production
Tester feedback cycle · McLennan decision (only on-prem Exchange client: EWS adapter ~a week, or their migration to M365 removes it) · production cutover plan (data migration, per-university OAuth consent, DNS) · real Pharos360 auth on the demo (currently email-only bypass) · appointment-edit modal and richer repeat UI.
-
What it does
Appointment scheduling, staff availability management, and bidirectional sync with Google Calendar and Microsoft (via Graph API).
-
Stack
Rails 4.2 (via RailsLTS subscription — security patches backported to 4.2), Ruby 2.7.8, PostgreSQL. Runs in Docker alongside all other apps. Available at
https://timefree.aidemo.pharos360.com.
-
Standalone mode
BYPASS_PHAROS_360=true in config/env_var.rb — email-only login, no password, no Pharos360 session check.
-
Google Calendar sync fix
Timefree was sending Google API webhook notifications to an invalid port by default. Fixed by correcting the callback URL and disabling SSL verification on that internal route.
-
Availability blocking fixes
Two edge-case bugs fixed: events overlapping the start of an availability window, and events overlapping the end — both were failing to block the slot correctly.
-
Other fixes
Google Calendar delete support, inaccessible calendar checks, external calendar sorting, option to have no default sync calendar, removed dead-end "Update" button from user settings, excessive sync log suppression.
-
Structure
Present/ at root of Claude/. Pharos360-2.x/ has its own .git (branch master). Present/ has its own .git. The root Claude/ repo tracks infrastructure files and this launchpad. terraform/ holds Terraform state locally.
-
.gitignore
Added
GoogleCreds (irreplaceable Google API credentials, no longer re-exportable from Google Console) and .DS_Store.
-
Hosted demo
pharos.cloudaidemo.com — this launchpad, hosted on Cloudflare Pages (
Launchpad/hosted/index.html). Access-controlled via Cloudflare Zero Trust (Google OAuth, allowlist by email). App links go to *.aidemo.pharos360.com subdomains.
Static code analysis · June 2026 · July remediation notes inline
Security findings
Findings from a static analysis pass across the four June codebases — Pharos360, Timefree, Present,
and Headsup. No runtime testing. The July Timefree rewrite (Timefree-next) post-dates this pass;
its posture notes are appended below. Professional penetration testing is conducted three times per
year plus one annual BurpSuite pentest; these findings are a complement to that, not a replacement.
2 High
6 Medium
1 Low
1 Informational
-
Headsup — OAuth secrets in git
Google OAuth client secrets are hardcoded in two source files that are tracked in git:
headsup-server/lib/authentication.js:265 and headsup-server-next/src/routes/auth.js:7. Anyone with repository access has live credentials. Both use the GOCSPX- prefix confirming they are active Google OAuth secrets. These should be rotated and moved to environment variables.
-
Headsup — Arbitrary MongoDB collection access
headsup-server-next/src/routes/items.js passes req.params.collection directly to db.collection(collName) with no whitelist. An authenticated user can read, write, update, or soft-delete any collection in the database — including users, web.sessions, and devices — by crafting a URL with an arbitrary collection name.
-
Pharos360 — CSRF coverage sparse
Only 26 of 776 PHP files include CSRF tokens. The token implementation is correct where used, but the vast majority of form-handling pages are unprotected. A likely finding in the next pentest.
-
Pharos360 — Technical reset tool missing auth guard
technical/reset_database/ajax.php runs shell_exec against database reset scripts. Unlike show_file_raw.php which calls $user->ensureMaintenance() explicitly, this file includes utilities.php but no explicit auth check was found. If utilities.php does not enforce auth by default, this endpoint may be callable without authentication. Needs verification.
-
Timefree — html_safe with user data
app/controllers/home_controller.rb:17 interpolates other_user.full_name into an html_safe string. If full_name contains user-supplied HTML it will be rendered unescaped — a stored XSS vector. Most other html_safe usages in Timefree are for Font Awesome icon markup and are low risk.
-
Timefree — raw JSON output without json_escape
home/index.html.erb:78 and home/_event_source.html.erb:17 use raw source.to_json without json_escape. If source objects contain user-controlled strings with </script> sequences this is an XSS vector. By contrast, appointments/_form.html.erb correctly uses raw json_escape(...) — the home view should match that pattern.
-
Pharos360 — MD5 passwords still accepted
utilities.php:104 — password_verify($password, $hash) || $hash === md5($password). The app accepts both bcrypt and MD5 hashes for legacy account compatibility. Any account not re-hashed since migration is vulnerable to rainbow table attacks if the database is compromised.
-
Pharos360 — Session regeneration minimal
Only 5
session_regenerate_id calls across 776 files. Session fixation is a likely finding in the next pentest.
-
Pharos360 — Path traversal partial mitigation
technical/show_file_raw.php filters / from the filename parameter but passes it to exec("cat /home/jails/$CLIENT/home/$CLIENT/uploads/$filename"). The slash-only filter may be bypassable depending on how the web server handles encoded characters. Auth check ($user->ensureMaintenance()) limits exposure to authenticated maintenance users.
-
Pharos360 — Hardcoded API key
utilities.php:1755 contains a 128-character hex string assigned to $api_key. Appears to be a live credential. The service it authenticates to was not identified in static analysis.
-
Timefree — secrets.yml in app directory
Timefree/config/secrets.yml contains real secret_key_base values for all three environments. Remediated July 3: the audit found the file was in fact tracked in git history along with a dead Google credential and a GitHub PAT embedded in the repo's remote URL. Production secret_key_base now reads from an environment variable, dev/test keys were regenerated, the credential files untracked, and a rotation checklist was produced for the remaining human steps (PAT revocation, production key rotation).
-
Present — Raw SQL queries (informational)
DB::select() is used in several controllers but all user input uses ? placeholders. The interpolated $statusSums is server-generated from a status list, not user input. No injection risk identified — noted for awareness given the pattern.
-
Runtime / network
TLS configuration, HTTP security headers (CSP, HSTS, X-Frame-Options), cookie flags (Secure, HttpOnly, SameSite), response-level information disclosure. These require runtime testing — covered by the existing BurpSuite pentest.
-
Dependency CVEs
Third-party packages not audited here. Timefree uses RailsLTS for Rails 4.2 security backports. Run
bundle audit (Timefree), npm audit (Headsup), and composer audit (Present) for current CVE status.
-
Timefree-next (July) — posture notes
Built after this analysis; not yet independently reviewed. Current-stack posture by construction: Laravel 13 / PHP 8.4 (supported, no LTS subscription), CSRF protection with explicit webhook exemptions, signed URLs for the unauthenticated .ics endpoint, webhook clientState verification, secrets in server-side env (never in git), read-only GitHub deploy key, OAuth refresh-token rotation persisted. Known accepted risk on the demo:
BYPASS_PHAROS_360 email-only login on a public URL — acceptable for fake data, must be replaced with real Pharos360 session auth before any real student data. Graph client secret expires ~Jan 2027 (tracked). An independent security review of the new codebase is recommended before production.
-
Infrastructure
EC2 security groups, IAM policies, S3 bucket permissions — partially assessed via Terraform review. Runtime network testing not performed.