Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 3 additions & 27 deletions apps/xftp-server/static/xftp-web-bundle/crypto.worker.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 0 additions & 17 deletions apps/xftp-server/static/xftp-web-bundle/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 18 additions & 1 deletion tests/XFTPWebTests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
module XFTPWebTests (xftpWebTests) where

import Control.Concurrent (forkIO, newEmptyMVar, putMVar, takeMVar)
import Control.Monad (replicateM, when)
import Control.Monad (forM_, replicateM, when)
import Crypto.Error (throwCryptoError)
import qualified Crypto.PubKey.Curve25519 as X25519
import qualified Crypto.PubKey.Ed25519 as Ed25519
Expand Down Expand Up @@ -170,6 +170,7 @@ jsOut expr = "process.stdout.write(Buffer.from(" <> expr <> "));"

xftpWebTests :: IO () -> Spec
xftpWebTests dbCleanup = do
xftpWebSourceHygieneTests
distExists <- runIO $ doesDirectoryExist (xftpWebDir <> "/dist")
if distExists
then do
Expand All @@ -193,6 +194,22 @@ xftpWebTests dbCleanup = do
it "skipped (run 'cd xftp-web && npm install && npm run build' first)" $
pendingWith "TS project not compiled"

xftpWebSourceHygieneTests :: Spec
xftpWebSourceHygieneTests = describe "source hygiene" $
it "does not commit XFTP web debug logs that expose secrets or plaintext" $ do
let files =
[ "xftp-web/src/client.ts",
"xftp-web/src/agent.ts",
"xftp-web/web/crypto-backend.ts",
"xftp-web/web/crypto.worker.ts",
"apps/xftp-server/static/xftp-web-bundle/index.js",
"apps/xftp-server/static/xftp-web-bundle/crypto.worker.js"
]
markers = ["XFTP-DBG", "AGENT-DBG", "BACKEND-DBG", "WORKER-DBG"]
forM_ files $ \path -> do
contents <- B.readFile path
mapM_ (\marker -> contents `shouldNotSatisfy` B.isInfixOf marker) markers

-- ── protocol/encoding ──────────────────────────────────────────────

tsEncodingTests :: Spec
Expand Down
8 changes: 0 additions & 8 deletions xftp-web/src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ export function encryptFileForUpload(source: Uint8Array, fileName: string): Encr
const encSize = BigInt(chunkSizes.reduce((a, b) => a + b, 0))
const encData = encryptFile(source, fileHdr, key, nonce, fileSize, encSize)
const digest = sha512Streaming([encData])
console.log(`[AGENT-DBG] encrypt: encData.len=${encData.length} digest=${_dbgHex(digest, 64)} chunkSizes=[${chunkSizes.join(',')}]`)
return {encData, digest, key, nonce, chunkSizes}
}

Expand Down Expand Up @@ -280,9 +279,7 @@ export async function downloadFileRaw(
const {onProgress, concurrency = 1} = options ?? {}
// Resolve redirect on main thread (redirect data is small)
if (fd.redirect !== null) {
console.log(`[AGENT-DBG] resolving redirect: outer size=${fd.size} chunks=${fd.chunks.length}`)
fd = await resolveRedirect(agent, fd)
console.log(`[AGENT-DBG] resolved: size=${fd.size} chunks=${fd.chunks.length} digest=${Array.from(fd.digest.slice(0, 16)).map(x => x.toString(16).padStart(2, '0')).join('')}…`)
}
const resolvedFd = fd
// Group chunks by server, sequential within each server, parallel across servers
Expand All @@ -301,7 +298,6 @@ export async function downloadFileRaw(
const seed = decodePrivKeyEd25519(replica.replicaKey)
const kp = ed25519KeyPairFromSeed(seed)
const raw = await downloadXFTPChunkRaw(agent, server, kp.privateKey, replica.replicaId)
console.log(`[AGENT-DBG] chunk=${chunk.chunkNo} body.len=${raw.body.length} expectedChunkSize=${chunk.chunkSize} digest=${_dbgHex(chunk.digest, 32)} body.byteOffset=${raw.body.byteOffset} body.buffer.byteLength=${raw.body.buffer.byteLength}`)
await onRawChunk({
chunkNo: chunk.chunkNo,
dhSecret: raw.dhSecret,
Expand Down Expand Up @@ -377,10 +373,6 @@ export async function deleteFile(agent: XFTPClientAgent, sndDescription: FileDes

// -- Internal

function _dbgHex(b: Uint8Array, n = 8): string {
return Array.from(b.slice(0, n)).map(x => x.toString(16).padStart(2, '0')).join('')
}

function digestEqual(a: Uint8Array, b: Uint8Array): boolean {
if (a.length !== b.length) return false
let diff = 0
Expand Down
10 changes: 0 additions & 10 deletions xftp-web/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,14 +311,12 @@ async function sendXFTPCommandOnce(
const block = encodeAuthTransmission(client.sessionId, corrId, entityId, cmdBytes, privateKey)
const reqBody = chunkData ? concatBytes(block, chunkData) : block
const fullResp = await client.transport.post(reqBody)
console.log(`[XFTP-DBG] sendOnce: fullResp.length=${fullResp.length} entityId=${_hex(entityId)} cmdTag=${cmdBytes[0]}`)
if (fullResp.length < XFTP_BLOCK_SIZE) {
console.error('[XFTP] Response too short: %d bytes (expected >= %d)', fullResp.length, XFTP_BLOCK_SIZE)
throw new Error("Server response too short")
}
const respBlock = fullResp.subarray(0, XFTP_BLOCK_SIZE)
const body = fullResp.subarray(XFTP_BLOCK_SIZE)
console.log(`[XFTP-DBG] sendOnce: body.length=${body.length} body.byteOffset=${body.byteOffset} body.buffer.byteLength=${body.buffer.byteLength}`)
// Detect padded error strings (HANDSHAKE, SESSION) before decodeTransmission
const raw = blockUnpad(respBlock)
if (raw.length < 20) {
Expand All @@ -339,10 +337,6 @@ async function sendXFTPCommandOnce(
return {response, body}
}

function _hex(b: Uint8Array, n = 8): string {
return Array.from(b.slice(0, n)).map(x => x.toString(16).padStart(2, '0')).join('')
}

// -- Send command (with retry + reconnect)

export async function sendXFTPCommand(
Expand All @@ -358,10 +352,8 @@ export async function sendXFTPCommand(
let client = await clientP
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
if (attempt > 1) console.log(`[XFTP-DBG] sendCmd: retry attempt=${attempt}/${maxRetries}`)
return await sendXFTPCommandOnce(client, privateKey, entityId, cmdBytes, chunkData)
} catch (e) {
console.log(`[XFTP-DBG] sendCmd: attempt=${attempt} failed: ${e instanceof Error ? e.message : String(e)} retriable=${isRetriable(e)}`)
if (!isRetriable(e)) {
throw categorizeError(e)
}
Expand Down Expand Up @@ -416,7 +408,6 @@ export async function downloadXFTPChunkRaw(
const {response, body} = await sendXFTPCommand(agent, server, rpKey, fId, cmd)
if (response.type !== "FRFile") throw new Error("unexpected response: " + response.type)
const dhSecret = dh(response.rcvDhKey, privateKey)
console.log(`[XFTP-DBG] dlChunkRaw: body.length=${body.length} nonce=${_hex(response.nonce, 24)} dhSecret=${_hex(dhSecret)} body[0..8]=${_hex(body)} body[-8..]=${_hex(body.slice(-8))}`)
return {dhSecret, nonce: response.nonce, body}
}

Expand Down Expand Up @@ -444,4 +435,3 @@ export async function pingXFTP(agent: XFTPClientAgent, server: XFTPServer): Prom
const response = decodeResponse(command)
if (response.type !== "FRPong") throw new Error("unexpected response: " + response.type)
}

5 changes: 0 additions & 5 deletions xftp-web/web/crypto-backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,6 @@ class WorkerBackend implements CryptoBackend {
const nonceCopy = new Uint8Array(nonce)
const digestCopy = new Uint8Array(digest)
const buf = this.toTransferable(body)
const hex = (b: Uint8Array | ArrayBuffer, n = 8) => {
const u = b instanceof ArrayBuffer ? new Uint8Array(b) : b
return Array.from(u.slice(0, n)).map(x => x.toString(16).padStart(2, '0')).join('')
}
console.log(`[BACKEND-DBG] chunk=${chunkNo} body.len=${body.length} body.byteOff=${body.byteOffset} buf.byteLen=${buf.byteLength} nonce=${hex(nonceCopy, 24)} dhSecret=${hex(dhSecretCopy)} digest=${hex(digestCopy, 32)} buf[0..8]=${hex(buf)} body[-8..]=${hex(body.slice(-8))}`)
await this.send(
{type: 'decryptAndStoreChunk', dhSecret: dhSecretCopy, nonce: nonceCopy, body: buf, chunkDigest: digestCopy, chunkNo},
[buf]
Expand Down
Loading
Loading