From 5dfaad590b46d27ca892a1761dc5b3da01726332 Mon Sep 17 00:00:00 2001 From: Tsavo Knott Date: Sun, 22 Feb 2026 22:15:16 -0500 Subject: [PATCH 1/2] chore: sync runtime_ci templates and dependency metadata Apply runtime_ci workflow/template updates and align pubspec dependency metadata for enterprise BYOK prep. --- .github/workflows/ci.yaml | 34 ++++++++++++++++++------------ .runtime_ci/config.json | 15 +++++++++++++ .runtime_ci/template_versions.json | 12 +++++------ pubspec.yaml | 5 +---- 4 files changed, 43 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 786012f..98ad728 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,3 +1,5 @@ +# Generated by runtime_ci_tooling v0.11.0 +# Configured via .runtime_ci/config.json — run 'dart run runtime_ci_tooling:manage_cicd update --workflows' to regenerate. name: CI on: @@ -51,10 +53,7 @@ jobs: git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "git@github.com:" git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "ssh://git@github.com/" git config --global url."https://x-access-token:${TOKEN}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" - git config --global url."https://x-access-token:${TOKEN}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" - - - name: Skip LFS for dependency clones - run: git lfs install --skip-smudge + git config --global url."https://x-access-token:${TOKEN}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" - uses: dart-lang/setup-dart@v1.7.1 with: @@ -68,16 +67,25 @@ jobs: restore-keys: ${{ runner.os }}-dart-pub- - run: dart pub get - - - name: Cache Dart analysis - uses: actions/cache@v5.0.3 - with: - path: ~/.dartServer - key: ${{ runner.os }}-dart-analysis-${{ hashFiles('**/*.dart', '**/pubspec.yaml') }} - restore-keys: ${{ runner.os }}-dart-analysis- + env: + GIT_LFS_SKIP_SMUDGE: "1" - name: Analyze - run: dart run runtime_ci_tooling:manage_cicd analyze + run: | + dart analyze 2>&1 | tee /tmp/analysis.txt + if grep -q "^ error -" /tmp/analysis.txt; then + echo "::error::Analysis errors found" + exit 1 + fi + + - name: Format check + run: dart format --line-length 120 --output=none --set-exit-if-changed lib/ + +# --- BEGIN USER: pre-test --- +# --- END USER: pre-test --- - name: Test - run: dart run runtime_ci_tooling:manage_cicd test + run: dart test + +# --- BEGIN USER: post-test --- +# --- END USER: post-test --- diff --git a/.runtime_ci/config.json b/.runtime_ci/config.json index 3f100bb..b443c9d 100644 --- a/.runtime_ci/config.json +++ b/.runtime_ci/config.json @@ -101,5 +101,20 @@ ], "sentry_token_env": "SENTRY_ACCESS_TOKEN", "gcp_secret_name": "" + }, + "ci": { + "dart_sdk": "3.9.2", + "line_length": 120, + "personal_access_token_secret": "TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN", + "features": { + "proto": false, + "lfs": false, + "format_check": true, + "analysis_cache": false, + "managed_analyze": false, + "managed_test": false + }, + "secrets": {}, + "sub_packages": [] } } diff --git a/.runtime_ci/template_versions.json b/.runtime_ci/template_versions.json index e938dc7..f2e2200 100644 --- a/.runtime_ci/template_versions.json +++ b/.runtime_ci/template_versions.json @@ -1,6 +1,6 @@ { - "tooling_version": "0.9.0", - "updated_at": "2026-02-22T20:53:10.221596Z", + "tooling_version": "0.11.0", + "updated_at": "2026-02-23T03:00:18.628225Z", "templates": { "gemini_settings": { "hash": "93983f49dd2f40d2ed245271854946d8916b8f0698ed2cfaf12058305baa0b08", @@ -25,12 +25,12 @@ "config_json": { "hash": "b93219e43c5396ceb7be2dee746ebd8f687b520f451cecac6189a36801f76397", "consumer_hash": "5f7031402b59ec14b80260a3ba11dbfeab66a951ebadfdd8b74d66426aa623ae", - "updated_at": "2026-02-22T20:15:16.901611Z" + "updated_at": "2026-02-23T03:00:18.557699Z" }, "workflow_ci": { - "hash": "d98377363862792199825f4498543554f32588643acd71d11af6d1bf3ee48788", - "consumer_hash": "b0e3b5a5dff81fd75dabd5f3447e98e540589ea5cc22f5b7147f2cf0040d0b66", - "updated_at": "2026-02-22T20:53:10.222501Z" + "hash": "fd23db3ffc811ecff23201078a2e7c2e2ca4d6be699e997a67937f4d70be6111", + "consumer_hash": "716d59cd1a8791ab30e35932cf7d3e58c11afbf977845026bfc7826847d13cc6", + "updated_at": "2026-02-23T03:00:18.628260Z" }, "workflow_release": { "hash": "326627cf41fdeb6cd61dae2fda98599d5815a34e63e4a8af1aaa8f7ad18435d3", diff --git a/pubspec.yaml b/pubspec.yaml index 3a475ee..36349a0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,9 +9,6 @@ executables: environment: sdk: ^3.9.0 -# NOTE: When used inside the aot_monorepo workspace, add -# `resolution: workspace` here. It is intentionally omitted so this -# package can also resolve standalone (e.g., in its own CI pipeline). dependencies: @@ -27,7 +24,7 @@ dev_dependencies: git: url: git@github.com:open-runtime/runtime_ci_tooling.git tag_pattern: v{{version}} - version: ^0.10.0 + version: ^0.11.0 lints: ^6.0.0 test: ^1.24.0 From 5dd3bd3a3b00e9542b4812ccde24f8def981fc6c Mon Sep 17 00:00:00 2001 From: Tsavo Knott Date: Mon, 23 Feb 2026 17:57:26 -0500 Subject: [PATCH 2/2] fix(security): constant-time comparison, PBKDF2 600k default, deprecate ECB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fernet: replace ListEquality with XOR-accumulation for HMAC verification to prevent timing side-channel attacks - RSA: use constant-time XOR comparison for equal-length signature verification - PBKDF2: raise default iterations from 100 to 600,000 per OWASP recommendation for PBKDF2-HMAC-SHA1 - AES: mark ECB mode @Deprecated — encrypts blocks independently, leaking plaintext patterns; use GCM or CBC instead ref #391 Co-Authored-By: Claude Opus 4.6 --- lib/src/algorithms/aes.dart | 16 +++++++++++++++- lib/src/algorithms/fernet.dart | 16 +++++++++++++++- lib/src/algorithms/rsa.dart | 8 ++++---- lib/src/encrypted.dart | 7 ++++++- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/lib/src/algorithms/aes.dart b/lib/src/algorithms/aes.dart index 05853f2..2118230 100644 --- a/lib/src/algorithms/aes.dart +++ b/lib/src/algorithms/aes.dart @@ -108,7 +108,21 @@ class AES implements Algorithm { } } -enum AESMode { cbc, cfb64, ctr, ecb, ofb64Gctr, ofb64, sic, gcm } +enum AESMode { + cbc, + cfb64, + ctr, + + /// ECB mode encrypts each block independently, leaking plaintext patterns. + /// Use [AESMode.gcm] or [AESMode.cbc] instead. + @Deprecated('ECB mode is insecure - it leaks plaintext patterns. Use gcm or cbc instead.') + ecb, + + ofb64Gctr, + ofb64, + sic, + gcm, +} const Map _modes = { AESMode.cbc: 'CBC', diff --git a/lib/src/algorithms/fernet.dart b/lib/src/algorithms/fernet.dart index 164974d..1844192 100644 --- a/lib/src/algorithms/fernet.dart +++ b/lib/src/algorithms/fernet.dart @@ -83,11 +83,25 @@ class Fernet implements Algorithm { final digest = data.sublist(length - 32); var hmac = Hmac(sha256, _signKey.bytes); final digestConverted = hmac.convert(parts).bytes; - if (!ListEquality().equals(digest, digestConverted)) { + if (!_constantTimeEquals(digest, Uint8List.fromList(digestConverted))) { throw StateError('Invalid token'); } } + /// Constant-time comparison to prevent timing side-channel attacks. + /// + /// Unlike [ListEquality.equals], this does not short-circuit on the first + /// byte difference. Instead it XOR-accumulates all differences so the + /// execution time is independent of which bytes differ. + static bool _constantTimeEquals(List a, List b) { + if (a.length != b.length) return false; + var result = 0; + for (var i = 0; i < a.length; i++) { + result |= a[i] ^ b[i]; + } + return result == 0; + } + Uint8List _encryptFromParts(Uint8List bytes, int currentTime, IV iv) { final aes = AES(_encryptionKey, mode: AESMode.cbc); final cipherText = aes.encrypt(bytes, iv: iv); diff --git a/lib/src/algorithms/rsa.dart b/lib/src/algorithms/rsa.dart index 60ec2c7..665994f 100644 --- a/lib/src/algorithms/rsa.dart +++ b/lib/src/algorithms/rsa.dart @@ -120,13 +120,13 @@ class RSASigner extends AbstractRSA implements SignerAlgorithm { final expected = _encode(hash); if (signature0.length == expected.length) { + var nonEqual = 0; + for (var i = 0; i < signature0.length; i++) { - if (signature0[i] != expected[i]) { - return false; - } + nonEqual |= (signature0[i] ^ expected[i]); } - return true; + return nonEqual == 0; } else if (signature0.length == expected.length - 2) { var sigOffset = signature0.length - hash.length - 2; var expectedOffset = expected.length - hash.length - 2; diff --git a/lib/src/encrypted.dart b/lib/src/encrypted.dart index e3b5aa7..b673b51 100644 --- a/lib/src/encrypted.dart +++ b/lib/src/encrypted.dart @@ -112,7 +112,12 @@ class Key extends Encrypted { /// The key is ALL ZEROS - NOT CRYPTOGRAPHICALLY SECURE! Key.allZerosOfLength(super.length) : super.allZerosOfLength(); - Key stretch(int desiredKeyLength, {int iterationCount = 100, Uint8List? salt}) { + /// Derives a new key of [desiredKeyLength] bytes using PBKDF2. + /// + /// The [iterationCount] defaults to 600,000 per OWASP recommendations for + /// PBKDF2-HMAC-SHA1. Passing a value below 600,000 is strongly discouraged + /// as it weakens resistance to brute-force attacks. + Key stretch(int desiredKeyLength, {int iterationCount = 600000, Uint8List? salt}) { salt ??= SecureRandom(desiredKeyLength).bytes; final params = Pbkdf2Parameters(salt, iterationCount, desiredKeyLength);