onym audit · revizor mreže Onym

Za programere

HTTP API

Sve što možete pročitati, provjeriti i poslati: adrese, tijela zahtjeva, provjere na serveru, odgovori i ograničenja. Svaka promjena je potpisan dokument; server čuva bajtove, ali ih ne može krivotvoriti.

Pregled

Osnova svih adresa je https://foldy.io/audit/; putanje ispod navedene su u odnosu na nju. Server se sastoji od tri dijela:

  • glavno mjesto „onym audit“ — stablo potpisanih dokumenata u korijenu i dvije adrese za upis (orders, responses);
  • hub — svako može postati revizor (hub/api/…), a njegovi dokumenti nalaze se u a/<ime>/;
  • Discovery provajder — potpisani katalozi u discovery/ koje čitaju Onym aplikacije.

Nema naloga, lozinki ni kolačića. Sve što mijenja stanje potpisano je Ed25519 ključem onoga ko djeluje: revizora, naručioca ili subjekta. Server provjerava potpis i čuva bajtove tačno onakve kakvi su potpisani. Dokument ne može falsifikovati, jer ne drži nijedan ključ revizora. Sve što server vraća može se provjeriti bez povjerenja u njega; u pregledaču to radi verify.js.

Formate samih dokumenata (manifest, atestacija, lista statusa, narudžba, ponuda, odgovor, opoziv) određuje profil implementacije. Ova stranica opisuje transport: adrese, tijela zahtjeva, provjere, odgovore i ograničenja. Izvorni kod: github.com/Dubieroj/onym-audit (hub/, server/).

Opšta pravila

Kanonski JSON

Isti kao u Discovery-Static-Ed25519 §3:

  • najviši nivo je objekat; ključevi su sortirani po UTF-8 bajtovima na svakoj dubini; zapis je kompaktan, bez razmaka;
  • escape sekvencom zapisuju se samo ", \ i U+0000–U+001F (dvoznakovni oblici gdje postoje, inače \u00xx malim slovima);
  • brojevi mogu biti samo nenegativni cijeli brojevi do 253−1;
  • duplirani ključevi, nevalidan UTF-8 i usamljeni surogati se odbijaju, a ne ispravljaju.

Server kanonizuje sve što prima i čuva kanonske bajtove.

Potpisi, ključevi, sažeci, vrijeme

PotpisEd25519 nad kanonskim bajtovima dokumenta bez polja signature (uklanja se samo na najvišem nivou); vrijednost je standardni base64 s dopunom (padding). Pored svakog potpisanog fajla nalazi se .sig: isti string i prelazak u novi red.
Ključonym:key:<64 hex znaka malim slovima> — javni Ed25519 ključ. Otisak na stranicama je prvih 8 bajtova SHA-256 ključa, razdvojenih dvotačkom.
Sažetaksha256:<64 hex> od tačnih bajtova fajla.
VrijemeRFC 3339 u UTC, s tačnošću do sekunde: 2026-09-25T10:00:00Z. Drugi oblici se odbijaju.

Potpisani zahtjev

Adrese označene sa 🔏 potpis primaju {"request": {…}}. Unutrašnji objekat je potpisan navedenim ključem, sadrži issuedAt koji od sata servera odstupa najviše ±10 minuta i nema suvišnih polja. Tako pregledač dokazuje da posjeduje ključ, bez sesija i lozinki:

import { canonical, parseStrict } from "https://foldy.io/audit/verify.js";

const now = new Date().toISOString().replace(/\.\d+Z$/, "Z");
const req = parseStrict(JSON.stringify({ action: "order-status", orderId, sponsor: key, issuedAt: now }));
const sig = await crypto.subtle.sign("Ed25519", privateKey, new TextEncoder().encode(canonical(req)));
req.set("signature", btoa(String.fromCharCode(...new Uint8Array(sig))));

await fetch("https://foldy.io/audit/hub/api/a/alice/order-status", {
  method: "POST", headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ request: JSON.parse(canonical(req)) }),
});

Odgovori i greške

Odgovori su JSON sa Cache-Control: no-store. Greška huba je {"error": "<tekst>"}; greška glavnog mjesta je {"error": "<kod>", "detail": "<tekst>"}.

400tijelo se ne može parsirati ili sadrži nepoznato polje
403potpis pogrešnog ključa ili zastario issuedAt
404nema takvog revizora, atestacije ili otvorenog zahtjeva
409sukob s već objavljenim: nepromjenljiv dokument, vraćanje verzije unazad, zauzeto ime
413tijelo prelazi ograničenje
415fetch: odgovor nije ni JSON ni tekst
422dokument nije prošao provjeru; razlog je u tekstu greške
429prekoračeno je ograničenje broja zahtjeva
502spoljna adresa je nedostupna ili ne odgovara kako treba
503red je pun ili je hub zauzet teškim provjerama
507prostor revizora na hubu je popunjen (20 MiB)

Ograničenja

  • Tijelo zahtjeva hubu ima najviše 4 MiB; svaki dokument najviše 1 MiB; stablo jednog revizora najviše 20 MiB.
  • Brzina: „kanta tokena“ po IP adresi (za IPv6 po cijeloj /64): kapacitet 60, dopuna 1 token u sekundi. Cijena poziva navedena je uz svaku adresu. GET zahtjevi za liste nisu ograničeni.
  • „Teški“ pozivi (digest, conformance) izvršavaju se najviše dva istovremeno na cijelom hubu.
  • Glavno mjesto: narudžba najviše 64 KiB, odgovor najviše 16 KiB, ukupno ograničenje od 20 zahtjeva uz dopunu od 1 u sekundi.

Statički fajlovi

Dokumenti se isporučuju onakvi kakvi jesu, bajt po bajt, sa Access-Control-Allow-Origin: *, X-Content-Type-Options: nosniff i Cache-Control: no-cache; bez kolačića i bez dnevnika pristupa. Stranice rade uz CSP script-src 'self'.

Čitanje i provjera

Glavno mjesto (korijen /audit/) i svaki revizor huba (a/<ime>/) imaju isto stablo:

manifest.json + .sigpotpisani manifest revizora: ključ operatora, delegirani ključ statusa, politike, metodologije, ponude
status.json + .siglista statusa, potpisana delegiranim ključem; ponovo se potpisuje svakih 6 sati, nextUpdate je 48 sati unaprijed
attestations/<id>.jsonatestacije, nepromjenljive
revocations/<id>.jsonpotpisani opozivi
responses/<atestacija>/<id>.jsonpotpisani odgovori subjekata
offers/<id>.jsonponude: metodologija, cijena, rok, uslovi objave
orders/<id>.jsonnarudžbe s tri potpisa, objavljuju se zajedno s atestacijom
scopes/, reports/, methodology/, policies/dokumenti fiksirani sažecima

Zapis u listi statusa:

{"attestationId": "onym-discovery-2026-09-24",
 "attestation": {"uri": "https://foldy.io/audit/attestations/onym-discovery-2026-09-24.json",
                 "digest": "sha256:…"},
 "state": "active" | "revoked" | "superseded" | "expired",
 "supersededBy": null, "revocation": null,
 "responses": [{"uri": "…", "digest": "…"}]}

Kako provjeriti atestaciju bez povjerenja u bilo šta:

  1. potpis manifesta odgovara njegovom operator;
  2. lista statusa je potpisana ključem statusKey iz manifesta, auditorKey se poklapa sa operator, a nextUpdate još nije nastupio;
  3. sažetak fajla atestacije poklapa se sa zapisom u listi, i zapis je u stanju active;
  4. atestacija je potpisana ključem operatora i nije istekla; fiksirani dokumenti (obim, metodologija, izvještaj) poklapaju se sa svojim sažecima.
import { verifyAttestation } from "https://foldy.io/audit/verify.js";
const get = (p) => fetch("https://foldy.io/audit/" + p).then((r) => r.text());
const d = await verifyAttestation({
  manifestText: await get("manifest.json"),
  statusText: await get("status.json"),
  attText: await get("attestations/onym-discovery-2026-09-24.json"),
  target: null, credited: true,
});
// d.display: "attested" | "revoked" | "superseded" | "expired" | "status-unknown" | …

Stranica svake atestacije (verdict/?id=<id> za glavno mjesto i verdict/?a=<ime>&id=<id> za revizora huba) izvršava ove provjere u pregledaču i prikazuje svaku od njih.

Glavno mjesto „onym audit“

POST orders

Prihvatanje narudžbe (Audit.md §6). Tijelo je ili gola narudžba koju su potpisali subjekt i sponzor, ili omotnica:

{"order": {…}, "scopeText": "šta pregledati", "contact": "bilo koji tekst, neobavezno"}

U omotnici je order.scope.uri = <osnova>scopes/order-<orderId>.md, a SHA-256 od scopeText (1–8192 bajta) jednak je order.scope.digest. Kontakt ima najviše 256 bajtova, bez kontrolnih znakova; ne objavljuje se.

202 {"orderId", "state": "queued-for-review", "digest"} — narudžba je u redu čekanja. To još nije saglasnost revizora.

Greške: 422 order_invalid, 409 order_conflict (pod ovim id-jem je već druga narudžba), 503 inbox_full (1000 narudžbi), 413 oversize, 429 rate_limited.

POST responses

Odgovor subjekta na atestaciju (Audit.md §5.7.5). Tijelo je goli potpisani odgovor, najviše 16 KiB; attestationId i responseId imaju 16–128 znakova iz [A-Za-z0-9_-]. Nakon upisa lista statusa se ponovo potpisuje, a odgovor se pojavljuje pored atestacije.

201 {"uri", "digest"}

Greške: 404 attestation_unknown, 422 subject_response_invalid, 409 response_conflict (odgovori su nepromjenljivi).

Hub: registracija i objavljivanje

Revizor huba drži svoj ključ u pregledaču (kabinet revizora); hub vidi samo potpisane dokumente. Put revizora: claim → potpisivanje manifesta → register → publish atestacija → po potrebi revoke.

POST hub/api/claim 5 tokena

Rezervacija imena i dobijanje delegiranog ključa statusa koji manifest mora navesti.

{"slug": "my-auditor"}

Ime: ^[a-z][a-z0-9-]{2,31}$, ne smije biti među rezervisanim imenima (admin, api, hub, audit, onym, onym-audit, foundation, sobor, root, status, www). Rezervacija traje 30 minuta; ponovljeni poziv u tom roku vraća istu rezervaciju.

200 {"slug", "statusKey", "base", "componentId", "expires"}

Greške: 409 — ime je zauzeto; 422 — ime nije dozvoljeno.

POST hub/api/register 10 tokena

Objavljivanje ili zamjena manifesta zajedno s dokumentima na koje upućuje.

{"slug": "my-auditor",
 "manifest": {…potpisani manifest…},
 "docs": {"policies/independence.md": "tekst", "offers/manual-review.json": "{…}", …}}
  • componentId = onym:component:<ime>; statusEndpoint = <osnova>status.json; statusKey je onaj izdat pri claim;
  • contact je bilo koji tekst od najviše 256 bajtova, bez kontrolnih znakova (na stranicama postaje link samo ako počinje sa mailto: ili https://);
  • ponovna registracija moguća je samo istim ključem operatora (inače 403) i ne smije biti starija od trenutne: validUntil nije raniji, a kad je jednak, bajtovi su isti (inače 409);
  • putanje dokumenata: (policies|methodology|scopes|reports|offers|notes)/<ime>.(md|json); prihvataju se samo dokumenti na koje upućuje manifest;
  • svaka referenca u manifestu upućuje ili na fajl ovog revizora ili na zajednički dokument glavnog mjesta, i sažetak se poklapa;
  • ponude su potpisane ključem revizora, odnose se na metodologiju iz manifesta, nisu istekle i ne vraćaju objavljenu verziju unazad.

201 {"page", "manifest", "digest"}

POST hub/api/a/{slug}/publish 5 tokena

Objavljivanje atestacije s njenim dokumentima.

{"attestation": {…},
 "docs": {"scopes/….md": "…", "reports/….json": "…", "orders/<orderId>.json": "…"}}
  • potpisana ključem revizora, status = statusEndpoint, unsolicited.policy = sažetak objavljene politike;
  • id je nov: atestacije su nepromjenljive, a ispravka je nova atestacija koja zamjenjuje staru (supersede);
  • metodologija, obim, skala i izvještaj fiksirani su isto kao pri registraciji;
  • za engagement: "commissioned" prilaže se tačno jedna narudžba s tri potpisa (subjekt, sponzor, revizor); orderRef je njen sažetak; subjekt, sponzor, artefakt, metodologija i obim se poklapaju.

201 {"uri", "digest"} — objavljeno.
202 {"uri", "digest", "held": true, "releaseAt"} — neuspjeh po narudžbi s embargom: hub zadržava atestaciju, izvještaj, narudžbu i obim, i objavljuje ih automatski kad embargo istekne.

POST hub/api/a/{slug}/revoke 5 tokena

{"revocation": {…potpisani opoziv…}}

Opoziv se uvijek prihvata, čak i kad je prostor popunjen.

201 {"uri"}

Greške: 404 — nema takve atestacije; 409 — već je opozvana.

POST a/{slug}/responses 5 tokena

Odgovor subjekta na atestaciju revizora huba. Tijelo je goli potpisani odgovor. Najviše 10 odgovora po atestaciji; odgovori su nepromjenljivi (409).

201 {"uri"}

GET hub/api/a/{slug}/export

Lista svih fajlova u stablu revizora, da biste ga prenijeli na svoj server: hub je zamjenjiva podrazumijevana opcija.

200 {"base", "files": ["manifest.json", …]}

Narudžbe

Pregled može naručiti bilo ko, kod bilo kog revizora koji ima ponude. Pregledač pravi ključ za narudžbu i njime potpisuje narudžbu i kao subjekt i kao sponzor. Revizor je supotpisuje kad objavi atestaciju.

POST a/{slug}/orders 10 tokena

{"order": {…}, "scopeText": "šta pregledati", "contact": "neobavezno"}
  • narudžbu su potpisali subjekt i sponzor, i ona tačno odgovara ponudi revizora: metodologija, model cijene, uslovi objave;
  • fee.offerId je ponuda iz manifesta ili rsp-…, odgovor na zahtjev koji je još otvoren (tada je sponzor autor zahtjeva, a subjekt, metodologija i artefakt se poklapaju sa zahtjevom);
  • scope.uri = <osnova>scopes/order-<orderId>.md, a scopeText (1–8000 bajtova) hešira se u scope.digest;
  • kontakt nije obavezan: bilo koji tekst od najviše 256 bajtova; vidi ga samo revizor.

202 {"orderId", "state": "queued-for-review", "digest"}. Zahtjev na koji je odgovoreno tom ponudom se zatvara.

Greške: 409 — narudžba je već izvršena ili je pod ovim id-jem druga narudžba; 503 — u redu je 100 narudžbi.

POST hub/api/a/{slug}/order-status 1 token 🔏 ključ sponzora

{"request": {"action": "order-status", "orderId": "…", "sponsor": "onym:key:…",
             "issuedAt": "…", "signature": "…"}}

200 {"queued": bool, "held": bool} — da li narudžba čeka u redu i da li je njen rezultat zadržan pod embargom. To može saznati samo vlasnik ključa narudžbe.

POST hub/api/a/{slug}/inbox 2 tokena 🔏 ključ revizora

{"request": {"action": "list" | "decline", "auditor": "onym:component:<ime>",
             "orderId": "…samo za decline", "issuedAt": "…", "signature": "…"}}

list → {"orders": [{"order", "scopeText", "contact", "receivedAt"}], "held": [{"attestationId", "releaseAt"}]}
decline → {"declined": "<orderId>"}

Zahtjevi

Naručilac koji ne zna kome da se obrati objavljuje zahtjev: tačne bajtove, komponentu subjekta, metodologiju i obim, upućen svim revizorima ili jednoj Stellar adresi. Revizori odgovaraju potpisanim ponudama; naručilac bira jednu i naručuje po njoj.

POST hub/api/requests 5 tokena

{"request": {
  "requestVersion": 1,
  "requestId": "req-<20 znakova [a-z0-9]>",
  "to": null | "onym:key:…",
  "subject": "onym:component:<ime>",
  "methodologyClass": "security-review" | "conformance-run",
  "artifact": {"kind": "source" | "build" | "deployment", "source": "…", "revision": "…", "artifactHash": null},
  "scopeText": "1–8000 bajtova",
  "requester": "onym:key:…",
  "issuedAt": "…", "expiresAt": "… (najkasnije za 30 dana)",
  "signature": "…ključem requester"
}}

to: null — zahtjev je vidljiv svima; inače samo adresatu. conformance-run se prihvata samo za kind: "deployment".

201 {"requestId", "digest"}

Greške: 409 — ovaj id već postoji; 503 — otvoreno je 500 zahtjeva.

GET hub/api/requests

200 — otvoreni javni zahtjevi, od najnovijih ka najstarijim: [{…zahtjev, "responses": N}].

POST hub/api/requests/for 2 tokena 🔏 ključ adresata

{"request": {"action": "requests-for", "key": "onym:key:…", "issuedAt": "…", "signature": "…"}}

200 — zahtjevi upućeni ovom ključu.

POST hub/api/requests/{id}/responses 5 tokena

Revizor odgovara ponudom.

{"slug": "my-auditor", "offer": {…}}

offerId = rsp-<sufiks requestId>; ponuda je potpisana ključem revizora, a metodologija je ona iz zahtjeva; na adresirani zahtjev odgovara samo adresat; obim je fiksiran; ponuda nije istekla i ne vraća prethodnu unazad.

201 {"offer": "<uri>"}

Greške: 404 — zahtjev je zatvoren ili je istekao.

POST hub/api/requests/{id}/mine 2 tokena 🔏 ključ autora zahtjeva

{"request": {"action": "responses", "requestId": "…", "issuedAt": "…", "signature": "…"}}

200 {"open": bool, "responses": [{"slug", "name", "operator", "fingerprint", "page", "completedOrders", "customers", "offer"}]}

Trezor

POST hub/api/vault 2 tokena 🔏 ključ trezora

Da bi naručilac vidio svoje narudžbe i zahtjeve na bilo kom uređaju, aplikacija čuva njihovu listu šifrovanu (AES-256-GCM, ključ se izvodi iz fraze) pod posebnim ključem za potpis, izvedenim iz iste fraze. Hub čuva šifrovani tekst koji ne može pročitati, pod ključem koji se nigdje drugdje ne pojavljuje.

{"request": {"action": "get" | "put", "key": "onym:key:…", "data": "base64, samo za put",
             "issuedAt": "…", "signature": "…"}}

get → {"data", "updatedAt"} ili {"data": null}
put → {"updatedAt"}; podaci najviše 256 KiB; ukupno na hubu najviše 20.000 trezora.

Alati za provjeru

Pregledač sam ne može pročitati većinu spoljnih adresa (CORS), pa to hub radi umjesto njega: samo javni HTTPS, bez kolačića, nikad u internu mrežu, najviše tri preusmjeravanja.

POST hub/api/fetch 2 tokena

{"url": "https://…"}

Samo JSON i tekst, najviše 1 MiB (inače 415).

200 {"url", "status", "contentType", "setsCookie", "digest", "size", "body"}

POST hub/api/digest 20 tokena

{"url": "https://…/app.apk"}

Hešira fajl kao tok, do 300 MiB, bez vraćanja sadržaja, da bi se fiksirali tačni bajtovi build-a.

200 {"url", "status", "digest", "size"}

POST hub/api/conformance/discovery 20 tokena

{"manifestUrl": "https://…/manifest.json"}

Pokreće skup provjera usklađenosti za Discovery provajdere (42 provjere) nad manifestom sa "seat": "discovery". Na druge servise skup se ne primjenjuje (422). Vremensko ograničenje: 3 minuta.

200 — kanonski izvještaj: {suite, suiteVersion, runAt, manifestUri, result, documents: [{role, uri, digest, size}], checks: [{id, clause, level, title, outcome, detail}]}

Liste

GET hub/api/auditors

[{"slug", "name", "operator", "fingerprint", "page", "attestations", "offers", "completedOrders", "customers"}]

GET hub/api/seat

Isto za glavno mjesto ("slug": "").

GET hub/api/library

Sve atestacije svih revizora, od najnovijih ka najstarijim. Ovo je indeks, a ne autoritet: pregledač svaki zapis provjerava prema potpisanim fajlovima.

[{"attestationId", "auditor", "auditorSlug", "auditorBase", "fingerprint", "subject", "result", "state", "methodologyClass", "kind", "source", "engagement", "issuedAt", "expiresAt", "orderRef", "uri"}]

curl -s https://foldy.io/audit/hub/api/library | jq '.[0]'

Discovery provajder

Provajder onym:component:onym-audit-discovery objavljuje potpisane kataloge koje Onym aplikacije dodaju u Settings → Discovery Providers. Sve su to statički fajlovi:

discovery/manifest.json + .sigseat: "discovery", profil static-ed25519-v1, mogućnosti signed-snapshot-v1 i local-filtering-v1
discovery/catalogs/onym-auditors.jsonrevizori (seatTypes: ["audit"]), najviše 512 zapisa
discovery/catalogs/onym-audited-services.jsonOnym servisi koji imaju važeću reviziju, sa statusom
discovery/policies/*.md, discovery/privacy.mdpolitike fiksirane sažecima u manifestu
discovery/status/<komponenta>/stranica statusa na koju upućuje zapis kataloga

Snimci se ponovo potpisuju pri svakoj promjeni na hubu, ali ne češće od jednom u 2 minuta, i isporučuju se sa Cache-Control: no-cache. Polja snimka: {version, catalogId, providerId, implementationProfileId, policyDigest, sequence, previousDigest, generatedAt, expiresAt, entries, signature}. Zapis u onym-audited-services:

{"componentId": "onym:component:onym-relayer", "seatType": "notary",
 "operator": "onym:key:…", "manifest": {"uri": "…", "digest": "sha256:…"},
 "profiles": […], "relationship": "other-disclosed", "placement": "policy-ranked",
 "listedAt": "…", "evidence": [],
 "status": {"state": "review" | "warning", "uri": "https://foldy.io/audit/discovery/status/onym-relayer/"}}

Aplikacija prikazuje status kao oznaku pored servisa i kao link u prozoru za saglasnost. Otisak ključa provajdera u aplikaciji je prvih 16 hex znakova ključa operatora: 4074 4514 be2a 1414.

Sidro na Stellar mreži

Registar revizora učvršćuje se na Stellar test mreži: zapis podataka onym-audit-status na nalogu revizora (to je isti ključ kao ključ operatora) čuva SHA-256 kanonskog JSON-a

{"anchorVersion": 1, "auditor": "onym:component:…",
 "entries": [{"attestationId", "attestation": "sha256:…", "state", "supersededBy", "revocation"}, …]}

sa zapisima sortiranim po attestationId. Ponovno potpisivanje radi svježine ne mijenja sidro; izdavanje i opoziv ga mijenjaju. Mreža čuva svaki zapis s vremenom ledgera, pa se registar ne može naknadno prepisati niti različitim čitaocima prikazati na različit način. Za provjeru služi stellar.js: anchorState(account, statusText) šalje upit javnom Horizon čvoru i poredi rezultat.