Skip to content

Commit 5c72640

Browse files
joanagmaiaclaude
andauthored
fix: privateer binary version compatibility with pvtr-github-repo-scanner v0.23.2 (IN-1170) (#4214)
Signed-off-by: Joana Maia <jmaia@contractor.linuxfoundation.org> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent b7d97d5 commit 5c72640

3 files changed

Lines changed: 79 additions & 31 deletions

File tree

scripts/services/docker/Dockerfile.security_best_practices_worker

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
ARG PVTR_VERSION=v0.23.2
2+
13
FROM alpine:3.21 AS core
24
RUN apk add --no-cache wget tar unzip
35

46
WORKDIR /app
5-
ARG VERSION=0.7.0
7+
ARG VERSION=0.21.2
68
ARG PLATFORM=Linux_x86_64
79

810
RUN wget https://github.com/privateerproj/privateer/releases/download/v${VERSION}/privateer_${PLATFORM}.tar.gz
@@ -12,6 +14,7 @@ FROM golang:1.26.3-alpine3.23 AS plugin
1214
RUN apk add --no-cache make git
1315
WORKDIR /plugin
1416
ARG PVTR_COMMIT=c7bd9538d64f7eaab94a05c9b5fd05458a387b1c
17+
ARG PVTR_VERSION
1518
# To run the latest version of the plugin, we need to use the latest commit of the pvtr-github-repo-scanner repository.
1619
# Currently using v0.23.2: https://github.com/ossf/pvtr-github-repo-scanner/commit/c7bd9538d64f7eaab94a05c9b5fd05458a387b1c
1720
RUN git clone https://github.com/ossf/pvtr-github-repo-scanner.git && cd pvtr-github-repo-scanner && git checkout ${PVTR_COMMIT}
@@ -34,8 +37,10 @@ FROM node:20-bookworm-slim as runner
3437

3538
RUN mkdir -p /.privateer/bin
3639
WORKDIR /.privateer/bin
37-
COPY --from=core /app/privateer .
40+
COPY --from=core /app/pvtr ./privateer
41+
ARG PVTR_VERSION
3842
COPY --from=plugin /plugin/github-repo /root/.privateer/bin/github-repo
43+
RUN echo "{\"plugins\":[{\"name\":\"github-repo\",\"version\":\"${PVTR_VERSION}\",\"binaryPath\":\"github-repo\"}]}" > /root/.privateer/bin/plugins.json
3944
COPY ./services/apps/security_best_practices_worker/example-config.yml /.privateer/example-config.yml
4045

4146
WORKDIR /usr/crowd/app

services/apps/security_best_practices_worker/src/activities/index.ts

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -96,62 +96,84 @@ export async function saveOSPSBaselineInsightsToDB(
9696
const CATALOG_ID = 'osps-baseline-2026-02'
9797
const redisCache = new RedisCache(`osps-baseline-insights`, svc.redis, svc.log)
9898
const result = await redisCache.get(key)
99+
if (!result) {
100+
throw new Error(`No cached privateer result found for key: ${key}`)
101+
}
99102
const parsedResult: ISecurityInsightsPrivateerResult = JSON.parse(result)
100-
const evaluationSuite = parsedResult.evaluation_suites.find((s) => s.catalog_id === CATALOG_ID)
103+
const evaluationSuite = parsedResult['evaluation-suites']?.find(
104+
(s) => s['catalog-id'] === CATALOG_ID,
105+
)
106+
if (!evaluationSuite) {
107+
throw new Error(
108+
`No evaluation suite found for catalog '${CATALOG_ID}' in privateer output for repo ${repo.repoUrl}`,
109+
)
110+
}
101111

102112
const qx = pgpQx(svc.postgres.writer.connection())
103113

104114
await addEvaluationSuite(qx, {
105115
repo: repo.repoUrl,
106116
insightsProjectId: repo.insightsProjectId,
107117
insightsProjectSlug: repo.insightsProjectSlug,
108-
catalogId: evaluationSuite.catalog_id,
118+
catalogId: evaluationSuite['catalog-id'],
109119
name: evaluationSuite.name,
110120
result: evaluationSuite.result,
111-
corruptedState: evaluationSuite.corrupted_state,
121+
corruptedState: evaluationSuite['corrupted-state'],
112122
})
113123

114-
const suite = await findEvaluationSuite(qx, repo.repoUrl, evaluationSuite.catalog_id)
124+
const suite = await findEvaluationSuite(qx, repo.repoUrl, evaluationSuite['catalog-id'])
125+
if (!suite) {
126+
throw new Error(
127+
`Evaluation suite not found after insert for repo ${repo.repoUrl}, catalog ${evaluationSuite['catalog-id']}`,
128+
)
129+
}
115130

116-
for (const evaluation of evaluationSuite.control_evaluations) {
131+
for (const evaluation of evaluationSuite['control-evaluations'].evaluations) {
132+
const controlId = evaluation.control['entry-id']
117133
await addSuiteControlEvaluation(qx, {
118-
controlId: evaluation['control-id'],
134+
controlId,
119135
name: evaluation.name,
120-
corruptedState: evaluation['corrupted-state'],
136+
corruptedState: false,
121137
message: evaluation.message,
122138
repo: repo.repoUrl,
123139
insightsProjectId: repo.insightsProjectId,
124140
insightsProjectSlug: repo.insightsProjectSlug,
125-
remediationGuide: evaluation['remediation-guide'] || '',
141+
remediationGuide: '',
126142
result: evaluation.result,
127143
securityInsightsEvaluationSuiteId: suite.id,
128144
})
129145

130146
const controlEvaluation = await findSuiteControlEvaluation(
131147
qx,
132148
repo.repoUrl,
133-
evaluation['control-id'],
149+
controlId,
134150
suite.id,
135151
)
136-
for (const assessment of evaluation.assessments) {
152+
if (!controlEvaluation) {
153+
throw new Error(
154+
`Control evaluation not found after insert for repo ${repo.repoUrl}, controlId ${controlId}, suiteId ${suite.id}`,
155+
)
156+
}
157+
for (const assessment of evaluation['assessment-logs']) {
158+
const runDuration = computeRunDuration(assessment.start, assessment.end)
137159
await addControlEvaluationAssessment(qx, {
138160
applicability: assessment.applicability,
139161
description: assessment.description,
140162
message: assessment.message,
141163
repo: repo.repoUrl,
142164
insightsProjectId: repo.insightsProjectId,
143165
insightsProjectSlug: repo.insightsProjectSlug,
144-
requirementId: assessment['requirement-id'],
166+
requirementId: assessment.requirement['entry-id'],
145167
result: assessment.result,
146-
runDuration: assessment['run-duration'] || '',
168+
runDuration,
147169
steps: assessment.steps,
148170
stepsExecuted: assessment['steps-executed'] || 0,
149171
securityInsightsEvaluationId: controlEvaluation.id,
150172
recommendation: assessment.recommendation,
151173
start: assessment.start,
152174
end: assessment.end,
153-
value: assessment.value,
154-
changes: assessment.changes,
175+
value: null,
176+
changes: null,
155177
})
156178
}
157179
}
@@ -174,6 +196,14 @@ export async function saveOSPSBaselineInsightsToRedis(
174196
await redisCache.set(key, JSON.stringify(insights), 60 * 60 * 24) // 1 day
175197
}
176198

199+
function computeRunDuration(start: string | undefined, end: string | undefined): string {
200+
if (!start || !end) return ''
201+
const startMs = new Date(start).getTime()
202+
const endMs = new Date(end).getTime()
203+
if (isNaN(startMs) || isNaN(endMs) || endMs < startMs) return ''
204+
return `${endMs - startMs}ms`
205+
}
206+
177207
async function cleanupFiles(repoName: string): Promise<void> {
178208
// Delete the file
179209
try {
@@ -221,11 +251,22 @@ async function runBinary(
221251
})
222252

223253
proc.on('close', (code) => {
224-
if (code === 0) {
225-
svc.log.info(`Binary completed successfully`)
254+
// exit code 0 = all tests passed, 1 = some tests failed — both mean the
255+
// evaluation ran to completion and wrote its output file
256+
if (code === 0 || code === 1) {
257+
svc.log.info(`Binary completed with exit code ${code}`)
226258
resolve({ stdout, stderr })
227259
} else {
228-
reject(new Error(`Binary exited with code ${code}\nStderr:\n${stderr}Stdout:\n${stdout}`))
260+
const truncated = (s: string) => (s.length > 500 ? s.slice(0, 500) + '…' : s)
261+
const truncStdout = truncated(stdout)
262+
const truncStderr = truncated(stderr)
263+
const err = Object.assign(
264+
new Error(
265+
`Binary exited with code ${code}\nStderr:\n${truncStderr}\nStdout:\n${truncStdout}`,
266+
),
267+
{ stdout: truncStdout, stderr: truncStderr },
268+
)
269+
reject(err)
229270
}
230271
})
231272
})

services/apps/security_best_practices_worker/src/types.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,31 @@
11
export interface ISecurityInsightsPrivateerResult {
2-
evaluation_suites: ISecurityInsightsPrivateerEvaluationSuite[]
2+
'evaluation-suites': ISecurityInsightsPrivateerEvaluationSuite[]
33
}
44

55
export interface ISecurityInsightsPrivateerEvaluationSuite {
66
name: string
7-
catalog_id: string
8-
start_time: string
9-
end_time: string
7+
'catalog-id': string
8+
'start-time': string
9+
'end-time': string
1010
result: string
11-
corrupted_state: boolean
12-
control_evaluations: ISecurityInsightsPrivateerResultControlEvaluations[]
11+
'corrupted-state': boolean
12+
'control-evaluations': {
13+
result: string
14+
evaluations: ISecurityInsightsPrivateerResultControlEvaluations[]
15+
}
1316
}
1417

1518
export interface ISecurityInsightsPrivateerResultControlEvaluations {
1619
name: string
17-
'control-id': string
20+
control: { 'reference-id': string; 'entry-id': string }
1821
result: string
1922
message: string
20-
'corrupted-state': boolean
21-
assessments: ISecurityInsightsPrivateerResultAssessment[]
23+
'assessment-logs': ISecurityInsightsPrivateerResultAssessment[]
2224
}
2325

2426
export interface ISecurityInsightsPrivateerResultAssessment {
25-
'requirement-id': string
27+
requirement: { 'reference-id': string; 'entry-id': string }
28+
plan?: { 'reference-id': string; 'entry-id': string }
2629
applicability: string[]
2730
description: string
2831
result: string
@@ -31,9 +34,8 @@ export interface ISecurityInsightsPrivateerResultAssessment {
3134
'steps-executed': number
3235
start: string
3336
end?: string
34-
value?: unknown
35-
changes?: Record<string, unknown>
3637
recommendation?: string
38+
'confidence-level'?: string
3739
}
3840

3941
export interface IUpsertOSPSBaselineSecurityInsightsParams {

0 commit comments

Comments
 (0)