SEOscanner.app

Free check · Security

Exposed secrets and source code checker

Check whether a site serves its Git repository, .env files, credential files, source maps, secrets in its pages and scripts, or access codes and password hashes its pages send without showing them. Search its source for your own field names too. Each finding says what and where, never the value.

Reads robots.txt, up to 8 pages, a fixed list of file paths such as /.git/HEAD and /.env, the site's own scripts and their source maps. It obeys robots.txt and saves nothing. Free, no account needed.

One term per line, up to 5: a field name your pages should never send, or a value from your own records. The result lists where each appears, by file, line and column, and never prints the text around it.

What the checker reads

It asks for what any visitor can ask for. After reading robots.txt, which it obeys for its own user agent, it loads up to eight pages: the home page, the address you entered and the pages they link to. It then requests a short list of files that should never be public, such as /.git/HEAD and /.env, reads the site's own scripts and looks for the source map each one names or keeps beside it. It never signs in, sends only GET requests and does not touch scripts hosted on other sites.

Everything those pages and scripts contain is searched, including the data a page embeds for its scripts to start from, such as the JSON a framework writes into a <script> tag or an HTML attribute. That data is part of the page even when nothing on screen shows it.

It lists what leaked, never the secret

A report that repeated your secrets would be one more place they leak. The checker records only the kind of secret and where it sits, and these rules apply to everything it prints:

  1. Never emit, store or log a matched value. A finding names the kind of value, where it was found (the URL, the line and column, and the source file when the value sits inside a source map) and the fields its template lists. Nothing else from a response body leaves the evaluation.
  2. From an environment file, emit variable names only, never values. A name is emitted only when it matches [A-Za-z_][A-Za-z0-9_.]* and is at most 64 characters long; other names are counted but not shown.
  3. From a URL that carries a password, emit its scheme and host only, never the user name or the password.
  4. From a credential file, emit counts and host names only, never user names.
  5. A check is not saved. Its result is not written to scan history, the research corpus or the browser's saved scans, and the fetched bodies are discarded once the result is built.
  6. A matched value of four or more characters can also appear where the result names a place, such as a token in a script URL's query string. Before the result leaves the evaluation, every occurrence of such a value anywhere in it is replaced with [redacted]. Access codes (SEC-DATA-01) are the exception: a code is short enough to match a date or a number elsewhere in the result by chance, and replacing that would reveal the code. The one place a code is replaced is a search term the result repeats (§12.7), compared without case, because a term that contains a code would otherwise repeat it.

Why these leaks happen

Front-end code is the most common route. OWASP's testing guide puts it bluntly: “Similar to the comments and metadata in HTML code, many programmers also hardcode sensitive information in JavaScript variables on the frontend.”[OWASP-WSTG-INFO-05] Build tools make this easy to do by accident. Next.js, for example, copies variables marked for the browser into the bundle at build time: “It will be inlined into any JavaScript sent to the browser.”[NEXT-ENV]

Source maps are the second route. They exist to make minified code readable again, which is exactly the problem:

“it is undeniable that source map files or files for debugging if released to the production environment will make their source more human-readable.”

[OWASP-WSTG-INFO-05] Summary

A map can carry the original files in full. The format defines sourcesContent plainly: “The sourcesContent field is an optional list of source content (i.e. the original source) strings, used when the source cannot be hosted.”[ECMA-426] Next.js turns browser source maps off for production builds for this reason:

“During production builds, they are disabled to prevent you leaking your source on the client, unless you specifically opt-in with the configuration flag.”

[NEXT-SOURCEMAPS] productionBrowserSourceMaps

The third route is the web server itself. A deploy that copies the whole project folder can publish the .git directory, and Git does not need special software to hand a repository over HTTP: “A "dumb" protocol which requires only a standard HTTP server on the server end of the connection”[GIT-HTTP] Environment and credential files can land on the server the same way, and OWASP files them among those that are “Should generally never be publicly served”[OWASP-WSTG-CONF-04]

Data a page sends but does not show

A page that renders a property listing or a profile often receives the whole record from the database and displays a few fields of it. The rest still travels to the browser, where anyone can read it in the page source. OWASP calls this out for APIs, and a page's embedded data is an API response by another name: “The API endpoint exposes properties of an object that are considered sensitive and should not be read by the user.”[OWASP-API3] The fix is an allowlist, not a filter in the browser: “Avoid using generic methods such as to_json() and to_string(). Instead, cherry-pick specific object properties you specifically want to return.”[OWASP-API3]

Server-rendered frameworks make the mistake easy to miss. The Next.js data security guide warns: “This ensures the app is secure by default, but it's possible to accidentally expose private data through how data is fetched or passed to components.”[NEXT-DATA-SECURITY] React's documentation is blunter: “A Client Component should never accept objects that carry sensitive data.”[REACT-TAINT-OBJECT]

Two kinds of field have no business in a page at all, so the checker looks for them by name. Access codes, such as a door, lockbox, gate, alarm or Wi-Fi code, are found by the property that holds them (SEC-DATA-01), because a code has no format to recognize. Password hashes are found by their format when a password field holds one (SEC-DATA-02). React's reference for its taint API names passwords and hashes among the values to keep out of the browser:

“Call taintUniqueValue with a password, token, key or hash to register it with React as something that should not be allowed to be passed to the Client as is”

[REACT-TAINT-VALUE] taintUniqueValue(message, lifetime, value)

Taint checks are a second line of defense. The first is the allowlist, as the Next.js guide puts it: “You should sanitize the data before passing it to the Client Component”[NEXT-DATA-SECURITY].

Only you know which fields in your records are private: an owner's phone number, internal notes, a guest list. Enter up to five search terms with the check, one per line, such as lockboxCode or internalNotes. The result lists every place each term appears in what the site served, by URL, line and column, and the source file when it sits inside a source map. A search judges nothing and never changes a check's result.

It never prints the text around a match either. When a term is the name of a property, the result says what kind of value the property holds (a string, a number, an empty string, null and so on), so you can tell a filled field from an empty one without the value appearing on screen. Terms are not saved, logged or sent to analytics.

How it avoids false alarms

Many servers answer every address with the same page, so a status code proves nothing. The checker follows OWASP's rule and reads the content before it reports anything:

“A hit in any category is a candidate finding only - confirm the response actually returns the file's content (not a custom 404 or an access-denied page), and read enough of that content to confirm it is genuinely sensitive, before reporting it.”

[OWASP-WSTG-CONF-04] Sensitive File Extensions

A /.env that comes back as an HTML page is not a finding. Neither is an example in documentation: token-shaped strings made of repeated characters, elided keys and passwords such as password on example.com are ignored. GitHub tokens are recognized by their documented prefixes, because “GitHub issues tokens that begin with a prefix to indicate the token's type.”[GH-TOKENS]

The checks

Each check, what it inspects and how it confirms a match, as the knowledge base defines them.

  • SEC-VCS-01The Git repository is not served

    Exposes source code

    Inspects: /.git/HEAD and /.git/config on the preferred origin.

    How a match is confirmed

    HTTP 200 and not an HTML body. For /.git/HEAD, the first line is ref: refs/ followed by a ref name, or a 40 or 64 character hexadecimal object name. For /.git/config, a line, with surrounding whitespace removed, reads [core].

  • SEC-FILE-01Environment files are not served

    Exposes credentials

    Inspects: /.env, /.env.local, /.env.production, /.env.production.local and /.env.development on the preferred origin.

    How a match is confirmed

    HTTP 200, not an HTML body, and at least one line of the form NAME=value, optionally preceded by export , where such lines make up at least half of the lines that are neither blank nor comments starting with #. Lines inside a quoted value that spans several lines belong to its assignment.

  • SEC-FILE-02Credential files are not served

    Exposes credentials

    Inspects: /.git-credentials, /.npmrc and /.htpasswd on the preferred origin.

    How a match is confirmed

    HTTP 200 and not an HTML body. For /.git-credentials, at least one line of the form scheme://user:password@host, where such lines make up at least half of the non-blank lines. For /.npmrc, at least one line that sets _authToken, _auth or _password, optionally scoped as //registry/path:_authToken, to a value that does not start with ${. For /.htpasswd, at least one line of the form user:hash whose hash starts with $apr1$, $2a$, $2b$, $2y$, $5$, $6$ or {SHA}, where such lines make up at least half of the lines that are neither blank nor comments.

  • SEC-MAP-01Source maps do not publish the original source

    Exposes source code (sources_content), file paths (paths_only)

    Inspects: the source map of each same-site script (§12.3).

    How a match is confirmed

    a confirmed source map, fetched with HTTP 200 or decoded from a data: URL.

  • SEC-SECRET-01Pages and scripts carry no GitHub tokens

    Exposes credentials

    Inspects: served text (§12.3).

    How a match is confirmed

    ghp_, gho_, ghu_, ghs_, ghr_ or github_pat_ followed by at least 36 characters from A-Z, a-z, 0-9 and _; or ghs_, digits, _ and three base64url segments separated by ., the stateless installation token format. The character before the prefix is not a letter, digit or _. A match whose characters after the prefix use fewer than 10 distinct characters is a placeholder and is ignored.

  • SEC-SECRET-02Pages and scripts carry no private keys

    Exposes credentials

    Inspects: served text (§12.3).

    How a match is confirmed

    the marker -----BEGIN PRIVATE KEY-----, -----BEGIN ENCRYPTED PRIVATE KEY-----, -----BEGIN RSA PRIVATE KEY-----, -----BEGIN EC PRIVATE KEY----- or -----BEGIN DSA PRIVATE KEY-----, followed within 16,384 characters by the matching -----END marker, where the text between them, once the escapes \n and \r are read as line breaks and \/ as /, and header lines containing : and all whitespace are removed, is at least 64 characters long and uses only A-Z, a-z, 0-9, +, / and =. A shortened body, such as ... in documentation, is a placeholder and is ignored.

  • SEC-SECRET-03URLs in pages and scripts carry no passwords

    Exposes credentials

    Inspects: served text (§12.3).

    How a match is confirmed

    a scheme of 2 to 20 characters, ://, a user name, :, a non-empty password, @ and a host, where none of them contains whitespace, quotes, <, >, \, /, ? or #, and the user name and host contain no : or @. Ignored as placeholders: a host that is example.com, example.net or example.org, or ends with . and one of those or with .example, .test or .invalid; a password that, compared without case, is password, pass, passwd, pwd, secret, changeme, mypassword, yourpassword, your_password or your-password; a password that contains ${, {{, <, [ or %s; a password made only of *, x, X and ..

  • SEC-SECRET-04Pages and scripts assign no values to names that mark a secret

    Exposes credentials

    Inspects: served text (§12.3).

    How a match is confirmed

    a name followed by : or = and a quoted string (§12.3). The name is an identifier, or a quoted key, of at most 64 characters which, lowercased with _, -, . and $ removed, contains secret, password, passwd or privatekey. The value is 16 to 512 characters from A-Z, a-z, 0-9, _, -, +, /, = and ., contains a letter and a digit, uses at least 8 distinct characters and does not start with / or .. Ignored as placeholders: a value that, lowercased, contains your, example, placeholder, xxxx, changeme, redacted, dummy or sample, and a value that contains the name itself, compared without case, as a generated class name does. A value that SEC-SECRET-01 already reports at the same position is not reported again.

  • SEC-DATA-01Pages and scripts carry no access codes

    Exposes credentials

    Inspects: served text (§12.3).

    How a match is confirmed

    a name followed by : or = and a value. The name is an identifier or a quoted string (§12.3) of at most 64 characters. Folded (§12.3 Name words), it ends with an access word, access, alarm, checkin, door, entry, garage, gate, keybox, keypad, keysafe, lock, lockbox, smartlock or wifi, followed by code, combination, passcode, password or pin, and the access word starts one of the name's words. The value is a quoted string of 3 to 64 characters that contains a digit, contains no whitespace, quote, backslash, &, <, >, { or }, and does not start with $; or an integer of 3 to 16 digits that no letter, digit, _ or . follows. Ignored as placeholders: a value made of one repeated character, such as 0000 or ****; a value that, lowercased, contains one of the placeholder words of SEC-SECRET-04; and a value that, folded, contains the folded name. A value that SEC-SECRET-01 or SEC-SECRET-04 already reports at the same position is not reported again.

  • SEC-DATA-02Pages and scripts carry no password hashes

    Exposes credentials

    Inspects: served text (§12.3).

    How a match is confirmed

    a name followed by : or = and a quoted string (§12.3). The name is an identifier or a quoted string of at most 64 characters which, folded (§12.3 Name words), contains password or passwd. The value starts with $2a$, $2b$, $2y$, $argon2id$, $argon2i$, $argon2d$, $5$ or $6$, is 40 to 512 characters long and contains no whitespace, quote, backslash, &, < or >. A value whose characters after the prefix use fewer than 10 distinct characters is a placeholder and is ignored.

If something is found

Removing the file or the line does not undo the leak, because anyone may already have a copy. The order that matters:

  1. Revoke the secret where it was issued, then replace it. OWASP's guidance on secrets: “Keys that were exposed should undergo immediate revocation.”[OWASP-SECRETS]
  2. Remove it from the file, the bundle or the source map that served it, and from the build settings that put it there.
  3. Close the route it took: block /.git/ and /.env at the server or CDN, turn off production source maps, and build the data each page receives from a list of the fields it shows. “Data files, log files, configuration files, etc. should be stored in directories not accessible by the web server, to prevent both information disclosure and, where permissions allow writing, data modification.”[OWASP-WSTG-CONF-04]
  4. Run this check again to confirm nothing is served.

What it does not report

  • Provider key formats other than GitHub's are not matched, because no owner's documentation of those formats is registered in SOURCES.md. A value in another format is still reported when it is assigned to a name that marks a secret (SEC-SECRET-04).
  • Keys that providers issue for use in browsers are not judged. Whether such a key is restricted cannot be seen from outside the site.
  • Personal data, such as names, email addresses and phone numbers, is not judged, because pages often show it on purpose. A search of the served text (§12.7) for the names of fields your pages should never send finds them.
  • The checker sends only GET requests, never signs in, never requests a path that robots.txt disallows for it, and never fetches a script or source map from another site.

Nothing here is part of the SEO audit. These checks carry no weight, never change a score and never run during a scan. A check is not saved to your history.

Primary sources

The documentation these checks rest on. The source registry records how and when each passage was read.