-
2d7abf9
chore: drop a stray .deb that got committed, and ignore them
Pablo Murad
-
0f036e4
feat: own visual identity, and gemini over a shared small web layer F7, the visual identity. The shared chrome stylesheet is now a set of CSS custom properties on :root, with a light variant behind prefers-color-scheme, and the nine internal pages draw their colours from those tokens instead of hardcoding them. The GTK theme in theme.lua is rebuilt around the same phosphor palette, private tabs read as their own mode rather than a darker shade, and the new tab page is an authored plate with the mark, the name and the keys worth knowing, its colours taken from the theme. F8, the small web. gopher and gemini now share lib/smallweb: one page shell, one stylesheet, and a render mode setting that applies to both. Terminal mode is full width and monospaced with a caret on every link; page mode reads like a document. gopher was refactored onto it, so the two schemes cannot drift. gemini:// is new. It speaks the protocol directly over TLS through LuaSec, renders gemtext to HTML, follows input and redirect status codes, and escapes every field so a hostile page cannot become markup. Trust is trust on first use, in lib/smallweb/tofu: the first certificate a host presents is remembered and a change is reported, not accepted, with the choice to trust the new one. Without LuaSec the scheme still loads and every page says what to install. skull://smallweb (gS) switches the render mode and lists the remembered gemini hosts, forgetting any of them. Verified end to end against geminiprotocol.net: the handshake completes and the fingerprint is stored. Tests cover gemtext rendering including markup escaping, url parsing and link resolution, and the full tofu lifecycle. 97 passing.
Pablo Murad
-
15fab09
docs: everything in English, and a security model in the README The comments written during this fork were in Portuguese, in ten files across Lua, C and shell. They are English now, and so is every message setup-deps.sh prints. docs/decisoes is docs/decisions, and the decision record about staying on webkit2gtk-4.1 is translated with it. The README gains three sections. Security model states what is on and off by default, and then says the part that matters: there is no sandbox around Lua. A module under ~/.config/skull runs with everything the user account has, and so does a userscript installed with :usinstall. Saved form data is plain Lua the browser executes on every page load, 0600 and host-anchored but not encrypted, and it says so rather than implying otherwise. Certificate exceptions, what they cover and how to see and revoke them. Extending covers the tree layout, writing a module, adding a skull:// page in ten lines, and adding binds and commands. Contributing covers the build, the suite, the style checks that fail the build, where tests live and what a commit message looks like. Two claims in the README were wrong and are corrected: there is no `?` binding for help, only :help, and run-tests takes no arguments.
Pablo Murad
-
8fc4eac
feat: certificate exceptions expire, and can be seen and revoked Two of the certificate error strings were wrong, and they were the two that matter. A revoked certificate had no entry at all, so it showed as "Unknown error code revoked", while "insecure", which means the certificate was signed with a weak algorithm, was labelled "The certificate has been revoked." An exception, meaning a decision to trust one certificate that failed verification, used to last forever. It now lapses after 90 days, set by error_page.cert_exception_lifetime, and expired rows are swept when the store is read and before one is reapplied. The button says how long it will last instead of "permanently". skull://certs lists every stored exception with the date it was made and the date it lapses, and revokes any of them. Bound to gC and to :certs. WebKit keeps a certificate it has already been told to allow until the browser restarts, and the page says so rather than implying the revoke takes effect at once. The status bar showed (trust) for a host with an exception, because WebKit reports no certificate errors once the exception is in place. It shows (exception) in the untrusted colour now. Correcting an earlier note of mine: the stored certificate itself was never the problem. luakit.allow_certificate pins one certificate to one host through webkit_web_context_allow_tls_certificate_for_host, so a different certificate on that host was already refused. What was missing was the expiry, the listing and the indicator.
Pablo Murad
-
6dd826d
fix: webview settings were applied after the load had started Everything but the user agent waited for load-status "committed", which fires once the document is already being parsed. A new view therefore ran on WebKit's own defaults until its first page was on the way in, and a per-domain enable_javascript = false was a race against the parser rather than a setting. Global defaults are now applied in the constructor, and domain rules at "provisional", when the target uri is known and nothing has been parsed yet. get_setting_for_view could not be called before a load because it passed a nil uri to domains_from_uri, which raises. A view with no uri now resolves to the global defaults, which is the only thing that can apply to it.
Pablo Murad
-
897687f
fix: data hygiene and network posture umask is now 0077 for the whole process. Only the cookie jar was narrowed before, so history, bookmarks, downloads, sessions, the saved forms and the whole cache inherited whatever the login shell had set. Every file the browser writes is 0600 now, verified by running it against a throwaway data directory. Downloads started in a private tab no longer reach the database. history.lua has always made that check on every visit; downloads did not, so the uri and the path outlived the tab that asked for them. The view was already being passed to downloads.add and ignored. A typed address without a scheme now goes to https. It used to go to http, so the first request for any address the user typed went out in the clear, and a redirect to https could be stripped on the way. Referer is cut back to the origin within a domain, and dropped entirely when a secure page links to an insecure one. Cross-domain was already dropped. Paths and queries carry search terms, document names and tokens that the server on the other end has already seen once. An unparseable proxy uri now raises instead of quietly setting nothing. webkit_network_proxy_settings_new returns NULL for a uri it cannot read, and that NULL was passed straight on: traffic left direct while soup.proxy_uri still named the proxy. The stored value is only updated once the setting took. The mime decision handler logged every requested uri at info level, private tabs included, to stderr and to skull://log. It is verbose now. The formfiller chmod ran through os.execute, a real shell, with Lua's %q for quoting, which does not cover $ or the backtick. It uses luakit.spawn and shell_escape now, though with the new umask the file is created 0600 to begin with. enable_java, enable_plugins and enable_xss_auditor are still accepted by WebKitGTK and still settable, but WebKit no longer acts on any of them. They are marked as such where they are documented, rather than removed, so existing configs keep loading. Correcting an earlier note of mine: enable_hyperlink_auditing is not among them, it still works.
Pablo Murad
-
6cc0495
fix: escape page data in internal pages, and drop the shell from styles The bookmarks, history, downloads and log pages built their HTML by interpolating values straight into a template. Titles were escaped; uris, tags, dates, domains, ids and log messages were not. All of those come from the page that was visited, and all four pages export Lua functions to their own origin, so markup that lands there runs with the bridge in reach. The settings page wrote the error text back with innerHTML. It is textContent now, which cannot carry markup at all. styles.lua ran its watcher through `bash -c`, interpolating the path with Lua's %q. That escapes the double quote and the backslash but not the single quote, and here a real shell was on the other end, so a stylesheet whose name held one ran the rest of the name as a command. The watcher now calls inotifywait directly, with no shell, and pauses a second before retrying when the tool is missing. The quoting lives in lousy.util.shell_escape next to sql_escape and lua_escape, since luakit.spawn parses its argument the way a shell would even when no shell runs. Its test feeds hostile paths through spawn_sync and checks each one comes back as a single unchanged argument.
Pablo Murad
-
94bd047
fix: IPC socket was open to every local process The socket was created in the shared temporary directory with the default mode, so any process on the machine could connect to it. Anything that connects can send IPC_TYPE_page_created carrying a page id of its choosing, which rebinds a real tab's endpoint to its own socket. It now lives in a per-user runtime directory created with mode 0700, whose owner and mode are verified when it already exists. The socket itself is set to 0600 between bind() and listen(), where nothing can connect yet, so there is no window. Peers are checked with SO_PEERCRED on accept() and refused when they belong to another user. The directory is named skull-ipc rather than skull because glib falls back to the cache directory when XDG_RUNTIME_DIR is unset, and the browser already keeps its cache in skull/ there.
Pablo Murad
-
61ad9ce
fix: download paths and formfiller credential handling viewpdf built its destination as dir .. filename with the name taken from Content-Disposition, so a name carrying ../ or a leading slash wrote wherever the user can write. Targets that turn that into execution are right there in the data directory: forms.lua runs on every page load, userscripts run on matching pages, newtab.html is served verbatim. Only the last path component is used now. downloads set allow_overwrite before deciding the destination, so any computed path clobbered an existing file with no prompt. It is now scoped to the branch where a handler or the user named the file. formfiller matched rules with an unanchored string.find against an escaped full URI, so a rule for bank.example/login also matched evil.example/x#bank.example/login. Autofill guarded against this by checking the page host; zl and zL did not, and a rule may carry submit, so one keypress on a hostile page could fill a password and send it. The host check now covers all three paths. forms.lua holds passwords in cleartext and was created with default permissions. It gets 0600 on creation, the same treatment the cookie jar already had. to_lua_str escaped quote and backslash but not newline. A form attribute containing one produced a broken chunk appended to the credential file, which then failed to load entirely. This lowers the risk; it does not make the formfiller safe. The file is still cleartext Lua executed on every page load. Suite: 78 of 79, the failure being test_scroll timing out under a shared display. CI runs it on its own Xvfb and passes.
Pablo Murad
-
a2df913
fix: escape error page fields at the boundary, not per caller show_error_page escaped nothing, and three callers handed it attacker data: the blocked URI in adblock_chrome, the requested path in help_chrome, and the page name in chrome.lua, which comes from ^skull://([^/]+) and so accepts <, > and quotes. Error pages force enable-scripts on, and two of those render on a skull:// origin where the Lua bridge lives. load_error_page now escapes every field the caller supplies except content, style and buttons, which carry markup by design. Future callers are covered without having to know. help_chrome needed two fixes of its own. It concatenated the path into content, which stays raw, so the path is now passed as a field. And lousy.load(prefix .. file) accepted .. and absolute paths, reading outside the documentation directory. Removed the two call site escapes added earlier; the boundary handles them now and doubling would render entities on screen. The gopher escape stays: it passes a socket error as content, which the boundary does not touch. Suite: 79 tests, all passing.
Pablo Murad
-
5e5edc8
fix: unpickle executed its input as Lua code lousy.pickle.unpickle was loadstring("return " .. s) followed by a call. Four callers feed it outside data: the single instance D-Bus channel (lib/unique_instance.lua:59), the session file (lib/session.lua:104), the command history (lib/cmdhist.lua:99) and the settings file (lib/settings.lua:30). The D-Bus one is remote in practice. GApplication exports org.gtk.Actions on the session bus, so any process that reaches it - including a flatpak started with --socket=session-bus - sends a string that becomes Lua running in the UI process, with os.execute and the cookie database in scope. Replaced with a reader that parses instead of executing. It accepts only what pickle() emits: table literals, string, number and boolean literals, and {n} table references. No call, no operator, no identifier other than true, false and nil is reachable. Nesting is capped and trailing data is refused. On-disk format is unchanged, so existing sessions, command history and settings still load without migration. Also renamed the application id from org.luakit to org.skullbrowser, which was squatting on a real luakit install's bus name. Tests cover both halves: the format round-trips strings holding quotes, backslashes, newlines, NUL and high bytes, shared table references survive, and seven code execution payloads are refused. Suite: 79 tests, all passing.Pablo Murad
-
6c47fac
feat: adblock funcional, CI, about redesenhada, e remocao de API morta adblock_bootstrap baixa e mantem as listas de filtro. O modulo adblock lia .txt de um diretorio e nada mais: sem capacidade de buscar lista alguma, uma instalacao nova bloqueava exatamente nada. Agora baixa EasyList, EasyPrivacy e a lista em portugues via curl, valida o conteudo antes de aceitar (uma pagina de erro HTML seria parseada como regras sem reclamar) e recarrega. O modulo nao faz nada ao ser carregado: quem dispara e o rc.lua, o que mantem a suite de testes fora da rede. Corrigido no adblock.lua: load transmitia update_rules mas nao o estado de cada lista, entao processo web existente ficava com a lista carregada e desligada ate alguem alternar na mao. CI em GitHub Actions: dependencias, build e testes a cada push e PR. process_limit removido por inteiro. O codigo que aplicava o valor estava sob #if !WEBKIT_CHECK_VERSION(2,26,0), morto ha anos, enquanto a API seguia exposta ao Lua aceitando valores. O proprio rc.lua definia process_limit = 4, com um comentario acima admitindo que nao tinha efeito. docs/decisoes/001 registra por que ficamos no webkit2gtk-4.1 e a regra de nao crescer extension/. skull://about redesenhada em ingles: nome, versao, developed by pmurad, link para pablomurad.com, e a mencao ao luakit sob GPLv3. Titulo da janela volta a ser texto puro. O emoji so renderiza se o proprio browser desenhar a barra de titulo; com GTK_WINDOW_TOPLEVEL sem titlebar quem desenha e o compositor, que no WSLg roda em outro conteiner com as fontes dele.
Pablo Murad
-
23c8ac8
fix: injecao de HTML no gopher e laco infinito no error_page Um servidor gopher hostil executava script no browser sem nenhuma interacao do usuario. Confirmado, nao hipotetico. O vetor principal era o href do menu. href_source honrava um prefixo URL: no seletor com captura gulosa e zero sanitizacao, e menu_to_html emitia o resultado cru dentro de href="". Uma linha de menu como 1Label<TAB>URL:x"><img src=x onerror="..."><TAB>host<TAB>70 fechava o atributo e disparava o onerror na abertura da pagina. JavaScript esta ligado nessas paginas por padrao - o proprio gopher depende disso para a busca tipo 7. Vetor secundario: url.title ia cru para <title>. Vem do seletor ja passado por uri_decode, que materializa < e >. Exige navegacao induzida, mas o script cai na origem do host gopher nomeado, que o atacante nao precisa controlar. Correcoes em gopher.lua: escape no href e nos dois pontos que emitem <title>, lista de esquemas aceitos no prefixo URL: (javascript e data rejeitados) e sanitizacao do campo host, que vinha cru do servidor enquanto a porta ja era filtrada. Em error_page.lua, problema separado: a substituicao de {palavra} repetia ate nsub == 0, mas gsub com tabela conta a ocorrencia mesmo quando a chave nao existe e nada e substituido. Uma unica chave desconhecida travava a interface para sempre. Agora ha teto de iteracoes, e uri e err.message passam por escape - vem do WebKit e ninguem os tratava. Seis testes novos com entrada hostil. Eram os primeiros testes de escape do projeto: nao havia nenhum, nem para lousy.util.escape. Suite: 75 testes, todos passando.Pablo Murad
-
4967e70
build: plugins de midia do GStreamer, e nota de uso sob WSL O WebKit reclamava em tempo de execucao de encoder de WebVTT e de fakevideosink ausentes: faltavam gst-plugins-bad e gst-libav, que o setup-deps nao instalava. Legendas e parte da reproducao de video ficavam degradadas. Nomes conferidos nas tres distros. README: chamar 'skull' pelo nome a partir do Windows exige shell de login (wsl -- bash -lc), senao ~/.local/bin nao entra no PATH e o comando nao resolve. Dentro do WSL funciona direto.
Pablo Murad
-
54d6dd7
feat: Google volta a ser o buscador padrao Praticidade no caminho comum. Os independentes continuam disponiveis por atalho: sx (SearXNG), marginalia, mojeek, wiby. DuckDuckGo volta como ddg. Pagina inicial segue skull://newtab/ - nao tem relacao com busca. Suite: 69 testes, todos passando.
Pablo Murad
-
06ece15
feat: pagina inicial propria e buscadores independentes por padrao window.home_page apontava para https://luakit.github.io/ - abrir a pagina inicial no Skull Browser levava ao site do projeto original. Agora e skull://newtab/, que carrega sem rede e nao avisa ninguem que o browser abriu. Buscadores: sairam Google, DuckDuckGo e IMDB. O padrao passa a ser SearXNG, que e metabusca e qualquer um pode hospedar. Junto vieram Marginalia e Wiby, que indexam a web pequena e textual - a mesma que gopher e Gemini habitam - e Mojeek, com rastreador proprio. Nenhum dos quatro revende resultado de Google ou Bing. Endpoints conferidos com requisicao real: Marginalia, Wiby e searx.be responderam 200. Mojeek devolveu 403 para curl por bloqueio de agente, o formato da URL esta correto mas nao foi possivel confirmar de fato. Instancia publica de SearXNG tem limite de requisicao; trocar para uma propria e uma linha na config do usuario. Suite: 69 testes, todos passando.
Pablo Murad
-
1e313d3
feat: comando vira skull, e buildopts.h passa a acompanhar o PREFIX O comando era skull-browser; agora e skull, e o rename foi ate o fim em vez de parar no executavel: .so, man page, share/, lib/, os diretorios XDG e de cache/config/data, o .desktop e os icones. O nome de exibicao continua Skull Browser, e o esquema skull:// agora casa com o binario. Correcao de build herdada do luakit: a regra do buildopts.h so disparava quando buildopts.h.in mudava. Como PREFIX e XDGPREFIX chegam pela linha de comando, compilar com outro prefixo mantinha os caminhos da primeira build cravados no binario, que ia procurar modulos no lugar errado. A regra agora regera sempre e so substitui o arquivo quando o conteudo muda, para nao recompilar a arvore a toa. README: a secao de configuracao mandava copiar rc.lua de /usr/local/etc/xdg, caminho que so existe em instalacao com root. Agora cobre os dois casos, inclusive o de instalacao no home, onde o arquivo cai fora dos diretorios que o browser procura. Suite: 69 testes, todos passando.
Pablo Murad
-
6dc32de
test: suite volta a rodar, e sem exigir root O rename do binario nao tinha alcancado o harness: run_test.lua ainda invocava ./luakit, entao todo teste morria em 'No such file'. Duas mudancas para rodar fora de um ambiente com root: SKULL_TEST_DISPLAY reaproveita um X server existente. O Xvfb quer /tmp/.X11-unix com dono proprio e um xkbcomp em /usr/bin; sob WSLg o diretorio e do sistema e o bind falha, mas ali ja existe display. LUA_PATH, LUA_CPATH e LD_LIBRARY_PATH passam a ser repassados ao processo filho. O runner usa env -i, que limpava tudo, e modulos Lua instalados fora do prefixo do sistema ficavam invisiveis. Dados de teste: eu tinha trocado as URIs para skull:// mas deixado o campo scheme esperando luakit em test_clib_soup e test_lib_lousy_uri. about_chrome ganhou o blurb de modulo e a documentacao do stylesheet que os testes de estilo exigem. setup-deps: xvfb e luacheck nas tres familias; removido lua-luassert do Fedora, que nao empacota isso - o aviso de luarocks agora cita luassert. Resultado: 69 testes, 68 passando. test_scroll oscila sob carga da suite completa em display compartilhado; isolado passa 4 de 4.
Pablo Murad
-
5985430
feat: pagina skull://about e README Pagina sobre com nome, versao e a mencao a base, acessivel por gA ou :about. O cranio vai embutido como SVG na propria pagina: nao existe handler de recurso para skull://icons, entao depender de arquivo externo quebraria. README reescrito em ingles, com instrucoes de build e instalacao por distro, incluindo o caminho sem root via PREFIX no home. luassert adicionado ao setup-deps: make run-tests falha sem ele.
Pablo Murad
-
0beed7c
feat: identidade propria - icones, binario skull-browser, esquema skull:// Icones gerados por build-utils/gen-icons.js a partir de grades de pixel 16x16: PNG RGBA sem antialiasing, que e o que mantem o desenho legivel no tamanho de aba. Seis glifos distintos (cranio para paginas internas, documento, cranio violeta para privado, cranio morto para crash, triangulo de alerta, cadeado aberto) mais o icone do aplicativo em PNG e SVG. Rename de instalacao, sem tocar no namespace Lua: binario, .so e man page -> skull-browser share/luakit, lib/luakit, XDG -> skull-browser diretorios de cache/config/data no C esquema das paginas internas luakit:// -> skull:// (26 arquivos) Correcoes encontradas no caminho: gopher: a regra CSS tinha virgula sobrando e era descartada inteira, entao a fonte monoespacada nunca valeu gopher: URI malformada levantava dentro do signal handler; agora vira pagina de erro gopher: erro de socket ia cru para o error_page, que expande {palavra} recursivamente e comia trechos da mensagem error_page: link de favicon apontava para skull://icons, que nao tem handler registrado webview.c: request de esquema sem web view retornava sem finish, e o carregamento ficava pendurado para sempre make install apontava para extras/luakit.desktop, ja renomeado Gopher deixa de vir comentado em rc.lua. Verificado no Ubuntu 24.04 / WebKitGTK 2.52.6: make limpo, binario roda, --check valida a config com o gopher carregado.Pablo Murad
-
60edfc7
build: script de dependencias com suporte a apt, dnf e pacman Substitui o script so-Ubuntu. Detecta a familia da distro e instala os pacotes correspondentes; --check apenas verifica. Verificado no Ubuntu 24.04 (build completo). Fedora e Arch tem os nomes de pacote por conferir - a deteccao de distro e o relatorio funcionam, a instalacao em si nao foi testada nessas duas.
Pablo Murad
-
0672a57
build: script de preparacao do ambiente WSL/Ubuntu Instala e verifica as dependencias que o config.mk exige via pkg-config (gtk+-3.0, gthread-2.0, webkit2gtk-4.1, sqlite3, javascriptcoregtk-4.1, luajit) mais os modulos Lua de runtime (lfs, socket). Idempotente e verboso: falha com lista do que ficou faltando. Fase 0 do plano do 0.01.
Pablo Murad
-
f6a137f
feat: identidade do fork - Skull Browser, autoria e titulo - AUTHORS: Pablo Murad <pablomurad@pm.me> como autor do fork, mantendo toda a autoria anterior do luakit conforme exige a GPLv3 - lib/window.lua: titulo padrao da janela agora e 'Skull Browser' - extras/: luakit.desktop -> skull-browser.desktop (Name, Exec, Icon)
Pablo Murad
-
cc12505
chore: remove upstream CI, changelog e docs de contribuicao do Luakit Removidos artefatos herdados que so faziam sentido no repositorio original: - .github/ (workflows de CI e template de issue apontando para luakit/luakit) - .travis.yml (CI desativado) - CHANGELOG.md e MIGRATE.md (historico de versoes do Luakit) - CONTRIBUTING.md (instrucoes de contribuicao do projeto original)
Pablo Murad
-
3ea493c
chore: initial import of skull browser Base de código derivada do Luakit (https://github.com/luakit/luakit), licenciado sob GNU GPLv3. Histórico upstream removido; este repositório inicia uma linha de desenvolvimento independente.
Pablo Murad