markoer-i-titlen
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8139003b35 |
feat(ai): dagens plan, mails der giver mening, og svar på et spørgsmål
De tre sidste af de fem. Alle tre samme sted hen: det, man ellers selv skulle sidde og gøre klokken syv om morgenen. Dagens plan. Én besked om morgenen om det, der forfalder i dag, og det, der er sprunget over. Tallene tælles i basen og gives til modellen som kendsgerninger, den ikke må regne om — en plan, der siger fire opgaver, hvor der er tre, er værre end ingen plan. Er der ingen model, står tallene alene, og det er stadig det, man havde brug for at vide. Er der hverken noget, der forfalder, eller noget, der er sprunget over, kommer der ingen besked: en morgen uden noget at gøre er en god morgen, og en besked om ingenting lærer folk at lade være med at åbne beskeder. Timen er personens egen — klokken syv er syv forskellige steder — og jobbet kører hver time frem for på et klokkeslæt, så en instans, der sov, ikke springer dagen over. Flytter man timen, ryger dagens mærke, så man kan se virkningen i dag i stedet for i morgen. Mails, der giver mening. Opgaven hed, hvad mailen hed: "Anders Jensen: SV: SV: Vedr. levering uge 12". Nok til at genkende, ikke nok til at gøre noget ved. Nu skriver modellen den handling, mailen beder om, og lader være, hvis den ikke beder om nogen. Ét kald pr. gennemløb frem for ét pr. mail — femogtyve mails er ellers femogtyve gange ventetid — og emnet bliver stående i beskrivelsen, så en misforstået mail stadig kan læses. Falder modellen ud, hedder opgaven afsender og emne som før: en model, der er nede, må ikke kunne stoppe posten. Og svar på et spørgsmål. "Hvad lovede jeg Anders i august?" — svaret står spredt over tre noter, og ingen af dem indeholder ordet "lovede". Søgningen kræver, at alle ord optræder, hvilket er rigtigt for et søgefelt og forkert for et spørgsmål, så kandidaterne hentes ord for ord og de noter, flere ord rammer, kommer øverst. Modellen får kun dem: den slår ikke selv noget op, så et svar kan ikke handle om nogen andens noter. Og svaret bærer, hvad det står på — en model, der siger "det gjorde du den 14.", er kun brugbar, hvis man kan slå op i det, den læste det i. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
40102d6038 |
feat(ai): forslag, man siger ja til — indbakken og noternes opgaver
To steder, hvor arbejdet ligger og venter på at blive skrevet færdigt. Indbakken er der, hvor ting lander, når man ikke havde tid til at skrive dem ordentligt — og derfor også der, hvor de bliver liggende. At åbne hver enkelt og sætte projekt, dato og prioritet er ti klik pr. linje. Nu er det ét spørgsmål og et ja pr. linje. Og et referat er fuldt af sætninger, der ligner opgaver, og af tre, der er det. Modellen bliver bedt om kun at tage dem, nogen har forpligtet sig på, og om at citere de ord, den tog dem fra — så et forkert punkt kan gennemskues frem for bare at være forkert. Modellen skriver ikke noget. Den foreslår en *linje* i quick-add-syntaks, og forslaget bliver til noget, når nogen siger ja. Det er ikke forsigtighed for forsigtighedens skyld: en model, der retter i folks opgaver direkte, skal have ret hver gang for at være til gavn, hvor en, der foreslår, skal have ret tit nok til at spare et par tastetryk. Linjen er quick-add-syntaks frem for et felt pr. egenskab, fordi parseren findes. Der er én fortolkning af "i morgen" i dette program, og forslaget går gennem den samme som alt andet — også når linjen er blevet rettet i hånden, inden den blev accepteret. Og et forslag, skrevet i det sprog man selv skriver i, kan læses og rettes af den, det bliver stillet til. Modellen får listen over de projekter, der findes, og må ikke nævne andre. Et `#Nyt projekt`, den fandt på, ville lave et projekt ved et uheld, første gang nogen sagde ja — og et gæt må ikke kunne flytte en opgave hen, hvor ejeren ikke leder efter den. Testet. Forslagene til indbakken bærer et nummer tilbage til den opgave, de handler om. Rækkefølgen alene ville også kunne bruges og gør det som reserve, men en model, der springer en opgave over, ville så flytte alle de følgende forslag én plads — og "sæt en dato på den her" ville lande på en anden. Noten bliver ikke rørt, når der laves opgaver ud af den. Det er fristende at skrive linket ind i den, så de to peger på hinanden, men en tekst, et program skriver i uden at blive bedt om det, er en tekst, man holder op med at stole på. Opgaven bærer i stedet, hvor den kom fra. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b6cbc6cbc2 |
fix(ai): din egen model kunne ikke sættes op — tre fejl i én oplevelse
Rapporteret som "AI gemmer ikke mine oplysninger". Anden gang det symptom meldes, og en helt anden årsag end sidst — dengang delte scopes i user_settings én række og slettede hinanden. Udbyderen hedder "OpenAI-kompatibel (Ollama, LM Studio, …)" og feltet foreslår selv http://localhost:11434/v1. safedial nægter præcis den slags adresser. Så den ene udbyder, der findes for at køre sin egen model, kunne ikke sættes op, og formularen foreslog en adresse, serveren ville afvise. Serveren svarede 422 med en fejl på base_url. Formularen havde ingen plads til at vise en fejl på det felt, og fordi fejlobjektet ikke var tomt, blev der heller ikke vist en toast. Beskeden forsvandt, og det så ud, som om der ikke skete noget ved at trykke Gem. Tre rettelser: - Private adresser tillades for admins. Muren står, fordi en server, der henter en brugerangivet adresse, lader den, der angiver den, nå ting han ellers ikke kan. En admin på en selvhostet instans ejer allerede værten og dens netværk, så det giver ham intet nyt. For en inviteret bruger er den lukket som før. Tilladelsen regnes ud pr. forespørgsel, ikke gemt ved siden af adressen — den, der ikke længere er admin, skal ikke kunne nå indenfor på et gammelt flag. - Tailnet er ikke "indenfor". Halvdelen virkede allerede ved et tilfælde: Gos IsPrivate dækker RFC1918 og ikke det CGNAT-område, Tailscale låner, så 100.x slap igennem uden at nogen havde besluttet det. IPv6-halvdelen gjorde ikke — fd7a:115c:a1e0::/48 ligger inde i fc00::/7. Samme maskine var altså tilgængelig eller ej alt efter adressefamilie. Begge områder er nu navngivet, hvilket gør IPv4-halvdelen bevidst frem for heldig. - Feltfejl kan ikke længere forsvinde. base_url og model har fået et sted at vise en, og en fejl på et felt, der ikke har et, råbes op som en toast frem for at blive tabt. |
||
|
|
4aa8bd6a40 |
fix(sikkerhed): resten af gennemgangen — SSRF, tre ubegrænsede veje, og fire hemmeligheder
Release / Build and publish image (push) Failing after 9m54s
**H2 — SSRF gennem AI-udbyderens adresse.** `base_url` blev gemt uden kontrol og hentet med en almindelig klient, og udbyderens svar blev givet tilbage ordret. Det var en fungerende skanner af det interne net for enhver bruger: peg den på en vært, læs de første tre hundrede bytes af det, der svarede. På en homelab er det det meste af, hvad der er værd at nå. Nu gennem `safedial`, og svarets krop går i loggen frem for i svaret. Operatøren har stadig brug for beskeden — for en lokal model er den som regel selve problemet — så den er der, bare kun for den, der kører instansen. **M2 — Web Push havde tre.** Endepunktet blev gemt ukontrolleret og POSTet til på en tidsplan, den, der ringer, selv vælger: sæt en påmindelse et minut ude, og serveren henter adressen, hvert minut. Upserten flyttede desuden ejerskabet med `user_id = excluded.user_id`, så enhver, der kendte en andens endepunkt, kunne overtage det — offerets telefon ville få angriberens beskeder og ingen af sine egne. Og `DeletePushSubscription` havde ingen ejer-kontrol. **H3 — tre veje uden loft.** FTS-udtrykket byggede omkring halvtreds bytes pr. ord uden grænse; en søgning på en kvart megabyte blev til to megabyte MATCH-udtryk og seksogtyve sekunder inde i SQLite, mens den holdt på én af kun fire til otte forbindelser. Nogle få af dem samtidig, og databasen er utilgængelig for alle på instansen, logins inklusive. `filterquery.Parse` er kvadratisk — halvtreds tusind led er syv sekunders CPU og seksogfyrre gigabyte allokering i én forespørgsel — og havde hverken loft på antal eller på dybde. Og noteimporten læste hver fil i arkivet uden grænse: 64 MB komprimeret kan blive til seksogtres gigabyte skrevet ned på den disk, databasen ligger på. **M1 — `comment_id` blev taget på tro.** Rettighedskontrollen så på opgaven i adressen, mens filen blev hængt på den kommentar, formularen bad om. En viewer på et delt projekt kan liste dets kommentarer, så hun kunne læse et id og lægge en fil under en andens kommentar i et projekt, hun kun må læse. **M4 — fire hemmeligheder lå i klartekst i hver sikkerhedskopi.** TOTP-koden og AI-nøglen forsegles nu; begge konverterer sig selv næste gang de skrives. Kalenderfeedets og mailadressens tokens gør ikke — de slås op på deres præcise værdi og vises i fladen mere end én gang, hvilket en hash ikke kan. Det står nu skrevet i `SECURITY.md`, som påstod det modsatte. At rette det betyder, at adressen kun kan vises én gang, og det er en beslutning om, hvordan funktionen virker. Og en prøve på, at politikken overhovedet bliver sendt. Det var den, der gjorde XSS-en i nat til indsat HTML frem for kørt script, og der var ingen prøve, der rørte den — en foranstaltning, ingen prøve rører, kan forsvinde i en oprydning, uden at nogen opdager det. Målt på script-src alene: `style-src 'unsafe-inline'` står der med vilje, og en indsat stil kan gøre en side grim, ikke køre kode. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6b19ed062d |
feat(phase5): MCP server, CalDAV, mail-to-task, Gmail settings and an AI layer
The MCP server speaks JSON-RPC over Streamable HTTP with the six tools the brief names, authenticated by a personal API token — so a connector reaches exactly what its owner reaches and there is no second identity to reason about. A failed tool call returns a tool error, not a protocol error: a model can recover from the first and can do nothing with the second. CalDAV serves VTODO, two-way, so Apple Reminders and Thunderbird can read, create and complete tasks. Clients authenticate with Basic using an API token as the password, never the account password — a client stores what it is given, and what it is given should not be able to sign in to the app. Completed tasks stay in the collection: a client that stops seeing one treats it as deleted and drops its own record of having done it. The AI layer is one interface with adapters for Anthropic, OpenAI, Google and any OpenAI-compatible endpoint. That last one is why the abstraction exists: an integration that only spoke to a hosted API would be useless to somebody running their own models. Every feature degrades to "off" when nothing is configured, and the stored API key is never returned to the settings page. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |