TweetX-web is distributed as source, not as a released package. Security fixes
land on main; there are no maintained release branches. Pull the latest
main.
Please do not open a public issue for a security problem.
Report it privately through GitHub Security Advisories, or by email to irajanbhattarai@gmail.com.
Please include what you can: what the issue is, how to reproduce it, and what an attacker could achieve. You will get an acknowledgement within a few days. This is a personal project maintained in spare time, so please allow reasonable time for a fix before disclosing publicly.
TweetX-web was written as a single-user dashboard on localhost. It is honest
about that, but the consequences are easy to miss, so they are spelled out here.
None of the following is a vulnerability report — these are documented
properties of the tool. If you deploy it to a public address without
addressing them, the exposure is yours.
Authentication is a single hardcoded user. POST /login compares
params[:username] and params[:password] against the TWEETX_USER and
TWEETX_PASS environment variables with ==, in plaintext. There is no
hashing, no rate limiting, no lockout, and no second factor. An attacker can
guess passwords as fast as your server will answer.
The login form ships with demo credentials prefilled. views/login.erb
prefills admin / password as a convenience for trying the app locally.
Remove that before deploying anywhere reachable, and obviously do not use
those values.
SESSION_SECRET signs the session cookie. Set it to a long random value —
ruby -rsecurerandom -e 'puts SecureRandom.hex(64)'. Rack requires at least 64
characters. If it is empty, guessable, or shared across deployments, session
cookies can be forged.
"Remember me" sets a 30-day cookie. It is httponly but not secure, so it
will travel over plain HTTP if you serve the app over plain HTTP. Terminate TLS
in front of it.
There is no CSRF protection. Every state-changing route is a plain form or
fetch POST, and deletion happens over GET /delete/:id. A logged-in user
visiting a hostile page can be made to delete or edit tweets.
The datastore is plain CSV, committed to git. Everything in data/ is
public in a public repo, and every mutation rewrites the whole file with no
locking. Concurrent writers can lose rows.
The X API needs four OAuth 1.0a values (X_API_KEY, X_API_KEY_SECRET,
X_ACCESS_TOKEN, X_ACCESS_TOKEN_SECRET), read from the environment via
.env, which is gitignored. .env.sample is the template and contains no real
values. If you fork this repo, check that you have not committed a real .env.
POST /tweet-now/:id is deliberately a simulation in this repo. It returns
simulated: true, calls no API, and writes nothing. It previously reported
success with a link to an unrelated real tweet, which was misleading; that is
fixed, and there are specs holding it to it.
If you wire it up to post for real — the route comment names the exact change — then everything above about authentication and CSRF applies to an endpoint that publishes to a live account. Please think about that first.