# Deployed Frontend Secret and Debug Endpoint Exposure Gate (`kingii98/deployed-frontend-secret-and-debug-endpoint-exposure-gate`) Actor

Reads the script bundles that your deployed pages load from their own origin and probes a fixed set of debug and schema paths. Reports each leaked key and each exposed endpoint with a redacted match, and gives one gate\_pass verdict for your build step. HT

- **URL**: https://apify.com/kingii98/deployed-frontend-secret-and-debug-endpoint-exposure-gate.md
- **Developed by:** [kingii98](https://apify.com/kingii98) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 run starteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Deployed Frontend Secret and Debug Endpoint Exposure Gate

After each deploy, this Actor answers one question: did this build publish a live
API key, and did it leave a debug or a schema endpoint open?

It reads each page that you give it, reads the scripts that the page loads **from
its own origin**, searches those scripts for secret patterns, and probes a fixed
set of debug and schema paths against the origin. It writes one record for each
finding, one record for each page and one summary record with a `gatePass`
boolean for your build step.

It uses HTTP only: `GET` and `HEAD` requests plus regular expression matching. It
uses no browser, no proxy, no CAPTCHA service, no paid API and no external
database. **It never sends a discovered key anywhere, and it never calls a
discovered endpoint with a discovered key.** Only the first four characters of a
match reach the dataset.

### What it checks

**Secrets in script bundles.** Private key blocks, Stripe live secret keys, AWS
access keys, GitHub tokens, Slack tokens, OpenAI keys, SendGrid keys, npm tokens,
Mailgun and Twilio identifiers, signed JSON Web Tokens, and long values that sit
next to a secret name.

**Exposed endpoints.** The default probe set holds 12 paths: `/.env`,
`/.env.local`, `/.git/config`, `/actuator/env`, `/debug/vars`, `/swagger.json`,
`/openapi.json`, `/api-docs`, `/swagger-ui.html`, `/graphql`, `/graphiql` and
`/server-status`.

### The false positive rules

A gate that reports a public key is removed from a build step in one week. Three
rules keep the report honest:

1. **A publishable key is never a finding.** A Stripe publishable key
   (`pk_live_`, `pk_test_`), a Mapbox public token (`pk.eyJ`), a PostHog project
   key (`phc_`) and any value behind a `public-` or `publishable` name are
   dropped before the report.
2. **A key type that is public by design keeps a low severity.** A Google or
   Firebase Web API key (`AIza...`) is reported as `low`, so it never fails the
   gate at the default threshold. Restrict such a key by referrer instead.
3. **A probe needs evidence, not a status code.** A single page application
   answers `200` with its own HTML shell for every path. A probe is a finding
   only when the body proves the exposure: `/.env` must look like an environment
   file, `/openapi.json` must hold an `openapi` or `swagger` key, `/graphql` must
   answer like a GraphQL server. An HTML answer is refused unless the rule
   expects HTML and finds its marker.

A documentation example value, for example `AKIAIOSFODNN7EXAMPLE`, is dropped as
well.

### Input

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `urls` | array | two public example pages | 1 to 20 page URLs on domains that you own |
| `ownershipConfirmed` | boolean | `true` | Confirm that you own, or may test, every domain |
| `scanBundles` | boolean | `true` | Read the same-origin scripts of each page |
| `maxBundleBytes` | integer | `2000000` | Stop the read of one script at this size |
| `maxScriptsPerPage` | integer | `10` | Read at most this many scripts for each page |
| `probePaths` | array | the 12 paths above | Maximum 30 paths |
| `failOn` | string | `high` | `info`, `low`, `medium`, `high`, `critical` or `never` |
| `timeoutSeconds` | integer | `15` | Timeout of one request |
| `maxRequests` | integer | `400` | Request budget of the run |
| `maxRedirects` | integer | `2` | Same-origin redirect hops for a page request |
| `trackBaseline` | boolean | `false` | Mark each finding with `isNew` against the last run |
| `baselineKey` | string | `baseline` | Key of the baseline record in the key-value store |

Every field has a default, so a run with empty input `{}` succeeds.

Only same-origin scripts are read. A script on a content delivery network belongs
to another party, so the Actor leaves it alone and counts it in
`scriptsSkippedCrossOrigin`.

### Output

**Finding record** — `recordType: "finding"`, `pageUrl`, `sourceUrl`,
`sourceType` (`bundle`, `inline_script`, `page` or `probe`), `findingType`
(`secret_pattern` or `exposed_schema_endpoint`), `patternName` (for example
`stripe_live_secret_key` or `exposed_env_file`), `redactedMatch` (the first four
characters plus the length), `byteOffset`, `severity`, `note`, `fingerprint`,
`isNew` and `foundAt`.

**Page record** — `recordType: "page"`, `pageUrl`, `finalUrl`, `httpStatus`,
`scriptsRead`, `bytesRead`, `inlineScripts`, `scriptsSkippedCrossOrigin`,
`scriptsSkippedOverLimit`, `probesRun`, `findingCount`, `error` and `auditedAt`.

**Summary record** — `recordType: "summary"`, `gatePass`, `failOn`,
`pagesRequested`, `pagesAudited`, `pagesSkipped`, `scriptsRead`,
`bundlesScanned`, `bytesRead`, `findingCount`, `newFindingCount`,
`findingsBySeverity`, `requestsSent`, `status`, `note` and `finishedAt`.

### Use it in a build step

Run the Actor after each deploy and read the last record of the dataset:

```bash
GATE=$(curl -s "https://api.apify.com/v2/datasets/$DATASET_ID/items?clean=true" \
  | jq -r '[.[] | select(.recordType=="summary")][0].gatePass')
[ "$GATE" = "true" ] || exit 1
```

Set `trackBaseline` to `true` to accept a known false positive once. The first
run stores the fingerprint of every finding. The next run marks a finding that
the store does not hold with `isNew: true`, so your step can fail on new findings
only. A fingerprint holds the origin, the finding type, the pattern name and the
redacted value, so it survives a new bundle name after the next build.

### A business verdict is never a failed run

A failed gate, a page that does not answer, a target that the Actor refuses and a
run with zero findings are all results. Each one is a dataset record plus a run
status message, and the run ends `SUCCEEDED`. A run ends `FAILED` only when the
input itself cannot be parsed.

### Safety

The Actor refuses every loopback, private, link-local and reserved address, and
refuses a URL that carries credentials. It re-checks the address after each
redirect hop, and it does not follow a redirect that leaves the origin. A script
read and a probe never follow a redirect.

Set `ownershipConfirmed` to `false` and the Actor audits nothing: it probes debug
paths, so it must run against your own deployment only.

### Pricing (pay per event)

| Event | Unit | Price |
| --- | --- | --- |
| `run-started` | one Actor run | USD 0.02 |
| `page-audited` | one page fetched, parsed and probed with the path set | USD 0.03 |
| `bundle-scanned` | one script file read and searched | USD 0.01 |
| `finding-reported` | one finding written to the dataset | USD 0.02 |

`run-started` is charged once, after the input is accepted. `page-audited` is
charged for each page that the Actor really fetched and probed; a page that was
refused or unreachable is not charged. `bundle-scanned` is charged for each
script that was read and searched; an inline script costs nothing extra, because
the page body already holds it. `finding-reported` is charged for each finding
record. A single page application with 6 bundles and no findings costs about
USD 0.11 for each deploy.

### Local development

```bash
uv sync
uv run pytest
uv run ruff check .
```

# Actor input Schema

## `urls` (type: `array`):

1 to 20 page URLs on domains that you own. The Actor reads each page, reads the scripts that the page loads from the same origin, and probes the path set against the origin of the page. Private, loopback and reserved addresses are refused.

## `ownershipConfirmed` (type: `boolean`):

Confirm that you own, or are authorized to test, every domain in the list. The Actor probes debug and schema paths, so it must run only against your own deployment. When this is false the Actor audits nothing and reports the reason.

## `scanBundles` (type: `boolean`):

Read each script that the page loads from the same origin and search it for secret patterns. Inline script text is always searched, because the page body already holds it.

## `maxBundleBytes` (type: `integer`):

The Actor stops the read of one script at this size. A larger bundle is searched up to this many bytes.

## `maxScriptsPerPage` (type: `integer`):

The Actor reads at most this many same-origin scripts for each page. The page record reports how many scripts were left out.

## `probePaths` (type: `array`):

Paths that the Actor probes against the origin of each page, with a HEAD first and a GET only when the HEAD may be open. A path in the default set is reported only when the body proves the exposure, so a single page application that answers 200 for every path is not reported. A path that you add yourself is reported when it answers 200 with a non-HTML body. Maximum 30 paths.

## `failOn` (type: `string`):

The gate reports gatePass false when a finding reaches this severity. Select "never" to report findings without a gate verdict.

## `timeoutSeconds` (type: `integer`):

Timeout of one HTTP request.

## `maxRequests` (type: `integer`):

The Actor stops when it has sent this many requests. A page that was left out is reported in the summary record.

## `maxRedirects` (type: `integer`):

The page request follows at most this many same-origin redirect hops. A script read and a probe never follow a redirect.

## `trackBaseline` (type: `boolean`):

Compare this run against the fingerprints of the last run in the key-value store, mark each finding with isNew, and then store this run. Use it to accept a known false positive once.

## `baselineKey` (type: `string`):

Key of the baseline record in the key-value store. Use one key for each deployment that you gate.

## Actor input object example

```json
{
  "urls": [
    "https://example.com/",
    "https://www.iana.org/"
  ],
  "ownershipConfirmed": true,
  "scanBundles": true,
  "maxBundleBytes": 2000000,
  "maxScriptsPerPage": 10,
  "probePaths": [
    "/.env",
    "/.env.local",
    "/.git/config",
    "/actuator/env",
    "/debug/vars",
    "/swagger.json",
    "/openapi.json",
    "/api-docs",
    "/swagger-ui.html",
    "/graphql",
    "/graphiql",
    "/server-status"
  ],
  "failOn": "high",
  "timeoutSeconds": 15,
  "maxRequests": 400,
  "maxRedirects": 2,
  "trackBaseline": false,
  "baselineKey": "baseline"
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://example.com/",
        "https://www.iana.org/"
    ],
    "ownershipConfirmed": true,
    "scanBundles": true,
    "maxBundleBytes": 2000000,
    "maxScriptsPerPage": 10,
    "probePaths": [
        "/.env",
        "/.env.local",
        "/.git/config",
        "/actuator/env",
        "/debug/vars",
        "/swagger.json",
        "/openapi.json",
        "/api-docs",
        "/swagger-ui.html",
        "/graphql",
        "/graphiql",
        "/server-status"
    ],
    "failOn": "high",
    "timeoutSeconds": 15,
    "maxRequests": 400,
    "maxRedirects": 2,
    "trackBaseline": false,
    "baselineKey": "baseline"
};

// Run the Actor and wait for it to finish
const run = await client.actor("kingii98/deployed-frontend-secret-and-debug-endpoint-exposure-gate").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "urls": [
        "https://example.com/",
        "https://www.iana.org/",
    ],
    "ownershipConfirmed": True,
    "scanBundles": True,
    "maxBundleBytes": 2000000,
    "maxScriptsPerPage": 10,
    "probePaths": [
        "/.env",
        "/.env.local",
        "/.git/config",
        "/actuator/env",
        "/debug/vars",
        "/swagger.json",
        "/openapi.json",
        "/api-docs",
        "/swagger-ui.html",
        "/graphql",
        "/graphiql",
        "/server-status",
    ],
    "failOn": "high",
    "timeoutSeconds": 15,
    "maxRequests": 400,
    "maxRedirects": 2,
    "trackBaseline": False,
    "baselineKey": "baseline",
}

# Run the Actor and wait for it to finish
run = client.actor("kingii98/deployed-frontend-secret-and-debug-endpoint-exposure-gate").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://example.com/",
    "https://www.iana.org/"
  ],
  "ownershipConfirmed": true,
  "scanBundles": true,
  "maxBundleBytes": 2000000,
  "maxScriptsPerPage": 10,
  "probePaths": [
    "/.env",
    "/.env.local",
    "/.git/config",
    "/actuator/env",
    "/debug/vars",
    "/swagger.json",
    "/openapi.json",
    "/api-docs",
    "/swagger-ui.html",
    "/graphql",
    "/graphiql",
    "/server-status"
  ],
  "failOn": "high",
  "timeoutSeconds": 15,
  "maxRequests": 400,
  "maxRedirects": 2,
  "trackBaseline": false,
  "baselineKey": "baseline"
}' |
apify call kingii98/deployed-frontend-secret-and-debug-endpoint-exposure-gate --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kingii98/deployed-frontend-secret-and-debug-endpoint-exposure-gate"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/mhlifQWmZLuQKs2aY/builds/YRxcL3XkiKpIyKliw/openapi.json
