Published rules
The xlogs repo scan protocol
What the repository scanner does, check by check, with what earns each severity and what each check refuses to count. Every row is derived from the check registry when this page renders, and a test fails the build if the registry and this page ever name different checks. A scanner that will not state its rules is asking to be trusted rather than read.
What a scan is
xlogs downloads one public GitHub tarball, reads it as text, and never installs, evaluates or executes anything in it. It runs the fixed list of 28 checks below. Each check either reports findings with a location and the line it matched, reports nothing, or reports that it could not complete. A check that could not complete is listed as such and is never counted as clear.
Two outputs, never blended
- A security verdict: PASS, REVIEW or REJECT, from the rule stated below.
- A license line: ADOPT, REVIEW or AVOID, labelled "adoption, not security".
The license line never enters the security verdict, and the verdict never changes the license line. There is no combined score. A number blending those two would manufacture certainty from unlike evidence.
Severity vocabulary
- CRITICAL: a pattern whose only common reading is active compromise or theft, with the file and line.
- HIGH: code that will run or fetch something you have not read, or a published advisory against an exact pinned version.
- NOTE: context worth a glance that legitimate projects have every day.
- INFO: an observation with no risk claim.
The verdict rule. Any CRITICAL finding: REJECT. Otherwise any HIGH finding: REVIEW. Otherwise: PASS. Notes and information never move the verdict. A check that could not complete makes the scan partial, stated beside the verdict; it does not lower or raise it.
The checks, derived from the registry
| Check | Severity | Scope | What earns it | What it refuses to count | Evidence |
|---|---|---|---|---|---|
| ci-prt A workflow runs untrusted PR code with your secrets | CRITICAL | GitHub workflow files. | A pull_request_target or workflow_run trigger that checks out the PR's HEAD ref, which runs untrusted code with the repository's secrets. | The trigger alone, or a checkout of the default branch, is NOTE. It is the head-ref checkout that earns CRITICAL; without it the standard deploy-after-CI workflow is not a finding. | File, and the sentence naming the trigger and the ref. |
| committed-secret A real secret is committed in the code | CRITICAL | Every text file in the tree, line by line. | A credential in one of nine key formats (AWS access key, Stripe live and restricted, OpenAI project and service-account, Anthropic, GitHub, Slack, Google, PEM private-key block) on a line that is not a placeholder. | Placeholder and example values are rejected by a shared filter. A key inside a test-fixture directory or file is NOTE, not CRITICAL. A PEM header with no key material after it is not a key. Boundaries on both sides of the token, so a hashed asset name or an uppercase checksum cannot match. | File and line, with the value masked. |
| cred-access Code reads local credentials or cloud metadata | CRITICAL | Code files, comments skipped. | Reads of the AWS credentials file, private SSH keys, a keychain dump, the cloud metadata address 169.254.169.254, or the Azure identity endpoint. | Defensive context is rejected: code that mentions these to warn about them, or to check they are absent. Using your own key to connect (ssh -i) is not reading a credential store. | File and line. |
| exfil-channel Code contacts a known exfiltration channel | CRITICAL | Code files, comments skipped. | A known exfiltration channel: Discord or Slack webhooks, the Telegram bot API, pastebin, ngrok, transfer.sh, requestbin, webhook.site. | A moderation blocklist that names these hosts in order to refuse them is not a channel. A community link in a footer is not a channel. | File and line. |
| iac-secret A cloud credential is hardcoded in infrastructure code | CRITICAL | Terraform and HCL files. | A cloud access key or secret assigned as a literal. | A variable interpolation ("${var.x}") is a reference, not a secret, and is excluded. The value is never shown. | File and line, value withheld. |
| obfuscated-exec Code decodes a hidden blob and runs it | CRITICAL | Code files, one file at a time. | A decode primitive (base64, hex, marshal) AND an execution primitive in the same file: the decode-then-run shape of a dropper. | A method call such as RegExp.prototype.exec is not an execution primitive; only a bare exec( counts. new Function("return this") is the webpack global idiom and is excluded. | File, and the sentence describing both halves. |
| agent-injection An AI-instruction file contains a possible prompt injection | HIGH | Agent-control files only: CLAUDE.md, AGENTS.md, .cursorrules, .cursor/rules/*.mdc, .claude/agents/*.md, SKILL.md at any depth. | A prompt-injection phrase on a line: ignore previous instructions, disregard instructions, exfiltrate, send the secret or token, and their close variants. | A negated rule ("never send", "do not exfiltrate") is a defence, not an injection, and is excluded. The gap inside a phrase is bounded so ordinary technical prose cannot bridge it. | File and line. |
| ci-pipe A workflow pipes a remote script into a shell | HIGH | GitHub workflow files. | A download piped into a shell (curl ... | bash, wget ... | sh). | The shell token must end the command; a pipe into sha256sum is verification, not execution. | File and line. |
| deps-source A dependency comes from a git or archive source, not a registry | HIGH | Seven lockfile formats, capped at forty hits. | A dependency resolved from a git or VCS source or a direct archive URL rather than a registry. | Registry hosts are rejected explicitly, so every ordinary lockfile line is silent. | File and line. |
| docker-pipe A Dockerfile pipes a remote script into a shell | HIGH | Dockerfiles. | A download piped into a shell during the build. | As ci-pipe: the shell must be the sink. A checksum verification is not. | File and line. |
| iac-public Infrastructure code exposes a resource to the whole internet | HIGH | Terraform and every YAML file. | A resource opened to the whole internet: public-read ACLs, 0.0.0.0/0 ingress, publicly_accessible = true. | Egress rules are not exposure and are excluded. Whether a public 443 ingress on a web server should be HIGH is an open product decision recorded in the ledger. | File and line. |
| install-hooks A script runs automatically when you install this | HIGH | package.json at the root and in nested packages; setup.py. | A non-empty preinstall, install or postinstall script, which runs on install before anyone reads the code; in setup.py, custom command classes or shell calls that run at build time. | Well-known benign lifecycle commands (a test runner, a generator, a native-addon rebuild) are reported at NOTE with the command shown, so the reader can see it is the usual thing. | File, the stage name, and the command text. |
| rag-doc-egress Document bytes may be sent in an outbound request | HIGH | Python, JavaScript and TypeScript files. | Document bytes (files=, an opened file, a .read()) placed in an outbound POST or PUT. | An ordinary request body (data=) is not document egress and is excluded. | File and line. |
| ci-perms A workflow grants broad permissions | NOTE | GitHub workflow files. | permissions: write-all. | Advisory only. | File and line. |
| ci-pin A workflow pins an action to a tag, not a commit | NOTE | GitHub workflow files, one hit per file. | An action pinned to a moving ref (main, master, a major tag) rather than a commit SHA. | Advisory only. | File and line. |
| deps-mirror A lockfile uses a third-party package mirror | NOTE | Lockfiles. | A third-party package mirror in the resolved URLs. | Advisory only. | The lockfile path. |
| docker-add A Dockerfile fetches a remote URL with ADD | NOTE | Dockerfiles. | ADD from an http(s) URL, which fetches at build time without a checksum. | Advisory only. | File and line. |
| dynamic-exec Code can run commands or evaluate code | NOTE | Code files, comments skipped. | eval, exec, new Function, child_process, execSync, spawnSync, os.system or pty.spawn present at all. | Advisory only. A primitive alone is NOTE; it becomes CRITICAL only through obfuscated-exec when a decode step sits beside it. | File and line. |
| mcp MCP / agent-tool code is present | NOTE | Code files. | MCP or agent-tool code is present. Tool descriptions are read by the model that loads them, so a hidden instruction there can steer it. | Presence only, NOTE. The first matching line is cited so the reader can start there. | File and line of the first match. |
| obfuscation Code decodes an encoded blob | NOTE | Code files, comments skipped. | A decode primitive on its own. | Advisory only; decoding is ordinary. See obfuscated-exec for the combination that matters. | File and line. |
| obfuscation-hex A long hex-escaped string is present | NOTE | Code files. | A run of eight or more consecutive hex escapes, the shape of an encoded payload. | Advisory only. | File and line. |
| outbound-unknown Code contacts external hosts we do not recognise | NOTE | Code files; first hit per host, capped at twenty hosts. | An http(s) host that is not on the allowlist of well-known package, cloud and API hosts. | Allowlisted hosts are never reported. One line per host, so a client library does not produce a hundred rows. | File and line, and the host. |
| rag-egress Embedding calls are present | NOTE | Code files. | Embedding calls (OpenAI, Cohere, Voyage, sentence-transformers) that send document text to a third party. | Presence only, NOTE: confirm the destination is disclosed. The first matching line is cited. | File and line of the first match. |
| rag-scraper A web scraper is present | NOTE | Code files. | A web scraper or browser automation library (BeautifulSoup, trafilatura, playwright, selenium, scrapy, cheerio, puppeteer). | Presence only, NOTE: scraped pages can carry injected instructions. The first matching line is cited. | File and line of the first match. |
| rls-migrations Row level security, as the migrations define it | NOTE | Every .sql file in the tree: create table, alter table enable row level security, and create policy statements, with comments stripped first. | NOTE, never more. A table created without row level security enabled; a table with it enabled and no policy; a policy whose predicate is using (true); a policy whose only test is that auth.uid() is not null, which is any signed-in user rather than the row owner. | Migrations state INTENT, not what the deployment does: a later migration or a dashboard click can change either way, so the CRITICAL stays with the live probe that actually received rows. Supabase internal schemas (auth, storage, extensions, graphql, realtime, vault) are never reported as the user's tables. A commented-out policy is not a policy. A predicate that checks ownership as well as is-not-null is correct and is not reported. Migrations that build SQL dynamically are named as unread, so a clean result is never mistaken for a complete one. | File and line of the statement, the schema-qualified table name, and for a using (true) policy the sentence naming the legitimate reference-data case. |
| telemetry Analytics or telemetry SDK present | NOTE | Code files, one hit per file. | An analytics or error-reporting SDK (segment, mixpanel, amplitude, posthog, sentry, datadog, bugsnag) or a call named telemetry or analytics.track. | Advisory only; telemetry is disclosed, not judged. | File and line. |
| agent-file AI-instruction files are present | INFO | The same agent-control files, plus .gitai/ and .cursor/ directories. | The file is present. An instruction file is a file that instructs your AI, so it is worth reading. | Presence only, no risk claim. Injection phrases are agent-injection's job. | File path. |
| env Environment variables read | INFO | Code files. | Environment variables are read; the count of distinct names is reported. | Observation only, INFO. Reading configuration is not a finding. | File and line of the first read, and the count. |
| known-vuln A pinned dependency has a published advisory | HIGH or NOTE | package-lock.json v2 and v3 only, capped at 1,500 packages and 20 advisory detail fetches inside one 12-second deadline; every cap is reported. | An exact pinned name@version that OSV.dev lists under a published advisory. HIGH when the advisory's own database rates it high or critical; NOTE otherwise. Never CRITICAL: an advisory against a pin is a review fact, not proof of compromise. | A lockfile we could not read, a lookup that did not finish, or a yarn.lock and pnpm-lock.yaml that exist but are not parsed, are stated as such rather than read as clean. | The lockfile, the package, direct or transitive, the advisory ids, and the fix version the advisory itself states. |
The dependency lookup runs beside the checks rather than inside them because it needs the network; it is listed here so nothing the scan does is undocumented.
The license line
xlogs looks in a fixed set of places: root LICENSE, LICENCE, COPYING and UNLICENSE files in every common spelling; declarations in package.json, Cargo.toml, pyproject.toml and composer.json; and nested license files for monorepos, bounded. The text is matched against signatures in a fixed order, specific and restrictive first, so a Business Source License whose change license names Apache is read as BUSL. The line reports the verdict word, the SPDX identifier or "unrecognised", the basis, and the file and line of the signature that matched.
- ADOPT: MIT, Apache-2.0, BSD, ISC, 0BSD, MIT-0, Zlib, PostgreSQL, Python-2.0, Unlicense, CC0, CC-BY (with the note that it was not written for software).
- REVIEW: GPL, LGPL, MPL, EPL, CDDL, CC-BY-SA, a declaration with no text, text the declaration disagrees with, unrecognised text, an empty file, a monorepo whose packages differ.
- AVOID: AGPL, SSPL, BUSL before its change date, Elastic, Commons Clause, CC-BY-NC, CC-BY-ND, all rights reserved, and no license found anywhere we looked.
Dual licenses: an OR expression, two license files, or "at your option" wording is the adopter's choice, and the line reports the most permissive option and names the choice. An AND expression reports the most restrictive. AVOID and REVIEW mean "for adoption into a closed product without further steps". They are not security statements.
Three worked results
- A clean MIT repository. Verdict PASS. License line: ADOPT MIT, with attribution. Eligible for the showcase.
- An AGPL repository with no security findings. Verdict PASS. License line: AVOID AGPL-3.0. The verdict is unchanged by the license; the line tells the adopter what the verdict cannot.
- An MIT repository with one HIGH. Verdict REVIEW, "1 thing to explain before you trust this repo". License line: ADOPT MIT. The permissive license does not soften the finding.
What a PASS means
These checks found nothing. It is not a statement that the code is safe. The scan cannot see git history, private repositories, or the meaning of the code; it reads what is in the tree today and reports what it matched. Widening what a check counts is a change to this page as well as to the code, and the two are tested against each other.
Scan a repository · Repositories that passed · Methodology for the live app scanner
