Skip to content

Commit 5d86f63

Browse files
authored
Merge pull request #28 from bitsandbots/feature/csp-style-src-tighten
feat(csp): drop 'unsafe-inline' from style-src
2 parents 4634ac8 + ff1bab6 commit 5d86f63

30 files changed

Lines changed: 212 additions & 319 deletions

customers/customers.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@
4242
<table class="table table-bordered table-striped">
4343
<thead>
4444
<tr>
45-
<th class="text-center" style="width: 100px;">Customer</th>
46-
<th class="text-center" style="width: 100px;">City</th>
47-
<th class="text-center" style="width: 50px;">Region</th>
48-
<th class="text-center" style="width: 50px;">Code</th>
49-
<th class="text-center" style="width: 50px;">Telephone</th>
50-
<th class="text-center" style="width: 50px;">Email</th>
51-
<th class="text-center" style="width: 50px;">Pay Method</th>
52-
<th class="text-center" style="width: 50px;">Actions</th>
45+
<th class="text-center col-w-100">Customer</th>
46+
<th class="text-center col-w-100">City</th>
47+
<th class="text-center col-w-50">Region</th>
48+
<th class="text-center col-w-50">Code</th>
49+
<th class="text-center col-w-50">Telephone</th>
50+
<th class="text-center col-w-50">Email</th>
51+
<th class="text-center col-w-50">Pay Method</th>
52+
<th class="text-center col-w-50">Actions</th>
5353
</tr>
5454
</thead>
5555
<tbody>

docs/gap-analysis.md

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Snapshot of feature completeness, test coverage, and known issues for the Inventory Management System.
44

5-
**Last regenerated**: 2026-05-15 (third pass — migrations applied, upload.php prepared, CSP tightened, CI added)
5+
**Last regenerated**: 2026-05-15 (fourth pass — `style-src` tightened to `'self'` only)
66
**Codebase commit**: post-install.sh-reinstall, post-PHP8-type-strictness fixes, post-hardening pass, post-CI
77

88
---
@@ -56,7 +56,7 @@ These were live bugs in `main` and have been patched:
5656
| `tests/CRUDTest.php` failed silently — wrong column name + missing NOT NULL fields + missing parent category | `tests/CRUDTest.php` | HIGH | Fixed column name, provisioned HARNESS category for FK, used `check()` helper instead of `assert()` |
5757
| `tests/bootstrap.php` did not buffer output, so `session_regenerate_id()` failed in the SessionTest after prior `echo`s | `tests/bootstrap.php` | MEDIUM | Added `ob_start()` at bootstrap entry |
5858
| `tests/*.php` used `assert()` — a no-op on default PHP 8 configurations | `tests/AuthTest.php`, `tests/CRUDTest.php` | MEDIUM | Added `check()` helper that throws on failure |
59-
| CSP / X-Frame-Options / Referrer-Policy / Permissions-Policy headers missing | `includes/load.php` | HIGH | Emit on every request (CSP allows `'self'` + `'unsafe-inline'` for bundled Bootstrap/jQuery) |
59+
| CSP / X-Frame-Options / Referrer-Policy / Permissions-Policy headers missing | `includes/load.php` | HIGH | Emit on every request (CSP is `'self'` only for `script-src` *and* `style-src` — no `'unsafe-inline'`) |
6060
| No login rate limiting — credential stuffing unmitigated | `users/auth.php`, `failed_logins` table | HIGH | Added migration 002, helpers in `sql.php`, check + record + clear in `auth.php` (5 attempts per 15 min per IP) |
6161
| No password complexity enforcement — single-char passwords accepted | `users/add_user.php`, `users/edit_user.php`, `users/change_password.php` | MEDIUM | Added `validate_password()` helper (min 8 chars, must contain letter + digit, denylist of common passwords) |
6262
| `quantity` columns were VARCHAR(50) | `schema.sql`, `migrations/001_quantity_int.up.sql`, `migrations/001_quantity_int.down.sql` | HIGH | Migration 001 created; `schema.sql` updated for fresh installs (INT NOT NULL DEFAULT 0) |
@@ -80,8 +80,6 @@ These were live bugs in `main` and have been patched:
8080

8181
**No browser-level UI/integration tests**`tests/SecurityHeadersTest.php` exercises HTTP responses but there's no Playwright/Selenium coverage of actual page interactions.
8282

83-
**CSP keeps `'unsafe-inline'` for `style-src`** — Bootstrap's JS sets inline style attributes (dropdowns, popovers, tooltips) at runtime; removing this would break common UI controls. `script-src` is now `'self'` only after moving inline handlers to `libs/js/functions.js`.
84-
8583
**`orders.customer` is a varchar (denormalized)** — should be FK to `customers.id` for referential integrity. Pre-existing schema decision; preserved to avoid breaking changes.
8684

8785
---
@@ -100,7 +98,7 @@ Cross-reference of `docs/*.md` claims against actual code:
10098
| "CI pipeline" | `gap-analysis.md` prior pass | ✅ ADDED 2026-05-15 — `.github/workflows/ci.yml` runs `php -l` + full test suite on push/PR |
10199
| "Security headers test" | `gap-analysis.md` prior pass | ✅ ADDED 2026-05-15 — `tests/SecurityHeadersTest.php` (7 tests) |
102100
| "failed_logins housekeeping" | `gap-analysis.md` prior pass | ✅ ADDED 2026-05-15 — probabilistic prune (~1% of page loads) via `prune_failed_logins()` |
103-
| "Inline JS / CSP tightening" | `gap-analysis.md` prior pass |MOSTLY DONE 2026-05-15 — moved `closePanel()` to `libs/js/functions.js`, replaced 4 redirect stubs with server-side redirects, CSP `script-src` is now `'self'` only |
101+
| "Inline JS / CSP tightening" | `gap-analysis.md` prior pass | ✅ DONE 2026-05-15 — `script-src 'self'` after moving `closePanel()` to `libs/js/functions.js`; `style-src 'self'` after replacing 117 `style="width:X"` attrs with `col-w-*` utility classes (`libs/css/main.css`) and extracting 4 print blocks into `libs/css/print.css` |
104102
| "log.user_id FK for audit-trail preservation" | `gap-analysis.md` prior pass | ✅ DESIGNED 2026-05-15 — migration 003 ready; schema.sql updated for fresh installs |
105103
| "Soft delete with restore" | none — but typical for audit-heavy apps | NOT IMPLEMENTED — `delete_by_id()` is hard delete. Scoped + deferred (see section 3) |
106104

@@ -121,9 +119,7 @@ Cross-reference of `docs/*.md` claims against actual code:
121119

122120
Most prior-pass items now resolved (see section 4 below). Remaining work:
123121

124-
1. **Apply migration 003 to running deployments** — backup, then `sudo mysql inventory < migrations/003_log_user_fk.up.sql`.
125-
2. **Soft-delete refactor** (its own PR) — `deleted_at` columns + `soft_delete_by_id()` + `restore_by_id()` + filter every SELECT. See section 3 above for scope.
126-
3. **Tighten `style-src`** — extract Bootstrap inline-style usages (animations, popovers) to either CSS classes or nonce-permitted blocks.
127-
4. **Per-tenant currency** — make `$CURRENCY_CODE` a column in a settings table or `.env` value.
128-
5. **Browser-level UI tests** — Playwright covering the login → add-product → add-sale → invoice happy path.
129-
6. **Pre-commit hook** for `php -l` on staged files (the CI catches this on push but pre-commit prevents bad commits).
122+
1. **Soft-delete refactor** (its own PR) — `deleted_at` columns + `soft_delete_by_id()` + `restore_by_id()` + filter every SELECT. See section 3 above for scope.
123+
2. **Per-tenant currency** — make `$CURRENCY_CODE` a column in a settings table or `.env` value.
124+
3. **Browser-level UI tests** — Playwright covering the login → add-product → add-sale → invoice happy path.
125+
4. **Pre-commit hook** for `php -l` on staged files (the CI catches this on push but pre-commit prevents bad commits).

includes/load.php

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,22 @@
3232
// -----------------------------------------------------------------------
3333
// Security response headers — emitted on every request.
3434
//
35-
// CSP is intentionally permissive ('self' + inline styles/scripts) because
36-
// the project bundles Bootstrap 5 and jQuery with inline onclick handlers.
37-
// Tightening this requires refactoring inline JS out of the templates;
38-
// tracked in docs/gap-analysis.md.
35+
// CSP is tight: 'self' only for scripts and styles, no 'unsafe-inline' or
36+
// 'unsafe-eval'. Inline JS lives in libs/js/functions.js; column-width
37+
// utility classes (libs/css/main.css) replaced inline style="" attributes;
38+
// print/report pages link libs/css/print.css.
3939
// -----------------------------------------------------------------------
4040
if (PHP_SAPI !== 'cli' && !headers_sent()) {
41-
// script-src: 'self' only — no 'unsafe-inline'. All inline JS has been
42-
// moved to libs/js/functions.js. New inline scripts must use a nonce
43-
// or be moved to an external file.
44-
// style-src: keeps 'unsafe-inline' because Bootstrap and jQuery
45-
// plugins (dropdowns, tooltips, popovers) set inline style attributes
46-
// at runtime. Removing it would break common UI controls. Tracked in
47-
// docs/gap-analysis.md as an accepted constraint.
41+
// script-src: 'self' only — no 'unsafe-inline'. New inline scripts must
42+
// use a nonce or be moved to an external file.
43+
// style-src: 'self' only — no 'unsafe-inline'. CSP style-src governs
44+
// <style> blocks and HTML-parsed style="" attributes, not runtime
45+
// element.style.X or jQuery .css() (those are script-side), so the
46+
// bundled Bootstrap/jQuery plugins continue to work.
4847
header("Content-Security-Policy: "
4948
. "default-src 'self'; "
5049
. "script-src 'self'; "
51-
. "style-src 'self' 'unsafe-inline'; "
50+
. "style-src 'self'; "
5251
. "img-src 'self' data:; "
5352
. "font-src 'self'; "
5453
. "object-src 'none'; "

libs/css/main.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,3 +309,11 @@ input[type=file]{
309309
width: 125px;
310310
height: 125px;
311311
}
312+
/* Column-width utilities — replace inline style="width:X" so style-src can drop 'unsafe-inline'. */
313+
.col-w-15{ width: 15px; }
314+
.col-w-50{ width: 50px; }
315+
.col-w-100{ width: 100px; }
316+
.col-w-10p{ width: 10%; }
317+
.col-w-15p{ width: 15%; }
318+
.col-w-20p{ width: 20%; }
319+

libs/css/print.css

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/* Shared print/invoice styles for sales_invoice, order_picklist, sale_report_process, stock_report_process. */
2+
@media print {
3+
html, body {
4+
font-size: 9.5pt;
5+
margin: 0;
6+
padding: 0;
7+
}
8+
.page-break {
9+
page-break-before: always;
10+
width: auto;
11+
margin: auto;
12+
}
13+
}
14+
.page-break {
15+
width: 980px;
16+
margin: 0 auto;
17+
}
18+
.sale-head {
19+
margin: 40px 0;
20+
text-align: center;
21+
}
22+
.sale-head h1,
23+
.sale-head strong {
24+
padding: 10px 20px;
25+
display: block;
26+
}
27+
.sale-head h1 {
28+
margin: 0;
29+
border-bottom: 1px solid #212121;
30+
}
31+
.table > thead:first-child > tr:first-child > th {
32+
border-top: 1px solid #000;
33+
}
34+
table thead tr th {
35+
text-align: center;
36+
border: 1px solid #ededed;
37+
}
38+
table tbody tr td {
39+
vertical-align: middle;
40+
}
41+
.sale-head,
42+
table.table thead tr th,
43+
table tbody tr td,
44+
table tfoot tr td {
45+
border: 1px solid #212121;
46+
white-space: nowrap;
47+
}
48+
.sale-head h1,
49+
table thead tr th,
50+
table tfoot tr td {
51+
background-color: #f8f8f8;
52+
}
53+
tfoot {
54+
color: #000;
55+
text-transform: uppercase;
56+
font-weight: 500;
57+
}

products/categories.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,11 @@
9090
<table class="table table-bordered table-striped table-hover">
9191
<thead>
9292
<tr>
93-
<th class="text-center" style="width: 50px;">#</th>
93+
<th class="text-center col-w-50">#</th>
9494
<!-- ************************* -->
9595
<th>Categories</th>
9696
<!-- ************************* -->
97-
<th class="text-center" style="width: 100px;">Actions</th>
97+
<th class="text-center col-w-100">Actions</th>
9898
</tr>
9999
</thead>
100100
<tbody>

products/media.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@
5656
<table class="table">
5757
<thead>
5858
<tr>
59-
<th class="text-center" style="width: 50px;">#</th>
59+
<th class="text-center col-w-50">#</th>
6060
<th class="text-center">Photo</th>
6161
<th class="text-center">Photo Name</th>
62-
<th class="text-center" style="width: 20%;">Photo Type</th>
63-
<th class="text-center" style="width: 50px;">Actions</th>
62+
<th class="text-center col-w-20p">Photo Type</th>
63+
<th class="text-center col-w-50">Actions</th>
6464
</tr>
6565
</thead>
6666
<tbody>

products/product_search.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@
4747
<thead>
4848
<th> Product Name </th>
4949
<th> Photo</th>
50-
<th class="text-center" style="width: 10%;"> SKU </th>
51-
<th class="text-center" style="width: 10%;"> Location </th>
52-
<th class="text-center" style="width: 10%;"> Stock </th>
53-
<th class="text-center" style="width: 10%;"> Cost Price </th>
54-
<th class="text-center" style="width: 10%;"> Sale Price </th>
55-
<th class="text-center" style="width: 100px;"> Actions </th>
50+
<th class="text-center col-w-10p"> SKU </th>
51+
<th class="text-center col-w-10p"> Location </th>
52+
<th class="text-center col-w-10p"> Stock </th>
53+
<th class="text-center col-w-10p"> Cost Price </th>
54+
<th class="text-center col-w-10p"> Sale Price </th>
55+
<th class="text-center col-w-100"> Actions </th>
5656
</thead>
5757
<tbody id="product_info"> </tbody>
5858
</table>

products/products.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,14 @@
8585
<!-- ************************* -->
8686
<th> Product </th>
8787
<th> Photo</th>
88-
<th class="text-center" style="width: 10%;"> SKU</th>
89-
<th class="text-center" style="width: 10%;"> Category </th>
90-
<th class="text-center" style="width: 10%;"> Location </th>
91-
<th class="text-center" style="width: 10%;"> Stock </th>
92-
<th class="text-center" style="width: 10%;"> Cost Price </th>
93-
<th class="text-center" style="width: 10%;"> Sale Price </th>
94-
<th class="text-center" style="width: 10%;"> Product Added </th>
95-
<th class="text-center" style="width: 100px;"> Actions </th>
88+
<th class="text-center col-w-10p"> SKU</th>
89+
<th class="text-center col-w-10p"> Category </th>
90+
<th class="text-center col-w-10p"> Location </th>
91+
<th class="text-center col-w-10p"> Stock </th>
92+
<th class="text-center col-w-10p"> Cost Price </th>
93+
<th class="text-center col-w-10p"> Sale Price </th>
94+
<th class="text-center col-w-10p"> Product Added </th>
95+
<th class="text-center col-w-100"> Actions </th>
9696
</tr>
9797
<!-- ************************* -->
9898
</thead>

products/stock.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@
3939
<table class="table table-bordered table-striped">
4040
<thead>
4141
<tr>
42-
<th class="text-center" style="width: 50px;">Product</th>
43-
<th class="text-center" style="width: 50px;">Quantity</th>
44-
<th class="text-center" style="width: 50px;">Comments</th>
45-
<th class="text-center" style="width: 50px;">Date</th>
46-
<th class="text-center" style="width: 100px;">Actions</th>
42+
<th class="text-center col-w-50">Product</th>
43+
<th class="text-center col-w-50">Quantity</th>
44+
<th class="text-center col-w-50">Comments</th>
45+
<th class="text-center col-w-50">Date</th>
46+
<th class="text-center col-w-100">Actions</th>
4747
</tr>
4848
</thead>
4949
<tbody>

0 commit comments

Comments
 (0)