From 08c8dd075b5a5d67331ef45c7564d186dce08d97 Mon Sep 17 00:00:00 2001 From: CaffeineFueled Date: Sat, 20 Sep 2025 17:48:21 +0200 Subject: [PATCH] init v.0.2.1 --- Dockerfile | 28 ++++++ LICENSE | 202 +++++++++++++++++++++++++++++++++++++++++++ README.md | 73 ++++++++++++++++ main.py | 220 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 4 + 5 files changed, 527 insertions(+) create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 main.py create mode 100644 requirements.txt diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..71c1cfc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,28 @@ +FROM python:3.11-slim + +# Create non-root user +RUN useradd -m -u 1000 appuser + +# Set working directory +WORKDIR /app + +# Copy requirements first for better caching +COPY requirements.txt . + +# Install dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Copy only necessary files +COPY main.py . + +# Create uploads directory +RUN mkdir -p uploads && chown appuser:appuser uploads + +# Switch to non-root user +USER appuser + +# Expose port +EXPOSE 8000 + +# Run the application +CMD ["python", "main.py"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2b146bc --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..bdc037c --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +# Linedump + +**CLI-only text pastebin service.** + +- Status: Beta - expect minor changes +- Instance: [linedump.com](https://linedump.com/) +- Inspired by: + - [0x0.st](https://git.0x0.st/mia/0x0) + - [transfer.sh](https://github.com/dutchcoders/transfer.sh) + +**Note:** content is stored unencrypted on the server - consider it public! - Use client-side encryption example in *Usage* section. + +--- + +## Features + +**Available**: +- save and share content via CLI +- up- and download in CLI possible +- rate-limits + +**Ideas**: +- integrated retention/purge function + +**Not planned**: +- GUI *(work around possible, WIP)* +- media besides text (abuse potential and moderation effort too high - there are other projects for it available) + +--- + +## Usage + +--- + +## Installation + +Use with reverse-proxy and HTTPS! + +### Environment Variables + +| Variable | Description | Default | Required | +|----------|-------------|---------|----------| +| `DOMAIN` | Domain name used in the application responses and examples | `linedump.com` | No | +| `DESCRIPTION` | Application description displayed in the root endpoint | `(open source*) text-only CLI paste and share application.` | No | +| `MAX_FILE_SIZE_MB` | Maximum file size limit in megabytes | `50` | No | +| `RATE_LIMIT` | Rate limit for uploads (format: "requests/timeframe") | `50/hour` | No | +| `URL_PATH_LENGTH` | Length of generated URL paths (number of characters) | `6` | No | + +--- + +## Security + +For security concerns or reports, please contact via `hello a t uphillsecurity d o t com` [gpg](https://uphillsecurity.com/gpg). + +--- + +## License + +**Apache License** +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +[LICENSE FILE](/LICENSE) + +✅ Commercial use +✅ Modification +✅ Distribution +✅ Patent use +✅ Private use +✅ Limitations +❌Trademark use +❌Liability +❌Warranty diff --git a/main.py b/main.py new file mode 100644 index 0000000..b603f24 --- /dev/null +++ b/main.py @@ -0,0 +1,220 @@ +from fastapi import FastAPI, HTTPException, Request, Depends +from fastapi.responses import PlainTextResponse +from slowapi import Limiter, _rate_limit_exceeded_handler +from slowapi.util import get_remote_address +from slowapi.errors import RateLimitExceeded +import bleach +import secrets +import string +import os +from pathlib import Path +import hashlib +from typing import Optional +from collections import defaultdict +import threading + + +DOMAIN = os.getenv('DOMAIN', 'linedump.com') +DESCRIPTION = os.getenv('DESCRIPTION', '(open source*) text-only CLI paste and share application.') +MAX_FILE_SIZE_MB = int(os.getenv('MAX_FILE_SIZE_MB', '50')) +RATE_LIMIT = os.getenv('RATE_LIMIT', '50/hour') +URL_PATH_LENGTH = int(os.getenv('URL_PATH_LENGTH', '6')) + +limiter = Limiter(key_func=get_remote_address) +app = FastAPI(title="linedump", version="1.0.0") +app.state.limiter = limiter +app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) + + +UPLOAD_DIR = Path("uploads") +UPLOAD_DIR.mkdir(exist_ok=True) + +MAX_FILE_SIZE = MAX_FILE_SIZE_MB * 1024 * 1024 # Convert MB to bytes +MAX_FILES_PER_IP = 100 + +file_counter = defaultdict(int) +lock = threading.Lock() + +def generate_random_path(length: int = None) -> str: + if length is None: + length = URL_PATH_LENGTH + alphabet = string.ascii_letters + string.digits + return ''.join(secrets.choice(alphabet) for _ in range(length)) + +def get_client_ip(request: Request) -> str: + x_real_ip = request.headers.get("X-Real-IP") + if x_real_ip: + return x_real_ip.strip() + return request.client.host + + +def check_file_limit(request: Request) -> bool: + client_ip = get_client_ip(request) + with lock: + return file_counter[client_ip] < MAX_FILES_PER_IP + +def validate_content(content: str) -> bool: + """Basic validation for content size and encoding""" + if len(content) > MAX_FILE_SIZE: + return False + + # Check for null bytes (file system attacks) + if '\x00' in content: + return False + + try: + # Ensure it's valid UTF-8 + content.encode('utf-8') + return True + except UnicodeEncodeError: + return False + +@app.post("/", response_class=PlainTextResponse) +@limiter.limit(RATE_LIMIT) +async def upload_text(request: Request): + + if not check_file_limit(request): + raise HTTPException(status_code=429, detail="File limit exceeded") + + body = await request.body() + content = body.decode('utf-8', errors='ignore') + + if not validate_content(content): + raise HTTPException(status_code=400, detail="Invalid content") + + if not content.strip(): + raise HTTPException(status_code=400, detail="Empty content") + + random_path = generate_random_path() + while (UPLOAD_DIR / random_path).exists(): + random_path = generate_random_path() + + file_path = UPLOAD_DIR / random_path + + try: + with open(file_path, 'w', encoding='utf-8') as f: + f.write(content) + + client_ip = get_client_ip(request) + with lock: + file_counter[client_ip] += 1 + + base_url = f"https://{request.headers.get('host', request.url.netloc)}" + return f"{base_url}/{random_path}\n" + + except Exception as e: + raise HTTPException(status_code=500, detail="Failed to save file") + +@app.get("/{file_path}", response_class=PlainTextResponse) +async def get_file(file_path: str): + if not file_path.isalnum(): + raise HTTPException(status_code=404, detail="File not found") + + file_location = UPLOAD_DIR / file_path + + if not file_location.exists() or not file_location.is_file(): + raise HTTPException(status_code=404, detail="File not found") + + try: + with open(file_location, 'r', encoding='utf-8') as f: + content = f.read() + return content + except Exception: + raise HTTPException(status_code=500, detail="Failed to read file") + +@app.get("/", response_class=PlainTextResponse) +async def root(): + return f"""C|_| {DOMAIN} + + ████ General ████ + +{DESCRIPTION} + +- File limit: {MAX_FILE_SIZE_MB} MB +- Rate limit: {RATE_LIMIT} +- text-only +- no server-side encryption, consider content public or use client-side encryption + + +████ Usage ████ + + + █ Upload curl: + +curl -X POST -d "Cheers" https://{DOMAIN}/ # string +curl -X POST https://{DOMAIN} --data-binary @- < file.txt # file +ip -br a | curl -X POST https://{DOMAIN} --data-binary @- # command output + + + █ Upload wget: + +echo "Cheers" | wget --post-data=@- -O- https://{DOMAIN}/ # string +wget --post-file=file.txt -O- https://{DOMAIN}/ # file +ip -br a | wget --post-data=@- -O- https://{DOMAIN}/ # command output + + + █ Upload Powershell: + +Invoke-RestMethod -Uri "https://{DOMAIN}/" -Method Post -Body "Cheers" # string +Invoke-RestMethod -Uri "https://{DOMAIN}/" -Method Post -InFile "file.txt" # file +ipconfig | Invoke-RestMethod -Uri "https://{DOMAIN}/" -Method Post -Body {{ $_ }} # command output + + + █ Download: + +curl https://{DOMAIN}/{{path}} + +wget -O- https://{DOMAIN}/{{path}} + +Invoke-RestMethod -Uri "https://{DOMAIN}/{{path}}" + + + +██ Encryption Examples with curl ██ + + + █ Upload text: + +echo 'Cheers' \ + | openssl enc -aes-256-cbc -pbkdf2 -base64 -pass pass:yourkey \ + | curl -X POST -d @- https://{DOMAIN}/ + + █ Upload file: + +openssl enc -aes-256-cbc -pbkdf2 -salt -pass pass:yourkey -base64 < YOURFILE.txt \ + | curl -sS -X POST https://{DOMAIN} --data-binary @- + + █ Upload command output: + +ip -br a \ + | openssl enc -aes-256-cbc -pbkdf2 -salt -pass pass:yourkey -base64 \ + | curl -sS -X POST https://{DOMAIN} --data-binary @- + + █ Download: + +curl -s https://{DOMAIN}/PASTE_THE_ID \ + | base64 -d \ + | openssl enc -d -aes-256-cbc -pbkdf2 -pass pass:yourkey + + + +██ Adv Examples ██ + + + █ Multiple Commands + +{{ cmd() {{ printf "\\n# %s\\n" "$*"; "$@"; }}; \\ + cmd hostname; \\ + cmd ip -br a; \\ + }} 2>&1 | curl -X POST https://{DOMAIN} --data-binary @- + + +████ Further Information ████ + + +More information will follow. Work in Progress. +""" + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ec1d8c8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +fastapi==0.104.1 +uvicorn[standard]==0.24.0 +slowapi==0.1.9 +bleach==6.1.0 \ No newline at end of file