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 34 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, REJECT or PARTIAL, 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.
- MEDIUM: a review item. The text of an act is there, but the act is not established: the line hands it to a function xlogs cannot see, which the finding names.
- NOTE: context worth a glance that legitimate projects have every day.
- INFO: an observation with no risk claim.
- INCOMPLETE: not a grade. A line or file xlogs could not finish reading (a string that never closes, syntax it does not read for the language, more of the file than it reads, or a call in a language whose definitions it does not read), with the reason.
The verdict rule. Any CRITICAL finding: REJECT. Otherwise any HIGH or MEDIUM finding: REVIEW. Otherwise, if every part of the scan completed and every line was read: PASS; if one did not (a check that failed, a dependency lookup that could not finish, read the lockfile or rate an advisory, a grading check whose pattern met its limit of 2,000 matching lines, a line xlogs could not finish reading): PARTIAL, never PASS, because a part that did not run found nothing only by not looking. Notes and information never move the verdict. What did not complete is named beside the verdict, and it never lowers a REJECT or a REVIEW. PARTIAL is verdict code 3, so a pipeline that requires complete analysis can fail on it. Of a binary file xlogs reads only its first bytes and its name: a compiled program or package is HIGH, and an image, font, document or archive that no check read is a note saying so, not a gap and not a grade. A line sweep examines up to 2,000 lines per pattern (a check that gives only notes says in its own last row how many it did not examine, which cannot move the verdict, and a presence check reads its first hit), and a check lists its most severe findings one by one (forty; twenty unknown hosts, eight shipped paths, thirteen binaries), the rest in one row graded like the most severe of them, so a long list never hides a higher grade. Until 2026-10-16 a scan with a part that did not complete could read PASS; until rules 1.10 a line xlogs could not read was graded as if it had read it; until rules 1.11 every binary file was HIGH, images included; until rules 1.12 a check stopped at the fortieth line it matched, and the dependency lookup at its twentieth advisory record, without saying so.
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. A line that only describes the theft is listed as a NOTE, not raised: every match sits inside a regular expression, a detection rule's text, a test's sample code that nothing in the file runs, a test assertion or a comment, and the line's own code reads, sends and runs nothing. A call on such a line is trusted only on evidence: a function the repository defines, in the same file or one relative import away, is judged by what its body does (reading, sending, writing or running anything, directly or through another function of its file), never by its name; a test framework's own assertions and pattern constructors are known by name. A function xlogs cannot see, one the repository does not define, makes the line a MEDIUM review item that names the function: never a NOTE by its name, and never a CRITICAL or HIGH by a call, because the act is not established either way. Since rules 1.10 an analysis limit is never evidence. A match inside a string or comment that never closes, inside syntax xlogs does not read for the language (a heredoc, a Ruby percent literal, block comment or regular expression, a Rust raw string, a PHP file's text outside its tags, text between JSX tags), behind more of the file than xlogs reads (2,000 functions in a file, 20,000 characters of one function, 2,000,000 in all), or on a line calling a function in a language whose definitions xlogs does not read (every language but JavaScript and Python) is INCOMPLETE: not graded, never a pass, and the scan reads PARTIAL. A line whose own code, read cleanly, reads, sends or runs keeps its grade, wherever the rest of the file could not be read. | 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. A line that only describes the channel (inside a regular expression, a detection rule's text, a test's sample code that nothing in the file runs, or a comment, with nothing on the line sending anything) is listed as a NOTE. A call on such a line is trusted only on evidence: a function the repository defines, in the same file or one relative import away, is judged by what its body does (reading, sending, writing or running anything, directly or through another function of its file), never by its name; a test framework's own assertions and pattern constructors are known by name. A function xlogs cannot see, one the repository does not define, makes the line a MEDIUM review item that names the function: never a NOTE by its name, and never a CRITICAL or HIGH by a call, because the act is not established either way. Since rules 1.10 an analysis limit is never evidence. A match inside a string or comment that never closes, inside syntax xlogs does not read for the language (a heredoc, a Ruby percent literal, block comment or regular expression, a Rust raw string, a PHP file's text outside its tags, text between JSX tags), behind more of the file than xlogs reads (2,000 functions in a file, 20,000 characters of one function, 2,000,000 in all), or on a line calling a function in a language whose definitions xlogs does not read (every language but JavaScript and Python) is INCOMPLETE: not graded, never a pass, and the scan reads PARTIAL. A line whose own code, read cleanly, reads, sends or runs keeps its grade, wherever the rest of the file could not be read. | 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 within three lines of each other: the decode-then-run shape of a dropper. The same two more than three lines apart is rated one step lower and asks you to read both, because a build script that reads a base64 argument and separately compiles something looks like this from a distance. | 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. Encoding is not decoding: writing binary data out as base64 is not this finding. Occurrences in comments do not count. When every decode, or every execution, in a file sits inside a string, a regular expression or a comment that nothing in the file runs as code, the file is listed as a NOTE: sample code or a detector's pattern, not a payload. A sample handed to a function of the same file that runs its parameter, through any number of that file's functions, counts as run; when every decode, or every execution, sits where xlogs could not finish reading (a string or comment that never closes, syntax it does not read for the language, a file past the analysis budget), the file is INCOMPLETE: not graded, and the scan reads PARTIAL. A decode and an execution in code read cleanly keep the grade. | File, the line the execution sits on, and the distance between the two 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. |
| committed-binary A compiled program or library is committed that no text scan could read | HIGH | Every file in the repository, including the ones no other check can read. Of a binary file xlogs reads its first bytes and its name, nothing more. | HIGH for compiled code or an installable package, and only on what identifies one: first bytes an operating system or runtime loads as code (ELF; Windows PE, with its PE header where the DOS header points; Mach-O; a Java class; WebAssembly; Android DEX; Lua bytecode; LLVM bitcode; an Erlang BEAM module; Flash SWF; or a #! line naming an interpreter, with binary data after it), or a compiled name (.exe .dll .so .dylib .node .jar .class .wasm .pyd .a .lib .msi .dmg .apk) or package name (.whl .egg .gem .pyz .nupkg .vsix .xpi .crx .deb .rpm .pkg .mpkg .msp .msu .aab .ipa .aar .war .ear .hpi .jpi .xap .appx .appxbundle .msix .msixbundle .snap). Every other check here works on text and skips binary files, so without this the report would describe a repository it had only partly read. | An analysis limit is not evidence (rules 1.11): a binary file xlogs could not identify as code is never graded as code. And what a binary appears to be is a DESCRIPTION, NOT A CLEARANCE (rules 1.13): no check here reads inside a binary, so three things are kept apart. What the file APPEARS to be is its finding. What was ANALYZED, and what was not, is the coverage beside the findings, built from the files themselves and never from the rows a reader shows. What was FOUND is the grade. Every binary except recognized media is INCOMPLETE, not a grade, and the scan reads PARTIAL: an archive or other container (ZIP, gzip, bzip2, xz, tar, 7-Zip, RAR, Zstandard, LZ4, ar, xar, Cabinet, a Microsoft compound file such as a Word, Excel or PowerPoint 97-2003 file, an Office file with macros enabled, or a ZIP named as a document such as .docx, .xlsx or .epub), because a container can carry code and xlogs does not open one; text that opens with a UTF-16 or UTF-32 byte order mark, in an encoding xlogs does not decode, whatever its name, because an agent or a program can read what no check here read; a file whose first bytes and name do not agree on a media format, or that has no name to agree with them (a GIF header on a file with no extension is valid JavaScript to node); media that also holds text shaped like code; media examined only in part; a Git LFS pointer, whose file is stored outside the repository and was not downloaded; and any binary whose first bytes and name match no format. Compiled code and installable packages keep their HIGH, and the coverage says that what they hold was not analyzed, so the finding and the analysis it did not get are both visible, and explaining the finding leaves the gap. Images, fonts, audio, video and documents whose first bytes and name agree (PNG, JPEG, GIF, WebP, ICO, BMP, TIFF, AVIF, HEIC, MP3, WAV, Ogg, FLAC, AMR, MP4, MOV, WebM, AVI, WOFF, WOFF2, TTF, OTF, EOT, PDF and the like) are described: the inspected content of each (every byte, when the reader holds the file's own bytes and all of them were examined, which the row says; a decoded string otherwise, which it also says) is searched for a FIXED LIST of code shapes (<script, eval(, require(, import(, new Function, process.env, child_process, execSync, spawn(, exec(, a shebang such as #!/bin/, powershell, Invoke-Expression, fromCharCode, atob(, PHP's <?php, system(, shell_exec(, passthru(, base64_decode( and module.exports, and the browser shapes fetch(, XMLHttpRequest, document.cookie, document.write( and window.location, since a polyglot need not export anything to run), and, in a PDF, its own JavaScript, JS and Launch actions, read with their case because those bytes occur by chance in compressed images. A shape found is possible code content, never evidence of harm: it moves the file from described to not analyzed, and nothing grades it. Finding none is not a clearance: no check here reads pixels, sound or page layout, text the list does not name is not searched for, and a PDF's compressed or encoded streams are not decoded, which its row and the coverage both say. Text apart from a few NUL bytes is not a binary at all: every check reads it, each NUL as a space (nul-text), unless its first bytes are a program's or its name marks compiled code or a package, which keep it a binary. What counts as a few is judged from EVERY byte of the file, never from a window of it. A #! counts only when an interpreter path follows it (#!/ or #! /): AMR and SILK voice files also start with #!, and they are media. When the first bytes and the name disagree, the first bytes decide what is said, except that a compiled or package name keeps its grade. A text file with a compiled name is read like any other text file. Python bytecode caches, and compiled files under a test or fixture path, are advisory rather than blockers: the first is generated from sources that ARE here, the second is usually a sample input. The path is not a clearance either: under __pycache__ only Python's own bytecode header is called bytecode, and anything else there is what its bytes say. EACH FILE IS ITS OWN FINDING, media included, so the counts reach every file. Thirteen rows are listed one by one, compiled code first, and past that the rest are counted in one row graded like the most severe of them, which carries every finding it stands for: a display limit groups rows and NEVER REMOVES A FINDING or a coverage line (until rules 1.13 the files past the thirteenth were one finding of their own, so the counts reached them as one, and a script xlogs could not read could vanish inside a HIGH row). A file larger than the reader reads (512KB on the web, 2MB on the command line; a lockfile 4MB and 8MB) is classified from its bytes and named in the coverage as unread, never dropped in silence; a folder xlogs excludes BY NAME (node_modules, .venv, a nested .git) is a stated limit naming every file in it, never a gap, because excluding it is a scope decision, and a name is not evidence that what it holds is a dependency: the report then says its finding covers the analyzed scope, and the repository badge reads "passing, folders excluded" rather than "passing"; but when a configured command (an install script, an agent hook, an MCP server, an editor task) runs or names a file the repository holds in such a folder, that file is code something here will run that no check read, so it is a gap and the scan reads PARTIAL; and a listing that did not complete is itself a gap, because files it never named cannot be counted. Until rules 1.11 every binary file was HIGH and described as a compiled file, images included. | The file path, what identified it (its first bytes or its name, and where the two disagree), what was examined, and the statement that nothing read what it holds. What was not analyzed is reported beside the findings, by reason, with every file it names. |
| deps-source A dependency comes from a git or archive source, not a registry | HIGH | Seven lockfile formats, every line (until rules 1.12 the check stopped at forty hits without saying so). | 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. npm's prepare (with preprepare and postprepare), which runs on npm install in a clone and when the package is installed from git, and prepack and postpack, which run when it is installed from git or packed, are not findings here, because none runs for a registry install (husky's prepare is in millions of repositories); the repository's what-runs inventory lists each with when npm runs it (until rules 1.12 it held no prepack or postpack). | File, the stage name, and the command text. |
| outbound-lookalike Code contacts a host that is dressed up as a well-known service | HIGH | Code files, every http(s) URL in them. | A hostname whose final labels are a domain we do not recognise while a well-known one sits inside it, such as api.github.com.attacker.ru: the request goes to the owner of the END of the name, not to the service the name reads as. | The allowlist is matched against the end of the host, so a genuine subdomain of a known service is not a lookalike. A plain misspelling is not this finding either: it carries no well-known domain inside it, so it lands in the unknown-host inventory instead. | File, line, and the host as written. |
| 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. |
| shipped-not-committed The published package carries files this repository does not have | HIGH | The package.json fields that name shipped files: files, bin and main. | A path the published package carries that is absent from this repository, so its tarball contains something this source does not show. A built binary or a build directory is rated above a dist folder. | A glob is skipped rather than guessed at, and a path that exists as a directory prefix counts as present. A dist build step is ordinary, so it is advisory; the point it makes is that the published package holds code this scan never saw. Every shipped path is examined; eight are listed one by one, a built artifact first, and past that seven are and the rest share one row graded like the most severe of them (until rules 1.12 the check stopped at the eighth path, so a built artifact named ninth was never graded). | package.json, and the shipped path that is missing. |
| agent-config An agent or editor config in this repository configures commands | NOTE | The files agents and editors load on their own: .mcp.json, .cursor/mcp.json, .vscode/mcp.json, the mcp.servers key of .vscode/settings.json, .claude/settings.json and settings.local.json, folder-open tasks in .vscode/tasks.json, and devcontainer.json. Never a README, a docs page or an example folder. | NOTE, one per file: the file configures commands an agent or editor runs on a trigger, subject to the client's trust or approval. Each command is quoted with its line, trigger and conditions; absolute paths to the author's machine, settings that pre-approve servers or skip permission prompts, and shell syntax xlogs does not interpret are named; a committed settings.local.json is called out as personal. | Presence and quotation only; it never moves the verdict. Values in env blocks and headers are never read or shown, and every quoted string passes one redaction: a login in an address, an Authorization or Bearer value, a secret-named flag or NAME=value, npm's _authToken, and every key format xlogs recognises. Commands are split into words and unwrapped (cmd /c, bash -c, powershell -c, env and cross-env prefixes, sudo, wsl) without running anything, by a hand parser that is linear on any input; a config the reader cannot read is named as unread, never counted as read. npm lifecycle scripts stay install-hooks' to report. | The file, each command with its line and trigger, any absolute path, and each setting that removes a prompt. |
| 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. |
| nul-text A text file holds NUL bytes | NOTE | Every file that holds a NUL byte and is otherwise text: valid UTF-8, NULs at most four or one in a hundred, and no other stray control byte, judged from EVERY byte of the file and never from a window of it (until rules 1.13 a NUL past the 8,192nd character left a file reading as plain text, and the bytes past the first 64 KB were never weighed). A file whose first bytes are a program's, or whose name marks compiled code or a package, stays a binary and is graded by committed-binary: an empty WebAssembly module is valid UTF-8 holding four NULs. A string a caller decoded is never read this way, because nothing in a string says which bytes it came from. | NOTE, never more. Every check here reads such a file, each NUL read as a space so its lines and columns stand, so what the file says is graded by the check that reads it; this row says it held NUL bytes. | A NUL has no ordinary use in source code, and it is a known way to hide a file from scanners: until rules 1.13 one NUL made every check here skip the whole file as binary, and from rules 1.11 a source file with a NUL in a comment could pass with nothing in it read. A file with more NULs, or bytes that are not text, is a binary, described by committed-binary. Where such a file's first bytes are a format's (a GIF header before a page of JavaScript), its row says so, since what it appears to be and what was read of it are different statements. | The file, and how many NUL bytes it held. |
| 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. Every host is examined; twenty are listed one by one, and past that nineteen are and the rest share one row that points to them (until rules 1.12 the hosts past twenty were not mentioned). | 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. A .sql file no check read (a binary, or one larger than the scan reads) is named in each row whose claim it could change, so "never enables row level security" and "no policy" speak only for the migrations read. | 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. |
| webhook-unverified A payment webhook route does not check the sender's signature | NOTE | Route handler files only (an App Router route file, or a file under pages/api) with webhook in a path segment. | NOTE. The file names a payment provider or a payment event, handles events, contains no signature verification (constructEvent, an HMAC, a verify call, svix, unmarshal) and imports no local helper whose name suggests verification. | Source states intent, not what the deployment does: verification can live in middleware or at the edge, where this file cannot show it. So it is context, like rls-migrations, and never moves the verdict. The live scan only ever LISTS a webhook route; it never sends one a forged event (the XL-180 refusal). | File and line where the event is trusted, and the sentence saying what a forged event could do. |
| 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. A file no check read (a binary, text in an encoding xlogs does not decode, a Git LFS pointer, or one larger than the scan reads) is said to be unread, with why, and never said to hold no injection phrase. | 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 60 advisory records, read six at a time, 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, an advisory whose record was not read (past the 60, or not arriving in time; it is listed as a NOTE, never rated upward), or a yarn.lock and pnpm-lock.yaml that exist but are not parsed, are stated as such rather than read as clean, and make the scan PARTIAL (until rules 1.12 an advisory that could not be rated was not stated). Lockfiles of other ecosystems (Cargo, Poetry, Go modules) are outside this lookup today and are not looked at, so a PASS says nothing about their dependencies. | The lockfile, the package, direct or transitive, the advisory ids, and the fix version the advisory itself states for the range that holds the pinned version (until rules 1.12 every fix of the package was listed and sorted as text, so a fix for an older line could be named). |
| agent-package An agent config runs a package the registry does not hold, or someone else's | HIGH or NOTE | Every package a command from those files would fetch through npx, npm exec, pnpm dlx, yarn dlx, bunx, uvx, uv tool run or pipx run, including through an npm run script (followed two levels) and, since rules 1.12, npm's prepack and postpack scripts, capped at 10 names in a fixed order, one read-only request each to registry.npmjs.org or pypi.org inside one 6-second wall-clock deadline that also cuts off a slow answer. For an unregistered npm name with punctuation, one more exact request for its bare form; for a look-alike of this project's own name, one request for its own package. | HIGH only for a security consequence the registry itself shows: npm holds the name as 0.0.1-security, the placeholder it publishes after removing a package for malicious code. An unscoped name that reads as this project's own, published by npm accounts its own package shares nobody with (both lists come from the registry, which a publisher cannot write), is a review NOTE: a fork, a rename or a second publishing account looks the same. A first publication within 90 days of the lookup, and install-time scripts on the version the configuration runs (the version it pins, or the latest for a bare name; a range or tag is not resolved, and the note says so), are listed on it as further reasons to check, never as a grade (rules 1.10; until then either made it HIGH, and the scripts were always the latest version's). NOTE also when the registry returns no metadata (404) or lists the name as unpublished, which shows only that it held no record then, not that the name can be registered or is malicious; also for a scoped 404 (a private package answers 404 too), a name npm should refuse as too similar to an existing one, a placeholder or example, this repository's own workspace package, a command whose lockfile package differs from the registry package npx would fetch, and a look-alike whose publisher could not be compared. A repository field is written by its publisher, so it is shown and never graded. Never CRITICAL. | Private registries and indexes (any .npmrc, .yarnrc.yml, .yarnrc, bunfig.toml, uv.toml, pyproject.toml, pip settings, requirements files, --registry, --index-url), variables, local paths, git sources, addresses, credential-shaped words, names after an option xlogs does not interpret, and the repository's declared dependencies are listed, never looked up, and no host a repository names is contacted. Redirects are not followed. A registry that does not answer, an answer that cannot be read or arrives after the deadline, a publisher comparison that could not be made, and the cap are stated as check errors, so the scan reads PARTIAL; nothing is ever called safe. Maintainer emails are dropped as the answer is read. | File and line, the command, the registry host, its answer and the UTC time it was asked. |
The dependency lookup and the agent package lookup run beside the checks rather than inside them because they need the network; they are 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.
Four 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.
- A clean repository whose only lockfile is a yarn.lock. Verdict PARTIAL: nothing blocking in what completed, and the dependency lookup named as not run, because only package-lock.json is read today. Its static checks pass, so its badge reads passing and it may join the showcase, both of which say they report the static checks only.
What a PASS means
These checks found nothing, and every one of them completed. It is not a statement that the code is safe. The README badge and the showcase report the static checks only, and their labels say so; the dependency advisory lookup runs in the full report. 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
