Skip to content
Closed
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
4 changes: 2 additions & 2 deletions .github/workflows/showcase-pr-review.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Showcase PR Auto-Review
name: Showcase PR Review

# Advisory pre-review for showcase contribution PRs. Posts (or updates) a single
# comment so a human reviewer starts from a first pass instead of a blank page.
Expand Down Expand Up @@ -42,7 +42,7 @@ concurrency:

jobs:
review:
name: Draft advisory review
name: Review
runs-on: ubuntu-latest

steps:
Expand Down
42 changes: 35 additions & 7 deletions scripts/showcase-review/gather.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -329,17 +329,45 @@ export async function check_urls(urls, { head_repo, head_sha, token } = {}) {
return checks
}

// A bare 0x + 64 hex is NOT evidence of a private key. Transaction hashes, EAS
// attestation and schema UIDs, block hashes, deliverable hashes and hex-encoded
// strings are all exactly that shape — and they are the entire point of this repo, so
// matching on shape alone flags every genuine proof artifact. (Measured on PR #95: six
// hits, all of them attestation UIDs, tx hashes or a hex-encoded "Approved".)
//
// So require key-ish wording near the value, and stand down when the surrounding text
// names a known-public identifier or the value sits inside a URL.
const SECRET_CONTEXT = /(private[_\s-]?key|privkey|secret[_\s-]?key|seed[_\s-]?phrase|mnemonic)/i
const PUBLIC_IDENTIFIER = /(tx|transaction|hash|uid|attestation|schema|block|deliverable|receipt|digest|commit|signature|proof|address|wallet)/i

function looks_like_leaked_key(text, match) {
const before = text.slice(Math.max(0, match.index - 80), match.index)
const after = text.slice(match.index + match[0].length, match.index + match[0].length + 40)

// Inside a URL it is a public identifier by definition.
if (/https?:\/\/\S*$/.test(before)) return false
if (PUBLIC_IDENTIFIER.test(before) && !SECRET_CONTEXT.test(before)) return false
return SECRET_CONTEXT.test(before) || SECRET_CONTEXT.test(after)
}

export function scan_for_secrets(package_files) {
const patterns = [
['raw private key', /0x[a-fA-F0-9]{64}/],
['PRIVATE_KEY assignment', /PRIVATE_KEY\s*[=:]\s*\S+/],
['mnemonic', /\bmnemonic\b\s*[=:]/i],
]
const hits = []
for (const file of package_files) {
if (typeof file.text !== 'string') continue
for (const [label, pattern] of patterns) {
if (pattern.test(file.text)) hits.push({ path: file.path, kind: label })

for (const match of file.text.matchAll(/0x[a-fA-F0-9]{64}/g)) {
if (looks_like_leaked_key(file.text, match)) {
hits.push({ path: file.path, kind: 'possible private key (64-hex next to key wording)' })
break
}
}

// These two are high-signal on their own: a hash is never introduced this way.
if (/PRIVATE_KEY\s*[=:]\s*["']?0x?[a-fA-F0-9]{32,}/i.test(file.text)) {
hits.push({ path: file.path, kind: 'PRIVATE_KEY assigned a key-shaped value' })
}
if (/\b(mnemonic|seed[_\s-]?phrase)\b\s*[=:]\s*["']?(\w+\s+){11,}/i.test(file.text)) {
hits.push({ path: file.path, kind: 'mnemonic / seed phrase assignment' })
}
}
return hits
Expand Down
9 changes: 9 additions & 0 deletions scripts/showcase-review/gemini.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ ${url_lines}

## Secret scan hits
${secret_hits.length ? secret_hits.map((hit) => `- ${hit.path}: ${hit.kind}`).join('\n') : '- none'}
NOTE: this scan is a crude keyword heuristic, NOT a verified finding. On-chain
identifiers (transaction hashes, EAS attestation and schema UIDs, block and
deliverable hashes, hex-encoded strings) share the shape of a private key and are the
normal, expected content of this repo. Before mentioning a hit at all, look at the
actual value in the file contents above and decide what it really is. If it is an
identifier in a URL or labelled as a hash/UID/tx, say nothing about it. Only if a
value is genuinely key-shaped AND presented as a credential should you raise it — and
then ask the author to confirm rather than asserting a leak, and do not tell anyone to
rewrite git history.

## Contributor file contents
${file_blocks || '[no text files read]'}`
Expand Down
19 changes: 5 additions & 14 deletions scripts/showcase-review/post.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ function find_undelivered_findings({ review, has_inline }) {
return has_inline ? missing.filter((entry) => entry.label === 'Blocker') : missing
}

function build_body({ review, model, head_sha, is_refresh, unapplyable, has_inline }) {
function build_body({ review, is_refresh, unapplyable, has_inline }) {
const parts = [MARKER, review.overview_comment.trim()]

const undelivered = find_undelivered_findings({ review, has_inline })
Expand All @@ -170,18 +170,13 @@ function build_body({ review, model, head_sha, is_refresh, unapplyable, has_inli
)
}

const footer = [
'---',
`*Automated pre-review (${model}) against commit \`${head_sha.slice(0, 7)}\`. ` +
'Advisory only — it can\'t approve, block, or merge, and a human maintainer still reviews this PR. ' +
'Anything it flags may be wrong; push back freely.*',
]
// No bot-disclosure footer, per maintainer preference — the comment reads as a
// plain review. The HTML marker above still identifies it for idempotent updates.
if (is_refresh) {
footer.push(
'*This comment was refreshed after a new push. Any inline suggestions from the first pass may now be stale.*',
parts.push(
'---\n*Refreshed after a new push. Any inline suggestions from the first pass may now be stale.*',
)
}
parts.push(footer.join('\n'))

return parts.join('\n\n')
}
Expand Down Expand Up @@ -241,8 +236,6 @@ export function preview_body({ review, model, head_sha, package_files = [], base
})
return build_body({
review,
model,
head_sha,
is_refresh: false,
unapplyable: [],
has_inline: kept.length > 0,
Expand Down Expand Up @@ -288,8 +281,6 @@ export async function post_review({

const body = build_body({
review,
model,
head_sha,
is_refresh: Boolean(existing),
unapplyable,
// Suggestions that fell back into the body still count as delivered feedback.
Expand Down