Teams that record meetings, interviews, or lectures need a way to turn raw audio into searchable, annotated notes that stay on their own infrastructure. Existing cloud services expose sensitive data, while local tools lack an end‑to‑end workflow for capture, transcription, speaker diarization, and knowledge extraction.
What This Does
Speakr stitches together a full pipeline in a single Docker image.
Capture: the web UI (templates/index.html, static/js/modules/composables/index.js) streams microphone or system audio to the Flask backend (src/app.py).
Transcribe: the /api/v1/transcriptions endpoint (src/api/recordings.py) forwards audio to an ASR connector chosen via environment config (config/env.asr.example).
Post‑process: WhisperX‑based diarization happens in src/api/recordings.py and speaker embeddings are stored through src/api/folders.py.
Insights: summaries and event extraction are triggered by src/api/inquire.py, which calls the prompt engine defined in src/api/naming_templates.py.
The result is a searchable transcript visualized in the React client (static/js/modules/computed/index.js).
How It Is Wired
Entry Point
src/app.py starts the Flask app.
app = Flask(__name__)
api = Api(app)
It imports the API namespace from src/api/__init__.py, which registers blueprints such as recordings, groups, and inquire.
Control Flow
HTTP request → Flask route in src/api/recordings.py (e.g., @recordings.route('/upload', methods=['POST'])).
File handling → src/api/utils.py writes the incoming FileStorage to the local UPLOADS directory.
ASR dispatch → src/api/asr.py reads os.getenv('ASR_BACKEND') and routes the file to the matching connector (src/api/whisperx.py, src/api/openai.py).
Post‑process → returned JSON is enriched by src/api/recordings.py calling src/api/diarization.py and src/api/embeddings.py.
docker-compose -f config/docker-compose.example.yml up -d
The server is reachable at http://localhost:5000.
Real-World Use
A research lab records interviews, then uploads them to Speakr. The lab’s CI pipeline runs scripts/migrate_docker.sh to move older recordings to S3, while a scheduled job (scripts/backfill_audio_duration_seconds.py) re‑indexes durations. Researchers query the transcript via the /api/v1/inquire endpoint, receiving timestamped answers that link back to the original audio.
Code Health & Issues
Low risk – package.json declares dependencies without a lockfile, potentially leading to non‑reproducible builds (package.json line 12).
Missing lockfile – no pnpm-lock.yaml or yarn.lock (package.json only).
No tests for API validation – only 116 test files, many targeting frontend utilities.
Documentation gaps – docs/user-guide/api-reference.md references endpoints that lack implementation comments.
The Bottom Line
Speakr delivers a fully self‑hosted transcription workflow bundled in Docker, suitable for privacy‑conscious teams. Its modular API and clear entry points make it approachable for contributors, though the lack of a JavaScript lockfile and sparse API documentation may slow onboarding. Organizations needing end‑to‑end audio‑to‑knowledge pipelines on premises will find it a solid foundation.
What the analyser found
Deployment readiness
6/7
✓Container image
✓CI pipeline
✗Lockfile committed
✓Test suite
✓README
✓License
✓No committed secrets
Composition
579 files
Python224
HTML78
JavaScript66
Markdown47
JSON10
YAML7
ReactFlaskRailsDocker
Architecture
Top-level areas of the codebase, sized by module count. Arrows show how many imports cross from one area into another.
Ranked by severity × confidence × production reach. Reach is the honest discriminator across a collection that is mostly other people's code: the same finding matters more in something that ships.
high3
medium7
low1
Pin third-party GitHub Actions to a commit SHAhigh5 occurrences
A tag can be moved, so the action running with your token and secrets is whatever its owner last pushed; this is how tj-actions/changed-files leaked secrets from thousands of repos.
Fix: Replace each @vN with the 40-character commit SHA, keep # vN as a comment, and let Dependabot bump the SHAs.
Upgrade the pinned dependency carrying a published advisoryhigh22 occurrences
The pinned version is the one that installs, so a published advisory against it describes this deployment rather than a hypothetical one, and the advisory is also a public description of how to exploit it.
Fix: Upgrade to the fixed version named in the advisory, then commit the lockfile.
Commit a lockfile beside the manifesthigh
package.json
manifest with no lockfile in scope
An unlocked range means the artifact you tested and the artifact you ship can contain different transitive code, so a malicious patch release reaches production with no diff.
Fix: Run the package manager once and commit the generated lockfile.
Declare least-privilege permissions for GITHUB_TOKENmedium
.github/workflows/test.yml
1 workflow(s) declare no permissions
With no declaration the token inherits the repository default, so any injected step can push commits or mint releases from inside your own CI.
Fix: Add permissions: contents: read at the top of the workflow and widen per job only where needed.
Enable Dependabot or Renovatemedium
5 manifest(s), no update bot configured
Without a bot a published advisory sits unpatched until someone audits by hand, which across 1,322 repositories means never.
Fix: Commit .github/dependabot.yml covering the repo ecosystems plus github-actions.
Pin the container base image by digestmedium3 occurrences
An untagged or mutable base means today's build and last month's contain different libc and a different CVE set, with no record of which shipped.
Fix: Use image:tag@sha256:<digest> and enable Dependabot's docker ecosystem.
Gate pull requests on a dependency vulnerability scanmedium
.github/workflows
no dependency scan in CI
This is the one gate that would catch a known-vulnerable package before it reaches a build, and no repository in the sample had it.
Fix: Add dependency-review-action on pull_request, or osv-scanner on push and a schedule.
Set persist-credentials: false on checkoutmedium
.github/workflows/docker-publish.yml
checkout keeps the token, then dependencies are installed
The token stays in .git/config for every later step, so a malicious postinstall script reads a pushable credential without one ever being passed to it.
Fix: Add with: persist-credentials: false, and pass an explicit token only to the step that pushes.
Add a non-root USER to the imagemedium
Dockerfile
CMD or ENTRYPOINT with no USER directive
A process running as root in the container is root against every mounted volume, and it turns any container escape or writable-mount mistake from a contained problem into a host one.
Fix: Create an unprivileged user, chown what it needs, and end the Dockerfile with USER.
Pin the resolution so a vulnerable version cannot installmedium27 occurrences
no lockfile, and the declared ranges reach vitest@2.1.0 CVE-2026-47429 (critical) +1 more; pydantic@2.0.0 CVE-2024-3772 (critical) +1 more; authlib@1.3.0 CVE-2026-27962 (critical) +21 more; and 1 more package(s)
The range includes versions with published advisories and nothing in the repository records which one was resolved, so two installs a month apart can differ in whether they are vulnerable and neither is reproducible from what is committed.
Fix: Commit the lockfile, which fixes the resolution and makes this answerable.
Set timeout-minutes on the workflow jobslow4 occurrences
.github/workflows/cla.yml
4 workflow(s) declare no job timeout
A wedged step runs to the six-hour platform default, which on a two-hourly schedule means three runs overlap behind it.
Fix: Add timeout-minutes with a realistic bound to each job.
Checked deterministically against the repository tree and a bounded set of its files: committed credentials, unpinned actions and base images, missing lockfiles and update bots, workflows that discard failures, published advisories against the declared dependencies, runtime configuration, licensing and notebook reproducibility. No language model is involved in this section.