{"slug": "in-an-ai-security-audit-the-skeptic-is-the-product", "title": "In an AI security audit, the skeptic is the product", "summary": "A developer audited Cloudflare's open-source security-audit-skill, a coding-agent skill that coordinates recon, hunter and verifier agents, and found that its bundled validate-findings.cjs validator passed four fabricated findings — including two false positives and one citing a nonexistent file — because it checks only schema and ledger consistency and never opens source files. The developer measured the skill at 15 markdown files (~46,000 tokens) plus two zero-dependency Node validators, and noted that two of its six phases exist solely to refute candidate findings. A 15-line script that opens each cited file and checks the referenced line eliminated the invented finding, illustrating that deterministic code, not the validator, carries the audit's real value.", "body_md": "I wrote four security findings against a 47-line Node service. Two of them were false. One cites a file that does not exist.\n\nCloudflare's findings validator passed all four.\n\nThat is not a bug in the validator. It is the clearest way I found to show where the value of an AI security review actually sits.\n\n[cloudflare/security-audit-skill](https://github.com/cloudflare/security-audit-skill) is a coding-agent skill that turns one agent into a coordinator of many: recon agents map the code, hunter agents look for bugs, and fresh verifier agents try to knock each finding down before it reaches a report. It was on GitHub trending this week with almost 2,000 stars in a day, 35,690 in total when I checked. MIT licensed. Cloudflare says it is the starting point of the vulnerability harness they now run across their whole fleet.\n\nI measured it before reading it closely. The skill is 15 markdown files: 1,778 lines, 24,768 words, about 183 KB. At the usual four bytes per token that is roughly 46,000 tokens of instructions, loaded in pieces as a run needs them. Cloudflare's [blog post](https://blog.cloudflare.com/build-your-own-vulnerability-harness/) says the first version was about 450 lines.\n\nTwo zero-dependency Node scripts sit beside the prose: `validate-findings.cjs` (773 lines) and `validate-coverage-ledger.cjs` (872 lines). Their own test suites ran clean on my machine, 34 and 31 tests.\n\nThe part that caught my eye is the proportion. Of the six phases, two exist only to disprove: Phase 3 hands every candidate to a fresh agent told to refute it, and Phase 5 sends another fresh agent over the final record (the quick profile merges the two). Each Phase 3 verifier prompt carries about 17.5 KB of fixed text before it sees the candidate: a six-step refutation prompt, a file-promotion procedure, and three schema branches copied in verbatim.\n\nI did not install the skill, and no agent ran it. The README's install path is a skills CLI that writes into your agent's config, and I don't do that for a post.\n\nInstead I wrote a small target, played the hunter by hand, and ran the skill's real validator on the result. The target is a notes service on `node:http` and `node:sqlite` with dummy users alice and bob. These are the two functions that matter:\n\n``` js\nfunction search(user, q) {\n  const sql = \"SELECT id, title FROM notes WHERE owner = '\" + user + \"' AND title LIKE '%\" + q + \"%'\";\n  return db.prepare(sql).all();\n}\n\nfunction getNote(user, id) {\n  return db.prepare('SELECT id, title, body FROM notes WHERE id = ? AND owner = ?').get(id, user);\n}\n```\n\nThere is also an `/admin/export` route gated on `X-Forwarded-For` starting with `10.`.\n\nThen I wrote four findings the way a hunter returns them, in the skill's schema:\n\n`search`. Real.` getNote`, marked confirmed, with an observed result claiming it returned bob's note. False.`findUser` at `src/users.js` line 212. Invented: that file does not exist.\n\n``` bash\n$ node validate-findings.cjs proposed.json\nPASS: 4 findings valid\n```\n\nTo be fair to it, it does enforce the contract. When I raised one record's overall severity above its impact, or put a severity on the `needs_validation` record, it failed, with exact paths. It checks shape, ordering, safe relative paths, and that a confirmed record has an observed result written in visible text.\n\nIt never opens a source file. It doesn't take the target path at all. The skill says this itself, in one line of Phase 4: validator success \"proves format and ledger consistency only.\" A hunter's observed result is a string, and my invented one was schema-valid.\n\nNext I wrote 15 lines of Node that open every cited file and check the line exists:\n\n```\nok   admin/export-xff-gate\nok   notes/note-sql-concat\nok   notes/search-sql-concat\nFAIL users/find-user-template-sql  src/routes/login.js: no such file; src/users.js: no such file\n```\n\nThat kills the invented file. Cloudflare's harness has the same idea in plain code: per the blog, deterministic code confirms the cited files and paths exist before a finding moves on.\n\nIt does nothing for the `getNote` claim. Line 24 is real, and it really runs a query with the id in it. Every fact the record cites is true except the one that matters.\n\nSo I walked the Phase 3 prompt by hand. Step 2 says to rebuild the strongest control on the path. For `getNote` that takes one read: `id` and `owner` are bound as placeholders. Step 3 says to reproduce the minimum result yourself. My service is my own code with dummy data, so I ran it on loopback (the skill demands an OS-enforced sandbox before it runs anyone else's target; this was not that). Output trimmed:\n\n```\nsearch, normal     200 [{\"id\":1,\"title\":\"groceries\"}]\nsearch, payload    200 [{\"id\":1,\"title\":\"groceries\"},{\"id\":2,\"title\":\"payroll\"}]\nnote, bob id       200 null\nnote, payload      200 null\nnote, payload 2    200 null\nexport, XFF 10.    200 [... both users' notes ...]\n```\n\nThe search payload `' OR 1=1 --` hands alice the title of bob's note. Every `getNote` payload returns nothing. The export gate opens to a header alone, but whether a client can send that header in production depends on a proxy the repo doesn't contain, so it stays `needs_validation` with an owner check attached. That is the skill's rule for a deployment fact, and I agree with it.\n\nThe final file: one confirmed, one needs validation, two rejected.\n\n``` bash\n$ node validate-findings.cjs findings.json\nPASS: 4 findings valid\n```\n\nSame validator, same four fingerprints, same PASS. In between, two of the three \"confirmed\" bugs became rejected. Only reading the code at the cited line and running the payload told the files apart.\n\nFinding candidates is cheap now. Cloudflare's blog reports 20,799 raw candidates from their discovery harness, of which about 12,057 survived validation, and an early validation rejection rate of 40% before better recon brought it down to 11%. At 40%, a team without a refuter gets four bad tickets in every ten. Somebody pays for those, and it's usually a maintainer.\n\nThat is also the theme of OpenAI's \"Patch the Planet\" post on coordinating disclosure as agents surface critical bugs, which was on the Hacker News front page the same day.\n\nSo my claim: when you judge an AI security tool, judge the step that tries to prove the finding wrong. The hunt is a hypothesis generator. The product is the record that survived.\n\nCost is real. In the [Hacker News thread](https://news.ycombinator.com/item?id=49736466), jesse_dot_id reported at least 150k tokens on a fairly small FastAPI project before hitting a session limit. Issue [#20](https://github.com/cloudflare/security-audit-skill/issues/20) is a blind test on one seeded target, three rounds per arm, by a team with its own competing tool, which it discloses. There the quick profile cost a median $29.95 against $2.06 for one plain agent, and found the same share of the seeded bugs (a median recall of 0.467 for both). On a small, clean target the skeptic may have nothing to kill.\n\nAnd a skeptic that shares the hunter's model is not fully independent. Per that same issue, the skill's verifiers inherit the session's model. Cloudflare's own pipeline triages with a different model from the one that hunts. Fresh context is not a different mind.\n\nIf you run AI reviews on your code today, what does the step that tries to disprove a finding look like: a second model, a test that must fail before the fix, or you?", "url": "https://wpnews.pro/news/in-an-ai-security-audit-the-skeptic-is-the-product", "canonical_source": "https://dev.to/ianwieds/in-an-ai-security-audit-the-skeptic-is-the-product-4fl4", "published_at": "2026-10-22 18:00:00+00:00", "updated_at": "2026-10-22 18:11:38.504837+00:00", "lang": "en", "topics": ["ai-agents", "ai-safety", "ai-tools", "developer-tools"], "entities": ["Cloudflare", "security-audit-skill", "validate-findings.cjs", "validate-coverage-ledger.cjs", "GitHub", "Node.js"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/in-an-ai-security-audit-the-skeptic-is-the-product", "markdown": "https://wpnews.pro/news/in-an-ai-security-audit-the-skeptic-is-the-product.md", "text": "https://wpnews.pro/news/in-an-ai-security-audit-the-skeptic-is-the-product.txt", "jsonld": "https://wpnews.pro/news/in-an-ai-security-audit-the-skeptic-is-the-product.jsonld"}}