dumbtv
dumbtv가 ADB로 Fire TV에서 광고·추적·업데이트 알림을 제거, 루트·앱 변경 없이, 무료 오픈소스
공격자가 챗봇을 어떻게 오용할 수 있는지 확인. 오픈소스·Apache 2.0, 로컬 혹은 CI에서 자체 배포에 적용
Find out what an attacker can make your chatbot do. Open source, Apache 2.0, runs on your machine or in your CI against your own deployment.
보안Python · HTMLGitHub @qatration온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Adversarial testing for AI chatbots and agents. Runs on your own machine or CI, against your own deployment.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Do not point this at a system you do not own.
It sends real attacks — prompt injection, data exfiltration, tool abuse — at whatever URL you
give it. Use it on your own deployment, or on one whose owner has given you written permission
in advance. Not a public chatbot you find interesting. Not a vendor's demo. Not to
"just check" somebody else's product.
AUTHORISED-USE.md says what that means in practice, and what to do if you
find something in a system that is not yours.
QA + [pene]tration — adversarial testing for LLM features and agents.README 전문 보기 ↗[!CAUTION] Do not point this at a system you do not own. It sends real attacks — prompt injection, data exfiltration, tool abuse — at whatever URL you give it. Use it on your own deployment, or on one whose owner has given you written permission in advance. Not a public chatbot you find interesting. Not a vendor's demo. Not to "just check" somebody else's product. AUTHORISED-USE.md says what that means in practice, and what to do if you find something in a system that is not yours.
QA + [pene]tration — adversarial testing for LLM features and agents.
qatration.com · what it catches · the evidence
Point it at a bot, fire a library of attacks (prompt injection, system-prompt leak, agent tool-abuse, SQL-injection-via-agent), and get an objective verdict per attack, because the oracle checks planted canaries and tool-call arguments rather than vibes. Built to answer the question no classic test suite can: our AI feature shipped — what can it be talked into doing?
Apache 2.0. Run it yourself. The engine is meant to run where the system under test is: on
your machine, in your CI, against your own deployment. Nothing here needs a service, and out/
ships with the repository, so the numbers in these pages can be checked against the evidence
rather than believed, and test_readme.py recounts the ones it lists and fails when a claim and
the code disagree.
The oracle for security is objective and the same for every target — a leaked secret is a string, a rogue tool call is a log line — unlike quality, which is subjective and has no oracle anyone can check. QAtration is built on that seam.
Three things follow from it, and they are what this project has instead of a bigger attack count:
pip install qatration
qatration init --url https://your-bot.example.com/chat --model your-model-id
# writes mybot.yaml, canary and all
qatration onboard --target-config mybot.yaml # one ordinary request (and the canary check): is the mapping right
qatration benign --target-config mybot.yaml # what fires when NOBODY is attacking
qatration run --target-config mybot.yaml # sweep it
PyYAML and pyfiglet, nothing else. The model frameworks belong to the practice bots in this
repository rather than to the engine, and install separately: pip install "qatration[fleet]"
covers LangChain, and the two bots that need smolagents and nemoguardrails each want
their own environment because those two cannot share one — see the note in
pyproject.toml. Evidence goes to ./qatration-out unless $QATRATION_OUT
says otherwise -- except in a checkout of this repository (including pip install -e .), where
it goes to the tracked out/: point QATRATION_OUT somewhere else for your own bot.
init writes the config so you do not have to invent one, and it mints the canary for you
rather than leaving it as a step to remember. qatration mint still exists on its own, for a
config you already have. Give it --system-prompt-file prompt.txt and the config also carries
your prompt as deployed, which the checks that compare a reply with your instructions need.
--shape responses writes one for OpenAI's Responses API with the conversation its
Conversations API keeps. --shape vercel writes the config for a Vercel AI SDK route that useChat talks to: its
request, its streamed reply, and the conversation in its parts shape. --shape dify,
flowise, langflow, gradio and rasa write one for an app built on those,
--shape directline one for a Copilot Studio agent, --shape botpress one for a Botpress bot,
--shape chainlit one for a Chainlit app and --shape websocket one for a chat whose
backend is a WebSocket, with the conversation the
app keeps under an id that is fresh for every trial. A streamed reply is
read as the reply it streams, from an OpenAI-compatible endpoint, Anthropic, the Responses
API, Gemini, Ollama, Dify, Flowise, Langflow or the Vercel AI SDK, by the config written for the same endpoint
unstreamed; a stream of any other protocol is said to be one, with the flag that cures it.
And the canary is not tidiness. It is worth exactly the fact that nothing
else in the world knows it. The example configs ship one so they run out of the box, and that
value is published here — it can be trained on, blocklisted, or matched by a guardrail that
knows nothing about the deployment behind it. A target that fails to leak a published string
has shown only that it recognises a famous string. qatration run refuses a config still
carrying one, before sending anything, rather than handing you a clean report that measured
nothing.
The pair it prints is two tokens, not one. The secret is what the attacks hunt for; the second is public by construction, so asking for it is an ordinary question that proves the snippet actually landed. An unplanted canary is invisible — every detector finds nothing and every attack comes back DEFENDED — and no instruction in a README prevents somebody skipping the step, so the sweep checks for itself.
init writes mybot.yaml for you, and qatration onboard tells you what is missing from it
rather than making you guess twice. Four configs are ready to copy. After pip install they sit
inside the package, in the folder python -c "import qatration, os; print(os.path.dirname(qatration.__file__))"
prints:
targets_openai_compatible.yaml — OpenAI, and
everything that copied its shape: Groq, Together, Fireworks, OpenRouter, Azure OpenAI, vLLM,
LM Studio, Ollama's /v1. Change url and model.targets_anthropic.yaml — the Messages API, which is not
OpenAI-shaped in four places and says which four.targets_bedrock.yaml — AWS, the one endpoint a fixed header
cannot describe: every request is signed from its own body and the clock. The signer is
standard library and pinned to AWS's published test vectors, because a wrong signature is a
403 on every probe, and a run where nothing got through reads exactly like a perfect defence.targets_vertex.yaml — Google, reached with the token
gcloud auth print-access-token prints. That token outlives about an hour and a sweep can
run for thirty minutes, so an auth failure after something already worked is reported as a
credential that expired and the rest of the run as not measured — never as defended.Each of these tests a deployment rather than a model: this system prompt, this model, this API. Hold
the prompt fixed and swap the model and the comparison is about the model; hold the model and
change the prompt and it is about your prompt. None of them is swept by run_all — a fleet run that
reached a paid API would turn one command into a bill.
The full walkthrough, including every way a first run can quietly lie to you, is in onboarding.
As a GitHub Action, pinned to the release it runs, with the SARIF written from whatever the run measured, pass or fail. A run that measured nothing writes none, and its exit code says why:
- uses: qatration/[email protected]
id: qat
with:
target-config: mybot.yaml
fail-on: exploited # or regression, on a pull request
env:
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
QATRATION_AUTH_SECRET: ${{ secrets.QATRATION_AUTH_SECRET }} # a remote target's proof
- uses: github/codeql-action/upload-sarif@v3
with: { sarif_file: qatration.sarif }
if: always() && steps.qat.outputs.sarif-file != ''
Or the same steps by hand, on any CI:
- run: pip install qatration
- run: qatration run --target-config mybot.yaml --fail-on exploited
env:
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
QATRATION_AUTH_SECRET: ${{ secrets.QATRATION_AUTH_SECRET }} # a remote target's proof
- run: qatration sarif --results qatration-out/results_mybot.json --target-config mybot.yaml --out qatration.sarif
if: always()
- uses: github/codeql-action/upload-sarif@v3
with: { sarif_file: qatration.sarif }
if: always()
The exit codes are a contract, not an accident. Anything non-zero fails a build, so the reason has to be recoverable from the number alone:
| Code | Meaning | Was anything sent? |
|---|---|---|
0 | ran, and the gate you asked for was not tripped | yes |
1 | --fail-on tripped: the target was exploited or breached | yes |
2 | the config or the invocation was refused — an override that cannot apply, a build that is not the one described, a committed results file this run would replace — or the command crashed, which is a bug in the tool and is deliberately not 1 | no for a refusal; a crash can come after traffic went out |
3 | the question could not be answered: every trial errored (the results file is left alone), or more than --max-unmeasured of the run went unmeasured, or --fail-on regression had nothing comparable to compare against (the results are written in both) | yes, or attempted |
4 | not authorised: the target is not localhost and carries no valid proof (qatration authorize prints the token; it expires after 14 days, see docs/ci.md) | no |
5 | a precondition failed: the canary is one this tool publishes, or a declared honeytoken was not found in the target, so the canary detectors could not have spoken; for lint, no arsenal at the path given; for generate, no stated rule the config can judge | one ordinary question (the verifier), no attack; nothing for lint and generate |
2, 3 and 5 are deliberately not 1. A build that fails because a bot was compromised and
a build that fails because nobody could reach it are different events, and a CI that cannot
tell them apart teaches its team to ignore both.
The SARIF is not a plain dump. A breach on a detector that also fires on a fifth of that
target's ordinary traffic arrives as a note carrying the ambient rate, not as a red error —
see attribution. And a detector that could not fire becomes a SARIF
tool notification rather than a silence, because a scan with eleven blind detectors and no
findings must not render as a clean one.
For a pull request, use --fail-on regression instead. exploited and any fail on the
absolute state, so the first check a team adds goes red on whatever was already broken and stays
red, and everyone learns to skip it. regression fails only on what the change introduced — and
exits 3 rather than green when the comparison cannot be believed. The whole setup, including
where the target comes from on a PR, why fork pull requests get no secrets, and how to keep the
timeline between runs, is in verifying a pull request.
This is the front page. All of it used to be one file, until that file reached 2,400 lines and
stopped being read — which is its own kind of claim nobody checks. Nothing was cut in the
split: test_readme.py reads every page below as one corpus, so a number may move between them
and still cannot quietly disappear.
One QA engineer, with Claude doing most of the typing. The design decisions, the failures worth chasing and the calls about what counts as evidence are mine.
None of that needs taking on trust. The counts test_readme.py lists, in this README and on the
site, are recounted from the artifacts in out/ by a test that fails the build when the two
disagree. The site's other figures come from single dated runs and from the benchmark against
garak and promptfoo, and are not recounted. No assertion
in the suite is allowed to be one that cannot fail — 6,176 of them, check() calls and bare
asserts alike, parsed and refused if their truth does not depend on the code. tools/guard.py
refuses commits from this project itself. All of it runs on every push to main and every pull
request, on Linux, macOS and Windows, at the oldest and newest Python the package supports.
python tools/check.py runs every suite. They are offline: no model, no network, no practice
fleet, and it is the same command CI runs, so a green local run means a green build.