@metasession.co/devaudit-cli 0.3.22 → 0.3.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metasession.co/devaudit-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.23",
|
|
4
4
|
"description": "DevAudit CLI — installs, syncs, and operates the Metasession SDLC across consumer projects.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@clack/prompts": "^0.8.2",
|
|
36
|
-
"@metasession.co/devaudit-plugin-sdk": "^0.3.
|
|
36
|
+
"@metasession.co/devaudit-plugin-sdk": "^0.3.23",
|
|
37
37
|
"ajv": "^8.20.0",
|
|
38
38
|
"commander": "^12.1.0",
|
|
39
39
|
"consola": "^3.2.3",
|
|
@@ -161,18 +161,38 @@ jobs:
|
|
|
161
161
|
run: |
|
|
162
162
|
# stderr → /dev/null so warnings can't corrupt the JSON (DevAudit #48)
|
|
163
163
|
npm audit --json > dependency-audit.json 2>/dev/null || true
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
# Optional, repository-owned exceptions must be advisory- and
|
|
165
|
+
# range-specific, approved, and unexpired. Missing means none.
|
|
166
|
+
UNACCEPTED=$(python3 - <<'PY' 2>/dev/null || echo "unknown"
|
|
167
|
+
import json, os
|
|
168
|
+
from datetime import date
|
|
167
169
|
with open('dependency-audit.json') as f:
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
170
|
+
audit = json.load(f)
|
|
171
|
+
path = 'compliance/security/accepted-vulnerabilities.json'
|
|
172
|
+
exceptions = []
|
|
173
|
+
if os.path.exists(path):
|
|
174
|
+
with open(path) as f:
|
|
175
|
+
exceptions = json.load(f).get('exceptions', [])
|
|
176
|
+
def accepted(package, finding):
|
|
177
|
+
advisory_ids = {
|
|
178
|
+
item.get('url', '').rsplit('/', 1)[-1]
|
|
179
|
+
for item in finding.get('via', []) if isinstance(item, dict)
|
|
180
|
+
}
|
|
181
|
+
for item in exceptions:
|
|
182
|
+
required = ('advisoryId', 'package', 'vulnerableRange', 'expiresAt', 'approvedBy', 'reason', 'remediationIssue')
|
|
183
|
+
if not all(item.get(key) for key in required):
|
|
184
|
+
continue
|
|
185
|
+
if item['expiresAt'] < date.today().isoformat():
|
|
186
|
+
continue
|
|
187
|
+
if item['package'] == package and item['vulnerableRange'] == finding.get('range') and item['advisoryId'] in advisory_ids:
|
|
188
|
+
print(f"Accepted temporary risk: {item['advisoryId']} ({package}) until {item['expiresAt']}")
|
|
189
|
+
return True
|
|
190
|
+
return False
|
|
191
|
+
findings = audit.get('vulnerabilities', {})
|
|
192
|
+
unresolved = [name for name, finding in findings.items()
|
|
193
|
+
if finding.get('severity') in ('high', 'critical') and not accepted(name, finding)]
|
|
194
|
+
print(len(unresolved))
|
|
195
|
+
PY
|
|
176
196
|
echo "Unaccepted high/critical: $UNACCEPTED"
|
|
177
197
|
if [ "$UNACCEPTED" != "0" ] && [ "$UNACCEPTED" != "unknown" ]; then
|
|
178
198
|
echo "::error::$UNACCEPTED unaccepted high/critical vulnerability(ies)."
|
|
@@ -96,6 +96,29 @@ jobs:
|
|
|
96
96
|
echo "Nothing to close out (idempotent no-op) — no PR opened."
|
|
97
97
|
fi
|
|
98
98
|
|
|
99
|
+
- name: Report verified manual close-out catch-up
|
|
100
|
+
if: github.event_name == 'workflow_dispatch' && steps.closeout.outputs.changed != 'true' && steps.merge.outputs.merged != 'true'
|
|
101
|
+
run: |
|
|
102
|
+
set -euo pipefail
|
|
103
|
+
REQ="${{ steps.in.outputs.req }}"
|
|
104
|
+
PR_REF="${{ steps.in.outputs.pr }}"
|
|
105
|
+
[ -n "$REQ" ] && [ -n "$PR_REF" ] || { echo "::error::Manual no-op reporting requires release and release_pr."; exit 1; }
|
|
106
|
+
PR_JSON="$(gh pr view "$PR_REF" --repo "$GITHUB_REPOSITORY" --json mergedAt,baseRefName,headRefName,mergeCommit,number,url)"
|
|
107
|
+
HEAD="$(jq -r '.headRefName // empty' <<<"$PR_JSON")"
|
|
108
|
+
BASE="$(jq -r '.baseRefName // empty' <<<"$PR_JSON")"
|
|
109
|
+
MERGED="$(jq -r '.mergedAt // empty' <<<"$PR_JSON")"
|
|
110
|
+
SHA="$(jq -r '.mergeCommit.oid // empty' <<<"$PR_JSON")"
|
|
111
|
+
NUMBER="$(jq -r '.number' <<<"$PR_JSON")"
|
|
112
|
+
URL="$(jq -r '.url' <<<"$PR_JSON")"
|
|
113
|
+
[ -n "$MERGED" ] && [ "$BASE" = develop ] && [ "$HEAD" = "chore/close-out-$REQ" ] && [ -n "$SHA" ] || { echo "::error::release_pr must be a merged develop close-out PR for $REQ."; exit 1; }
|
|
114
|
+
BASE_URL="${DEVAUDIT_BASE_URL:-}"
|
|
115
|
+
[ -n "$BASE_URL" ] && [ -n "$DEVAUDIT_API_KEY" ] || { echo "::error::DevAudit base URL and project API key are required."; exit 1; }
|
|
116
|
+
RELEASE="$(curl --fail-with-body -sS -G "${BASE_URL%/}/api/ci/releases/resolve" -H "Authorization: Bearer ${DEVAUDIT_API_KEY}" --data-urlencode "projectSlug={{PROJECT_SLUG}}" --data-urlencode "versionPrefix=${REQ}")"
|
|
117
|
+
RELEASE_ID="$(jq -r '.latest.id // empty' <<<"$RELEASE")"
|
|
118
|
+
RELEASE_VERSION="$(jq -r '.latest.version // empty' <<<"$RELEASE")"
|
|
119
|
+
[ "$RELEASE_VERSION" = "$REQ" ] && [ -n "$RELEASE_ID" ] || { echo "::error::Unable to resolve $REQ."; exit 1; }
|
|
120
|
+
curl --fail-with-body -sS -X POST "${BASE_URL%/}/api/ci/releases/${RELEASE_ID}/close-out" -H "Authorization: Bearer ${DEVAUDIT_API_KEY}" -H 'Content-Type: application/json' --data "$(jq -nc --arg workflowRunId '${{ github.run_id }}' --arg workflowUrl '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' --arg pullRequestUrl "$URL" --arg mergeSha "$SHA" --argjson pullRequestNumber "$NUMBER" '{status:"completed",workflowRunId:$workflowRunId,workflowUrl:$workflowUrl,pullRequestNumber:$pullRequestNumber,pullRequestUrl:$pullRequestUrl,mergeSha:$mergeSha}')"
|
|
121
|
+
|
|
99
122
|
- name: Close existing sync PRs for same REQ
|
|
100
123
|
if: steps.closeout.outputs.changed == 'true' || steps.merge.outputs.merged == 'true'
|
|
101
124
|
run: |
|
package/sdlc/package.json
CHANGED