Automation · Product Engineering

Daily security audit that ships one green fix PR

Every morning, audit your repository with Cloudflare's open-source security-audit skill, pick the most severe verified Critical or High finding, and open one fix PR that it drives to green CI.

Created by Komran Rashidov from type.com

What it does

  • Fixes only Critical and High findings that an independent check has verified.
  • Opens at most one fix PR a day and finishes an unfinished PR before starting a new one.
  • Adds integration tests that fail before the fix and pass after, for the attack and the legitimate use.
  • Never merges, deploys or probes production, and asks a person for help after repeated red CI.
  • Keeps a running backlog and coverage state in a daily Slack thread.

Before you start

  • GitHub connected with access to push branches, open PRs and read Actions logs.
  • Slack connected through the Type Slack app, invited to the reporting channel.
  • Optional: a read-only production database connection for feature-usage evidence.

See an example

Example output from a sample run. Unless the example says otherwise, company names and figures are sample data.

Example Slack report (illustrative, not real data)

Top-level message: Security audit · 2026-11-04 · main @ 3f9c2e1 :hammer_and_wrench: Fixing: :large_orange_circle: High – export endpoint returns another workspace's files when given its file ID · https://github.com/acme/app/pull/482|#482 · :white_check_mark: green Backlog: 2 confirmed Critical/High not yet fixed

Thread replies: • Finding — authorization check missing on file export; verified independently; fix scopes the lookup to the caller's workspace • Usage — 1,284 exports in the last 30 days across 63 accounts, so the feature is patched, not deleted • Tests — new integration test fails before the fix (cross-workspace export) and passes after; legitimate same-workspace export still passes • Other findings — :large_yellow_circle: Medium – verbose error in upload handler (details only) STATE: date=2026-11-04 covered=[auth,webhooks,uploads] backlog=[F-07 high, F-09 high] pr=#482 status=green attempts=1 runs_on_pr=1 escalated=[]

How to use Daily security audit that ships one green fix PR

  1. Select Use automation to open a new conversation with the automation attached.
  2. Choose a space and describe any changes you want before sending the message.
  3. Work with the agent to connect your integrations and review the automation before turning it on.
Browse the automation instructions
---
name: "Daily Security Audit with One Green Fix PR"
description: "Every day, security-audit a repository with Cloudflare's open-source security-audit skill, fix exactly one confirmed Critical or High finding in a pull request, and drive that PR to green CI."
connection_needs:
  - "GitHub (CLI in the Type sandbox, full access): clone and fetch the repository, list open PRs, push a branch, open a PR with an assignee, poll checks, read Actions job logs and rerun failed jobs. No merges."
  - "Slack (the Type Slack app, invited to the reporting channel): post a top-level message and thread replies, and read the channel's recent messages and threads."
  - "Optional: a read-only (SELECT-only) PostgreSQL connection to a production database or replica, used only to measure whether the vulnerable feature is used."
  - "Public GitHub access from the sandbox to clone https://github.com/cloudflare/security-audit-skill."
inputs:
  - "repository: the GitHub repository to audit, as owner/name"
  - "default_branch: the branch audited each day (source default: main)"
  - "github_connection: GitHub connection with full Space access that can push branches and open PRs in the repository"
  - "owner_name: the engineer who owns the automation, reviews the PRs and handles escalations"
  - "owner_github_login: that engineer's GitHub login, set as the PR assignee"
  - "owner_slack_user_id: that engineer's Slack user ID, mentioned only when human intervention is needed"
  - "slack_connection: the Slack connection"
  - "slack_channel_name: the reporting channel's name, without #"
  - "slack_channel_id: the reporting channel's ID"
  - "audit_areas: the parts of the codebase to cover over time, in priority order"
  - "usage_database_connection: optional read-only PostgreSQL connection for feature-usage evidence"
  - "usage_database_name: display name of that database, as referenced in the instructions"
  - "reference_integration_test: optional path of an existing integration test whose pattern new tests follow"
  - "prior_audit_thread: optional Slack channel ID and thread ts of an earlier audit progress thread to seed coverage on the first run"
  - "schedule_timezone: time zone of the daily run (source default: America/Los_Angeles, 05:00)"
---

# Daily Security Audit with One Green Fix PR

## Purpose

A security backlog shrinks only if someone fixes real vulnerabilities steadily. This instruction automation runs once a day against one repository:

1. It reconstructs its own state from the reporting Slack thread and the open `sec-audit/` pull requests.
2. If its previous fix PR is not green yet, today's run only works on getting that PR green.
3. Otherwise it audits the default branch with the Cloudflare security-audit skill, keeps only **independently verified Critical or High** findings, and fixes **exactly one** of them in a new pull request with integration tests, assigned to the owner.
4. It drives the PR to green CI within the run, and reports a short summary plus details and a machine-readable `STATE` block in Slack.

Hard limits: at most one new fix PR per day and one in-flight automation PR at a time; never merge, deploy, run production migrations, or probe live systems; the usage database is read-only.

## Setup

Settle every binding before creating the automation. None inherits a value from the original workspace.

| Binding | Meaning and how to supply it |
| --- | --- |
| `{{input:repository}}` | The repository to audit (`owner/name`). The run clones or fetches it in the Type sandbox with the GitHub CLI. |
| `{{input:default_branch}}` | The branch audited each day. The source used `main`. |
| `{{input:github_connection}}` | A GitHub connection with **full** Space access (the GitHub CLI needs it) that can push branches, open PRs, set assignees, read Actions logs and rerun jobs in the repository. The source used the owner's personal GitHub connection, so fix PRs were authored as that engineer; a team GitHub App also works. Save it in `integrationAccounts`. |
| `{{input:owner_name}}`, `{{input:owner_github_login}}`, `{{input:owner_slack_user_id}}` | The engineer who requested the automation: display name, GitHub login (PR assignee) and Slack user ID (mentioned only for escalations; find it with `slack_search_users`). |
| `{{input:slack_connection}}` | The Slack connection. Save it in `integrationAccounts`. Invite the Type Slack app to the reporting channel. |
| `{{input:slack_channel_name}}`, `{{input:slack_channel_id}}` | The reporting channel (`slack_search_channels` returns the ID). Also pass them to `create_task` as `slackChannelName` and `slackChannelId`. |
| `{{input:audit_areas}}` | The areas to broaden coverage over, highest risk first. Describe them in your codebase's terms. The source listed, for its own architecture: authentication, authorization and tenant isolation in API routes and use cases; webhooks and event sources; sandboxed code and agent tool execution; integrations and credential storage; file upload and artifacts; app preview hosting; admin surfaces; SSRF and proxy paths; SQL and shell injection; secrets handling. |
| `{{input:usage_database_connection}}`, `{{input:usage_database_name}}` | Optional. A PostgreSQL connection with a SELECT-only role on a production replica, queried with `postgresql_query`. Without it, usage is recorded as unverified and the run never deletes a feature (see Workflow step 3). |
| `{{input:reference_integration_test}}` | Optional path of an existing integration test that drives a real handler over HTTP against a real local or ephemeral database. If none, delete the "pattern" clause and follow the repository's testing guidance. |
| `{{input:prior_audit_thread}}` | Optional. A Slack thread (channel ID and `thread_ts`) where earlier manual audit progress was recorded. Delete the sentence that references it when there is none. |
| `{{input:schedule_timezone}}` | Time zone for the schedule. The source ran every day at 05:00 America/Los_Angeles. |

Create the automation with `create_task`: the instructions under Reference implementation; `scheduleConfig: { mode: "specific_time", time: "05:00", days: ["mon","tue","wed","thu","fri","sat","sun"], timezone: {{input:schedule_timezone}} }`; `runConfig: { modelId: "claude-opus-5-5", effort: "xhigh" }` (the source's settings; use the strongest available model with extra-high effort if that model is unavailable); `integrationAccounts` for GitHub, Slack and the optional database; `slackChannelId` and `slackChannelName`. It is agent-only (`agentModeReason`: each run audits source code, independently verifies findings, writes a fix with tests and iterates on CI failures, which is open-ended judgment in the repository and sandbox). Run `check_automation_readiness` for GitHub and Slack with full access first. Create it paused and enable it after review.

## Triggers and execution

Instruction (agent-only) automation, scheduled every day at 05:00 in `{{input:schedule_timezone}}`, with `runConfig` model `claude-opus-5-5` and effort `xhigh`. No code stage. The run's final Type response is one short line; the Slack message and its thread are the report. A run is not done until the fix PR's required checks are green, or the run must end with `status=red` recorded.

## Workflow

0. **Reconstruct state.** Read this automation's messages and threads in the reporting channel from the last ~14 days and take the latest `STATE` block (covered areas, backlog of confirmed-but-unfixed findings, in-flight PR, attempts). On the first run with no `STATE` in the channel, also read `{{input:prior_audit_thread}}` for previously covered areas and findings. List open PRs whose branch starts with `sec-audit/` through the REST API (`gh api "repos/{{input:repository}}/pulls?state=open&per_page=100"`); GraphQL-based `gh pr list` may return 403.
1. **Finish an in-flight PR first.** If an open `sec-audit/` PR from this automation is not green, today's work is getting that PR green (step 4). No new audit-driven PR today. If it is green, leave it for human review and continue.
2. **Audit.**
   1. Fetch the latest `{{input:default_branch}}` (clone the repository in the sandbox if needed). Clone or refresh the Cloudflare security-audit skill and follow its procedure: reconnaissance, hunting, independent verification, findings schema.
   2. Work the confirmed backlog first; otherwise audit areas from `{{input:audit_areas}}` not yet covered, so coverage broadens over time.
   3. Defensive source review only. Never probe, attack, or send exploit traffic to production or any live third-party system. Reproduction happens only in local integration tests.
   4. A finding qualifies only if it is independently verified as Critical or High under the skill's severity criteria. Unverified, Medium and Low findings go only into the details reply.
   5. Pick exactly one finding: Critical before High, then highest real-world impact. Skip findings that already have an open or merged `sec-audit/` PR. Add the rest to the backlog in `STATE`.
3. **Fix the chosen finding.**
   1. Usage check: search the codebase for live entry points and feature switches, and query recent usage in `{{input:usage_database_name}}` (SELECT only; for example rows in the last 30–90 days, active accounts). Put the counts in the details reply. Without a usage database, record usage as unverified.
   2. If the feature is unused, the PR deletes the feature (server, client, shared packages, switches, dead tests and docs) instead of patching it. Schema drops may be included as migration files but are never run. If usage is unverified, do not delete; make the minimal fix.
   3. If it is used, make a minimal root-cause fix that follows the repository's own agent and contributor instructions (for example `AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md` and the rules files they reference).
   4. Tests: integration tests only, following the repository's testing guidance and the pattern in `{{input:reference_integration_test}}`: drive the real handler (for example over HTTP) against a real local or ephemeral database, mocking only external services that cannot run locally. The tests must fail before the fix and pass after, and cover both the attack case and the legitimate-use counterpart. Update existing unit tests only when they encode the vulnerable behavior.
   5. Run lint, typecheck and the relevant tests locally before pushing.
   6. Branch `sec-audit/<short-slug>`, imperative commit subjects, and a PR body with the vulnerability description (no weaponized details), severity, impact, fix or deletion rationale, production-usage evidence and test evidence. Open it with `gh pr create --assignee {{input:owner_github_login}}` and verify the assignee is set.
4. **Get the PR green (required).** Poll the PR's required checks, read failing logs, fix root causes, push, and repeat within the run. Never skip, disable or weaken tests or checks, and never merge. Each push meant to fix red CI is one attempt, tracked in `STATE`. If CI is still red when the run must end, record `status=red` and continue on the next run (step 1) before any new work. If the PR is still red after **3 runs or 8 total attempts**, post `<@{{input:owner_slack_user_id}}> human intervention needed on #<PR>` in the thread with the failing checks and what was tried (one line each). While escalated, keep the daily audit going but open no other fix PR until the owner responds or the PR is closed or merged.
5. **Report.** One short top-level message in the reporting channel, then every detail as thread replies (`slack_post_message` with `thread_ts`), ending with the `STATE` block. Formats are in the Reference implementation.

## State and failure behavior

- **State lives in Slack and GitHub, not in Type data.** The last thread reply of each run is a code block: `STATE: date=... covered=[areas] backlog=[finding-id sev ...] pr=#123 status=green|red attempts=N runs_on_pr=N escalated=[...]`. Open `sec-audit/` PRs identify in-flight work.
- **One PR at a time.** An unfinished red PR always takes priority over new audit work; a green PR waits for human review.
- **Escalation.** 3 runs or 8 attempts on one red PR triggers a single mention of the owner; new fix PRs pause until the owner responds or the PR is closed or merged, while daily audits continue.
- **Partial runs.** If the run ends with red CI, `status=red` is recorded and the next run resumes there.
- **Severity gate.** Only independently verified Critical or High findings lead to code changes; everything else is reported in the thread only.
- **Read-only data.** The usage database is used only for SELECT queries; local and ephemeral test databases are fine.
- No merges, deploys or production migrations; the automation never disables itself. There is no other retry, rollback or failure channel in the source.

## Integration data and capabilities

| Integration | Data and capabilities used | Relevant semantics |
| --- | --- | --- |
| GitHub (CLI in the Type sandbox) | Clone/fetch the repository; `gh api "repos/<repo>/pulls?state=open&per_page=100"`; push `sec-audit/<slug>` branches; `gh pr create --assignee`; poll PR checks; `gh api .../actions/jobs/<id>/logs`; `gh api -X POST repos/<repo>/actions/jobs/<id>/rerun`. | Set `GH_TOKEN` from the repository's entry in `TYPE_GITHUB_CONNECTIONS` (a JSON array; select by `fullName`, use its `tokenEnv`; never print token values). GraphQL `gh pr list` may 403; use REST. Job logs need `--allow-escape-sequences`. A job rerun returns 403 until its whole workflow run finishes, and a new push cancels it. Only required checks gate "green". |
| Slack (Type Slack app) | `slack_post_message` (`channel_id`, `text`, `thread_ts`) for the summary and thread replies; `slack_get_channel_history` and `slack_get_thread_replies` to find the latest `STATE`. | Slack mrkdwn only. The app must be a member of the channel. |
| PostgreSQL (optional) | `postgresql_query` SELECT statements for recent usage counts of the affected feature. | Read-only; never writes or migrations. |
| Cloudflare security-audit skill (public GitHub, MIT) | Cloned at run time from `https://github.com/cloudflare/security-audit-skill`; the skill lives in `skills/security-audit/` with its `SKILL.md`, phase guides, findings schema and Node.js validators. | Its own requirements apply (tool use, sub-agents, Node.js for validators, sandboxed execution of target code). |

## Reference implementation

Original provider assumptions: the GitHub CLI in the Type managed sandbox with the repository's token from `TYPE_GITHUB_CONNECTIONS`, Slack tools from the Type Slack app, `postgresql_query` for the optional usage database, and the public Cloudflare skill. **Reuse when these assumptions hold; adapt when they do not, preserving the behavior above.**

```text
Requested by {{input:owner_name}} (GitHub login: `{{input:owner_github_login}}`, Slack user <@{{input:owner_slack_user_id}}>).

## Goal
Once a day, security-audit `{{input:repository}}` with the Cloudflare security-audit skill (https://github.com/cloudflare/security-audit-skill) and fix **exactly one** confirmed **Critical or High** vulnerability by opening a PR and getting it to **green CI**. Never open more than one new fix PR per day.

## Reporting (Slack #{{input:slack_channel_name}}, channel ID {{input:slack_channel_id}})
- Post one short top-level message per run in #{{input:slack_channel_name}}, then put all details (findings list, evidence, file paths, state block) as thread replies under that message using the Slack integration (slack_post_message with thread_ts).
- Slack renders mrkdwn: use `*bold*`, `•` bullets, `<url|label>` links, backticks. No HTML, no HTML entities, no Markdown headings/tables, no `**double asterisks**`.
- Severity labels: `:red_circle: Critical`, `:large_orange_circle: High`, `:large_yellow_circle: Medium`, `:white_circle: Low`.
- Top-level message shape (under ~600 chars):
  `*Security audit · <date>* · {{input:default_branch}} @ <sha>`
  `:hammer_and_wrench: Fixing: :large_orange_circle: High – <one-line summary> · <pr-url|#123> · :white_check_mark: green` (or `nothing new: no confirmed Critical/High`)
  `Backlog: N confirmed Critical/High not yet fixed`
- Keep vulnerability descriptions defensive and high level; never post weaponized exploit details.
- Last thread reply: a code block `STATE: date=... covered=[areas] backlog=[finding-id sev ...] pr=#123 status=green|red attempts=N runs_on_pr=N escalated=[...]`.
- Mention <@{{input:owner_slack_user_id}}> only when human intervention is needed.

## Step 0 — Reconstruct state
- Read this automation's recent messages and threads in #{{input:slack_channel_name}} (last ~14 days) for the latest STATE block: covered areas, backlog of confirmed-but-unfixed findings, in-flight PR.
- On the first run with no STATE in this channel, also read the earlier progress thread {{input:prior_audit_thread}} for previously covered areas and findings.
- List open PRs in {{input:repository}} whose branch starts with `sec-audit/` (use REST: `gh api "repos/{{input:repository}}/pulls?state=open&per_page=100"`; GraphQL `gh pr list` may 403. gh needs GH_TOKEN set from the {{input:repository}} entry's tokenEnv in TYPE_GITHUB_CONNECTIONS, a JSON array keyed by fullName; never print token values).

## Step 1 — Finish an in-flight PR first
If an open `sec-audit/` PR from this automation is not green, today's work is getting **that** PR green (Step 4). Do not audit for or open a new fix PR today. If it is already green, leave it for human review and continue to Step 2.

## Step 2 — Audit
1. Fetch latest `{{input:default_branch}}` (clone {{input:repository}} into the sandbox if it is not checked out). Clone/refresh the Cloudflare security-audit skill and follow its procedure (recon → hunting → independent verification → findings schema).
2. Prioritize the existing confirmed backlog first; otherwise audit areas not yet covered ({{input:audit_areas}}), so coverage broadens over time.
3. Defensive source review only. Never probe, attack, or send exploit traffic to production or any live third-party system. Reproduction happens only in local integration tests.
4. A finding qualifies only if it is **independently verified** as Critical or High per the skill's severity criteria. Unverified/Medium/Low findings go in the details reply only.
5. Pick **one** finding: highest severity first (Critical before High), then highest real-world impact. Skip any finding that already has an open or merged `sec-audit/` PR. Add the rest to the backlog in STATE.

## Step 3 — Fix the one chosen finding
1. Usage check: search the codebase for live entry points/feature switches and check recent usage on the read-only production database ({{input:usage_database_name}}, SELECT-only via postgresql_query; e.g. rows in last 30–90 days, active accounts). Include counts in the details reply. If no usage database is configured, record usage as unverified.
2. If the feature is unused, the PR deletes the feature (server, client, shared packages, switches, dead tests/docs) instead of patching. Schema drops may be included as migration files but never run. If usage is unverified, do not delete; make the minimal fix.
3. If used, make a minimal root-cause fix following the repository's own agent and contributor instructions (AGENTS.md, CLAUDE.md, CONTRIBUTING.md and the rules files they reference).
4. Tests: integration tests only (follow the repository's testing guidance; pattern {{input:reference_integration_test}}, driving the handler over HTTP against a real local or ephemeral database), mocking only external services that cannot run locally. Tests must fail before the fix and pass after, covering the attack case and the legitimate-use counterpart. Update existing unit tests only when they encode the vulnerable behavior.
5. Run lint, typecheck and the relevant tests locally before pushing.
6. Branch `sec-audit/<short-slug>`, imperative commit subjects, PR body with vulnerability description (no weaponized details), severity, impact, fix/deletion rationale, prod-usage evidence, test evidence. Open with `gh pr create --assignee {{input:owner_github_login}}` and verify the assignee is set.

## Step 4 — Get the PR green (required)
- The run is not done until the PR's required CI checks are green. Wait for CI (poll checks), read failing logs, fix root causes, push, and repeat within this run. Never skip, disable or weaken tests/checks to get green, and never merge.
- Each push intended to fix red CI is one attempt; track attempts in STATE.
- If CI is still red when the run must end, record status=red and continue on the next run (Step 1) before any new work.
- If the PR is still red after 3 runs or 8 total attempts, stop and post in the thread: `<@{{input:owner_slack_user_id}}> human intervention needed on #<PR>` with failing checks and what was tried (one line each). While escalated, keep the daily audit going but do not open another fix PR until {{input:owner_name}} responds or the PR is closed/merged.

## Operational notes
- The sandbox checkout may lack installed dependencies and local services (databases, caches). Set them up as the repository's instructions describe before running tests. Some suites may not run in the sandbox; run the ones that can and rely on CI for the rest.
- CI may inject real third-party credentials into integration tests; stub outbound calls to third-party services in new tests so they behave the same locally and in CI.
- `gh api .../actions/jobs/<id>/logs` needs `--allow-escape-sequences`. Rerun a failed job via `gh api -X POST repos/{{input:repository}}/actions/jobs/<id>/rerun` only after its whole run finishes (else 403); a new push cancels it.
- Only required checks gate "green"; do not wait on optional or informational workflows.
- Use the repository's documented typecheck command (a plain compiler run can run out of memory on large repositories), and run test files one per process when the test runner cannot reset modules between files.

## Hard limits
- At most one new fix PR per day; at most one in-flight automation PR at a time.
- No merges, deploys, or production migrations. The usage database is read-only usage analysis only; local/ephemeral test DBs are fine.
- Do not disable this automation.

Before sending your final response, post the top-level summary to Slack channel #{{input:slack_channel_name}} ({{input:slack_channel_id}}) with slack_post_message and put details in its thread. Keep the final Type response to one short line.

Before sending your final response for this scheduled task, call slack_post_message to post the final message to Slack channel "{{input:slack_channel_name}}" with channel_id "{{input:slack_channel_id}}". Do not finish with only a Type message.
```

When `{{input:usage_database_connection}}` is not configured, keep the "record usage as unverified" rules and remove the database name from step 3.1. When `{{input:prior_audit_thread}}` is not configured, delete that sentence from Step 0.

## Verification

**Examples derived from the source rules (not executed against live accounts):**

- An open `sec-audit/` PR with red CI exists: the run only works on that PR; no new audit-driven PR is opened.
- An open `sec-audit/` PR is green: it is left for human review and the daily audit continues.
- The audit finds only Medium and Low issues: the top-level message says `nothing new: no confirmed Critical/High`; findings appear only in the thread.
- Two confirmed findings, one Critical and one High: the Critical one is fixed; the High one goes to the `STATE` backlog.
- The vulnerable feature has no usage in the last 90 days: the PR deletes the feature instead of patching it; any schema drop is a migration file that is not run.
- No usage database configured: usage is "unverified" and the PR patches rather than deletes.
- The fix PR is still red after 3 runs: one mention of the owner with failing checks; daily audits continue, no new fix PR.

**Proposed acceptance checks after installation:**

1. Run once on a test repository with a planted, verifiable High issue; confirm exactly one `sec-audit/` PR assigned to the owner, with failing-then-passing integration tests, and a Slack summary plus thread ending in `STATE`.
2. Run again the next day while that PR is red; confirm no new PR and that attempts increase in `STATE`.
3. Close the PR; confirm the next run returns to auditing and picks the next backlog item.
4. Check that no comment or Slack reply contains exploit payloads, and that the database connection only runs SELECT statements.