Skip to content

security: Semgrep Pro triage report — 117 findings, 0 critical (FP catalog) #146

@fabiodalez-dev

Description

@fabiodalez-dev

Context

Ran semgrep scan --pro --config=auto (Semgrep Pro 1.157.0) on the full codebase. Documenting the triage so future scans can skip re-validating the same false positives.

Scan parameters:

semgrep scan --pro --config=auto \
  --exclude=node_modules --exclude=vendor \
  --exclude='*/vendor/*' \
  --exclude=public/assets/tinymce --exclude=public/assets/uppy \
  --exclude=public/assets/sweetalert2 \
  --exclude='*.min.js' --exclude='*.min.css'

Result: 468 rules, 582 files, 117 findings (117 blocking)0 real vulnerabilities after triage.

Summary by severity

Severity Count Real False Positive
ERROR 14 0 14
WARNING 101 ~3 (minor hardening) ~98
INFO 2 0 2

ERROR breakdown — all 14 false positives

Rule Count Files Why FP
generic.secrets.detected-stripe-api-key 3 storage/plugins/api-book-scraper/README.md, SERVER_IMPLEMENTATION_GUIDE.md Example placeholder sk_live_9f8e7d6c5b4a3210fedcba9876543210 in documentation — not a real key
generic.secrets.detected-picatic-api-key 3 same files Same placeholder, different rule match
generic.secrets.detected-bcrypt-hash 1 app/Controllers/AuthController.php:70 Dummy hash intentional — documented inline as OWASP timing-attack defense for the email-enumeration mitigation; not a leaked credential
php.lang.security.taint-cookie-http-false 2 app/Support/RememberMeService.php:330,345 Code literally sets 'httponly' => true. Rule misreads the array literal
php.lang.security.taint-cookie-secure-false 2 app/Support/RememberMeService.php:330,345 'secure' => $this->isSecureConnection() — conditional that's true on HTTPS, correct pattern. Rule can't statically resolve the call
php.lang.security.exec-use.exec-use 3 installer/classes/Installer.php:323,457, scripts/manual-upgrade.php:459 All exec() calls escape every variable argument with escapeshellarg(). Used at install/upgrade time only, mysql CLI bootstrap

WARNING breakdown

php.lang.security.unlink-use.unlink-use — 58 findings — mostly FP

The path argument is internally resolved (via realpath() + containment check on the upload root) before every unlink() callsite. Specifically:

File Count Status
app/Support/Updater.php 17 Internal storage/updates/* cleanup — paths come from glob() over a fixed directory
app/Support/QueryCache.php 9 Cache file invalidation — paths come from glob() over storage/cache/queries
scripts/manual-upgrade.php 7 Install-time scripts, run by operator
storage/plugins/archives/ArchivesPlugin.php 5 Cover/document upload cleanup — all paths run through realpath + base-dir containment
Other (controllers, plugins) 20 Same pattern: internal-resolved paths, no direct user input

app/Controllers/EventsController.php:1 is the canonical hardened pattern — see the deleteUploadedImageFile() helper for the prefix-check + realpath containment guard introduced in PR #144 (F00 fix).

php.symfony.security.audit.symfony-non-literal-redirect — 21 findings — all FP

All 21 are in app/Controllers/SettingsController.php. Every flagged site is $this->redirect($response, '/admin/settings?tab=<literal>') — a complete string literal in the source. The rule misclassifies any redirect() argument as "non-literal" because the helper's parameter is typed string (no statically-pinned constant). Tracked in #145 for the broader admin-route i18n decision.

php.lang.security.audit.openssl-decrypt-validate — 4 findings — all FP

File Line Status
app/Support/Updater.php 293 Code already does $decrypted !== false ? $decrypted : null — exactly what the rule asks for
storage/plugins/api-book-scraper/ApiBookScraperPlugin.php 275 Same pattern
storage/plugins/z39-server/Z39ServerPlugin.php 915 Same pattern
storage/plugins/z39-server/endpoint.php 222 Same pattern

php.lang.security.unserialize-use — 2 findings — all FP

app/Support/QueryCache.php:366,458 — both call unserialize($content, ['allowed_classes' => false]). The allowed_classes => false option is the documented mitigation (PHP prevents class instantiation → no object-injection RCE).

javascript.lang.security.audit.path-traversal.path-join-resolve-traversal — 4 findings — all FP

scripts/copy-vendor-assets.js:25-26 — build-time script that copies bundled vendor assets from node_modules to public/assets. No user input — paths come from a hardcoded vendor list. Runs only on npm run build.

javascript.lang.security.audit.detect-non-literal-regexp — 5 findings

public/assets/661.bundle.js (bundled JS — third-party libs) + dev tooling. Most likely FP from minified output. Low priority.

php.lang.security.injection.tainted-filename — 4 findings

Need case-by-case review (not done yet — TBD).

php.lang.security.taint-unsafe-echo-tag — 3 findings — hardened in commit c06fab01

All 3 sites use a map-lookup-with-fallback pattern: echo $errorMap[$_GET['key']] ?? __('default'). The map values are i18n-controlled, the fallback is a fixed literal — so the practical XSS is already gated by the allow-list. But the commit c06fab01 adds htmlspecialchars(…, ENT_QUOTES, 'UTF-8') on output anyway so future contributors adding free-form messages can't introduce XSS, and the rule stays quiet.

Files: app/Views/frontend/book-detail.php:1756, app/Views/profile/index.php:332, app/Views/prestiti/index.php:73.

Action items

  • Apply htmlspecialchars hardening on 3 unsafe-echo-tag sites — done in c06fab01 on PR release: unified roadmap — SwalApp + event layout + RiC-CM (#136 + #139 + #141) #144
  • Add a .semgrepignore or .semgrep.yml config to silence the documented FPs so future scans surface only NEW signal. Specifically:
    • Path exclusion: storage/plugins/api-book-scraper/README.md, SERVER_IMPLEMENTATION_GUIDE.md (docs with placeholder credentials)
    • Rule disable: php.symfony.security.audit.symfony-non-literal-redirect (over-aggressive against redirect() calls)
    • Rule disable: php.lang.security.audit.openssl-decrypt-validate (project already has the !== false check)
  • (Optional) Add a // nosemgrep: rule-id line above each known FP so the rule keeps firing globally but stays quiet on the validated sites.
  • (Optional) Verify the 4 tainted-filename findings and the 5 detect-non-literal-regexp findings on a future pass.

How to re-run

semgrep scan --pro --config=auto \
  --exclude=node_modules --exclude=vendor \
  --exclude='*/vendor/*' \
  --exclude=public/assets/tinymce --exclude=public/assets/uppy \
  --exclude=public/assets/sweetalert2 \
  --exclude='*.min.js' --exclude='*.min.css' \
  --json-output=/tmp/semgrep-pinakes.json \
  .

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions