RepoPilotOpen in app →

shrinerb/shrine

File Attachment toolkit for Ruby applications

Healthy

Healthy across all four use cases

Use as dependencyHealthy

Permissive license, no critical CVEs, actively maintained — safe to depend on.

Fork & modifyHealthy

Has a license, tests, and CI — clean foundation to fork and modify.

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isHealthy

No critical CVEs, sane security posture — runnable as-is.

  • Last commit 4mo ago
  • 30+ active contributors
  • MIT licensed
Show 4 more →
  • CI configured
  • Tests present
  • Slowing — last commit 4mo ago
  • Concentrated ownership — top contributor handles 63% of recent commits

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Healthy" badge

Paste into your README — live-updates from the latest cached analysis.

Variant:
RepoPilot: Healthy
[![RepoPilot: Healthy](https://repopilot.app/api/badge/shrinerb/shrine)](https://repopilot.app/r/shrinerb/shrine)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card (1200×630)

This card auto-renders when someone shares https://repopilot.app/r/shrinerb/shrine on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: shrinerb/shrine

Generated by RepoPilot · 2026-05-10 · Source

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/shrinerb/shrine shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

GO — Healthy across all four use cases

  • Last commit 4mo ago
  • 30+ active contributors
  • MIT licensed
  • CI configured
  • Tests present
  • ⚠ Slowing — last commit 4mo ago
  • ⚠ Concentrated ownership — top contributor handles 63% of recent commits

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live shrinerb/shrine repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/shrinerb/shrine.

What it runs against: a local clone of shrinerb/shrine — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in shrinerb/shrine | Confirms the artifact applies here, not a fork | | 2 | License is still MIT | Catches relicense before you depend on it | | 3 | Default branch master exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 157 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>shrinerb/shrine</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of shrinerb/shrine. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/shrinerb/shrine.git
#   cd shrine
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of shrinerb/shrine and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "shrinerb/shrine(\\.git)?\\b" \\
  && ok "origin remote is shrinerb/shrine" \\
  || miss "origin remote is not shrinerb/shrine (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
  && ok "license is MIT" \\
  || miss "license drift — was MIT at generation time"

# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
  && ok "default branch master exists" \\
  || miss "default branch master no longer exists"

# 4. Critical files exist
test -f "README.md" \\
  && ok "README.md" \\
  || miss "missing critical file: README.md"
test -f "lib/shrine.rb" \\
  && ok "lib/shrine.rb" \\
  || miss "missing critical file: lib/shrine.rb"
test -f "lib/shrine/uploader.rb" \\
  && ok "lib/shrine/uploader.rb" \\
  || miss "missing critical file: lib/shrine/uploader.rb"
test -f "lib/shrine/attacher.rb" \\
  && ok "lib/shrine/attacher.rb" \\
  || miss "missing critical file: lib/shrine/attacher.rb"
test -f "lib/shrine/storage/file_system.rb" \\
  && ok "lib/shrine/storage/file_system.rb" \\
  || miss "missing critical file: lib/shrine/storage/file_system.rb"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 157 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~127d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/shrinerb/shrine"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

TL;DR

Shrine is a modular Ruby toolkit for handling file attachments in web applications, providing pluggable storage backends (S3, GCS, Cloudinary, filesystem), metadata extraction and validation, direct/resumable uploads via Uppy, and background job processing integration. It prioritizes memory efficiency through streaming and separation of concerns via its plugin system, making it suitable for applications handling everything from profile pictures to large video files. Monolithic gem structure with clear separation: demo/ contains a full Roda application showcasing the library (routes/, models/, uploaders/, jobs/), lib/ likely contains core Shrine code (inferred), and doc-heavy setup with config/ examples. The demo shows real-world patterns: demo/uploaders/image_uploader.rb demonstrates uploader definition, demo/jobs/attachment/ shows async job patterns (promote_job.rb, destroy_job.rb), demo/routes/direct_upload.rb shows direct upload routing.

👥Who it's for

Ruby/Rails developers and maintainers of applications that need flexible file attachment handling—particularly those dealing with large files, cloud storage requirements, or needing fine-grained control over upload workflows. Also used by Roda/Sequel developers as demonstrated by the bundled Roda demo application.

🌱Maturity & risk

Production-ready and actively maintained. The codebase is substantial (759KB Ruby code), includes comprehensive CI via GitHub Actions (.github/workflows/ci.yml), has detailed documentation, and maintains a changelog (CHANGELOG.md). The demo app (demo/) is fully functional with migrations, models, and tests (demo/test/acceptance_test.rb), indicating stable architecture.

Low risk for production use. Single maintainer (shrinerb org suggests focused stewardship), but extensive plugin ecosystem and multiple ORM integrations (ActiveRecord, Sequel, ROM, Hanami, Mongoid per README) provide stability. No obvious red flags in visible structure; however, you should verify CI passing status and check recent commit dates since last sync, and review the CHANGELOG.md for breaking changes in your target version.

Active areas of work

Unable to determine from file list alone, but the structured CI setup (.github/workflows/ci.yml) and maintained demo (with test fixtures in demo/test/fixtures/) suggest active development. Check the CHANGELOG.md for recent version entries and GitHub Issues/Discussions (per README links to github.com/shrinerb/shrine/discussions) for current work.

🚀Get running

Clone the repo: git clone https://github.com/shrinerb/shrine.git && cd shrine. Install dependencies: bundle install (Gemfile present). Explore the demo: cd demo && bundle install && bundle exec rackup config.ru (inferred from Roda setup). Run tests: bundle exec rake (Rakefile present). See demo/README.md for demo-specific setup.

Daily commands: For the gem itself: bundle exec rake (infer tests run via Rakefile). For the demo app: cd demo && bundle install && bundle exec rackup config.ru and visit http://localhost:9292. Demo requires database setup via migrations: sequel -m db/migrations sqlite://db/demo.db (inferred from demo/db/migrations/ structure with 001_create_albums.rb, 002_create_photos.rb).

🗺️Map of the codebase

  • README.md — Entry point documenting Shrine's architecture, plugin system, and core capabilities that frame all development
  • lib/shrine.rb — Core Shrine class and plugin system loader—every feature depends on understanding this abstraction
  • lib/shrine/uploader.rb — Base uploader class that all custom uploaders inherit from; defines the primary API for file handling
  • lib/shrine/attacher.rb — Central attachment orchestrator managing file lifecycle, metadata, and persistence integration
  • lib/shrine/storage/file_system.rb — Default storage backend reference implementation; shows how to implement the Storage interface
  • doc/getting_started.md — Foundational guide explaining plugin loading, storage configuration, and basic uploader setup patterns
  • Rakefile — Test and build orchestration; needed to understand how to run the test suite and verify changes

🛠️How to make changes

Create a Custom Storage Backend

  1. Create a new storage class inheriting from Shrine::Storage, implementing upload/download/delete/exists?/clear! methods (lib/shrine/storage.rb)
  2. Implement the storage interface methods (upload, open, download, delete) matching S3/FileSystem patterns (lib/shrine/storage/file_system.rb)
  3. Register the storage in Shrine configuration via default_storage or dynamic_storage plugin (demo/config/shrine.rb)
  4. Add tests following the pattern in spec/storage.rb to validate interface compliance (Rakefile)

Add a New Plugin to Extend Uploader Behavior

  1. Create a plugin class in lib/shrine/plugins/ inheriting from Shrine::Plugin (lib/shrine/plugin.rb)
  2. Define plugin configuration in the configurable block and inject methods into Uploader/Attacher via included/uploader_class/attacher_class blocks (lib/shrine/plugins/processing.rb)
  3. Load the plugin in Shrine.plugin(:your_plugin_name) in user configuration or in demo/config/shrine.rb (demo/config/shrine.rb)
  4. Add integration tests verifying plugin interaction with uploaders and storage backends (Rakefile)

Integrate Shrine with a New ORM/Persistence Layer

  1. Create a persistence plugin in lib/shrine/plugins/ (similar to activerecord.rb) defining save/delete hooks (lib/shrine/plugins/activerecord.rb)
  2. Implement the plugin's included block to add attacher accessors and model callbacks (lib/shrine/plugins/model.rb)
  3. Define model behavior for column storage, validation callbacks, and attachment finalization (demo/models/photo.rb)
  4. Document integration pattern in doc/plugins/ following existing plugin documentation structure (doc/plugins/activerecord.md)

Add Image Processing / Derivative Generation

  1. Enable processing plugin in Shrine configuration via Shrine.plugin(:processing) (demo/config/shrine.rb)
  2. Define derivatives in uploader class using process block with ImageMagick/libvips operations (demo/uploaders/image_uploader.rb)
  3. Configure backgrounding plugin to promote derivatives asynchronously via background_helper (lib/shrine/plugins/backgrounding.rb)
  4. Integrate derivation_endpoint plugin to serve generated versions dynamically (doc/plugins/derivation_endpoint.md)

🔧Why these technologies

  • Plugin System (lib/shrine/plugin.rb) — Enables modularity and opt-in feature adoption; users only load required functionality (processing, persistence, metadata) without bloat
  • Storage Interface Abstraction (lib/shrine/storage.rb) — Decouples file backend from business logic; same code works with disk, S3, GCS, Cloudinary via compatible implementations
  • Attacher Orchestration (lib/shrine/attacher.rb) — Separates file lifecycle state machine from persistence layer; coordinates caching, promotion, metadata, validation, and cleanup independently
  • Streaming I/O (lib/shrine/file.rb) — Enables memory-efficient handling of large files; streams from disk/cloud without loading entire content into RAM

⚖️Trade-offs already made

  • Plugin-based composition over monolithic API

    • Why: Flexibility and modularity for diverse use cases (Sequel, ActiveRecord, ROM, Hanami, Mongoid ORM support)
    • Consequence: Steeper learning curve; users must understand plugin loading order and interaction points
  • Two-stage upload (cache then promote) instead of direct permanent storage

    • Why: Allows validation before committing to expensive storage; enables resumable/async workflows; decouples user interaction from backend
    • Consequence: Requires cleanup of stale cache files; adds state management complexity for concurrent uploads
  • Attacher as mixin injected into models vs. wrapper object

    • Why: Seamless ORM integration (callbacks, validations, eager/lazy loading) with familiar DSL
    • Consequence: Tight coupling to persistence layer conventions; makes testing isolated attachment logic harder
  • File metadata extracted at upload time vs. lazy-loaded

    • Why: Enables validation before promotion; avoids repeated I/O for same file
    • Consequence: Metadata accuracy depends on uploader configuration; no automatic re-extraction if external file changes

🚫Non-goals (don't propose these)

  • Does not handle user authentication or authorization; assumes caller validates permissions
  • Does not provide built-in image editing UI; integrates with Uppy.io for client-side preview/crop
  • Does not manage file versioning/retention policies; leaves cleanup to application code or plugins
  • Does not support atomic multi-file transactions; each file attachment is independent
  • Not a web server or HTTP layer; runs in app process and delegates to storage backends

🪤Traps & gotchas

  1. Database column naming: Shrine uses a <column>_data column (not just <column>) to store attachment metadata as JSON—this is non-obvious from the name alone (README mentions 'add the <name>_data column'). 2. Credentials in demo: demo/config/credentials.rb exists but is not in the file list content—if you run the demo locally, you may need to configure cloud storage credentials. 3. Image processing: demo/lib/generate_thumbnail.rb suggests ImageMagick or libvips is required for thumbnail generation, but system binaries may not be installed. 4. Sequel vs. ActiveRecord: the bundled demo uses Sequel (demo/config/sequel.rb), not Rails—different conventions apply (see demo/models/ for Sequel patterns). 5. Storage prefix behavior: note demo/config/shrine.rb uses prefix: 'uploads' for store and prefix: 'uploads/cache' for cache—this structure is important for cleanup logic.

🏗️Architecture

💡Concepts to learn

  • Storage abstraction / pluggable backends — Shrine's core design pattern—you can swap from filesystem to S3 to Cloudinary without changing application code, which is critical for development vs. production workflows.
  • Uploader class pattern — The primary API abstraction in Shrine (see demo/uploaders/image_uploader.rb)—understanding how to subclass Shrine::Uploader and declare plugins is essential to any Shrine implementation.
  • Streaming I/O and memory efficiency — Shrine is designed to stream files rather than buffer them in memory—critical for handling large files (GB+) without exhausting RAM, mentioned in README as a key highlight.
  • Metadata extraction and validation — Shrine extracts file metadata (dimensions, MIME type, size) on upload and allows validation rules based on this—prevents invalid files from being stored and is the basis of security in file uploads.
  • Background job integration (async promotion) — Shrine separates upload into two phases: cache (immediate) and store (async background job)—allows fast user response while processing happens asynchronously, as shown in demo/jobs/attachment/promote_job.rb.
  • Direct/presigned uploads to cloud storage — Shrine generates presigned URLs allowing clients to upload directly to S3/GCS without routing through your app—reduces bandwidth costs and server load, implemented via demo/routes/direct_upload.rb pattern.
  • ORM-agnostic persistence integration — Unlike Rails-only solutions, Shrine works with Sequel, ActiveRecord, ROM, Hanami, Mongoid via a plugin system—critical if you use non-Rails frameworks like the demo's Roda + Sequel stack.
  • carrierwaveuploader/carrierwave — Direct competitor in Ruby file attachment space—older, more monolithic, but still widely used in Rails apps; Shrine is the modern, modular alternative.
  • thoughtbot/paperclip — Legacy Rails attachment library now in maintenance mode—Shrine was designed to address Paperclip's architectural limitations with a plugin system.
  • uppy-io/uppy — Official JavaScript upload library that Shrine integrates with for direct and resumable uploads—mentioned prominently in Shrine docs and demo/routes/direct_upload.rb.
  • tus-io/tus-ruby-server — Resumable upload protocol implementation for Ruby—Shrine supports tus integration for large file uploads as noted in README under resumable uploads.
  • roda-routing/roda — The lightweight Rack framework used in Shrine's bundled demo application—shows how Shrine integrates with minimal routing frameworks beyond Rails.

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Complete the 'Resumable uploads' feature documentation in README.md

The README.md has an incomplete bullet point 'Resumable uploads –' with no description. Given that Shrine supports direct uploads and the demo includes direct_upload routes, this feature likely exists but lacks visibility. Completing this documentation with a link to relevant docs (e.g., doc/direct_s3.md) would help users discover this capability and improve discoverability of related documentation.

  • [ ] Research resumable upload implementation by examining demo/routes/direct_upload.rb and doc/direct_s3.md
  • [ ] Complete the 'Resumable uploads' bullet point in README.md with a brief description and link to detailed documentation
  • [ ] Verify the documentation reference exists or create a doc file if missing (e.g., doc/resumable_uploads.md)

Add CI workflow for Docusaurus documentation builds

The repo contains Docusaurus configuration in package.json with build/deploy scripts, but .github/workflows/ci.yml likely doesn't include documentation build validation. Adding a workflow to test npm run build ensures documentation changes don't break the site build before merge, preventing deployment failures.

  • [ ] Examine .github/workflows/ci.yml to confirm it lacks a Docusaurus build step
  • [ ] Create or extend the CI workflow to run npm install && npm run build in the documentation directory
  • [ ] Add a check to ensure the build succeeds before merging PRs that modify documentation files

Add integration tests for the demo application in demo/test/

The demo/test/ directory only contains acceptance_test.rb with limited coverage. Given the demo's complexity (multiple routes, job processing, direct uploads, multiple storage types), adding unit and integration tests for core demo models (demo/models/photo.rb, demo/models/album.rb) and jobs (demo/jobs/) would ensure the example application stays functional and serves as a reliable reference for users.

  • [ ] Review demo/test/acceptance_test.rb to identify gaps in test coverage for photo/album CRUD operations
  • [ ] Add unit tests for demo/models/photo.rb and demo/models/album.rb including attachment lifecycle
  • [ ] Add integration tests for demo/jobs/attachment/promote_job.rb and demo/jobs/attachment/destroy_job.rb to verify async processing works correctly

🌿Good first issues

  • Add integration test coverage for resumable uploads (tus-ruby-server integration): the demo has acceptance_test.rb but no specific test for resumable upload flow—create a test in demo/test/ that exercises the resumable upload pattern mentioned in README.
  • Document the metadata extraction workflow with a concrete example: README mentions 'metadata validation' and 'extracted metadata' but doesn't show the actual uploader code (likely in lib/) that extracts it—create a detailed example in the demo showing how to extract and validate file dimensions, format, or size.
  • Add a Dockerfile example for the demo app: Dockerfile exists in root but no demo-specific setup guidance—create demo/Dockerfile and demo/docker-compose.yml showing how to run the full demo stack (app + database + optional S3 mock) in containers, and document in demo/README.md.

Top contributors

Click to expand

📝Recent commits

Click to expand
  • 2f922c6 — Silence ruby-filemagic warnings (janko)
  • a1f7ae1 — Upgrade to Minitest 6.x (janko)
  • f2434ab — Test on JRuby 10.0.x (janko)
  • 511f254 — Drop support for Ruby 3.0 and 3.1 (janko)
  • 424fafe — Utilize Ruby 3.0 syntax for argument forwarding (janko)
  • da6df73 — Fix handling of attacher in abstract_persist and abstract_reload methods (#717) (EduardMcfly)
  • b89054c — Drop support for Ruby 2.5, 2.6 and 2.7 (janko)
  • 1a3febb — Add Replace argument to refresh_metadata! in RefreshMetadata plugin (#721) (JacobGalati)
  • accf8aa — Add keep_derivatives option to derivatives plugin to keep derivatives when attaching a new file (#743) (fnordfish)
  • 31ae9bb — Run tests on Ruby 4.0 (janko)

🔒Security observations

The codebase has critical and high-severity security issues primarily related to outdated dependencies and infrastructure configuration. The Node.js 8.11.4 base image is end-of-life and contains numerous known vulnerabilities. npm dependencies are outdated and vulnerable. The demo application contains credential configuration

  • Critical · Outdated Node.js Base Image — Dockerfile, line 1. Dockerfile uses Node.js 8.11.4, which reached end-of-life on December 31, 2019. This version contains numerous known security vulnerabilities including but not limited to OpenSSL vulnerabilities, HTTP/2 issues, and other critical flaws. Node.js 8.x has no security support and should never be used in production. Fix: Update to a supported LTS version of Node.js (e.g., node:18-alpine, node:20-alpine). Current LTS versions provide regular security updates and patches.
  • High · Vulnerable npm Dependencies — package.json, dependencies section. The package.json contains multiple dependencies with known vulnerabilities. Notably: rehype-pretty-code (^0.9.6) and shiki (^0.14.2) are outdated versions. The caret (^) versioning allows automatic minor/patch updates which may introduce breaking changes or vulnerable versions. React 17.0.2 is also an older version with potential security issues. Fix: Update all dependencies to their latest stable versions: '@docusaurus/core' to ^3.x, React to ^18.x, and use exact versions or tilde ranges (~) instead of caret (^) for better control. Run 'npm audit' and address all reported vulnerabilities.
  • High · Potential Hardcoded Credentials — demo/config/credentials.rb. File structure includes 'demo/config/credentials.rb' which suggests credential storage. The demo application should not contain real credentials, but the presence of this file in the repository indicates a potential for credential leakage if developers mistakenly commit real secrets. Fix: Ensure all credentials are stored in environment variables or secure credential management systems. Add credentials.rb to .gitignore. Use a .env.example file to document required environment variables without exposing actual values.
  • Medium · Missing EXPOSE Port Documentation — Dockerfile, line 5. Dockerfile exposes ports 3000 and 35729 without clear security context. Port 35729 is typically used for LiveReload, which should never be exposed in production environments as it allows remote code injection and development tool access. Fix: Remove port 35729 from production Dockerfile. Create separate Docker configurations for development and production. In production, only expose port 3000 and ensure proper network policies restrict access.
  • Medium · No Security Headers Configuration — demo/app.rb, demo/config/shrine.rb. The Shrine framework configuration files don't explicitly show security headers implementation (CSP, HSTS, X-Frame-Options, etc.). Demo application may be vulnerable to XSS, clickjacking, and other browser-based attacks. Fix: Implement security middleware to set proper HTTP headers. Use gems like 'secure_headers' or manually configure: Content-Security-Policy, X-Frame-Options: DENY, X-Content-Type-Options: nosniff, and Strict-Transport-Security headers.
  • Medium · Insecure Yarn Lock Absence — Dockerfile, line 8. The Dockerfile uses 'yarn install' without a yarn.lock file present in the provided file structure. This means dependency versions are not pinned, allowing non-deterministic builds and potential installation of compromised package versions. Fix: Commit yarn.lock (or package-lock.json if using npm) to version control. Use 'yarn install --frozen-lockfile' in Dockerfile to ensure reproducible builds and prevent dependency injection attacks.
  • Low · Demo Application in Production Context — demo/ directory. The repository contains a complete demo application with database, models, and upload functionality. While useful for documentation, including a working demo could lead to accidental deployment or misuse if proper security is not maintained. Fix: Ensure demo application includes clear documentation that it's for learning purposes only. Add security disclaimers. Consider moving to a separate repository or well-documented branch. Implement all security best practices in demo code as examples.

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Healthy signals · shrinerb/shrine — RepoPilot