@qa-ai-stlc/runner-security 0.0.0-stage → 1.7.0

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.
Files changed (94) hide show
  1. package/README.md +52 -2
  2. package/dist/checks/authz-check.d.ts +9 -0
  3. package/dist/checks/authz-check.d.ts.map +1 -0
  4. package/dist/checks/authz-check.js +100 -0
  5. package/dist/checks/authz-check.js.map +1 -0
  6. package/dist/checks/code-check-support.d.ts +9 -0
  7. package/dist/checks/code-check-support.d.ts.map +1 -0
  8. package/dist/checks/code-check-support.js +19 -0
  9. package/dist/checks/code-check-support.js.map +1 -0
  10. package/dist/checks/configuration-check.d.ts +10 -0
  11. package/dist/checks/configuration-check.d.ts.map +1 -0
  12. package/dist/checks/configuration-check.js +128 -0
  13. package/dist/checks/configuration-check.js.map +1 -0
  14. package/dist/checks/cookies-check.d.ts +10 -0
  15. package/dist/checks/cookies-check.d.ts.map +1 -0
  16. package/dist/checks/cookies-check.js +113 -0
  17. package/dist/checks/cookies-check.js.map +1 -0
  18. package/dist/checks/cors-check.d.ts +10 -0
  19. package/dist/checks/cors-check.d.ts.map +1 -0
  20. package/dist/checks/cors-check.js +55 -0
  21. package/dist/checks/cors-check.js.map +1 -0
  22. package/dist/checks/csrf-check.d.ts +10 -0
  23. package/dist/checks/csrf-check.d.ts.map +1 -0
  24. package/dist/checks/csrf-check.js +108 -0
  25. package/dist/checks/csrf-check.js.map +1 -0
  26. package/dist/checks/dependencies-check.d.ts +10 -0
  27. package/dist/checks/dependencies-check.d.ts.map +1 -0
  28. package/dist/checks/dependencies-check.js +160 -0
  29. package/dist/checks/dependencies-check.js.map +1 -0
  30. package/dist/checks/encoding-check.d.ts +10 -0
  31. package/dist/checks/encoding-check.d.ts.map +1 -0
  32. package/dist/checks/encoding-check.js +67 -0
  33. package/dist/checks/encoding-check.js.map +1 -0
  34. package/dist/checks/errors-check.d.ts +8 -0
  35. package/dist/checks/errors-check.d.ts.map +1 -0
  36. package/dist/checks/errors-check.js +60 -0
  37. package/dist/checks/errors-check.js.map +1 -0
  38. package/dist/checks/headers-check.d.ts +9 -0
  39. package/dist/checks/headers-check.d.ts.map +1 -0
  40. package/dist/checks/headers-check.js +110 -0
  41. package/dist/checks/headers-check.js.map +1 -0
  42. package/dist/checks/secrets-check.d.ts +10 -0
  43. package/dist/checks/secrets-check.d.ts.map +1 -0
  44. package/dist/checks/secrets-check.js +113 -0
  45. package/dist/checks/secrets-check.js.map +1 -0
  46. package/dist/checks/session-check.d.ts +10 -0
  47. package/dist/checks/session-check.d.ts.map +1 -0
  48. package/dist/checks/session-check.js +87 -0
  49. package/dist/checks/session-check.js.map +1 -0
  50. package/dist/checks/session-pages.d.ts +11 -0
  51. package/dist/checks/session-pages.d.ts.map +1 -0
  52. package/dist/checks/session-pages.js +25 -0
  53. package/dist/checks/session-pages.js.map +1 -0
  54. package/dist/cookie-flags.d.ts +14 -0
  55. package/dist/cookie-flags.d.ts.map +1 -0
  56. package/dist/cookie-flags.js +28 -0
  57. package/dist/cookie-flags.js.map +1 -0
  58. package/dist/default-checks.d.ts +4 -0
  59. package/dist/default-checks.d.ts.map +1 -0
  60. package/dist/default-checks.js +28 -0
  61. package/dist/default-checks.js.map +1 -0
  62. package/dist/fetch-page.d.ts +19 -0
  63. package/dist/fetch-page.d.ts.map +1 -0
  64. package/dist/fetch-page.js +36 -0
  65. package/dist/fetch-page.js.map +1 -0
  66. package/dist/finding.d.ts +8 -0
  67. package/dist/finding.d.ts.map +1 -0
  68. package/dist/finding.js +14 -0
  69. package/dist/finding.js.map +1 -0
  70. package/dist/identity-sessions.d.ts +42 -0
  71. package/dist/identity-sessions.d.ts.map +1 -0
  72. package/dist/identity-sessions.js +102 -0
  73. package/dist/identity-sessions.js.map +1 -0
  74. package/dist/index.d.ts +8 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +10 -0
  77. package/dist/index.js.map +1 -0
  78. package/dist/security-audit.d.ts +30 -0
  79. package/dist/security-audit.d.ts.map +1 -0
  80. package/dist/security-audit.js +173 -0
  81. package/dist/security-audit.js.map +1 -0
  82. package/dist/security-check.d.ts +30 -0
  83. package/dist/security-check.d.ts.map +1 -0
  84. package/dist/security-check.js +4 -0
  85. package/dist/security-check.js.map +1 -0
  86. package/dist/security-probe.d.ts +56 -0
  87. package/dist/security-probe.d.ts.map +1 -0
  88. package/dist/security-probe.js +128 -0
  89. package/dist/security-probe.js.map +1 -0
  90. package/dist/source-checkout.d.ts +56 -0
  91. package/dist/source-checkout.d.ts.map +1 -0
  92. package/dist/source-checkout.js +140 -0
  93. package/dist/source-checkout.js.map +1 -0
  94. package/package.json +36 -4
@@ -0,0 +1,108 @@
1
+ // Copyright The QA-AI-STLC Authors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { buildFinding } from '../finding.js';
4
+ import { isSignInLocation, sessionHeaders } from './session-pages.js';
5
+ // What a forged request looks like to the server: from a site it has no reason to trust.
6
+ const FOREIGN_ORIGIN = 'https://qa-audit-untrusted.example.test';
7
+ function judge(response) {
8
+ const { status } = response;
9
+ if (status >= 200 && status < 300) {
10
+ return 'accepted';
11
+ }
12
+ if (status >= 300 && status < 400) {
13
+ return isSignInLocation(response.headers.location) ? 'unclear' : 'accepted';
14
+ }
15
+ return status >= 400 && status < 500 ? 'rejected' : 'unclear';
16
+ }
17
+ /**
18
+ * Replays each mutation the authorization names with a valid session, a foreign `Origin`, and no
19
+ * anti-forgery token. A server that checks for forgery refuses it. Only the mutations the
20
+ * operator named can be sent (the probe enforces it), and a complete request body the operator
21
+ * vouched for is what makes a refusal mean something: an empty body is refused for reasons that
22
+ * have nothing to do with CSRF, so without a body a refusal is `uncertain`, not `passed`.
23
+ */
24
+ export const csrfCheck = {
25
+ checkClass: 'csrf',
26
+ run: async ({ probe, authorization, identities }) => {
27
+ // Signing out is the session check's job. Replaying it here would end the identity's session
28
+ // and make every later check read a dead session as a refusal.
29
+ const mutations = authorization.allowedMutations.filter((mutation) => mutation.path !== authorization.logoutPath);
30
+ if (mutations.length === 0) {
31
+ return {
32
+ status: 'skipped',
33
+ note: 'The authorization names no mutation to replay without a token (the sign-out path is left to the session check).',
34
+ findings: [],
35
+ };
36
+ }
37
+ const session = identities.byRole('low') ?? identities.all()[0];
38
+ if (session === undefined) {
39
+ const failures = identities.describeFailures();
40
+ return failures === undefined
41
+ ? { status: 'skipped', note: 'The check needs a signed-in identity.', findings: [] }
42
+ : { status: 'blocked', note: `The check needs a signed-in identity (${failures})`, findings: [] };
43
+ }
44
+ const findings = [];
45
+ let unclear = 0;
46
+ let unverifiedRefusals = 0;
47
+ for (const mutation of mutations) {
48
+ const response = await probe.request({
49
+ method: mutation.method,
50
+ path: mutation.path,
51
+ headers: {
52
+ ...sessionHeaders(session),
53
+ origin: FOREIGN_ORIGIN,
54
+ referer: `${FOREIGN_ORIGIN}/`,
55
+ ...(mutation.contentType !== undefined ? { 'content-type': mutation.contentType } : {}),
56
+ },
57
+ ...(mutation.body !== undefined ? { body: mutation.body } : {}),
58
+ });
59
+ const verdict = judge(response);
60
+ if (verdict === 'accepted') {
61
+ findings.push(acceptedFinding(mutation, response));
62
+ }
63
+ else if (verdict === 'unclear') {
64
+ unclear += 1;
65
+ }
66
+ else if (mutation.body === undefined) {
67
+ unverifiedRefusals += 1;
68
+ }
69
+ }
70
+ if (findings.length > 0 || (unclear === 0 && unverifiedRefusals === 0)) {
71
+ return {
72
+ status: 'passed',
73
+ note: `Replayed ${String(mutations.length)} mutation(s) without a token.`,
74
+ findings,
75
+ };
76
+ }
77
+ return {
78
+ status: 'uncertain',
79
+ note: unclear > 0
80
+ ? 'A replayed request got no clear answer (a server error, or a redirect to sign in), so it was not judged.'
81
+ : 'The authorization gave no request body, so a refusal may have another cause than the missing token.',
82
+ findings,
83
+ };
84
+ },
85
+ };
86
+ function acceptedFinding(mutation, response) {
87
+ const isRedirect = response.status >= 300;
88
+ return buildFinding({
89
+ idParts: ['csrf', mutation.method, mutation.path],
90
+ checkClass: 'csrf',
91
+ riskArea: 'Request forgery',
92
+ // A redirect away from a sign-in page after a full request is a clear acceptance; a 200 can be
93
+ // a form re-displayed with an error, and a request without a body proves less still.
94
+ confidence: mutation.body === undefined ? 'low' : isRedirect ? 'high' : 'medium',
95
+ severityProposal: 'critical',
96
+ title: `${mutation.method} ${mutation.path} is accepted without an anti-forgery token`,
97
+ steps: [
98
+ 'Sign in as an identity',
99
+ `Send ${mutation.method} ${mutation.path} with the session cookie, a foreign Origin and no token`,
100
+ ],
101
+ expectedResult: 'The server refuses a state-changing request that carries no valid anti-forgery token',
102
+ actualResult: `The server answered ${String(response.status)}${isRedirect ? ' with a redirect to the next page' : ''} and accepted it`,
103
+ remediation: 'Verify a per-session anti-forgery token (or an Origin check, or SameSite cookies as a second layer) on every state-changing route.',
104
+ regressionCheck: `Assert that ${mutation.method} ${mutation.path} without a token answers 403 or 400`,
105
+ requestIndexes: [response.requestIndex],
106
+ });
107
+ }
108
+ //# sourceMappingURL=csrf-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csrf-check.js","sourceRoot":"","sources":["../../src/checks/csrf-check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { SecurityCheck } from '../security-check.js';
2
+ /**
3
+ * Asks npm which installed dependencies have known vulnerabilities (`npm audit`, read-only: it
4
+ * never installs or changes a file). It sends package names and versions from the lockfile to the
5
+ * configured package registry, which is why the class has to be named in the authorization. Only
6
+ * npm projects with a lockfile are covered; any other layout is `skipped`, and an audit that could
7
+ * not run or answered with an error is `blocked`, never `passed`.
8
+ */
9
+ export declare const dependenciesCheck: SecurityCheck;
10
+ //# sourceMappingURL=dependencies-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dependencies-check.d.ts","sourceRoot":"","sources":["../../src/checks/dependencies-check.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAwB,MAAM,sBAAsB,CAAC;AAsChF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,EAAE,aAkE/B,CAAC"}
@@ -0,0 +1,160 @@
1
+ // Copyright The QA-AI-STLC Authors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { posix } from 'node:path';
4
+ import { QaError } from '@qa-ai-stlc/core';
5
+ import { z } from 'zod';
6
+ import { buildFinding } from '../finding.js';
7
+ const NPM_AUDIT_TIMEOUT_MS = 120_000;
8
+ const LOCKFILE_NAMES = ['package-lock.json', 'npm-shrinkwrap.json'];
9
+ const MAX_ADVISORIES_PER_FINDING = 3;
10
+ const MAX_ADVISORY_TITLE_LENGTH = 120;
11
+ const NpmSeveritySchema = z.enum(['info', 'low', 'moderate', 'high', 'critical']);
12
+ const NpmAuditReportSchema = z.union([
13
+ z.object({ error: z.object({ code: z.string().optional(), summary: z.string().optional() }) }),
14
+ z.object({
15
+ vulnerabilities: z.record(z.string(), z.object({
16
+ name: z.string(),
17
+ severity: NpmSeveritySchema,
18
+ isDirect: z.boolean().optional(),
19
+ // An entry is an advisory object, or the name of another vulnerable package it passes through.
20
+ via: z.array(z.union([z.string(), z.object({ title: z.string().optional() })])).default([]),
21
+ fixAvailable: z.union([z.boolean(), z.object({ name: z.string(), version: z.string() })]).optional(),
22
+ })),
23
+ }),
24
+ ]);
25
+ const SEVERITY_BY_NPM_SEVERITY = {
26
+ critical: 'critical',
27
+ high: 'major',
28
+ moderate: 'minor',
29
+ low: 'trivial',
30
+ info: 'trivial',
31
+ };
32
+ /**
33
+ * Asks npm which installed dependencies have known vulnerabilities (`npm audit`, read-only: it
34
+ * never installs or changes a file). It sends package names and versions from the lockfile to the
35
+ * configured package registry, which is why the class has to be named in the authorization. Only
36
+ * npm projects with a lockfile are covered; any other layout is `skipped`, and an audit that could
37
+ * not run or answered with an error is `blocked`, never `passed`.
38
+ */
39
+ export const dependenciesCheck = {
40
+ checkClass: 'dependencies',
41
+ run: async ({ source }) => {
42
+ if (source === undefined) {
43
+ return { status: 'skipped', note: 'No source.path is configured.', findings: [] };
44
+ }
45
+ const located = await locateLockfile(source);
46
+ if (located === undefined) {
47
+ return {
48
+ status: 'skipped',
49
+ note: 'No package-lock.json was found at or above source.path. Only npm projects with a lockfile are audited in this version.',
50
+ findings: [],
51
+ };
52
+ }
53
+ const report = await runNpmAudit(source, located.directory);
54
+ if ('error' in report) {
55
+ throw new QaError('SECURITY_DEPENDENCY_AUDIT_FAILED', `npm audit could not complete: ${report.error.summary ?? report.error.code ?? 'no reason given'}`, {
56
+ remediation: 'Check the registry is reachable and the lockfile is current, then run the audit again.',
57
+ });
58
+ }
59
+ const manifestPath = posix.join(source.relativeRoot, 'package.json');
60
+ const manifestText = await source.readText(manifestPath);
61
+ const findings = Object.values(report.vulnerabilities).map((vulnerability) => {
62
+ const advisoryTitles = vulnerability.via
63
+ .flatMap((entry) => (typeof entry === 'string' || entry.title === undefined ? [] : [entry.title]))
64
+ .slice(0, MAX_ADVISORIES_PER_FINDING)
65
+ .map((title) => title.replace(/\s+/gu, ' ').slice(0, MAX_ADVISORY_TITLE_LENGTH));
66
+ const manifestLine = vulnerability.isDirect === true ? lineOfDependency(manifestText, vulnerability.name) : undefined;
67
+ const directness = vulnerability.isDirect === true ? 'a direct dependency' : 'pulled in by another dependency';
68
+ const advisories = advisoryTitles.length > 0 ? `: ${advisoryTitles.join('; ')}` : '';
69
+ return buildFinding({
70
+ idParts: ['dependencies', vulnerability.name],
71
+ checkClass: 'dependencies',
72
+ riskArea: 'Known vulnerabilities in third-party dependencies',
73
+ confidence: 'high',
74
+ severityProposal: SEVERITY_BY_NPM_SEVERITY[vulnerability.severity],
75
+ title: `Dependency ${vulnerability.name} has a known ${vulnerability.severity} vulnerability`,
76
+ steps: [
77
+ `Run npm audit for ${source.relativeRoot === '' ? 'the project' : source.relativeRoot}`,
78
+ `Read the entry for ${vulnerability.name}`,
79
+ ],
80
+ expectedResult: 'No dependency has a published vulnerability',
81
+ actualResult: `${vulnerability.name} is reported as ${vulnerability.severity} (${directness})${advisories}`,
82
+ remediation: remediationFor(vulnerability.name, vulnerability.fixAvailable),
83
+ regressionCheck: `Run npm audit and assert it no longer reports ${vulnerability.name}`,
84
+ requestIndexes: [],
85
+ codeReferences: [
86
+ manifestLine !== undefined
87
+ ? { path: manifestPath, line: manifestLine }
88
+ : { path: posix.join(located.directory, located.lockfile) },
89
+ ],
90
+ });
91
+ });
92
+ return {
93
+ status: 'passed',
94
+ note: 'Audited the lockfile against the package registry advisories.',
95
+ findings,
96
+ };
97
+ },
98
+ };
99
+ async function locateLockfile(source) {
100
+ for (const lockfile of LOCKFILE_NAMES) {
101
+ const directory = await source.findDirectoryWith(lockfile);
102
+ if (directory !== undefined) {
103
+ return { directory, lockfile };
104
+ }
105
+ }
106
+ return undefined;
107
+ }
108
+ // A checkout inside a workspace shares the lockfile of the project above it, so the audit runs
109
+ // from the lockfile's directory and is narrowed to the checkout's own workspace.
110
+ async function runNpmAudit(source, lockDirectory) {
111
+ // The runner has no working directory of its own, so the project is named by absolute path.
112
+ const args = ['audit', '--json', '--prefix', source.absoluteDirectory(lockDirectory)];
113
+ if (lockDirectory !== source.relativeRoot) {
114
+ args.push('--workspace', posix.relative(lockDirectory, source.relativeRoot));
115
+ }
116
+ let stdout;
117
+ try {
118
+ // npm exits non-zero when it finds vulnerabilities, so the exit code carries no meaning here;
119
+ // the JSON report on stdout is the answer.
120
+ ({ stdout } = await source.processRunner.run('npm', args, {
121
+ signal: AbortSignal.timeout(NPM_AUDIT_TIMEOUT_MS),
122
+ }));
123
+ }
124
+ catch (error) {
125
+ throw new QaError('SECURITY_DEPENDENCY_AUDIT_FAILED', 'npm audit could not be started or timed out', {
126
+ remediation: 'Check npm is installed and on the path.',
127
+ cause: error,
128
+ });
129
+ }
130
+ let json;
131
+ try {
132
+ json = JSON.parse(stdout);
133
+ }
134
+ catch (error) {
135
+ throw new QaError('SECURITY_DEPENDENCY_AUDIT_FAILED', 'npm audit did not answer with JSON', {
136
+ remediation: 'Run "npm audit --json" in the project to see what npm printed.',
137
+ cause: error,
138
+ });
139
+ }
140
+ const parsed = NpmAuditReportSchema.safeParse(json);
141
+ if (!parsed.success) {
142
+ throw new QaError('SECURITY_DEPENDENCY_AUDIT_FAILED', 'npm audit answered with a report in an unknown shape', {
143
+ remediation: 'Update npm, or run "npm audit --json" in the project to see what it printed.',
144
+ });
145
+ }
146
+ return parsed.data;
147
+ }
148
+ function lineOfDependency(manifestText, name) {
149
+ const index = manifestText?.split(/\r?\n/u).findIndex((line) => line.trimStart().startsWith(`"${name}":`));
150
+ return index === undefined || index < 0 ? undefined : index + 1;
151
+ }
152
+ function remediationFor(name, fixAvailable) {
153
+ if (typeof fixAvailable === 'object') {
154
+ return `Update ${fixAvailable.name} to ${fixAvailable.version} or later.`;
155
+ }
156
+ return fixAvailable === true
157
+ ? `Update ${name} to a patched version (npm audit reports a fix is available).`
158
+ : `No fix is published for ${name}: replace it, or limit how the application uses it.`;
159
+ }
160
+ //# sourceMappingURL=dependencies-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dependencies-check.js","sourceRoot":"","sources":["../../src/checks/dependencies-check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { SecurityCheck } from '../security-check.js';
2
+ /**
3
+ * Sends a harmless marker in the usual query parameters and reads the page back. A marker that
4
+ * comes back with its angle brackets and quotes unencoded means input is written into the page
5
+ * without encoding, the precondition of cross-site scripting. Only GET requests are sent. Finding
6
+ * nothing proves little (the real parameters stay unknown), so a clean result is `uncertain`, not
7
+ * `passed`: the audit does not claim more than it tried.
8
+ */
9
+ export declare const encodingCheck: SecurityCheck;
10
+ //# sourceMappingURL=encoding-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encoding-check.d.ts","sourceRoot":"","sources":["../../src/checks/encoding-check.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAwB,MAAM,sBAAsB,CAAC;AAwBhF;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,EAAE,aAsC3B,CAAC"}
@@ -0,0 +1,67 @@
1
+ // Copyright The QA-AI-STLC Authors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { fetchPage } from '../fetch-page.js';
4
+ import { buildFinding } from '../finding.js';
5
+ const PAGE_PATHS = ['/', '/login'];
6
+ // Query parameter names that applications commonly echo into a page. A black-box check cannot know
7
+ // which parameters an application reads, so it can only try the usual ones.
8
+ const PARAMETER_NAMES = [
9
+ 'q',
10
+ 'search',
11
+ 'query',
12
+ 's',
13
+ 'name',
14
+ 'id',
15
+ 'message',
16
+ 'error',
17
+ 'redirect',
18
+ 'next',
19
+ ];
20
+ // A marker, not a payload: it contains the characters that matter in HTML (angle brackets and
21
+ // quotes) around a fixed token, and does nothing if it is reflected.
22
+ const TOKEN = 'qaaudit7q3';
23
+ const MARKER = `${TOKEN}<"'>`;
24
+ /**
25
+ * Sends a harmless marker in the usual query parameters and reads the page back. A marker that
26
+ * comes back with its angle brackets and quotes unencoded means input is written into the page
27
+ * without encoding, the precondition of cross-site scripting. Only GET requests are sent. Finding
28
+ * nothing proves little (the real parameters stay unknown), so a clean result is `uncertain`, not
29
+ * `passed`: the audit does not claim more than it tried.
30
+ */
31
+ export const encodingCheck = {
32
+ checkClass: 'encoding',
33
+ run: async ({ probe, authorization }) => {
34
+ const { baseUrl } = authorization.environment;
35
+ const query = PARAMETER_NAMES.map((name) => `${name}=${encodeURIComponent(MARKER)}`).join('&');
36
+ const findings = [];
37
+ for (const path of PAGE_PATHS) {
38
+ const page = await fetchPage(probe, baseUrl, `${path}?${query}`);
39
+ if (!page.response.bodyText.includes(MARKER)) {
40
+ continue;
41
+ }
42
+ findings.push(buildFinding({
43
+ idParts: ['encoding', 'reflected', path],
44
+ checkClass: 'encoding',
45
+ riskArea: 'Injection: output encoding',
46
+ confidence: 'medium',
47
+ severityProposal: 'major',
48
+ title: `Input from the query string is written into ${path} without encoding`,
49
+ steps: [
50
+ `Request ${path} with a marker containing angle brackets and quotes in the usual parameters`,
51
+ 'Search the page for the marker, unencoded',
52
+ ],
53
+ expectedResult: 'Characters that are special in HTML come back encoded',
54
+ actualResult: `The marker was returned unencoded by GET ${page.path.replace(/\?.*$/u, '')}`,
55
+ remediation: 'Encode all input for the context it is written into (HTML, attribute, script, URL).',
56
+ regressionCheck: `Assert that a marker with angle brackets sent to ${path} is returned encoded`,
57
+ requestIndexes: [page.response.requestIndex],
58
+ }));
59
+ }
60
+ return {
61
+ status: 'uncertain',
62
+ note: `Tried ${String(PARAMETER_NAMES.length)} common parameter names on ${String(PAGE_PATHS.length)} pages; parameters the application actually reads were not discovered.`,
63
+ findings,
64
+ };
65
+ },
66
+ };
67
+ //# sourceMappingURL=encoding-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encoding-check.js","sourceRoot":"","sources":["../../src/checks/encoding-check.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,sCAAsC;AAGtC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAG7C,MAAM,UAAU,GAAsB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAEtD,mGAAmG;AACnG,4EAA4E;AAC5E,MAAM,eAAe,GAAsB;IACzC,GAAG;IACH,QAAQ;IACR,OAAO;IACP,GAAG;IACH,MAAM;IACN,IAAI;IACJ,SAAS;IACT,OAAO;IACP,UAAU;IACV,MAAM;CACP,CAAC;AAEF,8FAA8F;AAC9F,qEAAqE;AACrE,MAAM,KAAK,GAAG,YAAY,CAAC;AAC3B,MAAM,MAAM,GAAG,GAAG,KAAK,MAAM,CAAC;AAE9B;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,aAAa,GAAkB;IAC1C,UAAU,EAAE,UAAU;IACtB,GAAG,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,EAAiC,EAAE;QACrE,MAAM,EAAE,OAAO,EAAE,GAAG,aAAa,CAAC,WAAW,CAAC;QAC9C,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/F,MAAM,QAAQ,GAAsB,EAAE,CAAC;QAEvC,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;YAC9B,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC;YACjE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC7C,SAAS;YACX,CAAC;YACD,QAAQ,CAAC,IAAI,CACX,YAAY,CAAC;gBACX,OAAO,EAAE,CAAC,UAAU,EAAE,WAAW,EAAE,IAAI,CAAC;gBACxC,UAAU,EAAE,UAAU;gBACtB,QAAQ,EAAE,4BAA4B;gBACtC,UAAU,EAAE,QAAQ;gBACpB,gBAAgB,EAAE,OAAO;gBACzB,KAAK,EAAE,+CAA+C,IAAI,mBAAmB;gBAC7E,KAAK,EAAE;oBACL,WAAW,IAAI,6EAA6E;oBAC5F,2CAA2C;iBAC5C;gBACD,cAAc,EAAE,uDAAuD;gBACvE,YAAY,EAAE,4CAA4C,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,EAAE;gBAC3F,WAAW,EAAE,qFAAqF;gBAClG,eAAe,EAAE,oDAAoD,IAAI,sBAAsB;gBAC/F,cAAc,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;aAC7C,CAAC,CACH,CAAC;QACJ,CAAC;QACD,OAAO;YACL,MAAM,EAAE,WAAW;YACnB,IAAI,EAAE,SAAS,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,8BAA8B,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,wEAAwE;YAC5K,QAAQ;SACT,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { SecurityCheck } from '../security-check.js';
2
+ /**
3
+ * Provokes two ordinary error responses and reads them for stack traces, database errors and
4
+ * framework banners. A match is something the page printed, so confidence is high; it is only a
5
+ * response body, so nothing here changes anything on the server.
6
+ */
7
+ export declare const errorsCheck: SecurityCheck;
8
+ //# sourceMappingURL=errors-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors-check.d.ts","sourceRoot":"","sources":["../../src/checks/errors-check.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAwB,MAAM,sBAAsB,CAAC;AAsChF;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,aA4BzB,CAAC"}
@@ -0,0 +1,60 @@
1
+ // Copyright The QA-AI-STLC Authors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { buildFinding } from '../finding.js';
4
+ // A path nothing serves, and a path with an invalid percent escape: the two ordinary ways to
5
+ // provoke an error page without sending anything harmful.
6
+ const ERROR_PATHS = [
7
+ { path: '/qa-audit-no-such-page', label: 'a page that does not exist' },
8
+ { path: '/qa-audit-%ZZ', label: 'a malformed percent escape' },
9
+ ];
10
+ // What an error page should never print: where the code lives, which query failed, which product
11
+ // and version is answering. Each pattern matches wording specific enough to be rare in ordinary prose.
12
+ const LEAK_PATTERNS = [
13
+ {
14
+ name: 'stack-trace',
15
+ pattern: /\n\s+at [\w$.<>[\] ]+\(?[^\n)]*:\d+:\d+\)?|Traceback \(most recent call last\)|Stack trace:|Exception in thread/u,
16
+ severity: 'major',
17
+ },
18
+ {
19
+ name: 'database-error',
20
+ pattern: /SQLSTATE\[|ORA-\d{5}|You have an error in your SQL syntax|PG::\w+Error|SQLite3?::|MongoServerError/iu,
21
+ severity: 'major',
22
+ },
23
+ {
24
+ name: 'framework-banner',
25
+ pattern: /Whitelabel Error Page|Server Error in '\/' Application|Werkzeug Debugger|Django.*DEBUG = True|Laravel.*Whoops|<pre>Cannot (GET|POST|PUT|DELETE) \//iu,
26
+ severity: 'trivial',
27
+ },
28
+ ];
29
+ /**
30
+ * Provokes two ordinary error responses and reads them for stack traces, database errors and
31
+ * framework banners. A match is something the page printed, so confidence is high; it is only a
32
+ * response body, so nothing here changes anything on the server.
33
+ */
34
+ export const errorsCheck = {
35
+ checkClass: 'errors',
36
+ run: async ({ probe }) => {
37
+ const findings = [];
38
+ for (const { path, label } of ERROR_PATHS) {
39
+ const response = await probe.request({ method: 'GET', path });
40
+ for (const leak of LEAK_PATTERNS.filter((candidate) => candidate.pattern.test(response.bodyText))) {
41
+ findings.push(buildFinding({
42
+ idParts: ['errors', leak.name, path],
43
+ checkClass: 'errors',
44
+ riskArea: 'Information disclosure: error pages',
45
+ confidence: 'high',
46
+ severityProposal: leak.severity,
47
+ title: `An error page discloses a ${leak.name.replace('-', ' ')}`,
48
+ steps: [`Request ${path} (${label})`, 'Read the response body'],
49
+ expectedResult: 'The error page says what went wrong for a visitor and nothing about the implementation',
50
+ actualResult: `GET ${path} answered ${String(response.status)} with a ${leak.name.replace('-', ' ')}`,
51
+ remediation: 'Show a generic error page in production and log the detail on the server.',
52
+ regressionCheck: `Assert that GET ${path} returns no ${leak.name.replace('-', ' ')}`,
53
+ requestIndexes: [response.requestIndex],
54
+ }));
55
+ }
56
+ }
57
+ return { status: 'passed', note: 'Provoked a missing-page and a malformed-escape error.', findings };
58
+ },
59
+ };
60
+ //# sourceMappingURL=errors-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors-check.js","sourceRoot":"","sources":["../../src/checks/errors-check.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,sCAAsC;AAGtC,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAG7C,6FAA6F;AAC7F,0DAA0D;AAC1D,MAAM,WAAW,GAAiE;IAChF,EAAE,IAAI,EAAE,wBAAwB,EAAE,KAAK,EAAE,4BAA4B,EAAE;IACvE,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,4BAA4B,EAAE;CAC/D,CAAC;AAQF,iGAAiG;AACjG,uGAAuG;AACvG,MAAM,aAAa,GAA2B;IAC5C;QACE,IAAI,EAAE,aAAa;QACnB,OAAO,EACL,kHAAkH;QACpH,QAAQ,EAAE,OAAO;KAClB;IACD;QACE,IAAI,EAAE,gBAAgB;QACtB,OAAO,EACL,sGAAsG;QACxG,QAAQ,EAAE,OAAO;KAClB;IACD;QACE,IAAI,EAAE,kBAAkB;QACxB,OAAO,EACL,sJAAsJ;QACxJ,QAAQ,EAAE,SAAS;KACpB;CACF,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAkB;IACxC,UAAU,EAAE,QAAQ;IACpB,GAAG,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,EAAiC,EAAE;QACtD,MAAM,QAAQ,GAAsB,EAAE,CAAC;QACvC,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,WAAW,EAAE,CAAC;YAC1C,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAC9D,KAAK,MAAM,IAAI,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;gBAClG,QAAQ,CAAC,IAAI,CACX,YAAY,CAAC;oBACX,OAAO,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC;oBACpC,UAAU,EAAE,QAAQ;oBACpB,QAAQ,EAAE,qCAAqC;oBAC/C,UAAU,EAAE,MAAM;oBAClB,gBAAgB,EAAE,IAAI,CAAC,QAAQ;oBAC/B,KAAK,EAAE,6BAA6B,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE;oBACjE,KAAK,EAAE,CAAC,WAAW,IAAI,KAAK,KAAK,GAAG,EAAE,wBAAwB,CAAC;oBAC/D,cAAc,EACZ,wFAAwF;oBAC1F,YAAY,EAAE,OAAO,IAAI,aAAa,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE;oBACrG,WAAW,EAAE,2EAA2E;oBACxF,eAAe,EAAE,mBAAmB,IAAI,eAAe,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE;oBACpF,cAAc,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC;iBACxC,CAAC,CACH,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,uDAAuD,EAAE,QAAQ,EAAE,CAAC;IACvG,CAAC;CACF,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { SecurityCheck } from '../security-check.js';
2
+ /**
3
+ * Looks at the security headers of the landing page, following same-origin redirects to the page
4
+ * a visitor actually sees. Header names are compared case-insensitively because the probe's HTTP
5
+ * client lower-cases them. A missing header is a fact about the response, so those findings carry
6
+ * high confidence; their severity stays low because each is defence in depth, not a flaw by itself.
7
+ */
8
+ export declare const headersCheck: SecurityCheck;
9
+ //# sourceMappingURL=headers-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers-check.d.ts","sourceRoot":"","sources":["../../src/checks/headers-check.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAwB,MAAM,sBAAsB,CAAC;AAoEhF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,EAAE,aA2D1B,CAAC"}
@@ -0,0 +1,110 @@
1
+ // Copyright The QA-AI-STLC Authors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { fetchPage } from '../fetch-page.js';
4
+ import { buildFinding } from '../finding.js';
5
+ const PAGE_PATH = '/';
6
+ // A product name followed by a version number tells an attacker which known flaws to try.
7
+ const VERSIONED_BANNER = /\d+\.\d+/u;
8
+ const EXPECTATIONS = [
9
+ {
10
+ name: 'content-security-policy',
11
+ isMissing: (headers) => headers['content-security-policy'] === undefined,
12
+ title: 'No Content-Security-Policy header',
13
+ riskArea: 'Browser hardening: script injection',
14
+ severity: 'minor',
15
+ expected: 'The response sets a Content-Security-Policy that limits where scripts can load from',
16
+ remediation: 'Send a Content-Security-Policy header, starting from a restrictive default-src.',
17
+ },
18
+ {
19
+ name: 'x-content-type-options',
20
+ isMissing: (headers) => headers['x-content-type-options']?.toLowerCase() !== 'nosniff',
21
+ title: 'Content type sniffing is not disabled',
22
+ riskArea: 'Browser hardening: content sniffing',
23
+ severity: 'minor',
24
+ expected: 'The response sets X-Content-Type-Options: nosniff',
25
+ remediation: 'Send X-Content-Type-Options: nosniff on every response.',
26
+ },
27
+ {
28
+ name: 'framing',
29
+ // A frame-ancestors directive in the CSP supersedes X-Frame-Options, so either one counts.
30
+ isMissing: (headers) => headers['x-frame-options'] === undefined &&
31
+ !(headers['content-security-policy'] ?? '').toLowerCase().includes('frame-ancestors'),
32
+ title: 'The page can be framed by any site',
33
+ riskArea: 'Browser hardening: clickjacking',
34
+ severity: 'minor',
35
+ expected: 'The response sets X-Frame-Options or a CSP frame-ancestors directive',
36
+ remediation: "Send Content-Security-Policy: frame-ancestors 'self', or X-Frame-Options: SAMEORIGIN.",
37
+ },
38
+ {
39
+ name: 'strict-transport-security',
40
+ isMissing: (headers, isHttps) => isHttps && headers['strict-transport-security'] === undefined,
41
+ title: 'No Strict-Transport-Security header on an HTTPS site',
42
+ riskArea: 'Transport security',
43
+ severity: 'minor',
44
+ expected: 'The HTTPS response sets Strict-Transport-Security',
45
+ remediation: 'Send Strict-Transport-Security with a max-age of at least six months.',
46
+ },
47
+ {
48
+ name: 'referrer-policy',
49
+ isMissing: (headers) => headers['referrer-policy'] === undefined,
50
+ title: 'No Referrer-Policy header',
51
+ riskArea: 'Information disclosure: referrer',
52
+ severity: 'trivial',
53
+ expected: 'The response sets a Referrer-Policy',
54
+ remediation: 'Send Referrer-Policy: strict-origin-when-cross-origin or stricter.',
55
+ },
56
+ ];
57
+ /**
58
+ * Looks at the security headers of the landing page, following same-origin redirects to the page
59
+ * a visitor actually sees. Header names are compared case-insensitively because the probe's HTTP
60
+ * client lower-cases them. A missing header is a fact about the response, so those findings carry
61
+ * high confidence; their severity stays low because each is defence in depth, not a flaw by itself.
62
+ */
63
+ export const headersCheck = {
64
+ checkClass: 'headers',
65
+ run: async ({ probe, authorization }) => {
66
+ const { baseUrl } = authorization.environment;
67
+ const isHttps = new URL(baseUrl).protocol === 'https:';
68
+ const page = await fetchPage(probe, baseUrl, PAGE_PATH);
69
+ const { headers } = page.response;
70
+ const requestIndexes = [page.response.requestIndex];
71
+ const reached = `GET ${page.path}`;
72
+ const findings = EXPECTATIONS.filter((expectation) => expectation.isMissing(headers, isHttps)).map((expectation) => buildFinding({
73
+ idParts: ['headers', expectation.name],
74
+ checkClass: 'headers',
75
+ riskArea: expectation.riskArea,
76
+ confidence: 'high',
77
+ severityProposal: expectation.severity,
78
+ title: expectation.title,
79
+ steps: [
80
+ `Request ${PAGE_PATH}${page.path === PAGE_PATH ? '' : ` and follow it to ${page.path}`}`,
81
+ 'Read the response headers',
82
+ ],
83
+ expectedResult: expectation.expected,
84
+ actualResult: `${reached} answered ${String(page.response.status)} without it`,
85
+ remediation: expectation.remediation,
86
+ regressionCheck: `Assert that ${reached} returns the ${expectation.name} protection`,
87
+ requestIndexes,
88
+ }));
89
+ const banners = [headers.server, headers['x-powered-by']].filter((value) => value !== undefined);
90
+ const disclosesTechnology = headers['x-powered-by'] !== undefined || banners.some((banner) => VERSIONED_BANNER.test(banner));
91
+ if (disclosesTechnology) {
92
+ findings.push(buildFinding({
93
+ idParts: ['headers', 'technology-banner'],
94
+ checkClass: 'headers',
95
+ riskArea: 'Information disclosure: technology',
96
+ confidence: 'medium',
97
+ severityProposal: 'trivial',
98
+ title: 'The response names the server technology',
99
+ steps: [`Request ${PAGE_PATH}`, 'Read the Server and X-Powered-By headers'],
100
+ expectedResult: 'The response does not name the framework or its version',
101
+ actualResult: `The response sent ${banners.map((banner) => `"${banner}"`).join(' and ')}`,
102
+ remediation: 'Remove X-Powered-By and drop version numbers from the Server header.',
103
+ regressionCheck: 'Assert that the response has no X-Powered-By header and no versioned Server header',
104
+ requestIndexes,
105
+ }));
106
+ }
107
+ return { status: 'passed', note: `Read the headers of ${page.path}.`, findings };
108
+ },
109
+ };
110
+ //# sourceMappingURL=headers-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers-check.js","sourceRoot":"","sources":["../../src/checks/headers-check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { SecurityCheck } from '../security-check.js';
2
+ /**
3
+ * Looks through the source checkout for credentials committed with the code. Only the kind of
4
+ * secret and where it sits are reported, never the value, so the audit result and the defect draft
5
+ * carry no credential themselves. A match in test code or sample data is reported with low
6
+ * confidence. A clean result is `passed` only when every file was read: a truncated or partly
7
+ * skipped scan cannot show that nothing is there.
8
+ */
9
+ export declare const secretsCheck: SecurityCheck;
10
+ //# sourceMappingURL=secrets-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets-check.d.ts","sourceRoot":"","sources":["../../src/checks/secrets-check.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAwB,MAAM,sBAAsB,CAAC;AA+DhF;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,aA+C1B,CAAC"}