Substitution Cipher Based on The Voynich Manuscript

Post Syndicated from Bruce Schneier original https://www.schneier.com/blog/archives/2025/12/substitution-cipher-based-on-the-voynich-manuscript.html

Here’s a fun paper: “The Naibbe cipher: a substitution cipher that encrypts Latin and Italian as Voynich Manuscript-like ciphertext“:

Abstract: In this article, I investigate the hypothesis that the Voynich Manuscript (MS 408, Yale University Beinecke Library) is compatible with being a ciphertext by attempting to develop a historically plausible cipher that can replicate the manuscript’s unusual properties. The resulting cipher­a verbose homophonic substitution cipher I call the Naibbe cipher­can be done entirely by hand with 15th-century materials, and when it encrypts a wide range of Latin and Italian plaintexts, the resulting ciphertexts remain fully decipherable and also reliably reproduce many key statistical properties of the Voynich Manuscript at once. My results suggest that the so-called “ciphertext hypothesis” for the Voynich Manuscript remains viable, while also placing constraints on plausible substitution cipher structures.

[$] An open seat on the TAB

Post Syndicated from corbet original https://lwn.net/Articles/1049035/

As has been recently announced,
nominations are open for the 2025 Linux Foundation Technical Advisory Board
(TAB) elections. I am one of the TAB members whose term is coming to an
end, but I have decided that, after 18 years on the board, I will not
be seeking re-election; instead, I will step aside and make room for a
fresh voice. My time on the TAB has been rewarding, and I will be sad to
leave; the TAB has an important role to play in the functioning of the
kernel community.

Python Workers redux: fast cold starts, packages, and a uv-first workflow

Post Syndicated from Dominik Picheta original https://blog.cloudflare.com/python-workers-advancements/

Last year we announced basic support for Python Workers, allowing Python developers to ship Python to region: Earth in a single command and take advantage of the Workers platform.

Since then, we’ve been hard at work making the Python experience on Workers feel great. We’ve focused on bringing package support to the platform, a reality that’s now here — with exceptionally fast cold starts and a Python-native developer experience.

This means a change in how packages are incorporated into a Python Worker. Instead of offering a limited set of built-in packages, we now support any package supported by Pyodide, the WebAssembly runtime powering Python Workers. This includes all pure Python packages, as well as many packages that rely on dynamic libraries. We also built tooling around uv to make package installation easy.

We’ve also implemented dedicated memory snapshots to reduce cold start times. These snapshots result in serious speed improvements over other serverless Python vendors. In cold start tests using common packages, Cloudflare Workers start over 2.4x faster than AWS Lambda and 3x faster than Google Cloud Run.

In this blog post, we’ll explain what makes Python Workers unique and share some of the technical details of how we’ve achieved the wins described above. But first, for those who may not be familiar with Workers or serverless platforms – and especially those coming from a Python background — let us share why you might want to use Workers at all.

Deploying Python globally in 2 minutes

Part of the magic of Workers is simple code and easy global deployments. Let’s start by showing how you can deploy a FastAPI app across the world with fast cold starts in less than two minutes.

A simple Worker using FastAPI can be implemented in a handful of lines:

from fastapi import FastAPI
from workers import WorkerEntrypoint
import asgi

app = FastAPI()

@app.get("/")
async def root():
   return {"message": "This is FastAPI on Workers"}

class Default(WorkerEntrypoint):
   async def fetch(self, request):
       return await asgi.fetch(app, request.js_object, self.env)

To deploy something similar, just make sure you have uv and npm installed, then run the following:

$ uv tool install workers-py
$ pywrangler init --template \
    https://github.com/cloudflare/python-workers-examples/03-fastapi
$ pywrangler deploy

With just a little code and a pywrangler deploy, you’ve now deployed your application across Cloudflare’s edge network that extends to 330 locations across 125 countries. No worrying about infrastructure or scaling.

And for many use cases, Python Workers are completely free. Our free tier offers 100,000 requests per day and 10ms CPU time per invocation. For more information, check out the pricing page in our documentation.

For more examples, check out the repo in GitHub. And read on to find out more about Python Workers.

So what can you do with Python Workers?

Now that you’ve got a Worker, just about anything is possible. You write the code, so you get to decide. Your Python Worker receives HTTP requests and can make requests to any server on the public Internet.

You can set up cron triggers, so your Worker runs on a regular schedule. Plus, if you have more complex requirements, you can make use of Workflows for Python Workers, or even long-running WebSocket servers and clients using Durable Objects.

Here are more examples of the sorts of things you can do using Python Workers:

Faster package cold starts than Lambda and Cloud Run

Serverless platforms like Workers save you money by only running your code when it’s necessary to do so. This means that if your Worker isn’t receiving requests, it may be shut down and will need to be restarted once a new request comes in. This typically incurs a resource overhead we refer to as the “cold start.” It’s important to keep these as short as possible to minimize latency for end users.

In standard Python, booting the runtime is expensive, and our initial implementation of Python Workers focused on making the runtime boot fast. However, we quickly realized that this wasn’t enough. Even if the Python runtime boots quickly, in real-world scenarios the initial startup usually includes loading modules from packages, and unfortunately, in Python many popular packages can take several seconds to load.

We set out to make cold starts fast, regardless of whether packages were loaded.

To measure realistic cold start performance, we set up a benchmark that imports common packages, as well as a benchmark running a “hello world” using a bare Python runtime. While Lambda is able to start just the runtime quickly, once you need to import packages, the cold start times shoot up.

Here are the average cold start times when loading three common packages (httpx, fastapi and pydantic):

Platform

Mean Cold Start (secs)

Cloudflare Python Workers

1.027

AWS Lambda

2.502

Google Cloud Run

3.069

In this case, Cloudflare Python Workers have 2.4x faster cold starts than AWS Lambda and 3x faster cold starts than Google Cloud Run. We achieved these low cold start numbers by using memory snapshots, and in a later section we explain how we did so.

We are regularly running these benchmarks. Go here for up-to-date data and more info on our testing methodology.

We’re architecturally different from these other platforms — namely, Workers is isolate-based. Because of that, our aims are high, and we are planning for a zero cold start future.

Package tooling integrated with uv

The diverse package ecosystem is a large part of what makes Python so amazing. That’s why we’ve been hard at work ensuring that using packages in Workers is as easy as possible.

We realised that working with the existing Python tooling is the best path towards a great development experience. So we picked the uv package and project manager, as it’s fast, mature, and gaining momentum in the Python ecosystem.

We built our own tooling around uv called pywrangler. This tool essentially performs the following actions:

  • Reads your Worker’s pyproject.toml file to determine the dependencies specified in it

  • Includes your dependencies in a python_modules folder that lives in your Worker

Pywrangler calls out to uv to install the dependencies in a way that is compatible with Python Workers, and calls out to wrangler when developing locally or deploying Workers. 

Effectively this means that you just need to run pywrangler dev and pywrangler deploy to test your Worker locally and deploy it. 

Type hints

You can generate type hints for all of the bindings defined in your wrangler config using pywrangler types. These type hints will work with Pylance or with recent versions of mypy.

To generate the types, we use wrangler types to create typescript type hints, then we use the typescript compiler to generate an abstract syntax tree for the types. Finally, we use the TypeScript hints — such as whether a JS object has an iterator field — to generate mypy type hints that work with the Pyodide foreign function interface.

Decreasing cold start duration using snapshots

Python startup is generally quite slow and importing a Python module can trigger a large amount of work. We avoid running Python startup during a cold start using memory snapshots.

When a Worker is deployed, we execute the Worker’s top-level scope and then take a memory snapshot and store it alongside your Worker. Whenever we are starting a new isolate for the Worker, we restore the memory snapshot and the Worker is ready to handle requests, with no need to execute any Python code in preparation. This improves cold start times considerably. For instance, starting a Worker that imports fastapi, httpx and pydantic without snapshots takes around 10 seconds. With snapshots, it takes 1 second.

The fact that Pyodide is built on WebAssembly enables this. We can easily capture the full linear memory of the runtime and restore it. 

Memory snapshots and Entropy

WebAssembly runtimes do not require features like address space layout randomization for security, so most of the difficulties with memory snapshots on a modern operating system do not arise. Just like with native memory snapshots, we still have to carefully handle entropy at startup to avoid using the XKCD random number generator (we’re very into actual randomness).


By snapshotting memory, we might inadvertently lock in a seed value for randomness. In this case, future calls for “random” numbers would consistently return the same sequence of values across many requests.

Avoiding this is particularly challenging because Python uses a lot of entropy at startup. These include the libc functions getentropy() and getrandom() and also reading from /dev/random and /dev/urandom. All of these functions share the same implementation in terms of the JavaScript crypto.getRandomValues() function.

In Cloudflare Workers, crypto.getRandomValues() has always been disabled at startup in order to allow us to switch to using memory snapshots in the future. Unfortunately, the Python interpreter cannot bootstrap without calling this function. And many packages also require entropy at startup time. There are essentially two purposes for this entropy:

  • Hash seeds for hash randomization

  • Seeds for pseudorandom number generators

Hash randomization we do at startup time and accept the cost that each specific Worker has a fixed hash seed. Python has no mechanism to allow replacing the hash seed after startup.

For pseudorandom number generators (PRNG), we take the following approach:

At deploy time:

  1. Seed the PRNG with a fixed “poison seed”, then record the PRNG state.

  2. Replace all APIs that call into the PRNG with an overlay that fails the deployment with a user error.

  3. Execute the top level scope of user code.

  4. Capture the snapshot.

At run time:

  1. Assert that the PRNG state is unchanged. If it changed, we forgot the overlay for some method. Fail the deployment with an internal error.

  2. After restoring the snapshot, reseed the random number generator before executing any handlers.

With this, we can ensure that PRNGs can be used while the Worker is running, but stop Workers from using them during initialization and pre-snapshot.

Memory snapshots and WebAssembly state

An additional difficulty arises when creating memory snapshots on WebAssembly: The memory snapshot we are saving consists only of the WebAssembly linear memory, but the full state of the Pyodide WebAssembly instance is not contained in the linear memory. 

There are two tables outside of this memory.

One table holds the values of function pointers. Traditional computers use a “Von Neumann” architecture, which means that code exists in the same memory space as data, so that calling a function pointer is a jump to some memory address. WebAssembly has a “Harvard architecture” where code lives in a separate address space. This is key to most of the security guarantees of WebAssembly and in particular why WebAssembly does not need address space layout randomization. A function pointer in WebAssembly is an index into the function pointer table.

A second table holds all JavaScript objects referenced from Python. JavaScript objects cannot be directly stored into memory because the JavaScript virtual machine forbids directly obtaining a pointer to a JavaScript object. Instead, they are stored into a table and represented in WebAssembly as an index into the table.

We need to ensure that both of these tables are in exactly the same state after we restore a snapshot as they were when we captured the snapshot.

The function pointer table is always in the same state when the WebAssembly instance is initialized and is updated by the dynamic loader when we load dynamic libraries — native Python packages like numpy. 

To handle dynamic loading:

  1. When taking the snapshot, we patch the loader to record the load order of dynamic libraries, the address in memory where the metadata for each library is allocated, and the function pointer table base address for relocations. 

  2. When restoring the snapshot, we reload the dynamic libraries in the same order, and we use a patched memory allocator to place the metadata in the same locations. We assert that the current size of the function pointer table matches the function pointer table base we recorded for the dynamic library.

All of this ensures that each function pointer has the same meaning after we’ve restored the snapshot as it had when we took the snapshot.

To handle the JavaScript references, we implemented a fairly limited system. If a JavaScript object is accessible from globalThis by a series of property accesses, we record those property accesses and replay them when restoring the snapshot. If any reference exists to a JavaScript object that is not accessible in this way, we fail deployment of the Worker. This is good enough to deal with all the existing Python packages with Pyodide support, which do top level imports like:

from js import fetch

Reducing cold start frequency using sharding

Another important characteristic of our performance strategy for Python Workers is sharding. There is a very detailed description of what went into its implementation here. In short, we now route requests to existing Worker instances, whereas before we might have chosen to start a new instance.

Sharding was actually enabled for Python Workers first and proved to be a great test bed for it. A cold start is far more expensive in Python than in JavaScript, so ensuring requests are routed to an already-running isolate is especially important.

Where do we go from here?

This is just the start. We have many plans to make Python Workers better:

  • More developer-friendly tooling

  • Even faster cold starts by utilising our isolate architecture

  • Support for more packages

  • Support for native TCP sockets, native WebSockets, and more bindings

To learn more about Python Workers, check out the documentation available here. To get help, be sure to join our Discord.

Intel Xeon 6 SoC Edge AI Demo with Dell PowerEdge XR8720t at OCP 2025

Post Syndicated from John Lee original https://www.servethehome.com/intel-xeon-6-soc-edge-ai-demo-with-dell-poweredge-xr8720t-at-ocp-2025/

At OCP 2025, we saw the Dell PowerEdge XR8720t running edge AI analytics, the Xeon 6 SoC, and systems with the 8x 25GbE Intel E830 NIC

The post Intel Xeon 6 SoC Edge AI Demo with Dell PowerEdge XR8720t at OCP 2025 appeared first on ServeTheHome.

Седмицата (1–6 декември)

Post Syndicated from Надежда Радулова original https://www.toest.bg/sedmitsata-1-6-dekemvri/

Седмицата (1–6 декември)

Първият протест (тогава митинг), в който участвах, беше на 3 декември 1989 г. на колодрума на остров Свобода в Пазарджик, двайсет и три дни след паметния 10 ноември. Бях на 14 години и отидох на митинга заедно с голяма част от класа ми; заведе ни класната ни ръководителка Евелина Трангозова, тогава двайсет и четири годишна учителка по английски и един от първите членове на току-що възстановената Радикалдемократическа партия в града. За мнозина това да заведеш учениците си осмокласници на митинг изглеждаше дръзко и неприемливо политизиране (вероятно и сега би изглеждало така), но аз и до днес съм благодарна на нашата Мис (така наричахме класната), че ме направи съзнателна свидетелка на епохален момент, който щеше да промени пътя на абсолютно всички.

Когато преди няколко дни, на 1 декември, гледах младите хора, изпълнили центъра на София, си помислих, че и сега, както и преди 36 години, исканията на протестиращите се свеждат всъщност до едно и също – свобода (в цялата условност на разбирането ѝ), справедливост, зачитане на правата на всеки… Само дето като че ли някога мечтаехме за бъдеще, в което да напуснем дома си и да видим света, а сега децата-на-света се борят за бъдеще тук, в застрашения си дом. Но и през 1989-та, както и през 2025-та, провокаторите са на линия, спекулациите избиват на повърхността като мръсна пяна и все се намира някоя и друга политическа мутра, която да яхне и осребри чуждия гняв, да си изплете кошницата и да свърши някоя и друга мерзопакост.

Какъв е тогава смисълът от протести, ще попитате, при положение че първо пораснахме, а после остаряхме по площадите с все същите лозунги, мечти и горести. Дали не се оказва, че през цялото време сме протестирали за непостижими неща? И има ли изобщо смисъл да се протестира за непостижимото?

Наскоро ми попадна статия за една от големите екоактивистки от края на ХХ век Джулия Бътърфлай Хил (р. 1974), която между 1997 и 1999 г. живее в продължение на 738 дни на върха на 60-метрова хилядолетна секвоя на име Луна, за да предотврати планираното ѝ отсичане. След две години в обятията на Луна Джулия подписва споразумение с Pacific Lumber Company за запазването на дървото и слиза на земята.

Да, Джулия Хил е късметлийка – защото не всички цели могат да бъдат постигнати в рамките на един човешки живот. И понякога се изискват непомерни усилия и търпение, и вяра – дано младите навреме осъзнаят това: че битката с вятърните мелници, освен да донесе горчивина за сражаващите се, ще задвижи и пренесе енергия и за идващите след тях. И че най-суровата, но и най-важната битка е за онова, което няма да се случи днес и утре, и може би никога, докато сме живи, но ще продължи да има смисъл дълго след нас.

Какви следи оставяме? С действията и бездействията си. С думите и с премълчаванията. Така бихме могли да обобщим и темата на броя ни тази седмица.

От една страна са градовете, които осъзнато и безсъзнателно обитаваме. Да вземем например втория по големина град – Пловдив. Какво се случва с него шест години след като беше Европейска столица на културата с всичките там интелектуални и финансови инвестиции във въпросната титла. Георги Велев анализира днешната ситуация според постигнатото и пропиляното в „Шест години по-късно. Изгуби ли културата в Пловдив своята посока?“.

Оставаме на урбанистична тема със Силвина Фурнаджиева от „Екипът на София“, която в статията „От „на парче“ към ясни цели. Как общината да работи ефективно?“ демонстрира стъпка по стъпка начина един град да се управлява чрез дългосрочна визия.

От темата за градовете минаваме към управлението на „полиса“ и държавата. Какво се случва със съвременната демокрация; пред какви избори и възможности е изправена; струва ли се да се борим за нея, когато средната класа е застрашена, а политическите свободи и правата на гражданите са противопоставени на икономическото им благосъстояние; не губи ли тягата си изобщо идеята за демокрация при нарастващия риск от разрив между САЩ и Европа? Повече по тези въпроси четете в есето на Искрен Иванов „(Не)демократичният световен ред“.

В основата на съвременната ни представа за демокрация неизменно са стояли свободата на словото и наличието на независими медии. В епохата на постистината, фалшивите новини, дезинформацията и пропагандата именно тази част от основата на демокрацията е сериозно разклатена. В анализа си „Има ли надежда за надеждните новини?“ Светла Енчева обръща внимание на нуждата демокрацията да бъде защитена именно чрез медийната свобода, както и на рисковете, които носи капсулирането или изчезването на качествено медийно съдържание. Следващата събота, 13 декември, Светла ще гостува на Владислав Севов във видеопоредицата ни „Тоест разговаряме“. Гледайте я на живо от 16:00 ч. в YouTube Live. Може предварително да ѝ зададете въпрос и да довършите изречението в нашата анкета.

За разлика от Светла, на Е.Т. не може да ѝ задавате въпроси, защото тя вече ви е дала всички отговори в новия епизод, „в който мразим всички“ и „айсиктирдействително!“.

В Sic transit gloria Boyki Емилия Милчева се връща към протеста от началото на седмицата и разнищва брауновото движение вследствие на създалото се в триъгълника на властта напрежение: най-вече хаотичните танцови стъпки на Борисов, който непрестанно настъпва опърпания шлейф на партията си и се препъва в него; и опитите на (п)резидента да се превърне в бенефициер на случващото се по площадите.

За това какви следи ще оставим след себе си и на каква цена, говори и Зорница Христова в последната си за годината колонка „По буквите“, в която на фокус са три книги – на Йорданка Белева, Роб Дън и Джон Бърджър. Апропо заглавието на Роб Дън е „Естествена история на бъдещето“ – чудесен реторически пример за това как миналото не е приключило, а винаги дебне зад ъгъла, определя бъдещите ни ходове, неизбежно предстои.

Завършваме в светлата следа на музиката с интервю на Ина Иванова с хоровата диригентка Яна Делирадева. „Този живот може да се изпее“ – казва Яна с убеденост, която ни е особено необходима днес, когато за пореден път пълним площадите, обединени повече от гняв, отколкото от надежда.

А ако искате да го пеем този живот заедно, подкрепете ни! Вие сте тези, които дават сила и смисъл на гласа ни.

Friday Squid Blogging: Vampire Squid Genome

Post Syndicated from Bruce Schneier original https://www.schneier.com/blog/archives/2025/12/friday-squid-blogging-vampire-squid-genome.html

The vampire squid (Vampyroteuthis infernalis) has the largest cephalopod genome ever sequenced: more than 11 billion base pairs. That’s more than twice as large as the biggest squid genomes.

It’s technically not a squid: “The vampire squid is a fascinating twig tenaciously hanging onto the cephalopod family tree. It’s neither a squid nor an octopus (nor a vampire), but rather the last, lone remnant of an ancient lineage whose other members have long since vanished.”

As usual, you can also use this squid post to talk about the security stories in the news that I haven’t covered.

Blog moderation policy.

Metasploit Wrap-Up 12/05/2025

Post Syndicated from Jack Heysel original https://www.rapid7.com/blog/post/pt-metasploit-wrap-up-12-05-2025

Twonky Auth Bypass, RCEs and RISC-V Reverse Shell Payloads

This was another fantastic week in terms of PR contribution to the Metasploit Framework. Rapid7’s very own Ryan Emmons recently disclosed CVE-2025-13315 and CVE-2025-13316 which exist in Twonky Server and allow decrypting admin credentials by reading logs without authentication (which contain them). The auxiliary module Ryan submitted which exploits both of these CVEs was released this week. Community contributor Valentin Lobsein aka Chocapikk has returned to the PR queue with a welcomed vengeance. Two modules from Chocapikk were landed this week, a Monsta FTP downloadFile Remote Code Execution module along with a WordPress AI Engine Plugin MCP Unauthenticated Admin Creation to RCE. In addition to some awesome module content, community contributor bcoles added Linux RISC-V 32-bit/64-bit TCP reverse shell payloads.

New module content (5)

Twonky Server Log Leak Authentication Bypass

Author: remmons-r7

Type: Auxiliary

Pull request: #20709 contributed by remmons-r7 

Path: gather/twonky_authbypass_logleak 

AttackerKB reference: CVE-2025-13316

Description: This module exploits two CVEs: CVE-2025-13315 and CVE-2025-13316. Both CVEs exist in Twonky Server and allow decrypting admin credentials by reading logs without authentication (which contain them). Then, because the module uses hardcoded keys, it decrypts those credentials.

Monsta FTP downloadFile Remote Code Execution

Authors: Valentin Lobstein [email protected], msutovsky-r7, and watchTowr Labs

Type: Exploit

Pull request: #20718 contributed by Chocapikk 

Path: multi/http/monsta_ftp_downloadfile_rce 

AttackerKB reference: CVE-2025-34299

Description: This add module for CVE-2025-34299. The module exploits a vulnerability in the downloadFile action which allows an attacker to connect to a malicious FTP server and download arbitrary files to arbitrary locations on the Monsta FTP server.

WordPress AI Engine Plugin MCP Unauthenticated Admin Creation to RCE

Authors: Emiliano Versini, Khaled Alenazi (Nxploited), Valentin Lobstein [email protected], and dledda-r7

Type: Exploit

Pull request: #20720 contributed by Chocapikk 

Path: multi/http/wp_ai_engine_mcp_rce 

AttackerKB reference: CVE-2025-11749

Description: This adds a new exploit module for an unauthenticated vulnerability in the WordPress AI Engine plugin, which has over 100,000 active installations. The vulnerability allows an attacker to create an administrator account via the MCP (Model Context Protocol) endpoint without authentication, then upload and execute a malicious plugin to achieve remote code execution. The vulnerability is being tracked as CVE-2025-11749.

Linux Command Shell, Reverse TCP Inline

Authors: bcoles [email protected] and modexp

Type: Payload (Single)

Pull request: #20712 contributed by bcoles 

Path: linux/riscv32le/shell_reverse_tcp

Description: This adds Linux RISC-V 32-bit/64-bit TCP reverse shell payloads.

Linux Command Shell, Reverse TCP Inline

Authors: bcoles [email protected] and modexp

Type: Payload (Single)

Pull request: #20712 contributed by bcoles 

Path: linux/riscv64le/shell_reverse_tcp

Description: This adds Linux RISC-V 32-bit/64-bit TCP reverse shell payloads.

Enhancements and features (3)

  • #20658 from jheysel-r7 – This adds a number of accuracy enhancements to the ldap_esc_vulnerable_cert_finder module. It also adds a CertificateAuthorityRhost datastore option to the esc_update_ldap_object module so the operator can specify an IP Address explicitly in cases where the hostname cannot be resolved via DNS.
  • #20677 from zeroSteiner – This enables sessions to MSSQL servers that require encryption. These changes add a new MsTds::Channel which leverages Rex’s socket abstraction to facilitate the necessary encapsulation for the TLS negotiation.
  • #20741 from SaiSakthidar – This removes CAIN as an output format for collected hashes.

Documentation

You can find the latest Metasploit documentation on our docsite at docs.metasploit.com.

Get it

As always, you can update to the latest Metasploit Framework with msfupdate and you can get more details on the changes since the last blog post from GitHub:

If you are a git user, you can clone the Metasploit Framework repo (master branch) for the latest. To install fresh without using git, you can use the open-source-only Nightly Installers or the commercial edition Metasploit Pro

Ubiquiti USP-PDU-PRO Power Distribution Pro Mini-Review

Post Syndicated from Ryan Smith original https://www.servethehome.com/ubiquiti-usp-pdu-pro-power-distribution-pro-mini-review/

Ubiquiti’s USP-PDU-PRO is a high-end, 2U PDU with 16 outlets plus four USB-C ports for power, as well as network capabilities for individual port monitoring and power cycling

The post Ubiquiti USP-PDU-PRO Power Distribution Pro Mini-Review appeared first on ServeTheHome.

The collective thoughts of the interwebz