Drive Rules Desk from your own code
Everything the page does over the network is available over HTTP. The natural uses are a CI job
that audits firestore.rules on every pull request and fails the build on a
critical finding, a pre-deploy gate that refuses a ruleset whose verdict is
open, and a migration script that runs the harden stage over a directory of
projects and opens a pull request per repository with the rewritten file.
One thing to know before you start: the free part is not on this API. The
parser, the coverage table and all twenty-four prescan checks run in the browser, in
/ruleslint.js — a dependency-free module you can read, vendor
and run under Node with global.window = {}. The API is for the three metered
stages. Send the prescan output with the run, or the reply has nothing to be accountable to.
The task field comes first
This app has one endpoint and three stages. Every run input carries a task field,
and it decides which contract comes back. Send it explicitly — if it is missing the model
picks the closest stage and names its choice in notes, which is a fallback, not a
feature.
task | What it does | Required fields | Body keys in the reply |
|---|---|---|---|
audit | Judges the rules as deployed | rules, surface, data_sensitivity, prescan | verdict, deciding_factor, paths, findings, checks |
claims | Works out the token the rules assume | rules, prescan; providers optional | identity_model, claims, gaps, steps, risks |
harden | Rewrites the whole file | rules, posture, prescan; keep_public, audit_refs optional | rules_file, posture_applied, changes, kept, tests, residual_risk |
The envelope, and the errors
Every response is {"data": ...} or {"error": {"code", "message", "details"}}
with an HTTP status that matches. The model's own reply is a JSON string inside
data.output.output — parse it, then expect the shared envelope
(lane, title, headline, coverage,
notes, warnings) with one stage body merged in.
| Status | error.code | What to do |
|---|---|---|
| 400 | VALIDATION_ERROR | The input shape is wrong. details names the field. |
| 401 | UNAUTHORIZED | No token, or an expired one. Mint a new one at /tokens.html. |
| 402 | INSUFFICIENT_CREDITS | Call /estimate first and compare hold_credits with the balance from /me. |
| 404 | NOT_FOUND | Wrong slug in the host header, or a job id that never existed. |
| 429 | RATE_LIMITED | Back off. Never tight-loop a retry. |
| 5xx | INTERNAL | Retry once with the SAME Idempotency-Key, so a completed run is not billed twice. |
1. Get a token
A guest token is enough for /me and /estimate. The three stages are metered, so they need a personal token — sign in and copy it from /tokens.html, which shows the token this browser already holds without opening a developer console.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/guest" \
-H "Authorization: Bearer none-needed" \
-H "Content-Type: application/json" \
-d '{}'
import json, urllib.request
TOKEN = "none-needed" # from /tokens.html
payload = {}
req = urllib.request.Request(
"https://api.skillsafe.ai/v1/app-api/guest",
data=json.dumps(payload).encode(),
headers={"Authorization": "Bearer " + TOKEN,
"Content-Type": "application/json"},
method="POST")
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"])
const TOKEN = "none-needed";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/guest", {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({})
});
const { data, error } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
console.log(data);
token := "none-needed"
payload := []byte(`{}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/guest", bytes.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer resp.Body.Close()
io.Copy(os.Stdout, resp.Body)
String token = "none-needed";
HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/guest"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.post(HttpRequest.BodyPublishers.ofString(
"""
{}
"""))
.build();
System.out.println(client.send(req, HttpResponse.BodyHandlers.ofString()).body());
require "net/http"
require "json"
token = "none-needed"
uri = URI("https://api.skillsafe.ai/v1/app-api/guest")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{token}"
req["Content-Type"] = "application/json"
req.body = {}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]
<?php
$token = "none-needed";
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/guest");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, '{}');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $token",
"Content-Type: application/json"
]);
$out = json_decode(curl_exec($ch), true);
print_r($out["data"]);
var token = "none-needed";
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var json = @"{}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
var res = await (await client.PostAsync("https://api.skillsafe.ai/v1/app-api/guest", content)).Content.ReadAsStringAsync();
Console.WriteLine(res);
2. Check who you are and what you can spend
/me tells you whether the token is a guest or a person, and what the balance is. Compare it with hold_credits before you run anything.
curl -s -X GET "https://api.skillsafe.ai/v1/app-api/me" \
-H "Authorization: Bearer YOUR_TOKEN"
import json, urllib.request
TOKEN = "YOUR_TOKEN" # from /tokens.html
req = urllib.request.Request("https://api.skillsafe.ai/v1/app-api/me",
headers={"Authorization": "Bearer " + TOKEN})
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"])
const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/me", {
method: "GET",
headers: { Authorization: `Bearer ${TOKEN}` }
});
const { data, error } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
console.log(data);
token := "YOUR_TOKEN"
req, _ := http.NewRequest("GET", "https://api.skillsafe.ai/v1/app-api/me", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer resp.Body.Close()
io.Copy(os.Stdout, resp.Body)
String token = "YOUR_TOKEN";
HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/me"))
.header("Authorization", "Bearer " + token)
.GET()
.build();
System.out.println(client.send(req, HttpResponse.BodyHandlers.ofString()).body());
require "net/http"
require "json"
token = "YOUR_TOKEN"
uri = URI("https://api.skillsafe.ai/v1/app-api/me")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{token}"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]
<?php
$token = "YOUR_TOKEN";
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/me");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $token"]);
$out = json_decode(curl_exec($ch), true);
print_r($out["data"]);
var token = "YOUR_TOKEN";
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var res = await client.GetStringAsync("https://api.skillsafe.ai/v1/app-api/me");
Console.WriteLine(res);
3. Price the run — free, no job created
Send the exact input you intend to run. The reply carries model, model_alias, markup_bps, hold_credits, min_credits and sponsor_enabled. Re-estimate whenever the task changes.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/estimate" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"task": "audit", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "surface": "production", "data_sensitivity": "personal", "notes": "Anonymous sign-in is enabled.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}}'
import json, urllib.request
TOKEN = "YOUR_TOKEN" # from /tokens.html
payload = {
"task": "audit",
"rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}",
"surface": "production",
"data_sensitivity": "personal",
"notes": "Anonymous sign-in is enabled.",
"prescan": {
"verdict": "leaky",
"stats": {
"lines": 8,
"rules_version": "2",
"match_blocks": 2,
"granting_blocks": 1,
"allow_statements": 1,
"helpers": 0,
"public_paths": 0,
"recursive_paths": 0,
"custom_claims": []
},
"coverage": [
{
"path": "/databases/{database}/documents/notes/{id}",
"ops": "get,list,create,update,delete",
"gate": "signed-in",
"line": 4
}
],
"findings": [
{
"ref": "FR-06#1",
"id": "FR-06",
"severity": "high",
"title": "Any signed-in user can write",
"path": "/databases/{database}/documents/notes/{id}",
"line": 5
},
{
"ref": "FR-11#2",
"id": "FR-11",
"severity": "medium",
"title": "read and write used instead of the granular methods",
"path": "/databases/{database}/documents/notes/{id}",
"line": 5
}
]
}
}
req = urllib.request.Request(
"https://api.skillsafe.ai/v1/app-api/estimate",
data=json.dumps(payload).encode(),
headers={"Authorization": "Bearer " + TOKEN,
"Content-Type": "application/json"},
method="POST")
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"])
const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/estimate", {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({"task": "audit", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "surface": "production", "data_sensitivity": "personal", "notes": "Anonymous sign-in is enabled.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}})
});
const { data, error } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
console.log(data);
token := "YOUR_TOKEN"
payload := []byte(`{"task": "audit", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "surface": "production", "data_sensitivity": "personal", "notes": "Anonymous sign-in is enabled.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/estimate", bytes.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer resp.Body.Close()
io.Copy(os.Stdout, resp.Body)
String token = "YOUR_TOKEN";
HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/estimate"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.post(HttpRequest.BodyPublishers.ofString(
"""
{"task": "audit", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "surface": "production", "data_sensitivity": "personal", "notes": "Anonymous sign-in is enabled.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}}
"""))
.build();
System.out.println(client.send(req, HttpResponse.BodyHandlers.ofString()).body());
require "net/http"
require "json"
token = "YOUR_TOKEN"
uri = URI("https://api.skillsafe.ai/v1/app-api/estimate")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{token}"
req["Content-Type"] = "application/json"
req.body = {"task": "audit", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "surface": "production", "data_sensitivity": "personal", "notes": "Anonymous sign-in is enabled.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]
<?php
$token = "YOUR_TOKEN";
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/estimate");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"task": "audit", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "surface": "production", "data_sensitivity": "personal", "notes": "Anonymous sign-in is enabled.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}}');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $token",
"Content-Type: application/json"
]);
$out = json_decode(curl_exec($ch), true);
print_r($out["data"]);
var token = "YOUR_TOKEN";
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var json = @"{""task"": ""audit"", ""rules"": ""rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}"", ""surface"": ""production"", ""data_sensitivity"": ""personal"", ""notes"": ""Anonymous sign-in is enabled."", ""prescan"": {""verdict"": ""leaky"", ""stats"": {""lines"": 8, ""rules_version"": ""2"", ""match_blocks"": 2, ""granting_blocks"": 1, ""allow_statements"": 1, ""helpers"": 0, ""public_paths"": 0, ""recursive_paths"": 0, ""custom_claims"": []}, ""coverage"": [{""path"": ""/databases/{database}/documents/notes/{id}"", ""ops"": ""get,list,create,update,delete"", ""gate"": ""signed-in"", ""line"": 4}], ""findings"": [{""ref"": ""FR-06#1"", ""id"": ""FR-06"", ""severity"": ""high"", ""title"": ""Any signed-in user can write"", ""path"": ""/databases/{database}/documents/notes/{id}"", ""line"": 5}, {""ref"": ""FR-11#2"", ""id"": ""FR-11"", ""severity"": ""medium"", ""title"": ""read and write used instead of the granular methods"", ""path"": ""/databases/{database}/documents/notes/{id}"", ""line"": 5}]}}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
var res = await (await client.PostAsync("https://api.skillsafe.ai/v1/app-api/estimate", content)).Content.ReadAsStringAsync();
Console.WriteLine(res);
4. Run the audit stage, then poll
/run returns a job_id immediately. Poll /jobs/{id} until status is succeeded or failed; the model's text is data.output.output.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/run" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"task": "audit", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "surface": "production", "data_sensitivity": "personal", "notes": "Anonymous sign-in is enabled.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}}'
import json, urllib.request
TOKEN = "YOUR_TOKEN" # from /tokens.html
payload = {
"task": "audit",
"rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}",
"surface": "production",
"data_sensitivity": "personal",
"notes": "Anonymous sign-in is enabled.",
"prescan": {
"verdict": "leaky",
"stats": {
"lines": 8,
"rules_version": "2",
"match_blocks": 2,
"granting_blocks": 1,
"allow_statements": 1,
"helpers": 0,
"public_paths": 0,
"recursive_paths": 0,
"custom_claims": []
},
"coverage": [
{
"path": "/databases/{database}/documents/notes/{id}",
"ops": "get,list,create,update,delete",
"gate": "signed-in",
"line": 4
}
],
"findings": [
{
"ref": "FR-06#1",
"id": "FR-06",
"severity": "high",
"title": "Any signed-in user can write",
"path": "/databases/{database}/documents/notes/{id}",
"line": 5
},
{
"ref": "FR-11#2",
"id": "FR-11",
"severity": "medium",
"title": "read and write used instead of the granular methods",
"path": "/databases/{database}/documents/notes/{id}",
"line": 5
}
]
}
}
req = urllib.request.Request(
"https://api.skillsafe.ai/v1/app-api/run",
data=json.dumps(payload).encode(),
headers={"Authorization": "Bearer " + TOKEN,
"Content-Type": "application/json"},
method="POST")
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"])
const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/run", {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({"task": "audit", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "surface": "production", "data_sensitivity": "personal", "notes": "Anonymous sign-in is enabled.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}})
});
const { data, error } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
console.log(data);
token := "YOUR_TOKEN"
payload := []byte(`{"task": "audit", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "surface": "production", "data_sensitivity": "personal", "notes": "Anonymous sign-in is enabled.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/run", bytes.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer resp.Body.Close()
io.Copy(os.Stdout, resp.Body)
String token = "YOUR_TOKEN";
HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/run"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.post(HttpRequest.BodyPublishers.ofString(
"""
{"task": "audit", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "surface": "production", "data_sensitivity": "personal", "notes": "Anonymous sign-in is enabled.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}}
"""))
.build();
System.out.println(client.send(req, HttpResponse.BodyHandlers.ofString()).body());
require "net/http"
require "json"
token = "YOUR_TOKEN"
uri = URI("https://api.skillsafe.ai/v1/app-api/run")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{token}"
req["Content-Type"] = "application/json"
req.body = {"task": "audit", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "surface": "production", "data_sensitivity": "personal", "notes": "Anonymous sign-in is enabled.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]
<?php
$token = "YOUR_TOKEN";
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/run");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"task": "audit", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "surface": "production", "data_sensitivity": "personal", "notes": "Anonymous sign-in is enabled.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}}');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $token",
"Content-Type: application/json"
]);
$out = json_decode(curl_exec($ch), true);
print_r($out["data"]);
var token = "YOUR_TOKEN";
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var json = @"{""task"": ""audit"", ""rules"": ""rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}"", ""surface"": ""production"", ""data_sensitivity"": ""personal"", ""notes"": ""Anonymous sign-in is enabled."", ""prescan"": {""verdict"": ""leaky"", ""stats"": {""lines"": 8, ""rules_version"": ""2"", ""match_blocks"": 2, ""granting_blocks"": 1, ""allow_statements"": 1, ""helpers"": 0, ""public_paths"": 0, ""recursive_paths"": 0, ""custom_claims"": []}, ""coverage"": [{""path"": ""/databases/{database}/documents/notes/{id}"", ""ops"": ""get,list,create,update,delete"", ""gate"": ""signed-in"", ""line"": 4}], ""findings"": [{""ref"": ""FR-06#1"", ""id"": ""FR-06"", ""severity"": ""high"", ""title"": ""Any signed-in user can write"", ""path"": ""/databases/{database}/documents/notes/{id}"", ""line"": 5}, {""ref"": ""FR-11#2"", ""id"": ""FR-11"", ""severity"": ""medium"", ""title"": ""read and write used instead of the granular methods"", ""path"": ""/databases/{database}/documents/notes/{id}"", ""line"": 5}]}}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
var res = await (await client.PostAsync("https://api.skillsafe.ai/v1/app-api/run", content)).Content.ReadAsStringAsync();
Console.WriteLine(res);
Then poll the job:
curl -s -X GET "https://api.skillsafe.ai/v1/app-api/jobs/job_REPLACE_WITH_ID" \
-H "Authorization: Bearer YOUR_TOKEN"
import json, urllib.request
TOKEN = "YOUR_TOKEN" # from /tokens.html
req = urllib.request.Request("https://api.skillsafe.ai/v1/app-api/jobs/job_REPLACE_WITH_ID",
headers={"Authorization": "Bearer " + TOKEN})
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"])
const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/jobs/job_REPLACE_WITH_ID", {
method: "GET",
headers: { Authorization: `Bearer ${TOKEN}` }
});
const { data, error } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
console.log(data);
token := "YOUR_TOKEN"
req, _ := http.NewRequest("GET", "https://api.skillsafe.ai/v1/app-api/jobs/job_REPLACE_WITH_ID", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer resp.Body.Close()
io.Copy(os.Stdout, resp.Body)
String token = "YOUR_TOKEN";
HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/jobs/job_REPLACE_WITH_ID"))
.header("Authorization", "Bearer " + token)
.GET()
.build();
System.out.println(client.send(req, HttpResponse.BodyHandlers.ofString()).body());
require "net/http"
require "json"
token = "YOUR_TOKEN"
uri = URI("https://api.skillsafe.ai/v1/app-api/jobs/job_REPLACE_WITH_ID")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{token}"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]
<?php
$token = "YOUR_TOKEN";
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/jobs/job_REPLACE_WITH_ID");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $token"]);
$out = json_decode(curl_exec($ch), true);
print_r($out["data"]);
var token = "YOUR_TOKEN";
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var res = await client.GetStringAsync("https://api.skillsafe.ai/v1/app-api/jobs/job_REPLACE_WITH_ID");
Console.WriteLine(res);
5. Stream it instead
/run-stream is server-sent events. The page uses it so the progress card can advance on section keys arriving in the delta stream rather than on a timer. Same body, same idempotency rules.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/run-stream" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"task": "audit", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "surface": "production", "data_sensitivity": "personal", "notes": "Anonymous sign-in is enabled.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}}'
import json, urllib.request
TOKEN = "YOUR_TOKEN" # from /tokens.html
payload = {
"task": "audit",
"rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}",
"surface": "production",
"data_sensitivity": "personal",
"notes": "Anonymous sign-in is enabled.",
"prescan": {
"verdict": "leaky",
"stats": {
"lines": 8,
"rules_version": "2",
"match_blocks": 2,
"granting_blocks": 1,
"allow_statements": 1,
"helpers": 0,
"public_paths": 0,
"recursive_paths": 0,
"custom_claims": []
},
"coverage": [
{
"path": "/databases/{database}/documents/notes/{id}",
"ops": "get,list,create,update,delete",
"gate": "signed-in",
"line": 4
}
],
"findings": [
{
"ref": "FR-06#1",
"id": "FR-06",
"severity": "high",
"title": "Any signed-in user can write",
"path": "/databases/{database}/documents/notes/{id}",
"line": 5
},
{
"ref": "FR-11#2",
"id": "FR-11",
"severity": "medium",
"title": "read and write used instead of the granular methods",
"path": "/databases/{database}/documents/notes/{id}",
"line": 5
}
]
}
}
req = urllib.request.Request(
"https://api.skillsafe.ai/v1/app-api/run-stream",
data=json.dumps(payload).encode(),
headers={"Authorization": "Bearer " + TOKEN,
"Content-Type": "application/json"},
method="POST")
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"])
const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/run-stream", {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({"task": "audit", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "surface": "production", "data_sensitivity": "personal", "notes": "Anonymous sign-in is enabled.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}})
});
const { data, error } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
console.log(data);
token := "YOUR_TOKEN"
payload := []byte(`{"task": "audit", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "surface": "production", "data_sensitivity": "personal", "notes": "Anonymous sign-in is enabled.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/run-stream", bytes.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer resp.Body.Close()
io.Copy(os.Stdout, resp.Body)
String token = "YOUR_TOKEN";
HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/run-stream"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.post(HttpRequest.BodyPublishers.ofString(
"""
{"task": "audit", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "surface": "production", "data_sensitivity": "personal", "notes": "Anonymous sign-in is enabled.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}}
"""))
.build();
System.out.println(client.send(req, HttpResponse.BodyHandlers.ofString()).body());
require "net/http"
require "json"
token = "YOUR_TOKEN"
uri = URI("https://api.skillsafe.ai/v1/app-api/run-stream")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{token}"
req["Content-Type"] = "application/json"
req.body = {"task": "audit", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "surface": "production", "data_sensitivity": "personal", "notes": "Anonymous sign-in is enabled.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]
<?php
$token = "YOUR_TOKEN";
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/run-stream");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"task": "audit", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "surface": "production", "data_sensitivity": "personal", "notes": "Anonymous sign-in is enabled.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}}');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $token",
"Content-Type: application/json"
]);
$out = json_decode(curl_exec($ch), true);
print_r($out["data"]);
var token = "YOUR_TOKEN";
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var json = @"{""task"": ""audit"", ""rules"": ""rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}"", ""surface"": ""production"", ""data_sensitivity"": ""personal"", ""notes"": ""Anonymous sign-in is enabled."", ""prescan"": {""verdict"": ""leaky"", ""stats"": {""lines"": 8, ""rules_version"": ""2"", ""match_blocks"": 2, ""granting_blocks"": 1, ""allow_statements"": 1, ""helpers"": 0, ""public_paths"": 0, ""recursive_paths"": 0, ""custom_claims"": []}, ""coverage"": [{""path"": ""/databases/{database}/documents/notes/{id}"", ""ops"": ""get,list,create,update,delete"", ""gate"": ""signed-in"", ""line"": 4}], ""findings"": [{""ref"": ""FR-06#1"", ""id"": ""FR-06"", ""severity"": ""high"", ""title"": ""Any signed-in user can write"", ""path"": ""/databases/{database}/documents/notes/{id}"", ""line"": 5}, {""ref"": ""FR-11#2"", ""id"": ""FR-11"", ""severity"": ""medium"", ""title"": ""read and write used instead of the granular methods"", ""path"": ""/databases/{database}/documents/notes/{id}"", ""line"": 5}]}}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
var res = await (await client.PostAsync("https://api.skillsafe.ai/v1/app-api/run-stream", content)).Content.ReadAsStringAsync();
Console.WriteLine(res);
6. The claims stage
Same rules file, different contract. providers is a free-text list of the sign-in methods the project allows; leaving it out makes the model infer and say that it inferred.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/run" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"task": "claims", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "notes": "There is no admin yet.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}, "providers": "password, anonymous"}'
import json, urllib.request
TOKEN = "YOUR_TOKEN" # from /tokens.html
payload = {
"task": "claims",
"rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}",
"notes": "There is no admin yet.",
"prescan": {
"verdict": "leaky",
"stats": {
"lines": 8,
"rules_version": "2",
"match_blocks": 2,
"granting_blocks": 1,
"allow_statements": 1,
"helpers": 0,
"public_paths": 0,
"recursive_paths": 0,
"custom_claims": []
},
"coverage": [
{
"path": "/databases/{database}/documents/notes/{id}",
"ops": "get,list,create,update,delete",
"gate": "signed-in",
"line": 4
}
],
"findings": [
{
"ref": "FR-06#1",
"id": "FR-06",
"severity": "high",
"title": "Any signed-in user can write",
"path": "/databases/{database}/documents/notes/{id}",
"line": 5
},
{
"ref": "FR-11#2",
"id": "FR-11",
"severity": "medium",
"title": "read and write used instead of the granular methods",
"path": "/databases/{database}/documents/notes/{id}",
"line": 5
}
]
},
"providers": "password, anonymous"
}
req = urllib.request.Request(
"https://api.skillsafe.ai/v1/app-api/run",
data=json.dumps(payload).encode(),
headers={"Authorization": "Bearer " + TOKEN,
"Content-Type": "application/json"},
method="POST")
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"])
const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/run", {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({"task": "claims", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "notes": "There is no admin yet.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}, "providers": "password, anonymous"})
});
const { data, error } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
console.log(data);
token := "YOUR_TOKEN"
payload := []byte(`{"task": "claims", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "notes": "There is no admin yet.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}, "providers": "password, anonymous"}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/run", bytes.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer resp.Body.Close()
io.Copy(os.Stdout, resp.Body)
String token = "YOUR_TOKEN";
HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/run"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.post(HttpRequest.BodyPublishers.ofString(
"""
{"task": "claims", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "notes": "There is no admin yet.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}, "providers": "password, anonymous"}
"""))
.build();
System.out.println(client.send(req, HttpResponse.BodyHandlers.ofString()).body());
require "net/http"
require "json"
token = "YOUR_TOKEN"
uri = URI("https://api.skillsafe.ai/v1/app-api/run")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{token}"
req["Content-Type"] = "application/json"
req.body = {"task": "claims", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "notes": "There is no admin yet.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}, "providers": "password, anonymous"}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]
<?php
$token = "YOUR_TOKEN";
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/run");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"task": "claims", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "notes": "There is no admin yet.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}, "providers": "password, anonymous"}');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $token",
"Content-Type: application/json"
]);
$out = json_decode(curl_exec($ch), true);
print_r($out["data"]);
var token = "YOUR_TOKEN";
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var json = @"{""task"": ""claims"", ""rules"": ""rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}"", ""notes"": ""There is no admin yet."", ""prescan"": {""verdict"": ""leaky"", ""stats"": {""lines"": 8, ""rules_version"": ""2"", ""match_blocks"": 2, ""granting_blocks"": 1, ""allow_statements"": 1, ""helpers"": 0, ""public_paths"": 0, ""recursive_paths"": 0, ""custom_claims"": []}, ""coverage"": [{""path"": ""/databases/{database}/documents/notes/{id}"", ""ops"": ""get,list,create,update,delete"", ""gate"": ""signed-in"", ""line"": 4}], ""findings"": [{""ref"": ""FR-06#1"", ""id"": ""FR-06"", ""severity"": ""high"", ""title"": ""Any signed-in user can write"", ""path"": ""/databases/{database}/documents/notes/{id}"", ""line"": 5}, {""ref"": ""FR-11#2"", ""id"": ""FR-11"", ""severity"": ""medium"", ""title"": ""read and write used instead of the granular methods"", ""path"": ""/databases/{database}/documents/notes/{id}"", ""line"": 5}]}, ""providers"": ""password, anonymous""}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
var res = await (await client.PostAsync("https://api.skillsafe.ai/v1/app-api/run", content)).Content.ReadAsStringAsync();
Console.WriteLine(res);
7. The harden stage
Returns the complete rewritten file in rules_file — never a diff, never a fragment. audit_refs carries the finding ids from an earlier audit so the rewrite has to answer them; keep_public names paths that must stay readable.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/run" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"task": "harden", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "notes": "Notes belong to whoever created them; the field is ownerId.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}, "posture": "strict", "keep_public": "", "audit_refs": ["FR-06#1", "RD-01"]}'
import json, urllib.request
TOKEN = "YOUR_TOKEN" # from /tokens.html
payload = {
"task": "harden",
"rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}",
"notes": "Notes belong to whoever created them; the field is ownerId.",
"prescan": {
"verdict": "leaky",
"stats": {
"lines": 8,
"rules_version": "2",
"match_blocks": 2,
"granting_blocks": 1,
"allow_statements": 1,
"helpers": 0,
"public_paths": 0,
"recursive_paths": 0,
"custom_claims": []
},
"coverage": [
{
"path": "/databases/{database}/documents/notes/{id}",
"ops": "get,list,create,update,delete",
"gate": "signed-in",
"line": 4
}
],
"findings": [
{
"ref": "FR-06#1",
"id": "FR-06",
"severity": "high",
"title": "Any signed-in user can write",
"path": "/databases/{database}/documents/notes/{id}",
"line": 5
},
{
"ref": "FR-11#2",
"id": "FR-11",
"severity": "medium",
"title": "read and write used instead of the granular methods",
"path": "/databases/{database}/documents/notes/{id}",
"line": 5
}
]
},
"posture": "strict",
"keep_public": "",
"audit_refs": [
"FR-06#1",
"RD-01"
]
}
req = urllib.request.Request(
"https://api.skillsafe.ai/v1/app-api/run",
data=json.dumps(payload).encode(),
headers={"Authorization": "Bearer " + TOKEN,
"Content-Type": "application/json"},
method="POST")
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"])
const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/run", {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({"task": "harden", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "notes": "Notes belong to whoever created them; the field is ownerId.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}, "posture": "strict", "keep_public": "", "audit_refs": ["FR-06#1", "RD-01"]})
});
const { data, error } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
console.log(data);
token := "YOUR_TOKEN"
payload := []byte(`{"task": "harden", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "notes": "Notes belong to whoever created them; the field is ownerId.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}, "posture": "strict", "keep_public": "", "audit_refs": ["FR-06#1", "RD-01"]}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/run", bytes.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer resp.Body.Close()
io.Copy(os.Stdout, resp.Body)
String token = "YOUR_TOKEN";
HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/run"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.post(HttpRequest.BodyPublishers.ofString(
"""
{"task": "harden", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "notes": "Notes belong to whoever created them; the field is ownerId.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}, "posture": "strict", "keep_public": "", "audit_refs": ["FR-06#1", "RD-01"]}
"""))
.build();
System.out.println(client.send(req, HttpResponse.BodyHandlers.ofString()).body());
require "net/http"
require "json"
token = "YOUR_TOKEN"
uri = URI("https://api.skillsafe.ai/v1/app-api/run")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{token}"
req["Content-Type"] = "application/json"
req.body = {"task": "harden", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "notes": "Notes belong to whoever created them; the field is ownerId.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}, "posture": "strict", "keep_public": "", "audit_refs": ["FR-06#1", "RD-01"]}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]
<?php
$token = "YOUR_TOKEN";
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/run");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"task": "harden", "rules": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}", "notes": "Notes belong to whoever created them; the field is ownerId.", "prescan": {"verdict": "leaky", "stats": {"lines": 8, "rules_version": "2", "match_blocks": 2, "granting_blocks": 1, "allow_statements": 1, "helpers": 0, "public_paths": 0, "recursive_paths": 0, "custom_claims": []}, "coverage": [{"path": "/databases/{database}/documents/notes/{id}", "ops": "get,list,create,update,delete", "gate": "signed-in", "line": 4}], "findings": [{"ref": "FR-06#1", "id": "FR-06", "severity": "high", "title": "Any signed-in user can write", "path": "/databases/{database}/documents/notes/{id}", "line": 5}, {"ref": "FR-11#2", "id": "FR-11", "severity": "medium", "title": "read and write used instead of the granular methods", "path": "/databases/{database}/documents/notes/{id}", "line": 5}]}, "posture": "strict", "keep_public": "", "audit_refs": ["FR-06#1", "RD-01"]}');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $token",
"Content-Type: application/json"
]);
$out = json_decode(curl_exec($ch), true);
print_r($out["data"]);
var token = "YOUR_TOKEN";
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var json = @"{""task"": ""harden"", ""rules"": ""rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /notes/{id} {\n allow read, write: if request.auth != null;\n }\n }\n}"", ""notes"": ""Notes belong to whoever created them; the field is ownerId."", ""prescan"": {""verdict"": ""leaky"", ""stats"": {""lines"": 8, ""rules_version"": ""2"", ""match_blocks"": 2, ""granting_blocks"": 1, ""allow_statements"": 1, ""helpers"": 0, ""public_paths"": 0, ""recursive_paths"": 0, ""custom_claims"": []}, ""coverage"": [{""path"": ""/databases/{database}/documents/notes/{id}"", ""ops"": ""get,list,create,update,delete"", ""gate"": ""signed-in"", ""line"": 4}], ""findings"": [{""ref"": ""FR-06#1"", ""id"": ""FR-06"", ""severity"": ""high"", ""title"": ""Any signed-in user can write"", ""path"": ""/databases/{database}/documents/notes/{id}"", ""line"": 5}, {""ref"": ""FR-11#2"", ""id"": ""FR-11"", ""severity"": ""medium"", ""title"": ""read and write used instead of the granular methods"", ""path"": ""/databases/{database}/documents/notes/{id}"", ""line"": 5}]}, ""posture"": ""strict"", ""keep_public"": """", ""audit_refs"": [""FR-06#1"", ""RD-01""]}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
var res = await (await client.PostAsync("https://api.skillsafe.ai/v1/app-api/run", content)).Content.ReadAsStringAsync();
Console.WriteLine(res);
8. Parse the reply
Strip an accidental code fence, take the first { to the last }, and
JSON.parse it — exactly what /app.js does in
parseResult. Then normalise: every array key is guaranteed present by the prompt,
but a defensive reader treats a missing key as an empty array rather than throwing.
The coverage array is the part worth checking in CI. It carries one entry per
prescan.findings[].ref you sent, with status of confirmed
or set-aside. A ref that comes back with neither was never answered, and that is
the signal that the review skipped something.
# Pull the verdict and the unanswered refs out of a finished job with jq.
JOB=$(curl -s "https://api.skillsafe.ai/v1/app-api/jobs/$JOB_ID" \
-H "Authorization: Bearer YOUR_TOKEN")
echo "$JOB" | jq -r '.data.output.output' | jq -r '.verdict, .deciding_factor'
echo "$JOB" | jq -r '.data.output.output' | jq -r '.coverage[] | "\(.ref) \(.status)"'
import json
def parse_reply(text):
t = text.strip()
if t.startswith("```"):
t = t.split("\n", 1)[1].rsplit("```", 1)[0]
return json.loads(t[t.index("{"): t.rindex("}") + 1])
reply = parse_reply(job["output"]["output"])
sent = {f["ref"] for f in payload["prescan"]["findings"]}
answered = {c["ref"] for c in reply.get("coverage", [])}
missing = sent - answered
if missing:
raise SystemExit(f"review never answered: {sorted(missing)}")
if reply["lane"] == "audit" and reply["verdict"] in ("open", "leaky"):
raise SystemExit(reply["deciding_factor"])
function parseReply(text) {
let t = String(text).trim().replace(/^```[a-z]*\s*/i, "").replace(/```\s*$/, "");
return JSON.parse(t.slice(t.indexOf("{"), t.lastIndexOf("}") + 1));
}
const reply = parseReply(job.output.output);
const sent = new Set(payload.prescan.findings.map(f => f.ref));
const answered = new Set((reply.coverage || []).map(c => c.ref));
const missing = [...sent].filter(r => !answered.has(r));
if (missing.length) throw new Error(`review never answered: ${missing.join(", ")}`);
if (reply.lane === "audit" && ["open", "leaky"].includes(reply.verdict)) {
process.exitCode = 1;
console.error(reply.deciding_factor);
}
type Reply struct {
Lane string `json:"lane"`
Verdict string `json:"verdict"`
DecidingFactor string `json:"deciding_factor"`
Coverage []struct {
Ref string `json:"ref"`
Status string `json:"status"`
} `json:"coverage"`
}
raw := job.Output.Output
raw = raw[strings.Index(raw, "{") : strings.LastIndex(raw, "}")+1]
var reply Reply
if err := json.Unmarshal([]byte(raw), &reply); err != nil { log.Fatal(err) }
if reply.Verdict == "open" { log.Fatal(reply.DecidingFactor) }
String raw = job.output().output();
raw = raw.substring(raw.indexOf('{'), raw.lastIndexOf('}') + 1);
JsonNode reply = new ObjectMapper().readTree(raw);
if ("audit".equals(reply.get("lane").asText())
&& "open".equals(reply.get("verdict").asText())) {
throw new IllegalStateException(reply.get("deciding_factor").asText());
}
raw = job["output"]["output"]
raw = raw[raw.index("{")..raw.rindex("}")]
reply = JSON.parse(raw)
answered = reply.fetch("coverage", []).map { |c| c["ref"] }
missing = payload[:prescan][:findings].map { |f| f[:ref] } - answered
abort("review never answered: #{missing.join(", ")}") unless missing.empty?
<?php
$raw = $job["output"]["output"];
$raw = substr($raw, strpos($raw, "{"), strrpos($raw, "}") - strpos($raw, "{") + 1);
$reply = json_decode($raw, true);
if ($reply["lane"] === "audit" && $reply["verdict"] === "open") {
fwrite(STDERR, $reply["deciding_factor"] . PHP_EOL);
exit(1);
}
var raw = job.Output.Output;
raw = raw[raw.IndexOf('{')..(raw.LastIndexOf('}') + 1)];
using var doc = JsonDocument.Parse(raw);
var root = doc.RootElement;
if (root.GetProperty("lane").GetString() == "audit"
&& root.GetProperty("verdict").GetString() == "open")
{
Console.Error.WriteLine(root.GetProperty("deciding_factor").GetString());
Environment.Exit(1);
}
Rate limits, idempotency and cost
- Always send
Idempotency-Keyon/runand/run-stream. The page hashes(task, input, attempt); a retry after a network failure must reuse the key or the same work is billed twice. /estimateis free and creates no job. Call it per stage — the hold differs between audit, claims and harden because the output caps differ.hold_creditsis a reservation, not a price. The settled cost is incharged_creditson the finished job and is usually much lower.- If the balance sits between
min_creditsandhold_creditsthe run still executes with a reduced output cap and comes back withtruncated: true. Treat that as a partial answer, not a complete one.
Credits
Rules Desk is a derived work built from three Firebase agent skills: @firebase/firebase-security-rules-auditor, @firebase/firebase-firestore and @firebase/firebase-auth-basics.