Compare commits

..

1 Commits

Author SHA1 Message Date
Gauvino
44492876b3 ci(security): add Trivy filesystem scan to code scanning
Streamyfin ships no container image, so this runs a Trivy *filesystem* scan
(vulnerable deps, secrets, misconfig) and uploads SARIF to GitHub code scanning,
complementing CodeQL and dependency-review. Runs on push to develop/master,
weekly, and on demand (not on PRs — fork PRs can't upload SARIF, and
dependency-review already gates PR dependencies). Reports findings (CRITICAL/HIGH,
ignore-unfixed) without failing the build; the Security tab surfaces them.
2026-06-01 17:31:29 +02:00
4 changed files with 151 additions and 70 deletions

View File

@@ -3,11 +3,6 @@ name: 🏷🔀Merge Conflict Labeler
on: on:
push: push:
branches: [develop] branches: [develop]
# SECURITY: pull_request_target runs with the base repo's write token and secrets.
# This job only labels via the API and is safe ONLY because it never checks out or
# runs the PR head's code. NEVER add `actions/checkout` of the PR head (or any `run:`
# that interpolates PR-controlled data) to this workflow — that would turn it into a
# full repo-compromise vector.
pull_request_target: pull_request_target:
branches: [develop] branches: [develop]
types: [synchronize] types: [synchronize]

62
.github/workflows/trivy-scan.yml vendored Normal file
View File

@@ -0,0 +1,62 @@
name: 🛡️ Trivy Security Scan
# Filesystem scan (Streamyfin ships no container image): finds vulnerable dependencies,
# leaked secrets and misconfigurations, and reports them to GitHub code scanning.
# Runs post-merge + weekly (not on PRs — dependency-review already gates PRs, and SARIF
# upload needs a write token that fork PRs don't get).
on:
push:
branches: [develop, master]
paths:
- "package.json"
- "bun.lock"
- "**/*.ts"
- "**/*.tsx"
- "**/*.js"
- "**/*.jsx"
- ".github/workflows/trivy-scan.yml"
schedule:
- cron: "50 7 * * 5" # Weekly, Friday 07:50 UTC
workflow_dispatch:
permissions:
contents: read
concurrency:
group: trivy-${{ github.ref }}
cancel-in-progress: true
jobs:
trivy:
name: 🔎 Filesystem scan
runs-on: ubuntu-24.04
permissions:
contents: read
security-events: write # upload SARIF to code scanning
steps:
- name: 📥 Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: 💾 Cache Trivy vulnerability DB
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.cache/trivy
key: trivy-db-${{ github.run_id }}
restore-keys: trivy-db-
- name: 🔎 Run Trivy filesystem scan
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
scan-type: fs
scan-ref: .
scanners: vuln,secret,misconfig
ignore-unfixed: true
severity: CRITICAL,HIGH
format: sarif
output: trivy-results.sarif
- name: 📤 Upload results to code scanning
uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
with:
sarif_file: trivy-results.sarif
category: trivy-fs

View File

@@ -1,22 +1,12 @@
#!/bin/bash #!/bin/bash
# Local helper: fast-forward master into develop and back. Aborts on any failure and [[ -z $(git status --porcelain) ]] &&
# restores the branch you started on. Not used in CI. git checkout master &&
set -euo pipefail git pull --ff-only &&
git checkout develop &&
if [[ -n $(git status --porcelain) ]]; then git merge master &&
echo "Error: working tree is not clean — commit or stash first." >&2 git push --follow-tags &&
exit 1 git checkout master &&
fi git merge develop --ff-only &&
git push &&
start_branch=$(git rev-parse --abbrev-ref HEAD) git checkout develop ||
trap 'git checkout "$start_branch" >/dev/null 2>&1 || true' EXIT (echo "Error: Failed to merge" && exit 1)
git checkout master
git pull --ff-only
git checkout develop
git merge master
git push --follow-tags
git checkout master
git merge develop --ff-only
git push
git checkout develop

View File

@@ -1,28 +1,62 @@
#!/usr/bin/env node #!/usr/bin/env node
// Symlinks the platform-specific native dirs to `ios` / `android` depending on EXPO_TV. const _fs = require("node:fs");
// Uses fs APIs (no shell) so there is no command-injection surface.
const fs = require("node:fs");
const path = require("node:path"); const path = require("node:path");
const process = require("node:process");
const { execSync } = require("node:child_process");
const root = process.cwd(); const root = process.cwd();
const isTV = process.env.EXPO_TV && process.env.EXPO_TV !== "0"; // const tvosPath = path.join(root, 'iostv');
// const iosPath = path.join(root, 'iosmobile');
// const androidPath = path.join(root, 'androidmobile');
// const androidTVPath = path.join(root, 'androidtv');
// const device = process.argv[2];
// const platform = process.argv[2];
const isTV = process.env.EXPO_TV || false;
const links = isTV const paths = new Map([
? { ios: path.join(root, "iostv"), android: path.join(root, "androidtv") } ["tvos", path.join(root, "iostv")],
: { ["ios", path.join(root, "iosmobile")],
ios: path.join(root, "iosmobile"), ["android", path.join(root, "androidmobile")],
android: path.join(root, "androidmobile"), ["androidtv", path.join(root, "androidtv")],
}; ]);
for (const [link, target] of Object.entries(links)) { // const platformPath = paths.get(platform);
fs.mkdirSync(target, { recursive: true });
try { if (isTV) {
fs.unlinkSync(link); // replace an existing symlink/file (ln -nsf) stdout = execSync(
} catch { `mkdir -p ${paths.get("tvos")}; ln -nsf ${paths.get("tvos")} ios`,
// nothing to remove );
} console.log(stdout.toString());
fs.symlinkSync(target, link); stdout = execSync(
console.log(`${link} -> ${target}`); `mkdir -p ${paths.get("androidtv")}; ln -nsf ${paths.get(
"androidtv",
)} android`,
);
console.log(stdout.toString());
} else {
stdout = execSync(
`mkdir -p ${paths.get("ios")}; ln -nsf ${paths.get("ios")} ios`,
);
console.log(stdout.toString());
stdout = execSync(
`mkdir -p ${paths.get("android")}; ln -nsf ${paths.get("android")} android`,
);
console.log(stdout.toString());
} }
// target = "";
// switch (platform) {
// case "tvos":
// target = "ios";
// break;
// case "ios":
// target = "ios";
// break;
// case "android":
// target = "android";
// break;
// case "androidtv":
// target = "android";
// break;
// }