Skip to content
Open
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.DS_Store
node_modules/
82 changes: 60 additions & 22 deletions 404.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,69 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>securebin | Powerful Pastebin Chrome Extension</title>
<link rel="canonical" href="https://securebin.org/404.html">
<meta property="og:url" content="https://securebin.org/404.html">
<meta property="og:image" content="https://securebin.org/assets/img/Logo/logo512.png">
<meta name="description" content="Google Chrome extension for interfacing securely with Pastebin. Users can encrypt plaintext and have it stored onto Pastebin, where they can copy the link and key to send it to another user for decryption. First, users will need to get a API key from Pastebin to be able to post onto Pastebin. Once the API key is added to the extension in the settings page, users can post their messages securely to Pastebin. To learn more about our project and the design decisions check out the Wiki page.">
<meta property="og:type" content="website">
<link rel="icon" type="image/png" sizes="128x128" href="assets/img/Logo/logo128.png?h=26221b62b57295eb8f6f5d397295a7fe">
<link rel="icon" type="image/png" sizes="128x128" href="assets/img/Logo/logo128.png?h=26221b62b57295eb8f6f5d397295a7fe">
<link rel="icon" type="image/png" sizes="192x192" href="assets/img/Logo/logo192.png?h=78dce701743c1fbc0bf5ca2d436c9eba">
<link rel="icon" type="image/png" sizes="512x512" href="assets/img/Logo/logo512.png?h=ebc3f90b972ea8456db4efa1d7df5613">
<link rel="icon" type="image/png" sizes="512x512" href="assets/img/Logo/logo512.png?h=ebc3f90b972ea8456db4efa1d7df5613">
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css?h=a549af2a81cd9900ee897d8bc9c4b5e9">
<link rel="stylesheet" href="assets/css/styles.min.css?h=d4d6c6936bc7e8eb291c9b48122fe3d4">
<meta name="description" content="Google Chrome extension for interfacing securely with Pastebin. Write or paste text in a modern code editor, encrypt it in your browser with AES-GCM, and post it to Pastebin in one click.">
<meta name="robots" content="noindex">
<link rel="icon" type="image/png" sizes="128x128" href="/assets/img/Logo/logo128.png?h=26221b62b57295eb8f6f5d397295a7fe">
<link rel="icon" type="image/png" sizes="192x192" href="/assets/img/Logo/logo192.png?h=78dce701743c1fbc0bf5ca2d436c9eba">
<link rel="icon" type="image/png" sizes="512x512" href="/assets/img/Logo/logo512.png?h=ebc3f90b972ea8456db4efa1d7df5613">
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css?h=a549af2a81cd9900ee897d8bc9c4b5e9">
<link rel="stylesheet" href="/assets/css/styles.min.css?h=d4d6c6936bc7e8eb291c9b48122fe3d4">
<link rel="stylesheet" href="/assets/css/viewer.css">
<script>
// GitHub Pages serves this file for every unknown path, which is how
// the pastebin.com mirror works: securebin.org/<pasteId> renders the
// paste viewer in place (paste ids are alphanumeric, so real pages —
// support/, donate/, files with extensions — never match). Decide
// before first paint to avoid flashing the 404 content.
window.__SB_IS_PASTE__ = /^\/[A-Za-z0-9]{4,12}\/?$/.test(window.location.pathname)
if (window.__SB_IS_PASTE__) {
document.documentElement.style.visibility = 'hidden'
// Never leave the page blank if the viewer module fails to load
setTimeout(() => { document.documentElement.style.visibility = '' }, 3000)
}
</script>
</head>

<body style="--bs-body-bg: #212121;">
<div style="margin: auto;display: block;/*width: 498px;*/margin-top: 81px;"><a href="index.html"><img src="assets/img/logo.svg?h=f161e67b873a063dc2a0ba9587073651" style="display: block;margin: auto;width: 116px;/*margin-top: 48px;*/" href="index.html"></a></div>
<div style="margin: auto;display: block;width: 498px;max-width: 90%;padding-bottom: 150px;">
<h1 style="border-color: rgb(244,244,244);--bs-body-color: #ffffff;color: white;margin-top: 15vh;/*max-width: 95%;*/text-align: center;">Not Found</h1>
<p style="color: #a2a2a2;text-align: center;font-size: 18px;/*margin-top: 27px;*/max-width: 75%;margin: 27px auto;"><span style="color: var(--text-primary); background-color: transparent;">We can't find the page you're looking for. Check the URL and try again.</span></p><a class="btn btn-primary" role="button" style="margin: auto;display: block;background-color: #1D6BC6;margin-top: 32px;border: none;max-width: 372px;padding: 9px 76px;/*color: var(--bs-indigo);*/" href="index.html" target="">Return Home</a><a class="btn btn-primary" role="button" style="margin: auto;display: block;background-color: #4b4b4b;margin-top: 16px;border: none;max-width: 372px;padding: 9px 88px;/*color: var(--bs-indigo);*/" href="support/" target="">Get Help</a>
<body style="--bs-body-bg: #212121;margin:0;">
<div id="sb-notfound">
<div style="margin: auto;display: block;margin-top: 81px;"><a href="/"><img src="/assets/img/logo.svg?h=f161e67b873a063dc2a0ba9587073651" style="display: block;margin: auto;width: 116px;"></a></div>
<div style="margin: auto;display: block;width: 498px;max-width: 90%;padding-bottom: 150px;">
<h1 style="border-color: rgb(244,244,244);--bs-body-color: #ffffff;color: white;margin-top: 15vh;text-align: center;">Not Found</h1>
<p style="color: #a2a2a2;text-align: center;font-size: 18px;max-width: 75%;margin: 27px auto;"><span style="color: var(--text-primary); background-color: transparent;">We can't find the page you're looking for. Check the URL and try again.</span></p><a class="btn btn-primary" role="button" style="margin: auto;display: block;background-color: #1D6BC6;margin-top: 32px;border: none;max-width: 372px;padding: 9px 76px;" href="/">Return Home</a><a class="btn btn-primary" role="button" style="margin: auto;display: block;background-color: #4b4b4b;margin-top: 16px;border: none;max-width: 372px;padding: 9px 88px;" href="/support/">Get Help</a>
</div>
<footer style="margin-top: 37vh;position: absolute;bottom: 20px;width: 100%;">
<div style="margin: auto;display: block;margin-bottom: 20px;"><img src="/assets/img/logo.svg?h=f161e67b873a063dc2a0ba9587073651" style="display: block;margin: auto;" width="90px"></div>
<p style="color: rgb(162,162,162);height: 9px;text-align: center;margin-top: -6px;font-size: 9px;">© 2026 securebin.</p>
</footer>
</div>
<footer style="/*padding-top: 111px;*/margin-top: 37vh;position: absolute;bottom: 20px;width: 100%;/*margin-left: auto;*/">
<div style="margin: auto;display: block;/*width: 20px;*/margin-bottom: 20px;/*width: 498px;*//*margin-top: 81px;*/"><img src="assets/img/logo.svg?h=f161e67b873a063dc2a0ba9587073651" style="display: block;margin: auto;/*width: 116px;*//*margin-top: 48px;*/" width="90px"></div>
<p style="color: rgb(162,162,162);height: 9px;text-align: center;margin-top: -6px;font-size: 9px;">© 2026 securebin.</p>
</footer>
<script src="assets/bootstrap/js/bootstrap.min.js?h=374d178d651fa0eaf680a1fa7b40c788"></script>
<div id="sb-viewer" class="sbv-root" hidden></div>
<script type="module">
import { resolvePasteRequest, initViewer } from '/assets/js/viewer.js'

const notfound = document.getElementById('sb-notfound')
const viewer = document.getElementById('sb-viewer')
// __SB_IS_PASTE__ only checks the path shape; resolvePasteRequest is the
// authority (e.g. /view with no #paste is a path-shaped match but not a
// paste). notfound is the default; swap to the viewer only for a real one.
const request = window.__SB_IS_PASTE__ ? resolvePasteRequest(window.location) : null

function showNotFound() {
viewer.hidden = true
notfound.hidden = false
}
if (request) {
notfound.hidden = true
viewer.hidden = false
// A missing paste on the mirror path may just be a mistyped page —
// fall back to this normal Not Found page instead of "Paste not found".
initViewer(viewer, request, { onMissing: showNotFound })
}
document.documentElement.style.visibility = ''
</script>
<noscript>
<style>html { visibility: visible !important; }</style>
</noscript>
</body>

</html>
</html>
Binary file removed assets/.DS_Store
Binary file not shown.
180 changes: 180 additions & 0 deletions assets/css/viewer.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/* securebin paste viewer — matches the site's dark style (#212121 bg,
#1D6BC6 primary, muted #a2a2a2) and the extension's rounded, minimal UI. */

.sbv-root {
min-height: 100vh;
display: flex;
flex-direction: column;
background: #212121;
color: #f2f2f7;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", system-ui, sans-serif;
-webkit-font-smoothing: antialiased;
}

.sbv-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28px clamp(20px, 6vw, 64px) 0;
}
.sbv-logo img { display: block; }
.sbv-header-link {
color: #a2a2a2;
font-size: 14px;
text-decoration: none;
}
.sbv-header-link:hover { color: #ffffff; }

.sbv-main {
flex: 1;
width: min(860px, 92vw);
margin: 0 auto;
padding: clamp(24px, 6vh, 56px) 0 40px;
}

.sbv-state[hidden] { display: none; }

.sbv-title {
color: #ffffff;
font-size: 26px;
font-weight: 700;
margin: 0 0 10px;
text-align: center;
}
.sbv-muted {
color: #a2a2a2;
font-size: 16px;
line-height: 1.5;
text-align: center;
max-width: 480px;
margin: 0 auto 22px;
}

/* Loading */
.sbv-state[data-state="loading"] { text-align: center; padding-top: 18vh; }
.sbv-spinner {
width: 28px;
height: 28px;
margin: 0 auto 16px;
border: 3px solid rgba(255, 255, 255, 0.15);
border-top-color: #1D6BC6;
border-radius: 50%;
animation: sbv-spin 0.8s linear infinite;
}
@keyframes sbv-spin { to { transform: rotate(360deg); } }

/* Encrypted prompt */
.sbv-state[data-state="locked"] { padding-top: 10vh; text-align: center; }
.sbv-lock-badge { font-size: 40px; margin-bottom: 14px; }
.sbv-keyform {
display: flex;
gap: 10px;
justify-content: center;
max-width: 460px;
margin: 0 auto;
}
.sbv-keyinput {
flex: 1;
padding: 11px 14px;
font-size: 14px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
color: #f2f2f7;
background: #2c2c2e;
border: 1px solid #3a3a3c;
border-radius: 12px;
outline: none;
}
.sbv-keyinput:focus { border-color: #1D6BC6; }
.sbv-keyerror { color: #ff453a; font-size: 14px; margin-top: 14px; }

/* Buttons */
.sbv-btn {
display: inline-block;
padding: 11px 22px;
font-size: 14px;
font-weight: 600;
border: none;
border-radius: 12px;
cursor: pointer;
text-decoration: none;
transition: background 0.15s, transform 0.1s;
}
.sbv-btn:active { transform: scale(0.98); }
.sbv-btn-primary { background: #1D6BC6; color: #ffffff; }
.sbv-btn-primary:hover { background: #1558a8; }
.sbv-btn-secondary { background: #4b4b4b; color: #ffffff; margin: 0 auto; }
.sbv-btn-secondary:hover { background: #5a5a5a; }
.sbv-btn-small { padding: 6px 14px; font-size: 12.5px; }

/* Content card */
.sbv-decrypted-banner {
border: 1px solid rgba(48, 209, 88, 0.35);
background: rgba(48, 209, 88, 0.08);
color: #30d158;
border-radius: 12px;
padding: 12px 16px;
font-size: 14px;
font-weight: 500;
margin-bottom: 14px;
}
.sbv-decrypted-banner[hidden] { display: none; }
.sbv-contentcard {
background: #1c1c1e;
border: 1px solid #38383a;
border-radius: 14px;
overflow: hidden;
}
.sbv-contentbar {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 16px;
border-bottom: 1px solid #38383a;
background: #252527;
}
.sbv-pasteid {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 13px;
color: #ffffff;
font-weight: 600;
}
.sbv-contentmeta { flex: 1; color: #8e8e93; font-size: 12.5px; }
.sbv-copy { background: #3a3a3c; color: #f2f2f7; }
.sbv-copy:hover { background: #48484a; }
.sbv-copy.sbv-copied { background: rgba(48, 209, 88, 0.2); color: #30d158; }
.sbv-pre {
margin: 0;
padding: 18px;
max-height: 68vh;
overflow: auto;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 13.5px;
line-height: 1.55;
color: #e5e5ea;
white-space: pre-wrap;
overflow-wrap: anywhere;
tab-size: 4;
}

/* Error */
.sbv-state[data-state="error"] { padding-top: 14vh; text-align: center; }
.sbv-errorlink { margin-top: 6px; }

/* Footer */
.sbv-footer {
display: flex;
flex-direction: column;
gap: 6px;
align-items: center;
padding: 26px 20px 30px;
color: #8e8e93;
font-size: 12.5px;
text-align: center;
}
.sbv-footer a { color: #a2a2a2; }
.sbv-footer a:hover { color: #ffffff; }

@media (max-width: 560px) {
.sbv-keyform { flex-direction: column; }
.sbv-header { padding-top: 20px; }
}
93 changes: 93 additions & 0 deletions assets/js/viewer-crypto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// securebin paste decryption — mirrors the extension's src/lib/crypto.ts
// exactly: ciphertexts are the JSON blob {C_TXT, IV, Mode, Tag, Salt?, Length?}
// produced by encrypt(); password keys are PBKDF2-SHA256 (10k iterations),
// random keys are raw base64. Everything runs locally via the Web Crypto API —
// no key or plaintext ever leaves the browser.

const utf8 = new TextEncoder()
const utf8dec = new TextDecoder()

/** The text is a securebin ciphertext blob (not prose mentioning C_TXT). */
export function isCiphertext(text) {
const trimmed = text.trim()
return trimmed.startsWith('{') && trimmed.includes('C_TXT')
}

/**
* Parsed cipher data, or null when the text isn't a securebin ciphertext.
* Matches the extension: anything shaped like a cipher blob (the C_TXT/IV/Mode
* keys present) is routed into the decrypt flow, and any malformation surfaces
* as a decryption failure rather than silently rendering as plaintext.
*/
export function parseCipherData(text) {
if (!isCiphertext(text)) return null
try {
const data = JSON.parse(text.trim())
return data && typeof data === 'object' && 'C_TXT' in data && 'IV' in data && 'Mode' in data ? data : null
} catch {
return null
}
}

function base64ToBytes(base64) {
const binary = atob(base64)
const bytes = new Uint8Array(binary.length)
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)
return bytes
}

function algorithmName(mode) {
// AES-CBC accepted for decryption only (legacy ciphertexts)
if (mode === 'AES-GCM' || mode === 'AES-CTR' || mode === 'AES-CBC') return mode
throw new Error(`Unsupported encryption mode: ${mode}`)
}

/**
* Decrypts a securebin cipher data object with the given passkey.
* Throws on a wrong key or corrupted ciphertext.
*/
export async function decryptCipherData(data, passkey) {
const algorithm = algorithmName(data.Mode)
const iv = base64ToBytes(data.IV)

let cryptoKey
if (data.Salt && data.Length) {
// Password-derived key: PBKDF2(SHA-256, 10000 iterations, 16-byte salt)
const keyMaterial = await crypto.subtle.importKey('raw', utf8.encode(passkey), 'PBKDF2', false, ['deriveKey'])
cryptoKey = await crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt: base64ToBytes(data.Salt), iterations: 10000, hash: 'SHA-256' },
keyMaterial,
{ name: algorithm, length: data.Length * 8 },
false,
['decrypt'],
)
} else {
// Randomly generated key, shared as raw base64
const rawKey = base64ToBytes(passkey)
cryptoKey = await crypto.subtle.importKey(
'raw',
rawKey,
{ name: algorithm, length: rawKey.byteLength * 8 },
false,
['decrypt'],
)
}

let cipherBytes = base64ToBytes(data.C_TXT)
if (algorithm === 'AES-GCM') {
// Web Crypto expects ciphertext with the 16-byte tag appended
const tag = base64ToBytes(data.Tag)
const joined = new Uint8Array(cipherBytes.length + tag.length)
joined.set(cipherBytes)
joined.set(tag, cipherBytes.length)
cipherBytes = joined
}

const params =
algorithm === 'AES-GCM' ? { name: 'AES-GCM', iv }
: algorithm === 'AES-CTR' ? { name: 'AES-CTR', counter: iv, length: 64 }
: { name: 'AES-CBC', iv }

const plaintext = await crypto.subtle.decrypt(params, cryptoKey, cipherBytes)
return utf8dec.decode(plaintext)
}
Loading