Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions .github/workflows/dependency-vulnerability-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Dependency Vulnerability Scan

on:
schedule:
- cron: '53 7 * * 3'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: folderview-plus-dependency-vulnerability-scan
cancel-in-progress: false

jobs:
scan:
name: Scan CycloneDX dependency inventory
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
actions: read
contents: read
security-events: write

steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false

- name: Scan generated SBOM against OSV
id: scan
continue-on-error: true
uses: google/osv-scanner-action/osv-scanner-action@06b2ab4348248b456ee06c9e953637f55e03504f # v2.5.0
with:
scan-args: |-
--output=results.json
--format=json
--sbom=docs/sbom.cdx.json

- name: Build SARIF vulnerability report
id: report
if: ${{ !cancelled() }}
continue-on-error: true
uses: google/osv-scanner-action/osv-reporter-action@06b2ab4348248b456ee06c9e953637f55e03504f # v2.5.0
with:
scan-args: |-
--output=results.sarif
--new=results.json
--gh-annotations=false
--fail-on-vuln=true

- name: Retain OSV report
if: ${{ !cancelled() && hashFiles('results.sarif') != '' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: osv-scanner-sarif
path: results.sarif
if-no-files-found: error
retention-days: 5

- name: Upload OSV results to code scanning
if: ${{ !cancelled() && hashFiles('results.sarif') != '' }}
uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4
with:
sarif_file: results.sarif

- name: Enforce clean vulnerability scan
if: ${{ always() }}
env:
SCAN_OUTCOME: ${{ steps.scan.outcome }}
REPORT_OUTCOME: ${{ steps.report.outcome }}
shell: bash
run: |
set -euo pipefail
if [[ "${SCAN_OUTCOME}" != 'success' || "${REPORT_OUTCOME}" != 'success' ]]; then
echo "::error title=Dependency vulnerability scan failed::OSV scanning failed or reported one or more known vulnerabilities. Review the SARIF result in GitHub code scanning."
exit 1
fi
echo "OSV dependency vulnerability scan passed."
303 changes: 239 additions & 64 deletions .github/workflows/unraid-docker-upstream-monitor.yml

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions docs/codeql-triage.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ no new high-or-higher security findings before a protected branch can be merged.
Dependency Review separately rejects pull requests that introduce high-or-critical
known vulnerabilities or licenses outside the repository's approved quality-tooling
set. OpenSSF Scorecard publishes a scheduled supply-chain posture report to code
scanning. Code-quality findings remain part of CI and triage without being
mislabeled as security vulnerabilities.
scanning. The weekly OSV job scans the generated CycloneDX dependency inventory,
uploads SARIF, and fails when it reports a known vulnerability. A scheduled-workflow
watchdog checks that CodeQL, Scorecard, and OSV continue producing timely successful
runs and maintains one recovery issue if they do not. Code-quality findings remain
part of CI and triage without being mislabeled as security vulnerabilities.

## Review Procedure

Expand Down
12 changes: 9 additions & 3 deletions docs/maintainability.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ FolderView Plus uses ratcheted checks so maintenance improvements cannot silentl
- Dev versions are allocated above versions visible in the archive, manifests, branch history, and version tags.
- Historical reproducibility checks use `FVPLUS_HISTORICAL_REBUILD=1` only inside isolated guard worktrees.
- Main-to-dev back-merges build a fresh dev package from merged source; packaged/source drift is never bypassed.
- Release-mode validation fails closed unless the configured Unraid version matrix, browser smoke target, and black/white theme matrix can run. A missing live target is a release failure, not a skipped check.
- Release notes include source commit, archive SHA-256, previous stable tag, a full comparison URL, and bounded commit history.
- Release-mode validation fails closed unless the isolated Unraid fixture profiles, browser smoke fixtures, and black/white theme matrix can run. Repository validation never accepts a live Unraid URL, session, cookie, or credential.
- Release notes include source commit, archive SHA-256, previous stable tag, and a full comparison URL alongside curated user-facing changes; they never embed raw commit history.
- Remote publication validation downloads and hashes the archive bytes. Its retry messages distinguish stale manifests, unavailable artifacts, stale checksum files, and stale archive content.

## Supply chain and dependencies
Expand All @@ -25,6 +25,8 @@ FolderView Plus uses ratcheted checks so maintenance improvements cannot silentl
- `scripts/action_pin_guard.mjs` rejects mutable references.
- Dependabot proposes grouped weekly npm and GitHub Actions updates. jQuery major upgrades remain deliberate because Unraid host compatibility must be reviewed.
- `docs/sbom.cdx.json` is a generated CycloneDX inventory of shipped browser libraries, Unraid-provided runtime contracts, npm development tools, and GitHub Actions. `scripts/runtime_components.json` is the canonical runtime inventory and classifies every file under `scripts/include`; run `npm run sbom` after runtime dependency, npm, or action changes. CI uses `npm run sbom:check`.
- `.github/workflows/dependency-vulnerability-scan.yml` scans that generated inventory against OSV every Wednesday and on demand. Versioned components with supported package identifiers are evaluated for known vulnerabilities; SARIF is retained and published to GitHub code scanning, and a reported vulnerability fails the job for review. Inventory entries without a usable package identifier remain visible for manual upstream review rather than being silently treated as scanned.
- Repository Actions policy permits only GitHub-owned actions plus the SHA-pinned `github/codeql-action@*`, `ossf/scorecard-action@*`, and `google/osv-scanner-action/*@*` patterns needed by the security workflows. Verified-publisher actions are not enabled globally.
- PHPStan is downloaded at a pinned version and SHA-256 by `scripts/phpstan_guard.sh`; it is development-only and is never shipped in the plugin archive.

## UI, localization, and diagnostics
Expand All @@ -39,8 +41,12 @@ FolderView Plus uses ratcheted checks so maintenance improvements cannot silentl

## Operational review

- The scheduled Unraid Docker monitor opens or updates one deduplicated GitHub issue when the upstream interface leaves its dormant state.
- The daily Unraid compatibility monitor opens or updates one deduplicated GitHub issue when a reviewed stable/prerelease OS version, PHP runtime, Docker API/schema, native-page gate, relevant Docker/VM/Dashboard webGUI file, plugin-manager install/update/downgrade contract, Community Applications starter contract, canonical template, or public catalog entry changes.
- `docs/unraid-compatibility-baseline.json` records human-reviewed upstream versions and Git blob signatures. Automation reports drift but never modifies or approves the baseline.
- Isolated PHP 8.3/8.4 profiles represent the oldest supported, current stable, and current prerelease Unraid runtimes. The compatibility lane syntax-checks every shipped PHP file and runs a request-authority smoke contract without connecting to a server.
- Community Applications validation uses the official public portal guidance, starter repository, catalog feed, and canonical template. The authenticated portal Validate/Scan session is never stored in GitHub Actions.
- The scheduled validation workflow runs deterministic fixtures in Chromium, Firefox, and WebKit every Monday. It does not connect to live Unraid targets or require live-system repository secrets.
- The scheduled workflow watchdog checks the most recent successful CodeQL, OpenSSF Scorecard, OSV dependency scan, compatibility, browser-fixture, and clone-traffic jobs. It maintains one deduplicated recovery issue if an expected success is missing, failed, or stale.
- Follow [Unraid Docker prerelease qualification](unraid-docker-prerelease-qualification.md) before changing native-page safe mode.
- Audit current and reachable package history with `bash scripts/artifact_history_audit.sh` and `bash scripts/artifact_history_audit.sh --history`.
- Follow [artifact retention](artifact-retention.md) before any coordinated Git LFS or history migration. History rewriting is intentionally never automated.
Expand Down
13 changes: 13 additions & 0 deletions docs/sbom.cdx.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,19 @@
}
]
},
{
"type": "application",
"name": "google/osv-scanner-action",
"version": "06b2ab4348248b456ee06c9e953637f55e03504f",
"scope": "optional",
"purl": "pkg:github/google/osv-scanner-action@06b2ab4348248b456ee06c9e953637f55e03504f",
"properties": [
{
"name": "folderview-plus:usage",
"value": "build-only-github-action"
}
]
},
{
"type": "application",
"name": "ossf/scorecard-action",
Expand Down
92 changes: 92 additions & 0 deletions docs/unraid-compatibility-baseline.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
{
"schemaVersion": 1,
"reviewedAt": "2026-08-21",
"unraidOs": {
"docsCommit": "346bede9d3685543a3d9325e4bc52f075073ddee",
"latestReviewedStable": {
"version": "7.3.2",
"releaseNote": "7.3.2.md",
"releaseNoteBlobSha": "273861365c84ef9e19f484cede87a1f42c69282d",
"phpVersion": "8.4.23"
},
"latestReviewedPrerelease": {
"version": "7.4.0-beta.1",
"releaseNote": "7.4.0.md",
"releaseNoteBlobSha": "a214604f2de133d278ed4ceeb441239fdb672fd6",
"phpVersion": "8.4.24"
},
"oldestSupported": {
"version": "7.0.0",
"releaseNote": "7.0.0.md",
"releaseNoteBlobSha": "bcd7091b3b8f94b599eb23a58ab4c8f0eefefe55",
"phpVersion": "8.3.8"
}
},
"webgui": {
"repository": "unraid/webgui",
"reviewedCommit": "995cc85e8801b8cf32a4016ea5d597ba400beb2d",
"files": {
"emhttp/plugins/dynamix.docker.manager/DockerContainers.page": "6e9f853f3a576367b003d5ca2d42fc6b5c29938a",
"emhttp/plugins/dynamix.docker.manager/include/DockerContainers.php": "b78393ceecf8a9878ca46656badf439a24de5c27",
"emhttp/plugins/dynamix.docker.manager/include/UserPrefs.php": "3a2ea460b99f2a521b5890107d15570df293ee52",
"emhttp/plugins/dynamix.docker.manager/javascript/docker.js": "bbf8b3543d8479b914732682e465783c526cd3a6",
"emhttp/plugins/dynamix.docker.manager/nchan/docker_load": "f5e99b98455f8827d20244012808516db589c084",
"emhttp/plugins/dynamix.vm.manager/VMMachines.page": "d937d6d4d582e890ce72e0d6ed69cdd7b992fe9a",
"emhttp/plugins/dynamix.vm.manager/include/VMMachines.php": "b791fc43a2a1472b3f7cb8a4373451e2aebacf45",
"emhttp/plugins/dynamix.vm.manager/include/VMajax.php": "34acadd4d73c7f1f1556db9ac685d11a4cb6bed2",
"emhttp/plugins/dynamix.vm.manager/include/UserPrefs.php": "57169f4224842b9151dabe8b9d2613e3f6206a20",
"emhttp/plugins/dynamix/Dashboard.page": "3c5d1a68ac7103fe7c286e8e36956b5174c22915",
"emhttp/plugins/dynamix/include/DashboardApps.php": "53ad8efab59e7453a350578204e317c3656c5c28",
"emhttp/plugins/dynamix.plugin.manager/PluginHelpers.page": "15a2397ead358ef9834922e9c3cb3880f8f3399c",
"emhttp/plugins/dynamix.plugin.manager/PluginInstall.page": "663dd369df119e0f8cfd2ac9ad9074b7d6da1024",
"emhttp/plugins/dynamix.plugin.manager/Plugins.page": "f98e82ed8bab64c83edac85114e69b9b231eaefd",
"emhttp/plugins/dynamix.plugin.manager/PluginsError.page": "10b746975962642cd17f00d3d14c29ac8246a094",
"emhttp/plugins/dynamix.plugin.manager/PluginsStale.page": "b226866b8fe80c236d98ab80ab706812ab7aa40e",
"emhttp/plugins/dynamix.plugin.manager/include/Downgrade.php": "9742e7bbc3fd41320f4f30b715a9712ad2006c5e",
"emhttp/plugins/dynamix.plugin.manager/include/PluginHelpers.php": "1ea0258b824d71316caf9f857a861e483a8a3dc4",
"emhttp/plugins/dynamix.plugin.manager/include/ShowChanges.php": "3500f5e326ac0458f37db67e3ef794cf4037816e",
"emhttp/plugins/dynamix.plugin.manager/include/ShowPlugins.php": "f238d1bec1399bc03280a263f199eec34ce59338",
"emhttp/plugins/dynamix.plugin.manager/post-hooks/post_plugin_checks": "0f9f6ad6178473844338b1f6fb15214fcddb1a04",
"emhttp/plugins/dynamix.plugin.manager/pre-hooks/pre_plugin_checks": "0bd6047ef6fd9aa50a6eee091773594a05901e67",
"emhttp/plugins/dynamix.plugin.manager/scripts/PluginAPI.php": "66b457448f9364320d941b6416a670a125bc6abb",
"emhttp/plugins/dynamix.plugin.manager/scripts/checkall": "3033b88051d663a02d9124772f7cd07e852181ca",
"emhttp/plugins/dynamix.plugin.manager/scripts/multiplugin": "3c87d6171ea679d10c3a9b8ee5a626d26478dfce",
"emhttp/plugins/dynamix.plugin.manager/scripts/plugin": "d9fa17beeae39b86863bcb57ca2123878b47acfb",
"emhttp/plugins/dynamix.plugin.manager/scripts/plugin_rm": "6a9210fdd14dfebcebf4ce85af606b7a12b926c3",
"emhttp/plugins/dynamix.plugin.manager/scripts/plugincheck": "301537304151ca963bd167f4d62c91e2215d1df4",
"emhttp/plugins/dynamix.plugin.manager/scripts/showchanges": "f5b85fba05e141e14146336cc4a99d6b5975b9f8"
}
},
"communityApplications": {
"starterRepository": "unraid/unraid-community-apps-starter",
"reviewedCommit": "01398ff8bd728ce92dd9595e33d8577299e0e971",
"files": {
"README.md": "8a3c8a2b8a0880990a5e0b392fd446a95bdb1a97",
"ca_profile.xml": "8ce639fe6d2e9bf81f3681cfcc7f8200ee2b6b59",
"plugins/example-plugin.xml": "1d343631a1b11057b3d487aee2c89b4848c02ce2"
},
"portalHelpUrl": "https://ca.unraid.net/submit/help/repository-xml",
"feedUrl": "https://ca.unraid.net/assets/feed/applicationFeed.json",
"catalogTemplateUrl": "https://raw.githubusercontent.com/alexphillips-dev/unraid-ca-templates/main/folderview.plus.xml"
},
"phpMatrix": [
{
"profile": "oldest-supported",
"unraidVersion": "7.0.0",
"phpVersion": "8.3.8",
"image": "php:8.3.8-cli-alpine"
},
{
"profile": "current-stable",
"unraidVersion": "7.3.2",
"phpVersion": "8.4.23",
"image": "php:8.4.23-cli-alpine"
},
{
"profile": "current-prerelease",
"unraidVersion": "7.4.0-beta.1",
"phpVersion": "8.4.24",
"image": "php:8.4.24-cli-alpine"
}
]
}
2 changes: 2 additions & 0 deletions docs/unraid-docker-api-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ Integration remains disabled regardless of those individual booleans until Unrai

The reviewed baseline is `docs/unraid-docker-upstream-baseline.json`. A gate change, schema change, missing required token, or newer API release produces a review signal and opens or updates the scheduled compatibility issue. Monitoring never changes runtime behavior automatically.

The daily `scripts/unraid_compatibility_monitor.mjs` orchestration adds stable/prerelease Unraid release notes, PHP runtime changes, exact relevant `unraid/webgui` file signatures, and Community Applications publication contracts. All baselines require a reviewed repository change; CI never advances them automatically.

## Validation

Focused contracts live in:
Expand Down
6 changes: 3 additions & 3 deletions docs/unraid-docker-interface-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This document defines how FolderView Plus coexists with the current table-based Unraid Docker page and the native component/API replacement being developed by Unraid.

The upstream implementation was last reviewed on 2026-08-18 against Unraid API v4.37.1. That release added PKCE support to Unraid OIDC without changing the Docker interface, generated Docker GraphQL contract, native Docker page, or organizer implementation. The generated Docker contract retained the reviewed signature, and Unraid's [`docker-containers-page` file modification](https://github.com/unraid/api/blob/main/api/src/unraid-api/unraid-file-modifier/modifications/docker-containers-page.modification.ts) still returned `shouldApply: false`, while its replacement markup contained `<unraid-docker-container-overview>`. The native Docker implementation therefore remains prerelease, and safe mode plus the existing organizer boundary remain unchanged.
The upstream implementation was last reviewed on 2026-08-21 against Unraid API v4.37.2. That release adds TXZ-install cleanup for stale Unraid API web-component files without changing the Docker interface, generated Docker GraphQL contract, native Docker page, or organizer implementation. The generated Docker contract retained the reviewed signature, and Unraid's [`docker-containers-page` file modification](https://github.com/unraid/api/blob/main/api/src/unraid-api/unraid-file-modifier/modifications/docker-containers-page.modification.ts) still returned `shouldApply: false`, while its replacement markup contained `<unraid-docker-container-overview>`. The native Docker implementation therefore remains prerelease, and safe mode plus the existing organizer boundary remain unchanged.

## Host generations

Expand Down Expand Up @@ -103,13 +103,13 @@ The `docker-api-legacy.html` fixture and `tests/fixtures/unraid-api/*.json` veri

## Activation triggers

`scripts/unraid_docker_upstream_monitor.sh` treats any of these as a review signal:
`scripts/unraid_docker_upstream_monitor.sh` treats any of these as a Docker/API review signal:

1. Upstream `docker-containers-page.modification.ts` changes to `shouldApply: true`.
2. Supplied official release notes announce a native/new Docker page or interface.
3. The tracked Docker GraphQL schema signature changes or a required capability disappears.
4. The latest official Unraid API release differs from the reviewed baseline.

An unrecognizable source shape is also a blocking signal because silently assuming the replacement remains disabled would be unsafe. The scheduled workflow `.github/workflows/unraid-docker-upstream-monitor.yml` checks the source gate, GraphQL schema, API release, and current Unraid release notes weekly and can be run manually.
An unrecognizable source shape is also a blocking signal because silently assuming the replacement remains disabled would be unsafe. The daily workflow `.github/workflows/unraid-docker-upstream-monitor.yml` combines that focused check with `scripts/unraid_compatibility_monitor.mjs`. It also compares reviewed stable/prerelease Unraid versions, PHP runtimes, exact Docker/VM/Dashboard webGUI file signatures, plugin-manager install/update/downgrade pages, helpers, hooks and scripts, the official Community Applications starter contract, the public catalog listing, and portal guidance. A drift issue never approves or rewrites a baseline automatically.

See [Unraid Docker Prerelease Qualification](unraid-docker-prerelease-qualification.md) before changing the coexistence policy.
Loading