feat(release): prepare 1.1.0 candidate
This commit is contained in:
parent
5a34f66981
commit
5205dcdfa9
736 changed files with 115667 additions and 12203 deletions
139
scripts/ci/check-no-hardcoded-secrets.mjs
Normal file
139
scripts/ci/check-no-hardcoded-secrets.mjs
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
import { execFileSync } from 'node:child_process'
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
const candidateOutput = execFileSync(
|
||||
'git',
|
||||
['ls-files', '--cached', '--others', '--exclude-standard', '-z'],
|
||||
{ encoding: 'utf8' },
|
||||
)
|
||||
|
||||
const supportedFile = /\.(?:bat|cjs|cs|gradle|html|js|json|mjs|pem|properties|ps1|sh|toml|ts|tsx|ya?ml)$/i
|
||||
const excludedPath = /(?:^|\/)(?:bin|build|dist|node_modules|obj|playwright-report|scratch)(?:\/|$)|(?:^|\/)(?:__tests__|fixtures?|tests?)(?:\/|$)|\.test\.[cm]?[jt]sx?$|\.spec\.[cm]?[jt]sx?$|(?:^|\/)migrations(?:\/|$)|(?:^|\/)package-lock\.json$|(?:^|\/)deno\.lock$|\.example\.(?:txt|json|ya?ml)$/i
|
||||
|
||||
const rules = [
|
||||
{
|
||||
name: 'basic-auth-literal',
|
||||
pattern: /Buffer\.from\(\s*(['"])[^\r\n'"]{1,100}:[^\r\n'"]{4,}\1/,
|
||||
},
|
||||
{
|
||||
name: 'browser-credential-literal',
|
||||
pathPattern: /^scripts\//,
|
||||
pattern: /(?:page\.fill\([^\r\n,]*(?:password|user_name|email)[^\r\n,]*,|(?:password|username|email|login)\w*\.fill\()\s*(['"])(?!\$|replace|example|dummy|test|changeme|your_|android)(?:(?!\1).){2,}\1/i,
|
||||
},
|
||||
{
|
||||
name: 'browser-operator-email-literal',
|
||||
pathPattern: /^scripts\//,
|
||||
pattern: /(?:email|user_name|username|login|operator)[^\r\n]{0,120}(['"])[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\1/i,
|
||||
},
|
||||
{
|
||||
name: 'keystore-password-literal',
|
||||
pattern: /--(?:ks|key)-pass\s+pass:[^\s'"`]+|(['"])-(?:store|key)pass\1\s*,\s*(['"])(?:(?!\2).){4,}\2|-?(?:store|key)pass\s+[^\s$%"'`][^\s"'`]*/i,
|
||||
},
|
||||
{
|
||||
name: 'google-oauth-client-secret',
|
||||
pattern: /GOCSPX-[A-Za-z0-9_-]{20,}/,
|
||||
},
|
||||
{
|
||||
name: 'google-oauth-client-id',
|
||||
pattern: /[0-9]{6,}-[A-Za-z0-9_-]{8,}\.apps\.googleusercontent\.com/,
|
||||
},
|
||||
{
|
||||
name: 'google-api-key',
|
||||
pattern: /AIza[A-Za-z0-9_-]{20,}/,
|
||||
},
|
||||
{
|
||||
name: 'private-key-material',
|
||||
pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----\r?\n[A-Za-z0-9+/=\r\n]{40,}-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/,
|
||||
},
|
||||
{
|
||||
name: 'legacy-license-prefix-fail-open',
|
||||
pattern: /\.startsWith\(\s*(['"])D3RO-(?:PRO|PLUS|TEAM)-\1\s*\)\s*&&\s*[A-Za-z_$][\w$]*\.length\s*>=/,
|
||||
},
|
||||
{
|
||||
name: 'known-live-token-prefix',
|
||||
pattern: /(?:ghp_|github_pat_|glpat-|rk_live_|sk_live_)[A-Za-z0-9_-]{12,}/,
|
||||
},
|
||||
{
|
||||
name: 'credential-in-url',
|
||||
pattern: /https?:\/\/[^\s\/@:]+:[^\s\/@]+@/,
|
||||
},
|
||||
{
|
||||
name: 'credential-assignment-literal',
|
||||
pattern: /(?:password|passwd|client[_-]?secret|api[_-]?secret|service[_-]?key|jwt[_-]?(?:secret|key)|admin[_-]?(?:bootstrap[_-]?token|session[_-]?secret)|service[_-]?role[_-]?key)\s*[:=]\s*(['"])(?!\s*(?:\$|%[A-Z_][A-Z0-9_]*%|replace|example|dummy|test|ci[-_]|changeme|your_|android)\b)(?:(?!\1).){8,}\1/i,
|
||||
},
|
||||
]
|
||||
|
||||
function scanSource(path, source) {
|
||||
return rules
|
||||
.filter((rule) => !rule.pathPattern || rule.pathPattern.test(path))
|
||||
.filter((rule) => rule.pattern.test(source))
|
||||
.map((rule) => `${path}: ${rule.name}`)
|
||||
}
|
||||
|
||||
if (process.argv.includes('--self-test')) {
|
||||
const legacyLicensePrefix = ['D3RO', 'PRO', ''].join('-')
|
||||
const unsafeCases = [
|
||||
['scripts/login.mjs', ['await emailInput.', 'fill(', "'operator", '@', "example.com')"].join('')],
|
||||
['scripts/login.mjs', `const secret = '${'GOCSPX-'}abcdefghijklmnopqrstuvwxyz'`],
|
||||
['scripts/login.mjs', `const id = '1234567890-abcdefghijklmnop.${'apps.'}googleusercontent.com'`],
|
||||
['scripts/release.mjs', `const token = '${'ghp_'}abcdefghijklmnopqrstuvwxyz'`],
|
||||
['scripts/deploy.ps1', `$JWT_SECRET = '${'a-fixed-'}jwt-signing-secret-value'`],
|
||||
['scripts/gen-keystore.js', `const args = ['-storepass', '${'fixed-keystore-password'}']`],
|
||||
[
|
||||
'release/evidence-private.pem',
|
||||
['-----BEGIN ', 'PRIVATE KEY-----\n', 'A'.repeat(64), '\n-----END ', 'PRIVATE KEY-----'].join(''),
|
||||
],
|
||||
[
|
||||
'packages/core/src/license.ts',
|
||||
`if (trimmed.startsWith('${legacyLicensePrefix}') && trimmed.length >= 14) return { valid: true }`,
|
||||
],
|
||||
]
|
||||
const safeCases = [
|
||||
['scripts/login.mjs', `await emailInput.fill(requireEnvironment('D3RO_PORTAL_OPERATOR_EMAIL'))`],
|
||||
['scripts/release.mjs', `const token = process.env.FORGEJO_TOKEN?.trim()`],
|
||||
['scripts/deploy.sh', `JWT_SECRET="$JWT_SECRET"`],
|
||||
['.github/workflows/ci.yml', `MOBILE_E2E_PASSWORD: \${{ secrets.MOBILE_E2E_PASSWORD }}`],
|
||||
['.env.example', 'JWT_SECRET='],
|
||||
[
|
||||
'release/evidence-public.pem',
|
||||
['-----BEGIN ', 'PUBLIC KEY-----\n', 'A'.repeat(64), '\n-----END ', 'PUBLIC KEY-----'].join(''),
|
||||
],
|
||||
]
|
||||
|
||||
const failures = []
|
||||
for (const [path, source] of unsafeCases) {
|
||||
if (scanSource(path, source).length === 0) failures.push(`missed unsafe fixture: ${path}`)
|
||||
}
|
||||
for (const [path, source] of safeCases) {
|
||||
if (scanSource(path, source).length > 0) failures.push(`flagged safe fixture: ${path}`)
|
||||
}
|
||||
if (failures.length > 0) {
|
||||
for (const failure of failures) console.error(failure)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('Hard-coded credential scanner self-test passed.')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const findings = []
|
||||
for (const path of candidateOutput.split('\0').filter(Boolean)) {
|
||||
const normalized = path.replaceAll('\\', '/')
|
||||
if ((!supportedFile.test(normalized) && normalized !== '.env.example') || excludedPath.test(normalized)) continue
|
||||
|
||||
let source
|
||||
try {
|
||||
source = readFileSync(path, 'utf8')
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
|
||||
findings.push(...scanSource(normalized, source))
|
||||
}
|
||||
|
||||
if (findings.length > 0) {
|
||||
console.error('Potential hard-coded credentials were found:')
|
||||
for (const finding of findings.sort()) console.error(`- ${finding}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log('No hard-coded credential patterns found in source-controlled runtime files.')
|
||||
Loading…
Add table
Add a link
Reference in a new issue