@runeditrun/rer 0.2.2 → 0.2.4

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 (86) hide show
  1. package/README.md +9 -2
  2. package/cli/README.md +21 -0
  3. package/cli/container.mjs +11 -10
  4. package/cli/contract.mjs +2 -2
  5. package/cli/demo-host.mjs +47 -0
  6. package/cli/demo.mjs +47 -0
  7. package/cli/dependencies.mjs +26 -4
  8. package/cli/deploy-command.mjs +94 -0
  9. package/cli/deploy-inputs.mjs +22 -0
  10. package/cli/deploy-observation.mjs +28 -0
  11. package/cli/deploy-owner.mjs +52 -37
  12. package/cli/deploy-readiness.mjs +16 -0
  13. package/cli/deploy.mjs +20 -10
  14. package/cli/detached-server.mjs +1 -1
  15. package/cli/export.mjs +1 -1
  16. package/cli/hosted-output.mjs +2 -1
  17. package/cli/lifecycle.mjs +20 -7
  18. package/cli/local.mjs +7 -0
  19. package/cli/main.mjs +1 -1
  20. package/cli/managed-server.mjs +27 -18
  21. package/cli/parse.mjs +7 -2
  22. package/cli/pnpm-probe.mjs +4 -1
  23. package/cli/portability.mjs +4 -1
  24. package/cli/scheduled.mjs +2 -2
  25. package/cli/setup-input.mjs +1 -1
  26. package/global/CHANGELOG.md +46 -0
  27. package/global/README.md +9 -2
  28. package/global/launch/capacity.mjs +1 -1
  29. package/global/launch/facts.mjs +12 -6
  30. package/global/launch/install-pinned.mjs +26 -2
  31. package/global/launch/prepare.mjs +20 -8
  32. package/global/launch/state.mjs +54 -1
  33. package/global/main.mjs +18 -20
  34. package/global/update/command.mjs +2 -1
  35. package/modules/container/build.mjs +4 -2
  36. package/modules/container/host.mjs +1 -1
  37. package/modules/container/ingress.mjs +4 -1
  38. package/modules/container/migrations.mjs +2 -1
  39. package/modules/container/worker-engine.mjs +3 -1
  40. package/modules/demo-host/README.md +30 -0
  41. package/modules/demo-host/capacity.mjs +19 -0
  42. package/modules/demo-host/claim.mjs +21 -0
  43. package/modules/demo-host/container.mjs +74 -0
  44. package/modules/demo-host/pages.mjs +56 -0
  45. package/modules/demo-host/plan.mjs +52 -0
  46. package/modules/demo-host/worker.mjs +72 -0
  47. package/modules/deploy-kit/README.md +27 -0
  48. package/modules/deploy-kit/env-file.mjs +2 -1
  49. package/modules/deploy-kit/health-request.mjs +7 -2
  50. package/modules/deploy-kit/migration-ledger.mjs +38 -1
  51. package/modules/deploy-kit/migrations.mjs +38 -2
  52. package/modules/deploy-kit/owner-cli.mjs +30 -9
  53. package/modules/deploy-kit/owner-remove.mjs +5 -2
  54. package/modules/deploy-kit/owner-secret.mjs +67 -22
  55. package/modules/deploy-kit/owner.mjs +71 -24
  56. package/modules/deploy-kit/prepare.mjs +3 -1
  57. package/modules/deploy-kit/provider.mjs +3 -0
  58. package/modules/erasure/README.md +2 -3
  59. package/modules/operator-auth/accounts/settings-page.mjs +8 -3
  60. package/modules/operator-auth/csp.mjs +26 -0
  61. package/modules/operator-auth/dev/badge.mjs +1 -22
  62. package/modules/operator-auth/page.mjs +2 -2
  63. package/modules/operator-auth/routes.mjs +1 -1
  64. package/modules/portability/README.md +9 -0
  65. package/modules/portability/bridge.mjs +4 -1
  66. package/modules/portability/catalogue.mjs +9 -2
  67. package/modules/portability/index.d.mts +4 -0
  68. package/modules/portability/index.mjs +1 -0
  69. package/modules/portability/lines.mjs +3 -1
  70. package/modules/release/produce.mjs +1 -1
  71. package/modules/release/source.mjs +5 -2
  72. package/onboarding/adapter-labels.mjs +1 -0
  73. package/onboarding/adapter.mjs +9 -4
  74. package/onboarding/dependency-environment.mjs +33 -0
  75. package/onboarding/local/owner.mjs +1 -1
  76. package/onboarding/operator-installer.mjs +10 -4
  77. package/onboarding/redaction.mjs +22 -3
  78. package/package.json +6 -1
  79. package/scripts/publish-check/README.md +22 -1
  80. package/scripts/publish-check/c2pa.py +121 -0
  81. package/scripts/publish-check/content_identities.py +3 -1
  82. package/scripts/publish-check/controller_metadata.py +31 -0
  83. package/scripts/publish-check/history_scan.py +13 -3
  84. package/scripts/publish-check/media.py +283 -0
  85. package/scripts/publish-check/png_text.py +34 -0
  86. package/scripts/publish-check/public_hygiene.py +39 -4
@@ -0,0 +1,34 @@
1
+ """Separate uncompressed PNG text fields from chunk framing and CRC bytes."""
2
+
3
+
4
+ def text_chunks(data):
5
+ # Detect the container by bytes, never by a publication path or extension.
6
+ if not data.startswith(b'\x89PNG\r\n\x1a\n'):
7
+ return
8
+ offset = 8
9
+ while offset + 12 <= len(data):
10
+ start = offset
11
+ length = int.from_bytes(data[offset:offset + 4], 'big')
12
+ end = offset + 8 + length
13
+ if end + 4 > len(data):
14
+ return
15
+ kind = data[offset + 4:offset + 8]
16
+ payload = data[offset + 8:end]
17
+ offset = end + 4
18
+ # Checksums do not confer an exemption: inspect a framed text field
19
+ # even if its CRC is wrong. Non-text chunks keep raw-byte inspection.
20
+ if kind == b'tEXt':
21
+ fields = payload.split(b'\0', 1)
22
+ if len(fields) == 2:
23
+ yield start, offset, [field.decode('latin-1').encode('utf-8') for field in fields]
24
+ elif kind == b'iTXt':
25
+ fields = payload.split(b'\0', 1)
26
+ if len(fields) != 2 or len(fields[1]) < 2:
27
+ continue
28
+ keyword, body = fields
29
+ fields = body[2:].split(b'\0', 2)
30
+ # The compression method is ignored when the flag is zero.
31
+ if body[0] == 0 and len(fields) == 3:
32
+ yield start, offset, [keyword.decode('latin-1').encode('utf-8'), *fields]
33
+ if kind == b'IEND':
34
+ return
@@ -8,6 +8,9 @@ from provenance import provenance_text
8
8
  from hygiene_config import EMAIL, LOCAL_PATH, public_email
9
9
  from font_licence import without_required_notices
10
10
  from trust_bundle import anchor_values
11
+ from png_text import text_chunks
12
+ from media import sample_spans
13
+ from c2pa import certificate_spans
11
14
  URI_AUTHORITY = re.compile(r'''(?i)(?a:(?P<scheme>[a-z][a-z0-9+.-]*:))?//(?P<authority>[^/?#\s<>"']+)''')
12
15
  # Match raw, complete commit trailer lines; decoded or embedded prose cannot
13
16
  # acquire trailer privileges. Only the address span is omitted from email checks.
@@ -47,7 +50,7 @@ def email_userinfo(match):
47
50
  def private_emails(text, config):
48
51
  # URL paths, queries and fragments remain visible to email inspection.
49
52
  email_text = URI_AUTHORITY.sub(email_userinfo, without_required_notices(text))
50
- email_text = re.sub(r'(?i)\b(?a:git@github\.com:)(?=[A-Za-z0-9])', '', email_text)
53
+ email_text = re.sub(r'(?i)\b(?a:git@github\.com:)(?=[A-Za-z0-9]|\$\{[A-Za-z_$][A-Za-z0-9_.$]*\})', '', email_text)
51
54
  # A package manager's patch filename joins the name and version with an at sign.
52
55
  version = r'(?:v?[0-9]+(?:\.[0-9]+)*(?:-[A-Za-z0-9-]+)?|[a-f0-9]{40})(?:\.patch)?(?![A-Za-z0-9_.-])'
53
56
  email_text = re.sub(r'(?m)^\s*-?\s*uses:\s*[\"\']?[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+@[^\s\"\']+', '', email_text)
@@ -92,11 +95,43 @@ def certificate_metadata(data, bundle, emails, config):
92
95
  return not Counter(emails) - inside
93
96
 
94
97
 
98
+ def without_spans(data, spans):
99
+ # Each span becomes one space: text on either side stays apart, and no
100
+ # long blank run is left for the line-anchored patterns to rescan.
101
+ kept, offset = [], 0
102
+ for start, end in sorted(spans):
103
+ kept.append(data[offset:start])
104
+ offset = max(offset, end)
105
+ kept.append(data[offset:])
106
+ return b' '.join(kept)
107
+
108
+
95
109
  def inspect(data, path, config, resolver, *, commit_message=False):
96
- return inspect_text(data, path, config, resolver, commit_message=commit_message)
110
+ chunks = list(text_chunks(data))
111
+ # Image and video samples are not text: they can spell an address or a
112
+ # path by chance. Comments, XMP, tags and other metadata remain text.
113
+ spans = sample_spans(data) or []
114
+ # Names in a C2PA manifest's signing certificates are certificate metadata.
115
+ spans += certificate_spans(data)
116
+ rules, provenance = set(), False
117
+ for start, end, fields in chunks:
118
+ spans.append((start, end))
119
+ for field in fields:
120
+ # A metadata value is text, not a standalone source manifest or
121
+ # trust bundle, even when the containing image has a JSON name.
122
+ field_rules, field_provenance = inspect_text(field, '@png-text', config, resolver, binary_content=False)
123
+ rules.update(field_rules)
124
+ provenance |= field_provenance
125
+ # Keep the original binary classification for content outside text chunks.
126
+ # CRC-adjacent bytes must not also create a synthetic raw mailbox match.
127
+ raw_rules, raw_provenance = inspect_text(without_spans(data, spans), path, config, resolver,
128
+ commit_message=commit_message, binary_content=binary(data))
129
+ rules.update(raw_rules)
130
+ provenance |= raw_provenance
131
+ return rules, provenance
97
132
 
98
133
 
99
- def inspect_text(data, path, config, resolver, *, commit_message=False):
134
+ def inspect_text(data, path, config, resolver, *, commit_message=False, binary_content=None):
100
135
  # NUL removal covers UTF-16/32 ASCII content, matching the secret scanner.
101
136
  text = decoded_text(data)
102
137
  rules = set()
@@ -111,7 +146,7 @@ def inspect_text(data, path, config, resolver, *, commit_message=False):
111
146
  if emails and not (bundle and certificate_metadata(data, bundle, emails, config)):
112
147
  # Inside binary data a mailbox is judged by its own shape: a host
113
148
  # name with mixed-case letters inside a label is not one.
114
- if not binary(data) or any(stored_host(email) for email in emails):
149
+ if not (binary(data) if binary_content is None else binary_content) or any(stored_host(email) for email in emails):
115
150
  rules.add('hygiene.email')
116
151
  ref_text, provenance = provenance_text(text, path, config.get('declaredSources', frozenset()))
117
152
  stated, ambiguous = reference_sets(ref_text, path, config.get('localPaths', frozenset()), config.get('projectIdentity'))