BLA
BLA (Blind LLM Agents) is the open-source Blue Defense MCP and CLI built to protect software artifacts against automated AI reverse-engineering workflows.
翻译准备中
Jitsi Meet portal: hosts create rooms and share invite links (QR / calendar); guests join via link. 8x8 JaaS or self-hosted Jitsi Meet; roles, 2FA, SSO, audit logs, scheduling, recording transcripts. | Jitsi Meet 會議管理系統:主持人建立會議室、分享邀請連結(QR / 行事曆),來賓憑連結加入;支援 8x8 JaaS 與自建 Jitsi Meet,含多帳號 / 角色 / 2FA、SSO、稽核、排程、錄影逐字稿與摘要。
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗繁體中文: README_zh-TW.md · 日本語: README_ja.md
A meeting portal built on Jitsi Meet with dual-mode support for 8x8 JaaS1 (cloud-hosted) and self-hosted Jitsi Meet. After signing in, hosts can create meeting rooms and generate invite links (with QR codes /
.icscalendar invitations); guests join through the invite link. Built-in enterprise features include multiple accounts / roles / 2FA, a complete audit log with SIEM forwarding, fail2ban-style lockout, scheduled time slots, and more.
[!IMPORTANT] Security update (2026-10-21): please upgrade to v1.22.1 as soon as possible. PHP has released security fixes (8.4.27 / 8.3.36). Docker installs: use the v1.22.1 image or rebuild with
docker build --pull. Direct installs: update PHP to 8.4.27 / 8.3.36 or later and setsession.upload_progress.enabled=Offinphp.ini. This setting also blocks CVE-2026-115560, disclosed on 10/23 and not yet patched, so be sure to set it. See CHANGELOG.md.
Project page / Demo: https://jasoncheng7115.github.io/jt-vc-portal/
.ics attachment (METHOD:REQUEST) that can be added to a calendar in one click.| Item | Minimum | Recommended |
|---|---|---|
| PHP | 8.3 | 8.4 |
| Web server | Apache + mod_rewrite (AllowOverride All) | Same |
| PHP extensions | openssl, fileinfo, json, mbstring, curl, zlib | Same |
| Other | A writable data directory; the 8x8 private key for JaaS mode | Docker 24+ |
Self-hosted Jitsi Meet mode additionally requires a working Jitsi Meet server (see "Connection modes").
PHP 8.2 is no longer supported: PHP 8.2 only receives security fixes until 2026-12-31 (end of life). Direct installs should move to 8.3 or later (8.3 gets security fixes until 2027-12-31, 8.4 until 2028-12-31); the Docker and Release images already use 8.4 and are not affected.
# 1) Get the code
git clone https://github.com/jasoncheng7115/jt-vc-portal.git
cd jt-vc-portal
# 2) Enable the Apache module and .htaccess (Debian/Ubuntu example)
a2enmod rewrite
# Make sure the site config uses AllowOverride All and DocumentRoot points to this folder
# 3) Enable .htaccess (shipped as dot.htaccess in this project to avoid accidental activation)
cp dot.htaccess .htaccess
# 4) Create the persistent data directory (default /var/jaas-data; adjustable via DATA_DIR in config.php)
sudo mkdir -p /var/jaas-data
sudo chown www-data:www-data /var/jaas-data
# 5) (JaaS mode only) Place the 8x8 private key
sudo mkdir -p keys
sudo cp /path/to/your/private.key keys/private.key
Example Apache site config (/etc/apache2/sites-available/jt-vc-portal.conf):
<VirtualHost *:80>
ServerName vc.example.com
DocumentRoot /var/www/jt-vc-portal
<Directory /var/www/jt-vc-portal>
Options -Indexes +FollowSymLinks
AllowOverride All # Required for .htaccess to take effect
Require all granted
</Directory>
# Hide the server version
ServerTokens Prod
ServerSignature Off
ErrorLog ${APACHE_LOG_DIR}/jt-vc-portal-error.log
CustomLog ${APACHE_LOG_DIR}/jt-vc-portal-access.log combined
</VirtualHost>
Enable the site and the required modules:
a2enmod rewrite headers
a2ensite jt-vc-portal
systemctl reload apache2
For production, use
*:443with a Let's Encrypt certificate, or put a reverse proxy in front to handle HTTPS. If you forward through a reverse proxy, preserve theX-Real-IPheader (used by the fail2ban-style lockout and the audit log to get the real source IP), and setJTVC_TRUSTED_PROXIES(see "Security essentials for public deployment" below).
Key settings (config.php):
DATA_DIR: persistent data directory (default /var/jaas-data).JWT_PRIVATE_KEY_PATH: path to the JaaS RS256 private key (default keys/private.key, relative to the docroot).JTVC_ADMIN_USERNAME / JTVC_ADMIN_EMAIL / JTVC_ADMIN_PASSWORD; if not provided, a random password is generated on first start and written to DATA_DIR/INITIAL_ADMIN_PASSWORD.txt (delete it after signing in).JTVC_TRUSTED_PROXIES (comma-separated IPs/CIDRs). Once set, X-Real-IP / X-Forwarded-* are trusted only when coming from these sources, preventing source-IP spoofing to bypass the fail2ban-style lockout; leaving it empty = compatibility mode (headers are trusted blindly, must be combined with container port isolation). See "Security essentials for public deployment".JTVC_SESSION_IDLE (idle seconds, default 1800), JTVC_SESSION_ABSOLUTE (absolute seconds, default 43200).PHP hardening (recommended in php.ini or conf.d): display_errors=Off, expose_php=Off, session.cookie_httponly=1, session.cookie_samesite=Lax, session.use_strict_mode=1, session.upload_progress.enabled=Off (not used by this app; also a mitigation for the October 2026 PHP security advisory). Keep PHP on the latest patch release (8.4.27 / 8.3.36 or later).
git clone https://github.com/jasoncheng7115/jt-vc-portal.git
cd jt-vc-portal
# Build (--pull recommended to get the latest base image)
docker build --pull -t jt-vc-portal .
# Prepare persistent directories on the host (www-data UID defaults to 33)
mkdir -p /opt/jt-vc-portal/keys /opt/jt-vc-portal/data
chown 33:33 /opt/jt-vc-portal/data
# JaaS mode: place the 8x8 private key
cp /path/to/private.key /opt/jt-vc-portal/keys/private.key
# Run (-p binds to 127.0.0.1: only the local reverse proxy can reach it; the container port is not exposed directly)
docker run -d --restart unless-stopped \
-p 127.0.0.1:58189:58189 \
-e JTVC_ADMIN_EMAIL="[email protected]" \
-e JTVC_ADMIN_PASSWORD="CHANGE_ME_strong_password" \
-e JTVC_TRUSTED_PROXIES="127.0.0.1,172.16.0.0/12" \
-v /opt/jt-vc-portal/keys/:/var/www/html/keys \
-v /opt/jt-vc-portal/data/:/var/jaas-data \
--name jt-vc-portal jt-vc-portal
The container listens on :58189; it is recommended to put an nginx / Apache reverse proxy in front to add HTTPS:
server {
listen 443 ssl;
server_name your-domain.com;
location / {
proxy_pass http://127.0.0.1:58189/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# ssl_certificate / ssl_certificate_key ...
}
Security essentials for public deployment (must do) The system uses
X-Real-IP(thenX-Forwarded-For) to determine the real source IP for the fail2ban-style lockout and the audit log. To prevent attackers from forging this header to bypass the lockout:
- Set
JTVC_TRUSTED_PROXIES(comma-separated IPs/CIDRs, IPv4/IPv6 supported). Only sources on this list have theirX-Real-IP/X-Forwarded-*trusted; everything else uses the actual connection IP (REMOTE_ADDR).- Bind the container port to localhost with
-p 127.0.0.1:58189:58189, or restrict it with a firewall, so that only the reverse proxy can reach it.- Do at least one of these; doing both is recommended. If
JTVC_TRUSTED_PROXIESis left empty = compatibility mode (headers trusted blindly): this is fine when the port is isolated, but if the container port is directly reachable from outside, an attacker can forge the source IP to bypass the fail2ban-style lockout and pollute the audit log.- When behind Cloudflare, have the reverse proxy populate
X-Real-IPfromCF-Connecting-IP.
Don't add security headers on the reverse proxy: jt-vc-portal sends its own CSP,
X-Frame-Options,X-Content-Type-OptionsandReferrer-Policy; adding another set on the proxy produces two conflicting sets (browsers apply both, or take only the last one). This matters most forPermissions-Policy: meetings embed Jitsi from another domain, so if the portal's domain sends something likecamera=(self)ormicrophone=(), the embedded meeting cannot use the camera or microphone (Chrome and Edge refuse outright). If you must add it, list the Jitsi domain forcamera,microphoneanddisplay-capture, e.g.microphone=(self "https://meet.example.com"). nginx pitfall: anadd_headerat thehttplevel, or in a file auto-loaded byinclude /etc/nginx/conf.d/*.conf;, applies to every site that has noadd_headerof its own. Keep site-specific header files outsideconf.d(e.g./etc/nginx/snippets/) andincludethem only inside that site'sserver/location.
Don't want to build it yourself? Download the packaged image (linux/amd64) attached to the Release, then docker load it and run.
# 1) Download the image and checksum file from the Release page (use the latest version number)
curl -LO https://github.com/jasoncheng7115/jt-vc-portal/releases/download/v1.22.1/jt-vc-portal-1.22.1-docker-amd64.tar.gz
curl -LO https://github.com/jasoncheng7115/jt-vc-portal/releases/download/v1.22.1/jt-vc-portal-1.22.1-docker-amd64.tar.gz.sha256
# 2) Verify integrity (should print OK)
sha256sum -c jt-vc-portal-1.22.1-docker-amd64.tar.gz.sha256
# 3) Load the image (creates the jt-vc-portal:1.22.1 and :latest tags)
docker load < jt-vc-portal-1.22.1-docker-amd64.tar.gz
# 4) Prepare persistent directories on the host (www-data UID defaults to 33)
mkdir -p /opt/jt-vc-portal/keys /opt/jt-vc-portal/data
chown 33:33 /opt/jt-vc-portal/data
cp /path/to/private.key /opt/jt-vc-portal/keys/private.key # JaaS mode only
# 5) Run (same parameters as option 2)
docker run -d --restart unless-stopped \
-p 127.0.0.1:58189:58189 \
-e JTVC_ADMIN_EMAIL="[email protected]" \
-e JTVC_ADMIN_PASSWORD="CHANGE_ME_strong_password" \
-e JTVC_TRUSTED_PROXIES="127.0.0.1,172.16.0.0/12" \
-v /opt/jt-vc-portal/keys/:/var/www/html/keys \
-v /opt/jt-vc-portal/data/:/var/jaas-data \
--name jt-vc-portal jt-vc-portal:latest
The image contains only the application itself — no keys or settings; the 8x8 private key is provided at runtime through the
keys/mounted volume. Onlylinux/amd64is provided; for other architectures (e.g. arm64), build it yourself using option 2. HTTPS reverse proxy setup is the same as option 2.
All settings and data (accounts, rooms, audit log, usage, etc.) are stored in
DATA_DIR(direct install) or the mounted volume (Docker), so updates do not lose data; JSON structures are upgraded automatically and compatibly. It is still recommended to back up the data directory andkeys/before updating.
The Docker image and the Release image already contain everything — nothing to install. For a direct install, check the rows for the versions you are skipping over:
| Upgrading from | What to add |
|---|---|
| before v1.10.0 | Single sign-on (optional) needs the PHP curl and openssl extensions (Debian / Ubuntu: apt install php-curl). |
| before v1.12.0 | Transcripts and summaries (optional) need curl and a background job that runs every minute — see "Meeting transcripts and summaries". Docker installs need it too (on the host). |
| before v1.16.0 | With a self-hosted Jibri, also update jibri-recordings-api/server.py on the Jibri host (path and header hardening) and systemctl restart jibri-recordings-api. |
| before v1.16.1 | Self-hosted Jitsi Meet on stable-11031 or later: in .env set XMPP_MUC_MODULES=token_affiliation,token_lobby_bypass, add JICOFO_ENABLE_AUTH=0, and disable the old GLOBAL_CONFIG=disable_cascading_set = false, then docker compose up -d. Otherwise guests become moderators (they can record and kick people). See "Moderator permission control" in JITSI-MEET-SETUP.md. |
| before v1.16.2 | Self-hosted Jibri on stable-11031 or later: add SE_AVOID_STATS=true and SE_OFFLINE=true to the jibri service environment and recreate the containers; otherwise, when the Jibri host's outbound connection is slow, starting a recording waits a long time and then says "All recorders are currently busy". See section 7 of JIBRI-SETUP.md. |
| before v1.14.0 | Exporting meeting minutes as PDF / DOCX / ODT needs the PHP zlib extension (built into the Debian / Ubuntu PHP packages) and the bundled font lib/fonts/NotoSansTC-Regular.ttf, which comes with git pull. |
| before v1.19.0 | Direct installs: the minimum PHP version is now 8.3 (PHP 8.2 only gets security fixes until 2026-12-31). The Docker and Release images already use 8.4; nothing to do there. |
| before v1.22.1 | Direct installs: update PHP to 8.4.27 / 8.3.36 or later (security update of 2026-10-21) and set session.upload_progress.enabled=Off in php.ini. Docker installs: just rebuild the image (docker build --pull). |
| before v1.20.0 | With a self-hosted Jibri, update jibri-recordings-api/server.py on the Jibri host and run systemctl restart jibri-recordings-api so long recordings get a waveform on the transcript page (it works without the update; recordings over 60 MB just have no waveform). Decoding uses the host's ffmpeg or, if missing, the one inside the Jibri containers — nothing to install. |
Check with php -m | grep -iE 'curl|mbstring|openssl|zlib|fileinfo|json'. After upgrading, open System settings: any missing component is listed at the top, and the Transcripts card warns if the background job is not running.
cd /var/www/jt-vc-portal # your installation directory
# 1) Back up (recommended)
sudo cp -a /var/jaas-data /var/jaas-data.bak-$(date +%Y%m%d)
# 2) Pull the latest code
git pull
# 3) If .htaccess was updated, re-apply it
cp dot.htaccess .htaccess
# 4) Reload (clears opcache)
sudo systemctl reload apache2
Afterwards, sign in and confirm the version number at the top left of the topbar (next to the site name) has been updated.
cd /path/to/jt-vc-portal
# 1) Back up the data volume (recommended)
cp -a /opt/jt-vc-portal/data /opt/jt-vc-portal/data.bak-$(date +%Y%m%d)
# 2) Get the latest code and rebuild the image (--pull also updates the base image)
git pull
docker build --pull -t jt-vc-portal .
# 3) Replace the container (data / private key live in mounted volumes and are unaffected)
docker stop jt-vc-portal && docker rm jt-vc-portal
docker run -d --restart unless-stopped \
-p 127.0.0.1:58189:58189 \
-e JTVC_TRUSTED_PROXIES="127.0.0.1,172.16.0.0/12" \
-v /opt/jt-vc-portal/keys/:/var/www/html/keys \
-v /opt/jt-vc-portal/data/:/var/jaas-data \
--name jt-vc-portal jt-vc-portal
# 4) Verify
docker ps --filter name=jt-vc-portal
The initial administrator environment variables (
JTVC_ADMIN_*) are only used when the account is first created and can be omitted when updating; however,JTVC_TRUSTED_PROXIES(and the optionalJTVC_SESSION_*) take effect on every run, so pass them with everydocker run. The version number is shown at the top left of the topbar after signing in, next to the site name (click it to open this project's GitHub page); use it to confirm you are on the new version.
# 1) Back up the data volume (recommended)
cp -a /opt/jt-vc-portal/data /opt/jt-vc-portal/data.bak-$(date +%Y%m%d)
# 2) Download the new image, verify, and load it
curl -LO https://github.com/jasoncheng7115/jt-vc-portal/releases/latest/download/jt-vc-portal-<new-version>-docker-amd64.tar.gz.sha256
curl -LO https://github.com/jasoncheng7115/jt-vc-portal/releases/latest/download/jt-vc-portal-<new-version>-docker-amd64.tar.gz
sha256sum -c jt-vc-portal-<new-version>-docker-amd64.tar.gz.sha256
docker load < jt-vc-portal-<new-version>-docker-amd64.tar.gz
# 3) Replace the container (data / private key live in mounted volumes and are unaffected)
docker stop jt-vc-portal && docker rm jt-vc-portal
docker run -d --restart unless-stopped \
-p 127.0.0.1:58189:58189 \
-e JTVC_TRUSTED_PROXIES="127.0.0.1,172.16.0.0/12" \
-v /opt/jt-vc-portal/keys/:/var/www/html/keys \
-v /opt/jt-vc-portal/data/:/var/jaas-data \
--name jt-vc-portal jt-vc-portal:latest
/jt-login as the initial administrator (password: see above)..ics invitation emails.| 8x8 JaaS | Self-hosted Jitsi Meet | |
|---|---|---|
| Domain | 8x8.vc | Your Jitsi domain |
| Required | App ID, Key ID (kid), RS256 private key | Service domain; (optional) JWT app_id + HS256 secret |
| Billing | Free Dev plan (25 MAU/month); usage beyond that is billed per the 8x8 plan | Self-operated |
| Recording | Built into 8x8 (plan-dependent) | With a self-hosted Jibri (browse, play and download in this system) |
| Transcripts | 8x8's separately billed live captions (per minute, files kept only 24 hours, never stored in this system); disabled by default here to avoid surprise charges | Jibri recording + a self-hosted jt-live-whisper: a speaker-labelled transcript after the meeting, stored in this system, data never leaves your premises |
| Meeting summary | Not available | Produced by jt-live-whisper with a self-hosted language model: key points, decisions and action items, risks, topics, speaker statistics — each citing a time in the recording |
| Minutes export | Not available | PDF / DOCX / ODT / HTML, plus plain text, SRT, JSON, Markdown |
If self-hosted Jitsi Meet uses JWT, token authentication must be enabled in prosody, and app_id / app_secret must match this system.
Full self-hosted integration steps (from the official Docker-based Jitsi Meet all the way to working with this system): see JITSI-MEET-SETUP.md.
Important — mobile device access limitation (when using JWT) Once JWT authentication is enabled (always required for 8x8 JaaS; for self-hosted Jitsi Meet when
ENABLE_AUTH=1), meeting rooms only accept tokens issued by jt-vc-portal. The official Jitsi Meet mobile apps (iOS / Android) cannot join directly — they do not go through this portal, cannot obtain a token, and will be rejected. Mobile users should instead open the invite link in their mobile browser and join through this portal (the meeting is embedded, so the experience is the same). If a self-hosted deployment uses "anonymous mode without JWT" (ENABLE_AUTH=0), the mobile apps can join directly, but anyone who knows the room name can enter, which is less secure.
The default login entry point is /jt-login. In System Settings → Login page path you can change it to a secret path only you know (only letters, digits and . _ - allowed, length 1–64), reducing exposure to automated scans / brute-force attempts.
/jt-login and any unmapped path return 404 directly; only the configured path shows the login page.# Docker deployment
docker exec -u www-data jt-vc-portal php /var/www/html/login-path.php show # show the current path
docker exec -u www-data jt-vc-portal php /var/www/html/login-path.php reset # restore to /jt-login
docker exec -u www-data jt-vc-portal php /var/www/html/login-path.php set xxx # set a new path directly
# Direct install (Apache + PHP): run in the project root directory
sudo -u www-data php login-path.php reset
When recording with self-hosted Jitsi Meet + Jibri, you can run the bundled jibri-recordings-api (a pure Python standard-library service) on the Jibri host, letting the portal list / play / download / delete recordings online and display the recording host's storage capacity.
Full setup guide (JTLW key and certificate, portal settings, background job, permissions, export, troubleshooting): see TRANSCRIPTS-SETUP.md.
After a recording on the self-hosted Jibri host finishes, the portal can hand it to the speech service jt-live-whisper (JTLW), which returns a transcript with speakers and a meeting summary. Results are stored by the portal next to the recording.
Requirements
jibri-recordings-api, see above).api_revision 2.4 or later) and an API key for this portal with the scopes jobs:write, jobs:read, jobs:cancel, profiles:read.<your site>/jtlw-webhook for completion notifications. Without it the portal still works — the background worker checks progress every minute.Setup
https://10.0.0.30:8790), the API key and, for a self-signed certificate, its PEM (it is trusted as given — certificate verification is never turned off; compare the SHA-256 fingerprint shown). Choose the meeting language (set it when known — "auto" decides from roughly the first 30 seconds) and whether to produce summaries. Click Save and test connection, then Register webhook.* * * * * docker exec -u www-data jt-vc-portal php /var/www/html/transcribe-worker.php/etc/cron.d/jtvc-transcribe with * * * * * www-data php /var/www/jt-vc-portal/transcribe-worker.php (your installation directory)
It uploads recordings one at a time, follows progress, retrieves the results and removes results whose recording is gone. Only one instance runs at a time.How it works and data retention
transcripts/<recording id>/) and follow the recording: deleting a recording, or the Jibri retention policy removing it, deletes its transcript and summary too.Follows OWASP Top 10:2025, item by item:
lib/, keys/, *.json) are denied; X-Real-IP is trusted only from allow-listed reverse proxies; secrets are never shown back in the settings page; System settings lists missing components and a background job that is not running.external_api.js) is bundled at a pinned version with SRI instead of being loaded live from a third party; the PDF font is bundled (SIL OFL); the image build applies the latest OS security updates; Release images are built by CI from the tagged source and published with a sha256.none and algorithm confusion; the TLS certificate of the speech service is always verified (a self-signed certificate is trusted through its pasted PEM — verification is never turned off); webhooks are HMAC-signed; session cookies are HttpOnly, SameSite and Secure..gitignore).This project is released under the GNU General Public License v3.0 (GPL-3.0-only).
Versions up to and including v1.7.0 were published under the Apache License 2.0; starting with v1.8.0 the project is licensed under GPL-3.0.
The bundled font lib/fonts/NotoSansTC-Regular.ttf (Noto Sans TC, used for PDF export) is licensed under the SIL Open Font License 1.1 — see lib/fonts/OFL.txt.
This software is provided "as is", without warranty of any kind, express or implied. Users are solely responsible for the security and compliance of their deployment environment (including the terms and fees of third-party services such as 8x8 JaaS). The author shall not be liable for any direct or indirect damages arising from the use of this software.
8x8 has been the company developing and maintaining Jitsi / Jitsi Meet since 2018; 8x8 JaaS (Jitsi as a Service) is its official cloud-hosted Jitsi service. ↩