# Technical documentation — silicon_security

Evidence date: 2026-10-03.

Source: `git@bitbucket.org:overdrive/silicon_security.git`, `main` at `514489afb1f349d985a7fa24ef2ca9a2ccaab928` (2025-12-15). Shallow clone. Secrets are not copied.

## What this repository is

A CodeIgniter 4 PHP application. Composer package name `codeigniter4/adminstarter`, description "Admin Overdrive", MIT license text from the CodeIgniter Foundation (`LICENSE`, copyright 2014–2021 BCIT and CodeIgniter Foundation). Homepage `https://www.overdrive.co.za`.

Repository folder name on Bitbucket: `silicon_security`. Sonar project key prefix: `overdrive_admin-overdrive-v2`. Those names are not reconciled in code.

## Stack

| Item | Evidence |
|---|---|
| Language | PHP. `composer.json` requires `^8.0`. `docs/README.md` says 8.3 plus intl, mbstring, mysqlnd, json, xml, and libcurl for HTTP. **Discrepancy.** |
| Framework | `codeigniter4/framework` `^4.0`. Front controller `public/`. CLI `spark`. |
| App database | MySQLi, default group in `app/Config/Database.php`. Name and user come from `database.default.*` environment keys. |
| Test database | SQLite3 `:memory:` on the `tests` group |
| Session | Keys `app.sessionDriver`, `app.sessionCookieName`, `app.sessionSavePath`, `app.sessionMatchIP`, `app.sessionTimeToUpdate`, `app.sessionRegenerateDestroy` in `env` |
| Tests | PHPUnit `^9.1`, config `phpunit.xml.dist`, script `composer test`. Suites are the framework examples under `tests/` |
| CI | `bitbucket-pipelines.yml`: on `main` only, SonarQube pipe `sonarsource/sonarqube-scan:3.0.2`, Docker service memory 3072 MB, full clone. No test, build, or deploy step |
| Scanner config | `sonar-project.properties` disables coverage and excludes most of `app/`, `public/`, and `tests/` |

`docs/README.md` also documents a WAMP virtual host `adminOverdrive.local` with document root `c:/wamp64/www/adminOverdrive/public`. That path is a local example, not a checked-in server definition.

## Directory map

```text
app/Config          Routes, filters, database, email, security
app/Controllers     HTTP and a few API-shaped classes
app/Models          Query builders for users, documents, chat, mail, audit
app/Views           PHP views, sidebar driven by menu tables
app/Filters         Authentication, Authorization, TimeAccessFilter, Cors
app/Helpers         Mail, menu, documents, RAG, chat memory, Slack, unused Salesforce and SMS
app/Libraries       OCR, PDF text, JWT, MySQL vector store
app/Neuron          DocumentRAG (Neuron AI)
app/Commands        monitor:system
app/Database        Migrations and seeds
public              Web root, assets, deploy.php (sonar excludes it)
docs                README, changelog, project rules, DMS and chat notes
scope               Scope and email templates for the viewer-profile task
writable            Logs, uploads, sample order and invoice XML, monitoring pid
tests               CodeIgniter sample tests
```

`vendor/` is not in the checkout that was read (dependencies are declared, not vendored in the listing). `builds` is a file at the repo root, not a directory. `overview.md` is a generated sketch and should not be preferred over `app/Config`.

## Request path

`app/Config/Filters.php`:

| Alias | Class | Where it runs |
|---|---|---|
| `isLoggedIn` | `App\Filters\Authentication` | Global before, with a long exception list (login, 2FA, reset, `cronJobs`, `slack`, `email/test`, `organisations`, `store-api`, tracking pixel, 404) |
| `isGranted` | `App\Filters\Authorization` | Global before, exceptions add `home`, `blocked`, `chatGPT/*`, `rag/*` |
| `csrf` | CodeIgniter CSRF | Every POST |
| `timeAccess` | `TimeAccessFilter` | Before `documents*` |
| `toolbar` | Debug toolbar | After, except `chatGPT/*` |
| `honeypot` | Present | Commented out |
| `throttle` | Aliased | Not applied in `$globals` or `$methods` |

`Authentication` redirects to `/` when `session('isLoggedIn')` is not exactly `TRUE`.

`Authorization` looks up segment 1 in `user_menu`. Unknown segment redirects to `/`. Known segment without `user_access` for the session role redirects to `blocked`.

`App::$baseURL` in `app/Config/App.php` is an empty string. Runtime base URL is expected from `app.baseURL` in the environment. **Unknown** for production.

## Controllers that exist

`Welcome`, `Home`, `Users`, `SystemUsers`, `SystemSettings`, `Documents`, `Admin\Documents`, `Chat`, `ChatGPT`, `RagStatus`, `CronJobs`, `Email`, `EmailPixelTracking`, `ServerMonitoring`, `Reports`, `Csv`, `Upload`, `UploadedFile`, `File`, `Slack`, `Rest`, `Soap`, `Store`, `SiteHealth`, `DbAdmin`, `LoginHistory`, `Test`, `TestLog`, `ApiSimulation`, `Developers\MenuManagement`, `Developers\CRUDGenerator`, `Developers\DbTableDataView`.

Routes that name a missing class: `Products`, `LabelStatus`, `GetXMLData`, `Orders`, `LabelUserGroups`, `LabelEmailTemplates`, `Company`, `FaultFinder`, `StoreApiController`, and the `organisations` resource. Hitting those URLs will fail when CodeIgniter tries to load the class.

`Reports.php` imports `OrdersModel`, `InvoicesModel`, and `ProductsModel`, which are not in `app/Models`. `index()` does not reference them.

## Data model (from migrations and models)

Application tables created or altered by files in `app/Database/Migrations/`:

| Area | Tables (names from migrations or models) |
|---|---|
| Identity | `users`, `user_role`, `user_access`, `user_menu`, `user_submenu`, `user_menu_category`, `user_groups`, `session` |
| Organisations | `organisations` |
| Documents | `documents`, `document_types`, `document_categories`, `document_tags` (changelog also names `document_tag_links`), `user_documents`, `user_access_times`, `audit_logs`, `document_search_cache` |
| Chat | `chatgpt_logs`, `chat_memories`, `document_contexts`, `vector_embeddings`, `chatgpt_settings` |
| Mail and cron | `sent_queue`, system email templates, email template types, `cronjob_actions`, `cronjob_log`, `public_holidays` |
| Security audit | `login_history` (migration name), `blocked_users_list` (model query; a migration file says `AddBlockedUsersListTable`) |
| Files | `media_file` migration `2023-06-02-162422_MediaFile.php` |
| Settings | `settings`, `language` |

`user_documents` columns used by the model: `user_id`, `document_id`, `access_start_time`, `access_end_time`, `access_type`, `login_active_hours`, `granted_by`, `active`, timestamps.

`documents` columns used by the model: `title`, `document_type_id`, `reference_number`, `description`, `file_path`, `file_name`, `file_size`, `mime_type`, `effective_date`, `category_id`, `subcategory_id`, `tags`, `ocr_text`, `ocr_processed`, `created_by`, `active`, timestamps.

Two-factor storage is migration `2024-09-12-123957_AddTwoFactorAuthenticationTable.php`. Column-level rules were not copied here.

Seeds: `Users` (calls `UserAccess`, `UserMenu`, `UserMenuCategory`, `UserRole`, `Organisations`), `DocumentTypes`, `UploadedFile`. `docs/README.md` says `php spark db:seed users` seeds the default user. The seeder class name is `Users`. CodeIgniter's seed command is case-sensitive on some hosts. **Requires Confirmation** of the exact spark argument used by the team.

## Auth implementation

- Password check: `password_verify` against `users.password`. The seeder uses `password_hash(..., PASSWORD_DEFAULT)`.
- Session keys set at login: `username`, `role`, `isLoggedIn`, `userID`, `labelID` (empty string).
- 2FA: email body includes the code, then `Welcome::confirmCode`.
- CSRF on POST globally.
- Document routes add `timeAccess`.
- Chat send and stream, and a few settings posts, also attach the route-level csrf filter.
- `JwtLibrary` implements HS256-style signing with a default secret of `secret` if the caller passes none. No controller in this tree was shown calling it for the organisations resource, because that controller is missing.

IP for history and block checks: `HTTP_CLIENT_IP`, then `HTTP_X_FORWARDED_FOR`, then `REMOTE_ADDR`. `::1` is stored as the string `Localhost`. **Inferred** that the forwarded headers are trusted as sent. **Requires Confirmation** if the app is behind a proxy that does not strip them.

## Documents, OCR, and RAG

Upload flow: `Documents::uploadStep1` writes a temp file and runs `OcrLibrary::extractText`. `upload` moves it to `writable/uploads/documents/doc_<uniqid>_<time>.<ext>`.

`OcrLibrary` handles PDF (text extract, then image render fallback) and images. `PdfTextExtractor` tries `smalot/pdfparser`, then simpler reads. External binaries are not declared in Composer. `docs/SSL_CERTIFICATE_FIX.md` and `DocumentRAG` deal with Windows CA problems in development by disabling peer verification when `ENVIRONMENT === 'development'`.

`indexDocumentInRAG`:

- Returns false immediately when `OPENAI_API_KEY` is empty.
- Builds text from title, description, reference, and OCR body.
- Chunks at 1000 characters (`chunkDocumentText`).
- Stores Neuron `Document` chunks with ids `doc_{id}_chunk_{index}`.

`DocumentRAG::vectorStore()`:

| `VECTOR_STORE_TYPE` | Store |
|---|---|
| `mysql` | `MySQLVectorStore`, topK default 4, dimension default 1536 |
| `pinecone` | `PineconeVectorStore` if `PINECONE_API_KEY` and `PINECONE_INDEX_URL` are set; otherwise memory and a warning |
| `memory` (default) | `MemoryVectorStore` |
| other | throws |

Embeddings provider: `EMBEDDINGS_PROVIDER` default `openai`. Any other value throws.

Reindex deletes by source type `document` and source name `doc_{id}` before writing again.

Chat memory embeddings use `vector_embeddings` with source types `chat_memory` and `document_context` (`docs/IMPLEMENTATION_SUMMARY.md`).

## Email and cron

`Email::updateSentQue` inserts into `sent_queue`. `CronJobs::index` loads active cron rows, checks recurrence against the last log, and calls the controller method named on the row.

Known methods: `sendScheduledEmails`, `getAllUnsentEmails`, `sendEmailsViaCron`, `checkServerLogs`, `logCronActions`, `checkCronTiming`.

`mail_helper.php` also has `sendMailDirect`.

Configuration source: `docs/README.md` says SMTP was moved from hardcoded values to `.env` on 2025-11-07. Keys are the `email.*` names listed in the environment template.

## Monitoring

`ServerMonitoring` routes: `monitoring`, `monitoring/getSystemUsage`, `monitoring/getSystemUsageChart`, `monitoring/getMonitorLogs` (GET and POST), `monitoring/updateAlertThresholds`.

Command `php spark monitor:system` (`app/Commands/MonitorSystemUsage.php`):

- Pid file `writable/monitoring.pid`. If the pid is alive (`posix_kill($pid, 0)`), it exits.
- Infinite loop, `top -b -n 1`, log when CPU or memory percent is at or above the threshold.
- `sleep(5)` despite a comment that says 10 seconds.
- Depends on `posix_kill` and `top`. Those are not typical Windows PHP functions. **Inferred** the command targets Linux. The README's dev setup is WAMP. **Requires Confirmation** of where monitoring is expected to run.

## Environment keys

Names present in the `env` template (values omitted):

`CI_ENVIRONMENT`, `CI_DEVELOPER`, `OPENAI_API_KEY`, `OPENAI_MODEL`, `email.SMTPHost`, `email.SMTPUser`, `email.SMTPPass`, `email.SMTPPort`, `email.SMTPCrypto`, `email.fromEmail`, `email.fromName`, `app.baseURL`, `app.sessionDriver`, `app.sessionCookieName`, `app.sessionSavePath`, `app.sessionMatchIP`, `app.sessionTimeToUpdate`, `app.sessionRegenerateDestroy`, `database.default.hostname`, `database.default.database`, `database.default.username`, `database.default.password`, `database.default.DBDriver`, `encryption.key`, `VECTOR_STORE_TYPE`, `EMBEDDINGS_DIMENSION`, `VECTOR_STORE_TOP_K`.

Also read by code and not in that template list: `EMBEDDINGS_PROVIDER`, `EMBEDDINGS_API_KEY`, `EMBEDDINGS_MODEL`, `PINECONE_API_KEY`, `PINECONE_INDEX_URL`, `TESSERACT_PATH` (named in the changelog).

`.env` and `.env copy` are in the repository. `.gitignore` does not list `.env`. **Human review required.** Do not publish the file contents.

`encryption.key` is required by CodeIgniter's encrypter. `docs/README.md` says to run `php spark key:generate`.

## Local setup as documented

From `docs/README.md`:

1. Copy `env` to `.env` and set base URL and database.
2. `php composer install`.
3. `php spark db:create`.
4. `php spark migrate`.
5. `php spark db:seed users`.
6. `php spark key:generate`.
7. `php spark serve`.

Alternative: Apache vhost `adminOverdrive.local` as quoted in the README.

`docs/PROJECT_RULES.md` and `.cursorrules` are contributor rules (CSRF, migrations, git workflow, browser testing). The tip commit says it added git workflow and browser testing to those rules. They are process docs, not runtime configuration.

## Background work

| Mechanism | How it starts | Auth |
|---|---|---|
| Email and other cron actions | HTTP `GET /cronJobs` | None in `Filters.php` |
| System usage loop | `php spark monitor:system` | CLI, no HTTP auth |
| RAG reindex | `POST rag/status/reindex` | Logged in; menu grant skipped |

No queue worker (Redis, database queue driver, or supervisor config) was found. The email "queue" is the `sent_queue` table plus cron.

## Security notes from the tree (observations, not a pentest)

- `cronJobs`, `slack`, `email/test`, `tracking-pixel`, and the organisations and store-api patterns are outside the login filter. Organisations and store-api controllers are missing, so those URLs fail closed only by autoload, not by an auth check.
- Global CSRF applies to POST. GET cron is unaffected.
- Development RAG code disables TLS verification.
- JWT helper default secret is the literal `secret`.
- Users seeder contains a known development password. Not repeated here.
- `test_cookies.txt` is tracked. Contents were not read.
- Sonar exclusions remove filters, helpers, libraries, and controllers from analysis. Coverage is disabled. Pipeline quality signal is narrow.
- `public/deploy.php` exists and is sonar-excluded. It was not reviewed line by line. **Requires Confirmation** of what it deploys and who can call it.

## Testing and development process

**Documented** desired process in `docs/PROJECT_RULES.md` and `.cursorrules` (the December 2025 commit message names git workflow and browser testing).

**Implemented** automated tests in-repo are CodeIgniter samples (`tests/unit/HealthTest.php`, example session and database tests). No document, chat, or auth tests were found under `tests/`.

`docs/TESTING_SUMMARY.md`, `docs/TESTING_RESULTS.md`, and `docs/TESTING_MEMORY_SYSTEM.md` record manual or scripted checks of the memory system. They were not re-executed.

PHPUnit bootstrap expects `vendor/codeigniter4/framework`. A clean clone needs `composer install` before tests can run.

## Deployment

No Dockerfile, no compose file, no host playbook. The only pipeline is SonarQube on `main`.

`docs/DEPLOYMENT_CHECKLIST.md` is a checklist for the chat-memory migrations and OpenAI keys, not a server deploy script.

`docs/EMAIL_QUEUE_AND_CRON_SETUP.md` tells an operator to register a cron job against `http://localhost/dms/cronJobs` and a row in `cronjob_actions`. The `/dms` prefix is not in `Routes.php`.

Production host, document root, cron user, and MySQL version are **Unknown / Requires Confirmation**.
