# Technical documentation — SecOps

Evidence date: 2026-10-04. Source: `git@bitbucket.org:overdrive/secops.git`, `master` at `b6fa27b` (2022-03-30).

Credential values are omitted. The committed `main.sql` contains a password hash, reCAPTCHA site and secret keys, a personal company email, and activity-log IP addresses. Those values are not copied here.

## Repository

| Item | Value |
|---|---|
| Remote | `git@bitbucket.org:overdrive/secops.git` |
| Default branch | `master` |
| Inspected commit | `b6fa27b` |
| Other branches | `forgot_password_template` (merged by PR #1), `basic_mvc_structure` (not merged, tip `cad6860`) |
| Silicon Ark directory | `SecOps/` |

`README.md` is the Bitbucket starter and does not describe setup. There is no root `composer.json`, `package.json`, Dockerfile, or pipeline file.

The scan listing included `sonar-project.properties` and `sonarqube_scan.bat`. They are not in either branch.

## Stack

| Layer | Choice | Evidence |
|---|---|---|
| Language | PHP | `index.php`, `application/` |
| Framework | CodeIgniter 3.1.11 | `system/core/CodeIgniter.php` `CI_VERSION` |
| UI | AdminLTE 3.0.5, Bootstrap layout, jQuery | `assets/css/alt/adminlte.plugins.css` header |
| Database | MySQL via `mysqli` | `application/config/database.php`, `main.sql` |
| Sessions | Files, cookie name `ci_session`, 7200s | `config.php` |
| Mail | CodeIgniter email library, no app email config | autoload `email`; no `application/config/email.php` |
| Templates | CI Parser on `email_templates.data` | `Users_model::resetPassword` |

`index.php` sets `ENVIRONMENT` from `CI_ENV` when present, otherwise `production`. Database error display is on when the environment is not `production`.

## Layout

```text
index.php                 front controller
.htaccess                 rewrite to index.php when the path is not a file or directory
application/
  config/                 routes, database, autoload, config, hooks (empty), migration (disabled)
  controllers/            product controllers plus adminlte/ samples
  core/MY_Controller.php  auth gate
  core/MY_Model.php       get/create/update/delete helpers
  helpers/basic_helper.php
  libraries/Uploadlib.php
  models/
  views/
  language/en|es|hi
assets/                   css, js, img, plugins
uploads/                  login-bg.jpeg, users/1.png … 4.png, users/default.png
system/                   CodeIgniter core
main.sql                  schema and seed
```

Routes (`application/config/routes.php`): default controller `dashboard`. No custom map. URLs are `index.php/{controller}/{method}/{id}` after rewrite. An AdminLTE catch-all route is commented out.

Autoloaded libraries: `session`, `database`, `parser`, `form_validation`, `uploadlib`, `email`.

Autoloaded helpers: `basic`, `url`, `file`, `form`, `cookie`, `security`, `directory`, `language`.

Autoloaded models: `users_model`, `settings_model`, `role_permissions_model`, `permissions_model`, `roles_model`, `activity_model`, `templates_model`.

Hooks file has no enabled hooks. `migration_enabled` is FALSE. `composer_autoload` is FALSE. `log_threshold` is 0. `csrf_protection` is FALSE. `global_xss_filtering` is FALSE. `encryption_key` is empty. `cookie_secure` and `cookie_httponly` are FALSE.

`base_url` is built from `HTTPS`, `HTTP_HOST`, and `SCRIPT_NAME` on each request.

## Database

Connection in git: host `localhost`, database `personal`, username `root`, password empty, charset `utf8`, collation `utf8_general_ci`, persistent connections off. **Unknown** whether any deployed database still uses this.

Tables in `main.sql` (InnoDB, `latin1`):

| Table | Columns that matter |
|---|---|
| `users` | id, name, username, email, password, phone, address, last_login, role, reset_token, status, img_type, timestamps |
| `roles` | id, title |
| `permissions` | id, title, code |
| `role_permissions` | id, role, permission (the code string, not the permission id) |
| `settings` | id, key, value |
| `email_templates` | id, name, code, data |
| `activity_logs` | id, title, user, ip_address, timestamps |

`MY_Model` provides `get`, `getById`, `getByWhere`, `create`, `create_batch`, `update`, `delete`, `countAll`. Child models set `$table`.

Settings keys in the seed: `company_name`, `company_email`, `timezone`, `login_theme`, `date_format`, `datetime_format`, `google_recaptcha_enabled`, `google_recaptcha_sitekey`, `google_recaptcha_secretkey`, `bg_img_type`, `default_lang`.

`setting($key)` reads `settings_model->getValueByKey`. A missing key returns false.

## Auth implementation

`is_logged()` (`basic_helper.php`):

1. Read `login`, `logged`, and `login_token` from the session.
2. If absent, read the same names from cookies. `logged` is JSON.
3. Load the user by id and require `sha1(id + password + time)` to equal the token.

`Users_model::login` writes those values and sets `last_login` with format `Y-m-d H:m:i`. That format uses `m` (month) in the minute position. **Implemented.**

`Users_model::logout` unsets the session keys and deletes the three cookies.

Password reset token: `password_hash(time().$user->id, PASSWORD_BCRYPT)` stored on the user, then placed in `login/new_password?token=`.

## Uploads

`Uploadlib` allows `gif|jpg|png|jpeg`, overwrite true, remove spaces true, base path `./uploads/`. `uploadImage` appends a subpath. User images go to `uploads/users/{id}.{ext}`. Login background code in `Settings::loginthemeUpdate` would store `login-bg.{ext}` at the uploads root. A `login-bg.jpeg` is already in `uploads/`.

## Email

`resetPassword` sets from, to, subject, and HTML body, then `send()`. Body HTML comes from template code `reset_password`. Parser data: `site_url`, `company_name`, and the user and `reset_link` fields added in the model.

No SMTP host, port, or protocol is configured in the application. The CodeIgniter email library's own default applies. **Unknown** on a real server.

## HTTP endpoints

Conventional CodeIgniter routes. Product ones:

| URI | Controller | Auth |
|---|---|---|
| `/` | `Dashboard::index` | Signed in |
| `login`, `login/check` | `Login` | Public; signed-in users redirected |
| `login/forget`, `login/reset_password` | `Login` | Public |
| `login/new_password`, `login/set_new_password` | `Login` | Public, token required to show the form |
| `logout` | `Logout` | Signed in |
| `users`, `users/add`, `users/save`, `users/view/{id}`, `users/edit/{id}`, `users/update/{id}`, `users/delete/{id}` | `Users` | Signed in + permission |
| `users/check` | `Users` | Signed in |
| `users/change_status/{id}` | `Users` | Signed in, no permission code |
| `roles`, `roles/add`, `roles/save`, `roles/edit/{id}`, `roles/update/{id}` | `Roles` | Signed in + permission |
| `permissions` and add/edit/save/update/delete | `Permissions` | Signed in + permission |
| `permissions/checkIfUnique` | `Permissions` | Signed in |
| `activity_logs`, `activity_logs/view/{id}` | `Activity_logs` | Signed in + permission |
| `settings/general`, `settings/generalUpdate` | `Settings` | `general_settings` |
| `settings/company`, `settings/companyUpdate` | `Settings` | `company_settings` |
| `settings/email_templates`, `settings/edit_email_templates/{id}` | `Settings` | `email_templates` |
| `settings/update_email_templates/{id}` | `Settings` | `login_theme` |
| `settings/login_theme`, `settings/loginthemeUpdate` | `Settings` | `login_theme`; view file missing |
| `backup`, `backup/exportDB` | `Backup` | `backup_db` |
| `profile`, `profile/updateProfile`, `profile/updatePassword`, `profile/updateProfilePic`, `profile/change_language/{code}` | `Profile` | Signed in |
| `errors/permission_denied` | `Errors` | Signed in (extends `MY_Controller`) |
| `adminlte/...` | `application/controllers/adminlte/*` | Signed in |

`postAllowed()` dies with `Invalid Request` when the POST array is empty. It does not check a CSRF token.

## Integrations

| System | Direction | Trigger | Failure |
|---|---|---|---|
| MySQL | App → DB | Every request after autoload | Empty config string dies with `Database is not configured`. Query errors follow CI `db_debug` |
| Google reCAPTCHA | App → `siteverify` | Sign-in when setting is `1` | Validation error `Google Recaptcha not valid !`. Curl errors are not handled separately |
| PHP mail | App → local mail | Password reset | `send()` return value is ignored. The UI still says the link was sent |

## Background work

None. No cron, queue, or worker. Backup runs inside the HTTP request.

## Security notes visible in the tree

These are observations, not a remediation plan.

- Unsalted SHA-256 passwords.
- CSRF off, cookies not HttpOnly or Secure, empty encryption key.
- `main.sql` is a data dump with secrets and historical IPs committed to git.
- `Users::change_status` and profile role assignment are reachable by any signed-in user.
- Activity IP trusts forwarding headers.
- Database config in git points at local `root` with an empty password.
- `db_debug` is on outside production, and the default environment is production only when `CI_ENV` is unset.

## Development and testing

No PHPUnit suite, no seed separate from `main.sql`, no documented local setup beyond importing that file and configuring `database.php` (already filled with local defaults).

CodeIgniter's own `system/libraries/Unit_test.php` is the framework file, unused by this application.

To run, the evidence implies: PHP, Apache with `mod_rewrite`, MySQL database `personal` loaded from `main.sql`, document root at this repository. That procedure is **Inferred** from the framework layout. It was not executed for this document.

## Unmerged branch

`basic_mvc_structure` at `cad6860` changes:

- `application/controllers/projects/Clients.php`, `Sites.php`, `History.php`, `Reporting.php`, `Tools.php`
- `application/helpers/basic_helper.php`
- `application/views/includes/nav.php`
- `main.sql` (adds role `User` and permission codes listed in `project-plan.md`)

The new controllers extend `MY_Controller`, check one permission, set a title, and load `dashboard`. They do not call SonarQube, nmap, or a PDF library.
