diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..a888efe
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,30 @@
+version: 2
+updates:
+ - package-ecosystem: "npm"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ groups:
+ production:
+ update-types:
+ - "minor"
+ - "patch"
+ development:
+ dependency-type: "development"
+ open-pull-requests-limit: 10
+
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ open-pull-requests-limit: 5
+
+ - package-ecosystem: "docker"
+ directory: "/apps/journal"
+ schedule:
+ interval: "monthly"
+
+ - package-ecosystem: "docker"
+ directory: "/apps/planner"
+ schedule:
+ interval: "monthly"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e6a6846..3eab10a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -11,6 +11,27 @@ concurrency:
cancel-in-progress: true
jobs:
+ security:
+ name: Security Scan
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ - name: Gitleaks
+ uses: gitleaks/gitleaks-action@v2
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - uses: pnpm/action-setup@v4
+ - uses: actions/setup-node@v6
+ with:
+ node-version: 24
+ cache: pnpm
+ - run: pnpm install --frozen-lockfile
+ - name: Dependency audit
+ run: pnpm audit --audit-level=high
+ continue-on-error: true
+
typecheck:
name: Typecheck
runs-on: ubuntu-latest
diff --git a/.gitleaks.toml b/.gitleaks.toml
new file mode 100644
index 0000000..a3f3fed
--- /dev/null
+++ b/.gitleaks.toml
@@ -0,0 +1,7 @@
+[extend]
+# Use default gitleaks rules
+
+[[allowlist.regexTarget]]
+# Sentry DSNs are public (ingest-only, not secrets)
+description = "Sentry DSN"
+regex = '''https://[a-f0-9]+@o\d+\.ingest\.\w+\.sentry\.io/\d+'''
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..cd52f74
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,44 @@
+# Security Policy
+
+## Reporting a Vulnerability
+
+If you discover a security vulnerability in trails.cool, please report it
+responsibly.
+
+**Do NOT open a public GitHub issue for security vulnerabilities.**
+
+Instead, please email: **security@trails.cool**
+
+Include:
+- Description of the vulnerability
+- Steps to reproduce
+- Impact assessment (what could an attacker do?)
+- Any suggested fixes
+
+We will acknowledge your report within 48 hours and provide a timeline for
+a fix. We aim to resolve critical vulnerabilities within 7 days.
+
+## Scope
+
+The following are in scope:
+- trails.cool and planner.trails.cool (production)
+- The trails-cool/trails GitHub repository
+- Authentication, authorization, and session management
+- Data exposure or leakage
+- Server-side vulnerabilities
+
+The following are out of scope:
+- Denial of service attacks
+- Social engineering
+- Self-hosted instances (report to the instance operator)
+- Issues in third-party dependencies (report upstream)
+
+## Security Practices
+
+- **Authentication**: Passkey (WebAuthn) + magic link login. No passwords stored.
+- **Cookies**: httpOnly, secure, sameSite=lax
+- **Database**: Parameterized queries via Drizzle ORM (no raw SQL)
+- **Headers**: HSTS, CSP, X-Frame-Options, X-Content-Type-Options
+- **CI**: Gitleaks secret scanning, dependency auditing
+- **Docker**: Non-root containers
+- **Monitoring**: Sentry error tracking, fail2ban SSH protection
diff --git a/apps/journal/Dockerfile b/apps/journal/Dockerfile
index 036454a..1320ba3 100644
--- a/apps/journal/Dockerfile
+++ b/apps/journal/Dockerfile
@@ -21,6 +21,7 @@ COPY . .
RUN pnpm --filter @trails-cool/journal build
FROM base AS runtime
+RUN addgroup --system app && adduser --system --ingroup app app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=deps /app/apps/journal/node_modules ./apps/journal/node_modules
COPY --from=build /app/apps/journal/build ./apps/journal/build
@@ -28,6 +29,7 @@ COPY --from=build /app/apps/journal/package.json ./apps/journal/package.json
COPY --from=build /app/packages ./packages
WORKDIR /app/apps/journal
+USER app
EXPOSE 3000
ENV PORT=3000
CMD ["npx", "react-router-serve", "./build/server/index.js"]
diff --git a/apps/journal/app/routes/privacy.tsx b/apps/journal/app/routes/privacy.tsx
index b0c1fd0..012ad02 100644
--- a/apps/journal/app/routes/privacy.tsx
+++ b/apps/journal/app/routes/privacy.tsx
@@ -90,6 +90,18 @@ export default function PrivacyPage() {
+ Security Practices
+
+
+
Last updated: March 2026. If this manifest changes, we'll note it here.
diff --git a/apps/planner/Dockerfile b/apps/planner/Dockerfile index 49ba1c6..b8810df 100644 --- a/apps/planner/Dockerfile +++ b/apps/planner/Dockerfile @@ -21,6 +21,7 @@ COPY . . RUN pnpm --filter @trails-cool/planner build FROM base AS runtime +RUN addgroup --system app && adduser --system --ingroup app app COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/apps/planner/node_modules ./apps/planner/node_modules COPY --from=build /app/apps/planner/build ./apps/planner/build @@ -30,6 +31,7 @@ COPY --from=build /app/apps/planner/package.json ./apps/planner/package.json COPY --from=build /app/packages ./packages WORKDIR /app/apps/planner +USER app EXPOSE 3001 ENV PORT=3001 CMD ["node", "--experimental-strip-types", "server.ts"] diff --git a/docker/brouter/Dockerfile b/docker/brouter/Dockerfile index 8ff91d5..6ea7073 100644 --- a/docker/brouter/Dockerfile +++ b/docker/brouter/Dockerfile @@ -12,13 +12,15 @@ RUN apt-get update && apt-get install -y --no-install-recommends wget unzip curl && rm "brouter-${BROUTER_VERSION}.zip" \ && apt-get purge -y wget unzip && apt-get autoremove -y && rm -rf /var/lib/apt/lists/* -# Create directories for segments and profiles -RUN mkdir -p /data/segments /data/profiles +# Create non-root user and directories for segments and profiles +RUN addgroup --system app && adduser --system --ingroup app app \ + && mkdir -p /data/segments /data/profiles # Copy default profiles from release and rename JAR for simpler CMD RUN cp -r profiles2/* /data/profiles/ 2>/dev/null || true \ && mv brouter-*-all.jar brouter.jar +USER app EXPOSE 17777 # BRouter server: