@produtype/core 0.40.0 → 0.41.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.
@@ -45,6 +45,26 @@ function namesItself(snippet) {
45
45
  const flatten = (value) => value.toLowerCase().replace(/[^a-z0-9]/g, '');
46
46
  return flatten(assignment[1]) === flatten(assignment[2]);
47
47
  }
48
+ /**
49
+ * The value is the name of something, spelled the way every language spells a name.
50
+ *
51
+ * `REDIS_SECRET_KEY = "SECRET_TOKEN"` in Discourse is the Redis key under which the
52
+ * secret is stored — the comment directly above it says so — and it was reported as a
53
+ * hardcoded secret at `critical`, the severity that bars a report from the top band.
54
+ * `namesItself` could not catch it: the constant and its value are two different
55
+ * names, so the letters do not match.
56
+ *
57
+ * What settles it is the shape. `SECRET_TOKEN` is SCREAMING_SNAKE_CASE, which is how
58
+ * constants, environment variables and Redis keys are written and not how secrets are
59
+ * written — a secret is entropy, and this has none. Kept to that one casing on
60
+ * purpose: `dev-secret` and `changeme` are lowercase and stay caught.
61
+ */
62
+ function valueIsAnIdentifier(snippet) {
63
+ const assignment = /([A-Za-z_][A-Za-z0-9_]*)\s*[:=]\s*["'`]([^"'`]+)["'`]/.exec(snippet);
64
+ if (!assignment)
65
+ return false;
66
+ return /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)+$/.test(assignment[2]);
67
+ }
48
68
  async function detectEnv(ctx) {
49
69
  const evidence = [];
50
70
  const weakSecretEvidence = [];
@@ -98,6 +118,7 @@ async function detectEnv(ctx) {
98
118
  const weakHits = fallbackHits.filter((m) => WEAK_SECRET_VALUE_RE.test(m.snippet)
99
119
  && SECRET_ASSIGNMENT_CONTEXT_RE.test(m.snippet)
100
120
  && !namesItself(m.snippet)
121
+ && !valueIsAnIdentifier(m.snippet)
101
122
  && !isTableEntry(m));
102
123
  for (const m of weakHits) {
103
124
  const hitEvidence = { type: 'snippet', value: m.snippet, file: m.file, line: m.line };
@@ -5,6 +5,7 @@ const detectContext_1 = require("./detectContext");
5
5
  const readTextFileSafe_1 = require("../utils/readTextFileSafe");
6
6
  const textSearch_1 = require("../utils/textSearch");
7
7
  const absenceEvidence_1 = require("./absenceEvidence");
8
+ const developmentOnly_1 = require("./developmentOnly");
8
9
  /** Lines that decide which origins may call this server. */
9
10
  const ORIGIN_HANDLING = [/Access-Control-Allow-Origin/i, /ALLOWED_ORIGINS/, /allowedOrigins/i];
10
11
  /**
@@ -145,6 +146,10 @@ async function detectSecurity(ctx) {
145
146
  const corsLoose = [];
146
147
  const corsStrict = [];
147
148
  for (const file of source) {
149
+ // Rails picks one environment file by RAILS_ENV, so a header set in
150
+ // development.rb is not a header this product sends.
151
+ if ((0, developmentOnly_1.isDevelopmentOnlyFile)(file))
152
+ continue;
148
153
  const text = await (0, readTextFileSafe_1.readTextFileSafe)(ctx.root, file);
149
154
  if (!text)
150
155
  continue;
@@ -0,0 +1 @@
1
+ export declare function isDevelopmentOnlyFile(file: string): boolean;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isDevelopmentOnlyFile = isDevelopmentOnlyFile;
4
+ /**
5
+ * A file the product does not run in production.
6
+ *
7
+ * Discourse was reported as allowing every cross-origin request, and one of the two
8
+ * lines behind it was `config/environments/development.rb:34`. Rails loads exactly one
9
+ * of those files, chosen by `RAILS_ENV`, so a header set in `development.rb` is as
10
+ * absent from production as a line inside `if settings.DEBUG:` — the same mistake this
11
+ * analyzer had already made in Python and fixed there.
12
+ *
13
+ * Deliberately only the frameworks that name the environment in the path, because
14
+ * that is the only case where the file's own name settles the question. A file called
15
+ * `dev-server.js` might be anything; `config/environments/test.rb` cannot be.
16
+ */
17
+ const DEVELOPMENT_ONLY_PATHS = [
18
+ /(^|\/)config\/environments\/(?!production)[a-z_]+\.rb$/i,
19
+ /(^|\/)config\/environments\/(?!production)[a-z_]+\.exs$/i,
20
+ ];
21
+ function isDevelopmentOnlyFile(file) {
22
+ return DEVELOPMENT_ONLY_PATHS.some((pattern) => pattern.test(file));
23
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@produtype/core",
3
- "version": "0.40.0",
3
+ "version": "0.41.0",
4
4
  "description": "Deterministic CLI and library that analyzes a web application repository and reports how far it is from production-ready for the kind of product it is meant to be.",
5
5
  "license": "MIT",
6
6
  "bin": {