Skip to content

Commit 25831bc

Browse files
jplhomerpi-mono
andcommitted
Remove score from App Security scan output
Co-authored-by: AI (Pi/Claude Opus 5.5) <noreply@pi.dev>
1 parent 584a36b commit 25831bc

13 files changed

Lines changed: 15 additions & 98 deletions

File tree

‎packages/app/src/cli/services/app-security-engine/run.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,6 @@ export async function compileFindings(
203203
// Stale documents must not suppress current findings or crash compile when fingerprints no longer exist.
204204
const suppressions = provenanceRejected.length > 0 ? [] : (document.suppressions ?? [])
205205
if (rejected.length > 0) {
206-
result.score = null
207206
result.scan.coverage_complete = false
208207
result.scan.coverage_gaps.push(
209208
...rejected.map((message) => {
@@ -216,7 +215,7 @@ export async function compileFindings(
216215
}),
217216
)
218217
}
219-
result.scan.result_hash = computeResultHash(result.issues, result.score)
218+
result.scan.result_hash = computeResultHash(result.issues)
220219

221220
const trace = compileTrace(result, {engineVersion, agentChecksExecuted, suppressions})
222221
return {

‎packages/app/src/cli/services/app-security-engine/scanners/index.ts‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
findDependencyAutomationInputs,
1313
} from './discover.js'
1414
import {detectCapabilities, detectProject} from '../capabilities/detect.js'
15-
import {calculateScore, computeScanMetadata} from '../scorer/index.js'
15+
import {computeScanMetadata} from '../scorer/index.js'
1616
import {deprecatedScriptTagScope, insecureWebhookUrl} from '../rules/config-rules.js'
1717
import {
1818
scanCredentialBrowserLeakage,
@@ -754,14 +754,12 @@ export async function scan(startPath?: string, configFileName?: string): Promise
754754
],
755755
),
756756
]
757-
const score = coverageGaps.length === 0 ? calculateScore(issues) : null
758757
const rulesRun = checksExecuted.filter((execution) => execution.status === 'executed').length
759758
const scanMetadata = computeScanMetadata(
760759
sourceFiles.filter((file) => file.content !== undefined).length,
761760
rulesRun,
762761
checksExecuted.length - rulesRun,
763762
issues,
764-
score,
765763
fileHashMap,
766764
skippedFiles,
767765
checksExecuted,
@@ -774,7 +772,6 @@ export async function scan(startPath?: string, configFileName?: string): Promise
774772
app: {name: redactText(String(appToml?.raw.name ?? 'Unknown')), type: 'public'},
775773
capabilities,
776774
detection,
777-
score,
778775
scan: scanMetadata,
779776
issues,
780777
}

‎packages/app/src/cli/services/app-security-engine/scorer/index.ts‎

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,8 @@
11
import {canonicalJson, sha256} from '../trace/index.js'
22
import {getEngineVersion} from '../version.js'
3-
import type {CheckExecution, CoverageGap, Issue, ScoreResult, Grade, ScanMetadata, SkippedFile} from '../types.js'
3+
import type {CheckExecution, CoverageGap, Issue, ScanMetadata, SkippedFile} from '../types.js'
44

5-
const BASELINE = 100
6-
7-
/** Only deterministic, definite evidence can affect a grade. */
8-
export function calculateScore(issues: Issue[]): ScoreResult {
9-
let total = BASELINE
10-
const deductedEvidence = new Set<string>()
11-
12-
for (const issue of issues) {
13-
if (issue.found_by === 'agent' || issue.found_by === 'external') continue
14-
if (issue.confidence !== 'definite' && issue.confidence !== undefined) continue
15-
const evidenceKey = canonicalJson({
16-
id: issue.id,
17-
location: issue.location,
18-
evidence: issue.evidence ?? [],
19-
})
20-
if (deductedEvidence.has(evidenceKey)) continue
21-
deductedEvidence.add(evidenceKey)
22-
total += issue.points
23-
}
24-
25-
total = Math.max(0, Math.min(100, total))
26-
return {total, baseline: BASELINE, grade: scoreToGrade(total)}
27-
}
28-
29-
function scoreToGrade(score: number): Grade {
30-
if (score >= 90) return 'EXCELLENT'
31-
if (score >= 75) return 'GOOD'
32-
if (score >= 60) return 'NEEDS_WORK'
33-
return 'POOR'
34-
}
35-
36-
export function computeResultHash(issues: Issue[], score: ScoreResult | null): string {
5+
export function computeResultHash(issues: Issue[]): string {
376
const canonicalIssues = issues
387
.map((issue) => ({
398
id: issue.id,
@@ -51,15 +20,14 @@ export function computeResultHash(issues: Issue[], score: ScoreResult | null): s
5120
fix: issue.fix,
5221
}))
5322
.sort((left, right) => canonicalJson(left).localeCompare(canonicalJson(right)))
54-
return sha256({issues: canonicalIssues, score})
23+
return sha256({issues: canonicalIssues})
5524
}
5625

5726
export function computeScanMetadata(
5827
filesScanned: number,
5928
rulesRun: number,
6029
rulesSkipped: number,
6130
issues: Issue[],
62-
score: ScoreResult | null,
6331
fileHashMap: Record<string, string>,
6432
filesSkipped: SkippedFile[],
6533
checksExecuted: CheckExecution[],
@@ -80,7 +48,7 @@ export function computeScanMetadata(
8048
coverage_complete: coverageGaps.length === 0,
8149
coverage_gaps: coverageGaps,
8250
input_hash: sha256(inputs),
83-
result_hash: computeResultHash(issues, score),
51+
result_hash: computeResultHash(issues),
8452
file_hashes: fileHashMap,
8553
checks_executed: checksExecuted,
8654
}

‎packages/app/src/cli/services/app-security-engine/tests/dependency-automation.test.ts‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ describe('dependency automation scanner integration', () => {
9494
findings: 1,
9595
inspected_files: ['extensions/app-home/package.json', 'package.json'],
9696
})
97-
expect(execution.scan.score).toEqual({total: 95, baseline: 100, grade: 'EXCELLENT'})
9897
expect(formatJson(execution.scan)).toContain(checkId)
9998
expect(validateTrace(execution.trace)).toEqual({valid: true, errors: []})
10099
const submission = buildSubmission(execution.trace, {cliVersion: '3.99.0', submittedAt: '2026-09-15T00:00:00Z'})
@@ -126,7 +125,6 @@ describe('dependency automation scanner integration', () => {
126125
findings: 0,
127126
inspected_files: expectedFiles,
128127
})
129-
expect(execution.scan.score).toEqual({total: 100, baseline: 100, grade: 'EXCELLENT'})
130128
expect(execution.trace.project.input_hashes[path]).toBe(sha256(content))
131129
expect(securityExitCode({...execution, elapsedMilliseconds: 0}, 'low')).toBe(0)
132130
const submission = buildSubmission(execution.trace, {cliVersion: '3.99.0', submittedAt: '2026-09-15T00:00:00Z'})
@@ -174,7 +172,7 @@ describe('dependency automation scanner integration', () => {
174172
const result = await scan(root)
175173
expect(dependencyFindings(result)).toEqual([])
176174
expect(dependencyExecution(result)).toMatchObject({status: 'unresolved', findings: 0})
177-
expect(result.score).toBeNull()
175+
expect(result.scan.coverage_complete).toBe(false)
178176
})
179177
})
180178

‎packages/app/src/cli/services/app-security-engine/tests/scan-contract.test.ts‎

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@ import {assertRegistryInvariants, getRegistry} from '../registry/index.js'
44
import {DETERMINISTIC_CHECKS, scan} from '../scanners/index.js'
55
import {compileTrace, sha256, validateTrace} from '../trace/index.js'
66
import {RULE_CATALOG} from '../rules/catalog.js'
7-
import {calculateScore} from '../scorer/index.js'
87
import {afterEach, describe, expect, test} from 'vitest'
98
import {mkdir, mkdtemp, rm, writeFile} from 'node:fs/promises'
109
import {tmpdir} from 'node:os'
1110
import {join} from 'node:path'
12-
import type {Issue, TraceV2} from '../types.js'
11+
import type {TraceV2} from '../types.js'
1312

1413
const directories: string[] = []
1514
afterEach(async () => {
@@ -38,7 +37,7 @@ function resign(trace: TraceV2): void {
3837
}
3938

4039
describe('framework and surface detection', () => {
41-
test('grades the React Router green path only when package and structure agree', async () => {
40+
test('detects React Router only when package and structure agree', async () => {
4241
const directory = await app({
4342
'shopify.app.toml': appConfig(),
4443
'package.json': reactPackage,
@@ -53,7 +52,7 @@ describe('framework and surface detection', () => {
5352
test('detects config-only, theme extension, mixed, and unknown surfaces', async () => {
5453
const configOnly = await scan(await app({'shopify.app.toml': appConfig()}))
5554
expect(configOnly.detection).toMatchObject({framework: 'none', surface: 'config_only'})
56-
expect(configOnly.score).not.toBeNull()
55+
expect(configOnly.scan.coverage_complete).toBe(true)
5756

5857
const theme = await scan(
5958
await app({
@@ -78,7 +77,7 @@ describe('framework and surface detection', () => {
7877

7978
const unknown = await scan(await app({'shopify.app.toml': appConfig(), 'server.ts': 'export const server = {}'}))
8079
expect(unknown.detection).toMatchObject({framework: 'unknown', surface: 'unknown'})
81-
expect(unknown.score).toBeNull()
80+
expect(unknown.scan.coverage_complete).toBe(false)
8281
expect(
8382
unknown.scan.checks_executed.find((execution) => execution.id === 'MISSING_COMPLIANCE_WEBHOOKS'),
8483
).toMatchObject({status: 'executed'})
@@ -415,7 +414,6 @@ redirect_urls = ["http://app.example/callback"]
415414
reason: {code: 'input_rejected'},
416415
})
417416
expect(result.scan.coverage_complete).toBe(false)
418-
expect(result.score).toBeNull()
419417
expect(result.issues.map((issue) => issue.id)).toContain('DEPRECATED_SCRIPT_TAG_SCOPE')
420418
})
421419
})
@@ -459,21 +457,6 @@ describe('runtime identities', () => {
459457
})
460458

461459
describe('coverage and trace invariants', () => {
462-
test('does not double-deduct agent and deterministic evidence for one product', () => {
463-
const issue: Issue = {
464-
id: 'UNSAFE_INNERHTML',
465-
severity: 'high',
466-
points: -25,
467-
title: 'Unsafe HTML',
468-
message: 'Unsafe HTML',
469-
location: {file: 'app/a.ts', line: 1},
470-
evidence: [{location: {file: 'app/a.ts', line: 1}, quote: 'element.innerHTML = input'}],
471-
fix: {automated: false, description: 'Sanitize input.'},
472-
found_by: 'static',
473-
}
474-
expect(calculateScore([issue, {...issue, found_by: 'agent', confidence: 'agentic'}]).total).toBe(75)
475-
})
476-
477460
test('rejects impossible execution and completeness combinations', async () => {
478461
const directory = await app({
479462
'shopify.app.toml': appConfig(),

‎packages/app/src/cli/services/app-security-engine/tests/trace.test.ts‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ const result = (issues: Issue[] = []): ScanResult => ({
3535
declared_ip_allowlist: false,
3636
checkout_extension: false,
3737
},
38-
score: {total: 70, baseline: 100, grade: 'NEEDS_WORK'},
3938
scan: {
4039
timestamp: '2026-08-28T00:00:00.000Z',
4140
security_version: '0.1.0',
@@ -265,14 +264,12 @@ describe('trace v2', () => {
265264

266265
test('recomputes the scan result hash over merged finding content', () => {
267266
const scanResult = result()
268-
const before = computeResultHash(scanResult.issues, scanResult.score)
267+
const before = computeResultHash(scanResult.issues)
269268
scanResult.issues.push(deterministicIssue())
270-
expect(computeResultHash(scanResult.issues, scanResult.score)).not.toBe(before)
269+
expect(computeResultHash(scanResult.issues)).not.toBe(before)
271270
const changed = deterministicIssue()
272271
changed.evidence = [{location: changed.location, quote: 'different'}]
273-
expect(computeResultHash([changed], scanResult.score)).not.toBe(
274-
computeResultHash([deterministicIssue()], scanResult.score),
275-
)
272+
expect(computeResultHash([changed])).not.toBe(computeResultHash([deterministicIssue()]))
276273
})
277274

278275
test('hashes full finding messages, locations, snippets, and evidence independent of ordering', () => {

‎packages/app/src/cli/services/app-security-engine/types.ts‎

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,20 +85,10 @@ export interface ScanResult {
8585
}
8686
capabilities: Capabilities
8787
detection: ProjectDetection
88-
/** Null means the deterministic coverage is insufficient to grade safely. */
89-
score: ScoreResult | null
9088
scan: ScanMetadata
9189
issues: Issue[]
9290
}
9391

94-
export interface ScoreResult {
95-
total: number
96-
baseline: number
97-
grade: Grade
98-
}
99-
100-
export type Grade = 'EXCELLENT' | 'GOOD' | 'NEEDS_WORK' | 'POOR'
101-
10292
export interface SkippedFile {
10393
path: string
10494
reason: 'too_large' | 'unreadable'

‎packages/app/src/cli/services/app-security-json-fixtures/compile.json‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,6 @@
3333
"declared_ip_allowlist": false,
3434
"checkout_extension": false
3535
},
36-
"score": {
37-
"total": 100,
38-
"baseline": 100,
39-
"grade": "EXCELLENT"
40-
},
4136
"scan": {
4237
"timestamp": "2026-08-24T00:00:00.000Z",
4338
"security_version": "1.2.3",

‎packages/app/src/cli/services/app-security-json-fixtures/scan.json‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,6 @@
3333
"declared_ip_allowlist": false,
3434
"checkout_extension": false
3535
},
36-
"score": {
37-
"total": 100,
38-
"baseline": 100,
39-
"grade": "EXCELLENT"
40-
},
4136
"scan": {
4237
"timestamp": "2026-08-24T00:00:00.000Z",
4338
"security_version": "1.2.3",

‎packages/app/src/cli/services/app-security-json.test.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ const scan: ScanResult = {
3232
declared_ip_allowlist: false,
3333
checkout_extension: false,
3434
},
35-
score: {total: 100, baseline: 100, grade: 'EXCELLENT'},
3635
scan: {
3736
timestamp: '2026-08-24T00:00:00.000Z',
3837
security_version: '1.2.3',

0 commit comments

Comments
 (0)