Skip to content

Fix/security review 1.0.0 - #1

Open
tahir242 wants to merge 7 commits into
mainfrom
fix/security-review-1.0.0
Open

tahir242 wants to merge 7 commits into
mainfrom
fix/security-review-1.0.0

Conversation

@tahir242

Copy link
Copy Markdown
Owner

Security fixes & quality improvements (v1.0.0 review)

Fixes all findings from the 2026-08-27 security review.

Security

  • HIGH front/report.php: Replace global canView() with per-object can($id, READ),
    enforcing entity boundaries on the standalone report endpoint.
  • LOW src/Capacity.php: Restrict VM UUID→Computer resolution to the current session's
    accessible entities via getEntitiesRestrictCriteria().

Quality

  • Remove dead Session::checkLoginUser() (GLPI 11 framework handles auth)
  • Remove double htmlspecialchars() pre-escaping (Twig auto-escapes)
  • Merge duplicate getVmAllocations() queries into one round-trip
  • Route all hardcoded template strings through __() / i18n bag
  • Remove malformed node_modules (?) from phpstan.neon

The global Computer::canView() only checks Session::haveRight('computer',
READ) — it performs no entity check. Any authenticated user with the
standard computer-read right could iterate computers_id to dump inventory
from entities they have no access to.

Replace with the per-object Computer::can(\, READ) which calls
canViewItem() -> checkEntity() -> Session::haveAccessToEntity(), enforcing
the entity boundary before any data is read.

Also change the exception class from BadRequestHttpException (400) to
AccessDeniedHttpException (403), which is the correct HTTP semantics for
an authorization failure.

Fixes: HIGH / broken-access-control (security review 2026-08-27)
Capacity::getLinkedVMs() resolved a VM's inventory UUID to a Computer row
without scoping the lookup to the current session's entities. A VM migrated
between two entities (or a shared hypervisor whose guests are recorded under
a different customer entity) would cause a storage figure computed from a
Computer in an inaccessible entity to appear in the VM breakdown table of
a user who should not see it.

Fix: add getEntitiesRestrictCriteria('glpi_computers') to the WHERE clause
so the lookup only matches Computer rows in entities the session can access.
Also add is_template => 0 to exclude template records, which are never
real inventory hosts.

When no accessible Computer matches the UUID the storage column falls back
to 0 (the existing no-match path), displayed as '0 GB'.

Fixes: LOW / cross-entity-data-access (security review 2026-08-27)
…mment

In GLPI 11, all plugin routes that are not explicitly declared stateless
are authenticated by the framework before the PHP script is executed.
Session::checkLoginUser() therefore does nothing and was misleadingly
documented as the security control for this endpoint.

Remove the call and update the file-level block comment to accurately
describe the real security controls: framework authentication and the
per-object Computer::can(\, READ) check added in the previous commit.

Quality note from security review 2026-08-27.
Twig auto-escapes every {{ }} expression by default. Calling
htmlspecialchars() on values before passing them to Twig causes
double-encoding: a computer named 'R&D-host01' renders in the
browser as 'R&D-host01'.

Remove the htmlspecialchars() calls from:
  - CapacityRenderer::render() for \, \, \
  - front/report.php for the \ argument to Html::header()
    (Html::header() accepts plain text, not HTML, so pre-escaping
    was doubly wrong there)

Let Twig handle HTML-context escaping, which is the documented
GLPI 10+/11 contract for plugin templates.

Quality note from security review 2026-08-27.
getVmAllocations() issued two separate DB->request() calls against
glpi_itemvirtualmachines with identical FROM and WHERE clauses, fetching
SUM(ram) and SUM(vcpu) in separate round-trips.

Replace with a single query that selects both aggregates together.
No behaviour change — the returned array shape is unchanged.

Quality note from security review 2026-08-27.
…on system

Several user-facing strings were hardcoded in English directly inside Twig
templates, bypassing the plugin's own __() / _n() translation infrastructure
and breaking any future localisation effort:

  capacity_tab.html.twig:25   'Capacity Report — %s'
  capacity_tab.html.twig:31   'Generated: %s'
  metric_cards.html.twig:68   'drive' / 'drives'
  metric_cards.html.twig:81   '% used'
  metric_cards.html.twig:84   'No volume data'
  metric_cards.html.twig:84   ' GB free'  (reused existing i18n.free)
  storage_tables.html.twig:107 'used'     (reused existing i18n.used)

Add the missing keys (capacity_report_title, generated_at_label,
drive_singular, drive_plural, pct_used_suffix, no_volume_data, used_label)
to CapacityRenderer::buildI18n() and replace the raw strings in the
templates with i18n bag references.

No behaviour change for English users; enables correct translation for all
other locales.

Quality note from security review 2026-08-27.
The excludePaths list contained the literal string 'node_modules (?)'
which is not a valid path or glob pattern. PHPStan silently ignored it,
but the entry was misleading — suggesting the config had been validated
against a project that contains node_modules. This plugin has no JS
build system and no node_modules directory.

Remove the invalid entry.

Quality note from security review 2026-08-27.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@tahir242

Copy link
Copy Markdown
Owner Author

@copilot review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant