Sno Station
Sno Station은 AI 에이전트를 하나의 팀으로 묶는 오픈소스 워크스테이션으로, 로컬 머신에서 에이전트 협업을 가능하게 함
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기API Security Researcher
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A Chrome extension (MV3) that maps a site's API surface and finds client-side XSS by running the page's own JavaScript bundle under forced multi-path exploration, on a forked QuickJS-ng engine over a spec-built Lexbor DOM. It uses no debugger, no proxy and no webRequest.
In one sentence: a browser with a BFS time-travel solver. The browser half (Lexbor and QuickJS-ng, in C) follows the specs. The solver half — forced multi-path concolic execution over copy-on-write snapshots — exists to do two things a scanner cannot:README 전문 보기 ↗A Chrome extension (MV3) that maps a site's API surface and finds client-side XSS by running the page's own JavaScript bundle under forced multi-path exploration, on a forked QuickJS-ng engine over a spec-built Lexbor DOM. It needs no proxy and no debugger permission: requests are captured inside the page, and the extension's own sends get their headers from declarativeNetRequest rules (see Permissions).
In one sentence: a browser with a BFS time-travel solver. The browser half (Lexbor and QuickJS-ng, in C) follows the specs. The solver half — forced multi-path concolic execution over copy-on-write snapshots — exists to do two things a scanner cannot:
What the tool adds on a given page is measurable: the addresses a run emitted that no parse of the served bytes can state, because they exist only at run time. A total is not that number — most of a total is the markup counted back.
Browse any site. In the background:
URL. Closures, prototypes, call/apply/bind, Proxy, async code and framework state machines behave exactly as ECMAScript says, because this is a real engine rather than a model of one.location, document.cookie, postMessage, storage, response bodies) carries its identity, the path constraints a flow has placed on it, and a concrete example when the code pins or computes one. Control flow forks on it; real values propagate.postMessage traffic is decoded (JSON, JSPB/protobuf, gRPC-Web, batchexecute, multipart, SSE/NDJSON, GraphQL) into its own pool. Every value carries its provenance — observed, derived or forced — so what execution contributed stays measurable.Open the popup to inspect, send and export what it found.
switch/case, .includes(), equality chains), offered in the Send panel. A value is concrete only where the code pins it (an equality gate) or computes it; a range, prefix or pattern gate stays a domain-annotated shape, never an invented guess.google.rpc.Status replies name the expected fields (lib/discovery-probe.js, lib/req2proto.js).Taint is observed in the same execution used for API learning. There is no separate traversal and no name matching, so minified code is no obstacle: a sink is whatever an executed call reaches at the host edge.
location.hash and search are percent-encoded per the WHATWG per-component sets, and a forgeable message.origin check is solvable while an exact === is not.innerHTML, is not a finding.default policy at the sink.The browser half is a rendering engine: CSS parsing and cascade, computed and used values, block, inline, flex and table layout, CSS 2.1 Appendix E painting order, and a rasterizer. Layout and paint ride the same per-flow copy-on-write state as everything else, so an explored world is a resumable document rather than a log line. The logged-in and logged-out surfaces are two live documents of one page, each renderable and continuable from its own snapshot. WPT reftests measure layout and paint fidelity directly.
Two halves: the C engine is the browser and owns all analysis; the JavaScript is the platform edge (network, storage, chrome.*, loading the WASM, the popup), one problem per file.
Every engine instance runs in a renderer: an <iframe sandbox="allow-scripts" src="renderer.html"> whose opaque origin is the security boundary, reached over a mojom-style interface. The offscreen document is the only trusted zone and holds the network policy, the store and the popup's data, none of which the engine can reach. There is one WASM instance per origin-keyed agent cluster.
extension/
intercept.js Main-world fetch/XHR/WebSocket/EventSource capture
content.js Isolated-world relay; a seed it forwards is an address, never document bytes
doc-lifetime.js Isolated-world port whose close tells the zone its document is gone
background.js Stateless service worker: offscreen lifecycle, browser events, privileged chrome.* calls
speak-as-rule.js The service worker's rule that makes a zone send carry a page's headers
ast-worker.html The offscreen document: the trusted zone's page
offscreen-brain.js The trusted zone: wires lib/*, owns IndexedDB
bridge.js Network, IndexedDB and renderer edges for the engine
renderer.html One sandboxed engine instance
renderer-host.js The trusted side of one renderer; render-process-host.js owns the pool
mojo.js, mojom.js The interface between the trusted zone and a renderer
popup.js Popup controller; lib/popup-*.js are its panels
dev.html, dev.js Diagnostics page: engine readings a developer reads, over the popup's messages
check.js JS mirror of the engine's DCHECK/CHECK
lib/safe-fetch.js The one external-fetch chokepoint: scheme, SOP/CORS, credentials, provenance, destination
lib/ Learning, schema, keys, persistence, grouping, merge, send, encode, serialize,
protocol decoders, discovery, OpenAPI import/export
engine/
host/main.c Native host entry and the shipped ABI
host/solver/ Scheduler (one weighted fair queue), per-flow copy-on-write heap and DOM, concolic values,
constraint domains, HTML/JS breakout analysis, reply learning, the cold tier, @S search
host/browser/core/ The browser by component: css, layout, paint, dom, html, frame, events, fetch, loader,
indexeddb, streams, crypto, canvas, url, xml, storage, image, ...
host/check.h CHECK (always fatal) and DCHECK (dev builds): every invariant asserted at its origin
qjs/ The forked QuickJS-ng, tracked in this repository; engine/qjs/test262 is a submodule
lexbor/ The vendored Lexbor (DOM, HTML and CSS parsers)
build.mjs Builds the Chrome WASM target and the native host from the same sources
Each permission in extension/manifest.json and what it is for:
| Permission | Used for |
|---|---|
offscreen | The offscreen document (ast-worker.html) that holds the trusted zone, IndexedDB and every engine renderer. |
declarativeNetRequestWithHostAccess | One session rule per in-flight send that speaks as a document, which sets the Origin, Referer, Sec-Fetch-* and Cookie headers the zone computed on exactly that request (speak-as-rule.js). |
cookies | Reading a document's cookies and its partition key, so a credentialed send carries that document's jar (lib/request-identity.js). |
webNavigation | Frame identities (getAllFrames) for the document a send speaks as, and navigation events the popup follows. |
publicSuffix | The browser's own registrable domains, which the zone's sites, SameSite statuses, cookie partitions and cookie Domain checks are computed over (lib/site.js), so they agree with Chrome's cookie jar. Read-only. It exists from Chrome 153, which minimum_chrome_version states. |
tabs | The active tab, the live-verify attacker tab, and messages to content scripts. |
scripting | Arming the live-verify attacker tab: a predefined injector run in that tab's main world, refused for any tab the service worker did not open. |
webRequest | The browser's own record of each fetch() and XHR a document makes (request line, headers, body, status), joined by documentId to the response body intercept.js captures (lib/web-request.js). It blocks and modifies nothing. |
unlimitedStorage | The IndexedDB store, which holds the cross-session frontier and cold snapshots. |
<all_urls> host access | The content scripts on every page, and the zone's requests, which leave through the service worker's fetch. |
Not held: debugger and a proxy. A page's response bodies, WebSocket frames and EventSource messages are captured in the page itself (intercept.js, which wraps fetch, XHR, WebSocket and EventSource in the main world); webRequest reports none of them. The extension's pages are cross-origin isolated (COEP require-corp with COOP same-origin, for SharedArrayBuffer), and under that Chrome 148 reports none of the offscreen document's requests to webRequest, while it does report the service worker's own fetches. So the zone's sends leave through the service worker's fetch (wire.js), whose webRequest record of each 3xx lets the zone follow every redirect hop itself, under every gate. SECURITY.md states what this leaves uncovered.
All learned state lives in IndexedDB in the offscreen document, whose lifetime survives service-worker eviction. chrome.storage.local is not used.
Trust is decided by browser-stamped sender fields, never sender.id: MessageSender.origin for a message from an extension document, and sender.url only for the service worker, which has no frame. The principal for a credentialed read is MessageSender.origin. The content script, the website and the WASM engine are untrusted.
Every request is decided in the offscreen document by lib/safe-fetch.js, and leaves through the one fetch the service worker holds (wire.js). A request that speaks as a page, which covers the engine's page requests and an operator's Send-panel request, carries that document's headers: its Origin, its Referer under Referrer Policy, its Sec-Fetch-*, and, where credentialed, its cookies, partitioned ones included. The headers are computed in the trusted zone from browser-stamped identity (lib/request-identity.js) and set on that one request by a declarativeNetRequest rule the service worker installs and removes (speak-as-rule.js). The observed request's own credentials mode, mode, referrer and referrer policy are recorded by intercept.js and replayed. Fetch's forbidden request-headers (Cookie, Origin, Referer, …) cannot be typed into a request, because the browser owns them. The page's service worker does not see these sends, and the page's CSP does not bind them. The engine holds no network policy. Whether a request fires is a per-origin setting. By default an origin is loaded the way a browser loads it: what the page's markup names and its own code requests, and nothing else. Probes, discovery and requests built on forked values need that origin deliberately widened. The chokepoint shows each request's signals (method, credential state, provenance, URL-carried authority, destination) as separate rows with their reliability, and an undetermined signal reads unknown rather than being left out.
Extension pages run under a CSP of script-src 'self' 'wasm-unsafe-eval' with no inline script, so page-derived text shown in the popup cannot run script.
The full threat model, every boundary and its known residuals are in SECURITY.md. Authorized security research only.
node engine/build.mjs # both targets: WASM into extension/lib/qjs/, native host into engine/host/out/
node engine/build.mjs native # the native host only
A build reads the working tree. A number that will be quoted comes from a frozen snapshot at a named revision:
engine/frozen_snapshot.sh <revision> <lane-name> node engine/build.mjs
The engine asserts its invariants where they originate, so a change is verified by the asserts staying silent. The corpora exercise code so the asserts can fire; a passing suite proves nothing about what it did not exercise.
node engine/test262.mjs [subdir] # ECMAScript conformance
node engine/wpt.mjs [subdir] # web-platform-tests
node engine/solvergate.mjs [doc] # one document under several schedules must emit the same findings
node engine/citegen.mjs [path] # every spec citation checked against the standard's text
node engine/idlgen.mjs # Web IDL diffed against what each component installs
Driving a document directly:
node engine/trusted.mjs <url> <binary> [--paint <dir>] # run one document natively; optionally write images
engine/host/out/qjs-native-none --doc <name> # one fixture document (--list-docs lists them)
node testing/harness.js restart # live Chrome: clear storage, load the rebuilt extension
node testing/harness.js goto <url> # real Chrome on a real page
The spec is the source of truth and real Chrome confirms it. A fidelity gap is fixed by implementing the spec at the root.